Compare commits

...

271 Commits

Author SHA1 Message Date
shadcn
51e1940525 test: implement fuzzing 2025-03-27 17:41:32 +04:00
shadcn
074eed5605 feat(shadcn): extend styles (#7033)
* feat(shadcn): extend styles

* fix(shadcn): typecheck
2025-03-26 17:11:08 +04:00
shadcn
ca7fbc3b64 fix(v4): minor focus styles (#7030) 2025-03-26 13:03:23 +04:00
shadcn
b3b2fe2755 feat(shadcn): registry updates (#7016)
* feat(shadcn): registry updates

* tests: fix snapshots

* feat(shadcn): add new theme prop

* fix: handle theme for index

* tests(shadcn): fix

* docs(www): update registry item docs

* chore: add changeset

* docs: update theming docs
2025-03-26 13:03:10 +04:00
shadcn
1fcb318c56 feat: use latest cmdk version 2025-03-24 09:45:59 +04:00
shadcn
2a783175c5 docs: update tanstack start 2025-03-24 08:40:00 +04:00
Jack Herrington
69fc8e23cc fix: allow silent mode with npm on React 19 (#6965)
* fix: allow silent mode with npm on React 19

* chore: changeset

* style: format

---------

Co-authored-by: shadcn <m@shadcn.com>
2025-03-20 13:02:18 +04:00
FatahChan
84ec2f6079 fix(v4): card - css syntax warning in tailwind v4 and vite (#6955)
when building using vite and tailwind vite plugin
this following warning appears 


```
rendering chunks (10)...warnings when minifying css:
▲ [WARNING] Unexpected ")" [css-syntax-error]
 grid-cols-\[1fr_auto\]:has(:is()){grid-template-columns:1fr
                                ^
```


this is fixed by complying to tailwind new way of writing data attribute documented  [here](https://tailwindcss.com/docs/hover-focus-and-other-states#data-attributes)

change

```
[data-slot=card-action]:
```
to 

```
data-[slot=card-action]:
```

resolves https://github.com/shadcn-ui/ui/issues/6930
2025-03-20 09:01:20 +00:00
shadcn
37e5192fe7 fix(shadcn): only add tw-animate-css if no tailwindcss-animate (#6972)
* feat(shadcn): fix existing tailwindcss-animate

* docs: changelog

* tests(shadcn): update snapshots
2025-03-19 15:35:59 +04:00
shadcn
205bfc637e feat(shadcn): replace tailwindcss-animate with tw-animate-css (#6968)
* feat(shadcn): replace tailwindcss-animate with tw-animate-css

* chore: changeset
2025-03-19 10:57:10 +04:00
shadcn
9eae13639c fix(shadcn): only show deprecated message for v4 (#6967)
* fix(shadcn): only show deprecated for v4

* chore: changeset
2025-03-19 10:00:05 +04:00
shadcn
c77ddfc8b7 fix: tailwind config 2025-03-18 13:59:28 +04:00
shadcn
516ed3d17e feat(www): add dashboard-01 (#6961)
* fix(v4): minor fixes

* fix: format

* feat(www): add dashboard-01 block

* chore: capture screenshots
2025-03-18 13:42:39 +04:00
shadcn
bbda8c0b5a fix(v4): minor fixes for dashboard (#6959)
* fix(v4): minor fixes

* fix: format
2025-03-18 11:28:42 +04:00
shadcn
d5be468d32 fix: meta 2025-03-17 22:39:47 +04:00
shadcn
9f094a13b9 fix: meta 2025-03-17 22:34:10 +04:00
shadcn
829ef0cbf6 feat(v4): move dashboard 2025-03-17 22:26:43 +04:00
shadcn
4ebfd6896f Merge branch 'main' of github.com:shadcn-ui/ui 2025-03-17 22:07:10 +04:00
shadcn
911c52022a feat(v4): dashboard demo 2025-03-17 22:07:02 +04:00
Yordan Iliev
61e21e3356 docs: fix typo in CSS selector (#6928)
There was a typo
2025-03-17 11:29:20 +00:00
shadcn
28d97b7fc1 fix: github link 2025-03-17 15:10:44 +04:00
Kian Bazza
5085e48241 feat(components): add origin-aware animations (#6945)
This PR adds origin-aware animations to the following components:

- Context menu
- Dropdown menu
- Hover card
- Menu bar
- Popover
- Select
- Tooltip

This is accomplished with the custom property `--radix-<component>-transform-origin`, exposed by Radix on the following components:

For more information on this custom property, see the Radix docs; context menu as an example: https://www.radix-ui.com/primitives/docs/components/context-menu#origin-aware-animations

You can see a side-by-side comparison in the following video:

https://github.com/user-attachments/assets/ad8d548b-eb8d-4818-acb4-73c7cdaecc3f
2025-03-17 11:04:00 +00:00
shadcn
b7afb5aa1e feat(v4): dashboard 02 (#6950)
* feat(v4): dashboard 02

* fix: lint
2025-03-17 15:00:23 +04:00
t.yang
a6e2d3672f fix(sidebar): update type SidebarContext -> SidebarContextProps (#6926)
Hey, I noticed that the original code triggers the `ts/no-redeclare` ESLint error due to the naming conflict between the `SidebarContext` interface and the `SidebarContext` component.

![image](https://github.com/user-attachments/assets/33e6a8de-02bd-43a0-8279-beac8b051602)

I think `ts/no-redeclare` is a pretty common ESLint rule, I renamed the interface to `SidebarContextProps` to avoid the conflict. This naming style is consistent with the `carousel` component: (0f6efb9769/apps/v4/registry/new-york-v4/ui/carousel.tsx (L24)).

Hope this makes sense!
2025-03-14 12:53:22 +00:00
JEM
0f6efb9769 fix(navigation-menu v4): 6888 - Remove "use client" directive (#6913)
Removes unnecessary "use client" directive from navigation menu components to simplify the code and avoid potential issues.

Co-authored-by: shadcn <m@shadcn.com>
2025-03-13 15:03:20 +04:00
shadcn
415b02b4bc docs: add dark mode changelog (#6918)
* docs: dark mode changelog

* docs: add base colors reference
2025-03-13 14:42:19 +04:00
shadcn
f1dd9c6903 feat: update dark mode colors (#6903)
* feat: update dark colors

* feat(v4): update dark mode colors

* fix

* fix

* fix: slate and stone mismatches

* feat(v4): update skeleton and switch colors

* feat(v4): add dashboard example

* fix(v4): update dashboard components

* fix: themes

* feat: update sonner

* feat(v4): update dashboard buttons

* fix: test new colors

* fix: build commands

* feat(v4): more color updates

* feat(v4): update theme selector

* fix(v4): minor component fixes
2025-03-13 13:06:08 +04:00
shadcn
abde54987b chore: create FUNDING.yml 2025-03-12 23:18:49 +04:00
shadcn
79f2498b28 docs(www): add auth section for registry (#6910) 2025-03-12 18:33:36 +04:00
Valentin Gavran
d999f803a8 fix(select): make select content scrollable if height is limited (#6681)
* fix(select): make select content scrollable if height is limited

Select content now becomes scrollable when the browser window height is too small to display the full menu. This improves usability on smaller screens.

Radix also supports the same behavior for other components. I will also submit PRs for the other components separately.

Added the following properties to the Select Content component:

- `max-h-[--radix-select-content-available-height]`
- `overflow-y-auto` & `overflow-x-hidden` instead of `overflow-hidden` (existed before)

* fix(select): make select content scrollable if height is limited

Select content now becomes scrollable when the browser window height is too small to display the full menu. This improves usability on smaller screens.

Radix also supports the same behavior for other components. I will also submit PRs for the other components separately.

Added the following properties to the Select Content component:

- `max-h-[--radix-select-content-available-height]`
- `overflow-y-auto` & `overflow-x-hidden` instead of `overflow-hidden` (existed before)

---------

Co-authored-by: shadcn <m@shadcn.com>
2025-03-12 11:16:21 +04:00
Valentin Gavran
1d8fe41d64 fix(context-menu): make context menu scrollable if height is limited (#6682)
* fix(context-menu): make context menu scrollable if height is limited

Context menu content now becomes scrollable when the browser window height is too small to display the full menu. This improves usability on smaller screens.

Radix also supports the same behavior for other components. I will also submit PRs for the other components separately.

Added the following properties to the Context Menu Content component:

- `max-h-[--radix-context-menu-content-available-height]`
- `overflow-y-auto` & `overflow-x-hidden` instead of `overflow-hidden` (existed before)

* fix(v4): context menu

---------

Co-authored-by: shadcn <m@shadcn.com>
2025-03-12 10:48:56 +04:00
Kasper Aamodt
33b6146fde docs(www): fix typo in site-header.tsx (#6897)
Fixed a small typo in the site-header.tsx
2025-03-10 17:41:00 +00:00
shadcn
1d55420629 docs(www): add callout for tailwind v3 2025-03-10 21:15:23 +04:00
shadcn
12d4cf2ab0 Merge branch 'main' of github.com:shadcn-ui/ui 2025-03-06 09:46:06 +04:00
shadcn
5466432b01 docs(www): update registry 2025-03-06 09:45:53 +04:00
Hichem Taboukouyout
f9e4991f15 Add @radix-ui/react-slot dependency to badge registry entry (#6841)
* Add dependency to badge registry entry

Added @radix-ui/react-slot as a dependency for the badge component in the registry. This ensures proper functionality and resolves potential missing dependency issues.

- Updated the "badge" registry entry by including "@radix-ui/react-slot" in its dependencies list.
- Fixed the missing newline at the end of the file for better formatting.

* Revert "Add dependency to badge registry entry"

This reverts commit d12ddaa511.

* Add Radix slot dependency to badge registry entry

Included @radix-ui/react-slot as a dependency for the badge component in the registry. This change ensures the component functions as intended and avoids potential runtime errors.

- Updated "badge" registry entry dependencies list.
- Fixed formatting by adding a missing newline at the file end.

---------

Co-authored-by: shadcn <m@shadcn.com>
2025-03-05 20:33:53 +04:00
Denish Navadiya
67e7364b3c fix: registry-item-json link (#6485)
Co-authored-by: shadcn <m@shadcn.com>
2025-03-05 20:15:23 +04:00
shadcn
2f6c3e74bb fix(v4): use tailwind var 2025-03-05 16:58:06 +04:00
Valentin Gavran
a106fc5412 fix(dropdown-menu): make dropdown scrollable if height is limited (#6679)
* fix(dropdown-menu): make dropdown content scrollable if height is limited

Dropdown menu content now becomes scrollable when the browser window height is too small to display the full menu. This improves usability on smaller screens.

Radix also supports the same behavior with other components. I will also submit a PR for the other components separately.

* chore: build registry

---------

Co-authored-by: shadcn <m@shadcn.com>
2025-03-05 16:51:46 +04:00
shadcn
19665adeed feat(shadcn): add --base-color flag (#6864) 2025-03-05 16:14:03 +04:00
omar2205
fc0e8c0c52 use type import in react-hook-form (#4183)
* use type import in react-hook-form

* fix(form): fix type and rebuild registry

---------

Co-authored-by: shadcn <m@shadcn.com>
2025-03-05 15:34:13 +04:00
shadcn
c16c58d0f9 feat(shadcn): add --template flag (#6863)
* feat(shadcn): add --template flag

* chore: changeset

* fix: type
2025-03-05 15:13:34 +04:00
Kaikai
a3fe5074c1 feat(monorepo): use tailwindcss v4 in monorepo example (#6724)
* feat(monorepo): use tailwindcss v4 in monorepo example

* feat(shadcn): add monorepo tailwind detection

* feat: update default monorepo template

* fix: minor fixes

* chore(shadcn): changeset

* docs(www): update monorepo docs

* docs: updates

---------

Co-authored-by: shadcn <m@shadcn.com>
2025-03-05 12:07:50 +04:00
shadcn
3baef994d7 feat(v4): add combobox with checkbox example (#6862)
* feat(v4): add combobox with checkbox example

* fix: remove unused checkbox

* fix: rounded
2025-03-05 11:21:07 +04:00
shadcn
a7b3dbf121 feat(www): site updates (#6854)
* feat(www): site updates

* fix: lint

* docs: add tailwind notice
2025-03-04 16:51:58 +04:00
shadcn
26dc03b545 fix: chart and input-otp lint errors (#6853) 2025-03-04 14:00:25 +04:00
shadcn
be3c1a9a98 chore: minor fixes for registry blocks (#6850) 2025-03-04 13:26:40 +04:00
shadcn
418b2d9e14 fix(v4): misc select and tabs fixes (#6836) 2025-03-03 11:56:29 +04:00
JEM
7eb77fb3b9 fix(navigation-menu): 6825 - Update open state styles and remove unused classes (#6827)
* fix(navigation-menu): 6825 - Update open state styles and remove unused classes

Adjusts navigation menu trigger open styles to apply opacity /50 only when not hover/focused.
Removes unused data-[active] and data-[active=true] classes.

Fixes #6825

* Adds data-[active=true] back to NavigationMenuLink component.

Includes text color changes for open/active states to improve UI consistency.

* v3 registry

* v4 registry
2025-03-03 11:56:15 +04:00
Bryan Lee
f759a25354 docs(www): update Tailwind v4 migration guide with CSS and React changes (#6813)
Co-authored-by: shadcn <m@shadcn.com>
2025-03-03 11:53:09 +04:00
JEM
0b288883d2 fix(sidebar, sidebar-16): 6651 - Resolve unnecessary vertical scroll (#6782)
* fix(sidebar, sidebar-16): 6651 - Resolve unnecessary vertical scroll

Includes "label" in registry dependencies for sidebar-16 components.
Adjusts sidebar and sidebar inset dimensions to eliminate unwanted scroll behavior.

Fixes #6651

* chore: build registry

* Add label component to registry and adjust sidebar styles

Adds 'label' component to the registry.
Corrects sidebar styles and layout for better responsiveness.

* fix(sidebar-09): Add missing Label component dependency

Ensures the Label component is included in the registry dependencies for sidebar-09 to prevent missing component errors.

---------

Co-authored-by: shadcn <m@shadcn.com>
2025-03-02 15:33:09 +04:00
Christian Ivicevic
cf4ccb34e2 docs(www): mention to add suppressHydrationWarning in the dark mode docs for next (#5879)
Users have repeatedly run into hydration errors because they don't read the next-themes readme and don't pay attention to the shadcn/ui docs and miss that the `suppressHydrationWarning` prop must be added.

This commit mentions this explicitly and highlights the line in the snippet (as well as fixing the previously wrong highlighting).
2025-03-02 11:27:48 +00:00
shadcn
dc80a326c2 ci: fix stale bot 2025-03-02 15:24:05 +04:00
Lloyd Richards
92e5c7ab2c ci: fix up stale issue handler (#6457)
Co-authored-by: shadcn <m@shadcn.com>
2025-03-02 14:59:26 +04:00
shadcn
d44971b6c2 feat(v4): add products-01 block 2025-02-28 23:14:59 +04:00
shadcn
8539dd6eec fix(shadcn): do not add outline-ring for v3 (#6814)
* fix(shadcn): do not add ring if tailwind v3

* chore: changeset

* test(shadcn): update snapshots
2025-02-28 21:23:08 +04:00
shadcn
bc7df68620 feat(shadcn): install routes for next-pages, laravel and react-router (#6811)
* feat(shadcn): install routes for next-pages, laravel and react-router

* chore: changeset
2025-02-28 18:06:06 +04:00
迷渡
1832f258bd fix(shadcn): Remove duplicate spaces (#6696) 2025-02-28 17:19:28 +04:00
shadcn
e2730f7276 docs(www): update docs for cli, components.json and theming 2025-02-28 16:38:39 +04:00
shadcn
22ba26152a docs(www): update tanstack docs 2025-02-28 16:21:29 +04:00
shadcn
76d6a59f9f docs(www): update all framework docs (#6809)
* docs(www): update all framework docs

* docs(www): update Tailwind v4 docs
2025-02-28 15:53:04 +04:00
shadcn
3a2a87386f fix(v4): badge overflow 2025-02-28 15:44:04 +04:00
shadcn
1822d95883 docs(www): update docs for vite (#6807) 2025-02-28 14:30:10 +04:00
shadcn
f90f5148eb fix(v4): responsive for the demo (#6803)
* fix(v4): responsive

* fix: type

* fix(v4): remove the login link
2025-02-28 13:31:06 +04:00
shadcn
99b0a5ac90 fix(v4): remove login and sandbox links 2025-02-28 13:29:16 +04:00
shadcn
984cb2a1ea feat(v4): update default ring colors 2025-02-28 13:28:17 +04:00
shadcn
fed2bac1d9 Merge branch 'main' of github.com:shadcn-ui/ui 2025-02-27 15:51:17 +04:00
shadcn
1ebfcd7cd9 feat(registry): update sidebars 2025-02-27 15:51:05 +04:00
Keit Oliveira
535a7d9220 fix(form): avoid undefined message error as string (#6729)
Add validation to prevent converting `undefined` error messages to string in the `FormMessage` component.

This fix addresses issues that occur in complex validation structures, such as validating entire objects.

```ts
const formSchema = z.object({
  location: z.object({
    city: z.string().nonempty(),
    state: z.string().nonempty(),
    country: z.string().nonempty(),
  }).superRefine((data, ctx) => {
    ...
  });
```

```tsx
<FormField
  control={form.control}
  name="location"
  render={() => (
    <FormItem>
      <FormField
        control={form.control}
        name="location.city"
        render={({ field }) => (
          <FormItem>
            <FormLabel>City</FormLabel>
            <FormControl>
              <Input placeholder="Enter city" {...field} />
            </FormControl>
          </FormItem>
        )}
      />

      <FormField
        control={form.control}
        name="location.state"
        render={({ field }) => (
          <FormItem>
            <FormLabel>State</FormLabel>
            <FormControl>
              <Input placeholder="Enter state" {...field} />
            </FormControl>
          </FormItem>
        )}
      />

      <FormField
        control={form.control}
        name="location.country"
        render={({ field }) => (
          <FormItem>
            <FormLabel>Country</FormLabel>
            <FormControl>
              <Input placeholder="Enter country" {...field} />
            </FormControl>
          </FormItem>
        )}
      />

      <FormMessage />
    </FormItem>
  )}
/>
```
2025-02-26 20:35:01 +00:00
Cyrus Zei
e7f8cd4566 chore(www): update the user info to use example instead (#6766) 2025-02-26 20:47:23 +04:00
shadcn
8506977f83 chore: deprecate shadcn-ui (#6780)
* chore: deprecate shadcn-ui

* deps(shadcn-ui): remove unused dependencies

* fix: tests

* deps: bump version
2025-02-26 20:34:51 +04:00
shadcn
575c0214da feat(v4): minor component updates 2025-02-26 17:28:49 +04:00
shadcn
a1ab28bf58 Merge branch 'main' of github.com:shadcn-ui/ui 2025-02-25 16:00:45 +04:00
shadcn
190ae2dcd8 docs(www): link get started to installation 2025-02-25 16:00:19 +04:00
Fin Chen
d5920cc3c1 fix(www): extra semicolon of --radius (#6737) (#6762) 2025-02-25 15:51:38 +04:00
shadcn
89652889db docs(www): update tanstack start docs (#6765) 2025-02-25 15:51:19 +04:00
Pavi
57d15bb2d5 fix(docs): update monorepo docs (#6647)
This pull request includes a small change to the `templates/monorepo-next/README.md` file. The change corrects the import path for the `Button` component to reflect the correct directory structure.

* [`templates/monorepo-next/README.md`](diffhunk://#diff-4f9dc7da681b34ccf99bccdcb804625f61e47afc70a483548b3ae093b564e072L30-R30): Updated the import path for the `Button` component to `@workspace/ui/components/button`.
2025-02-23 07:35:26 +00:00
shadcn
779517a1d4 fix(shadcn): check for empty css vars (#6733)
* fix(shadcn): check for empty css vars

* chore: changeset
2025-02-22 13:44:13 +04:00
Pasquale Vitiello
b567f7a6c1 fix: class name typo (#6633)
Co-authored-by: shadcn <m@shadcn.com>
2025-02-22 13:43:57 +04:00
shadcn
839afa714f feat(shadcn): cache registry calls (#6732)
* fix(shadcn): cache registry calls

* chore: changeset
2025-02-22 13:21:16 +04:00
Aaron
32f0bc0de9 fix(select): make select placeholder text show as faded (#6055)
Use the [data-placeholder] data attribute supplied by radix-ui instead of the `placeholder` state selector, which doesn't do anything for buttons (afaict, which radix-ui renders `SelectTrigger` into).

Reference: https://www.radix-ui.com/primitives/docs/components/select#api-reference

Before:
![Screenshot from 2024-12-12 15-52-50](https://github.com/user-attachments/assets/106c4c44-d6b9-4513-b2a9-77814ae46b8b)

After:
![image](https://github.com/user-attachments/assets/b09556f7-e274-4918-90c4-32c5aa8773cc)
2025-02-22 08:17:56 +00:00
shadcn
7d2499c803 fix(v4): dark mode 2025-02-21 21:08:07 +04:00
shadcn
84b88440c3 fix(v4): login spacing 2025-02-21 21:05:52 +04:00
shadcn
a5122f9029 fix(shadcn): do not override existing vars (#6721)
* fix(shadcn): do not override existing vars

* test(shadcn): update snapshots

* chore: changeset
2025-02-21 20:58:02 +04:00
shadcn
3b90317e3c feat(v4): update cards 2025-02-21 20:44:09 +04:00
shadcn
9d7c7b8978 fix(shadcn): fallback style (#6720) 2025-02-21 20:19:59 +04:00
shadcn
957df8997c fix: misc fixes for v4 components (#6719)
* feat(v4): login tests

* feat(v4): fix button and toggle
2025-02-21 20:07:48 +04:00
shadcn
460240a86c chore: remove console 2025-02-21 19:58:09 +04:00
shadcn
bfffb08f42 fix(shadcn): issue with workspace config (#6712) 2025-02-21 15:18:06 +04:00
shadcn
5282332e52 feat(shadcn): do not ask if file is identical (#6711)
* feat(shadcn): do not ask if file is identical

* fix: add missing files
2025-02-21 15:04:19 +04:00
shadcn
3db8a07b3f feat(shadcn): default to cssVariables: true. add --no-css-variables option (#6707)
* feat(shadcn): default to css vars

* chore: changeset

* fix(shadcn): add command
2025-02-21 11:49:17 +04:00
shadcn
187959435e Merge branch 'main' of github.com:shadcn-ui/ui 2025-02-20 23:41:43 +04:00
shadcn
5953229417 fix(v4): input otp 2025-02-20 23:41:37 +04:00
Zach Warunek
d6159023ed fix(cli): monorepo cn import (#6530)
* fix monorepo cn import

* chore: changeset

* style(shadcn): format

---------

Co-authored-by: shadcn <m@shadcn.com>
2025-02-20 23:41:21 +04:00
shadcn
b0774b0578 fix: slider and demo components 2025-02-20 23:03:39 +04:00
shadcn
7cefabbe98 fix: switch focus 2025-02-20 22:56:33 +04:00
shadcn
3740373f99 feat(v4): update colors (#6693)
* feat(v4): update colors

* fix: sonner button

* feat(shadcn): update base colors to oklch

* fix: button gaps

* fix: sidebar and chart

* feat: update ring colors

* feat(v4): neutral color and fixes

* fix: fonts

* chore: changeset

* fix: revert utils
2025-02-20 19:32:56 +04:00
David Leger
9c4419f249 docs: fix TanStack logo on dark mode (#6695)
Inherit logo color from font color so that it renders properly in dark mode.

### Before
<img width="939" alt="image" src="https://github.com/user-attachments/assets/66327284-354e-4f07-a87b-465c37bac6f3" />


### After
<img width="930" alt="image" src="https://github.com/user-attachments/assets/2cd0b22d-f8be-4993-976e-0b9751400bfc" />
2025-02-20 14:34:51 +00:00
shadcn
ea9b81594d docs(www): update link for vaul 2025-02-17 23:04:58 +04:00
shadcn
4810f744e3 fix(www): misc site fixes 2025-02-16 15:48:54 +04:00
shadcn
38c5fb4ace feat(www): update og (#6658)
* feat(www): update og

* fix: remove export
2025-02-15 14:06:30 +04:00
shadcn
f37425bdb0 feat(shadcn): use canary for create-next-app 2025-02-14 00:21:42 +04:00
Ajay Pawar
592ef33658 style-new-york/mode-toggle button fixed (#6575) 2025-02-12 20:05:58 +04:00
shadcn
1a6da427ff feat(shadcn): add internal_registryResolveItemsTree (#6626) 2025-02-12 16:49:58 +04:00
shadcn
bd8533bd26 (11/n) shadcn: filter out deprecated from --all (#6617)
* fix(shadcn): filter out deprecated from --all

* chore: changeset
2025-02-11 13:28:41 +04:00
Pálmi Þór Valgeirsson
202131cd7b chore(deps): Upgrade @antfu/ni to v23.2.0 (#6414)
* chore(deps): Upgrade @antfu/ni to v23.2.0

* chore: changeset

* test(shadcn): add bun.lock

---------

Co-authored-by: shadcn <m@shadcn.com>
2025-02-11 12:46:53 +04:00
shadcn
7977975c9d fix(www): announcement icon color 2025-02-10 21:20:12 +04:00
shadcn
a23fec1c31 docs(www): typo 2025-02-10 21:12:25 +04:00
shadcn
2af2979cf4 docs(www): typo 2025-02-10 21:10:42 +04:00
HichemTech
aee53d90e2 Update calendar.mdx (#6501)
Expicitly define the version of react-date-picker
2025-02-10 15:43:28 +04:00
shadcn
1a68715048 Merge branch 'main' of github.com:shadcn-ui/ui 2025-02-10 12:00:15 +04:00
shadcn
34c99c1728 fix(www): spacing 2025-02-10 12:00:01 +04:00
shadcn
86642840e2 docs(www): update for tailwind v4 (#6608) 2025-02-10 11:43:03 +04:00
shadcn
fac0daac7b fix(www): code tabs (#6607) 2025-02-10 11:34:02 +04:00
shadcn
13fe24e1f8 docs(www): update introduction (#6605)
* docs(www): update introduction

* feat(www): update vercel cta

* docs(www): minor fixes
2025-02-09 20:43:49 +04:00
Long Zheng
1eb2d74d7c fix: rename sidebar cookie to be valid name (#6558)
* fix: rename sidebar cookie to be valid name

* fix: rename sidebar cookie to underscore

---------

Co-authored-by: shadcn <m@shadcn.com>
2025-02-07 19:40:47 +04:00
Prateek (प्रतीक)
16d4d38f56 fix(tanstack): fix paths value in tsconfig and check for tanstack start in devDeps also (#6590)
* fix(docs): tanstack start guide fix the paths value in tsconfig

* fix(tanstack): check for @tanstack/start in devDependencies as well

* chore: changeset

* fix(shadcn): tanstack detection

---------

Co-authored-by: shadcn <m@shadcn.com>
2025-02-07 14:31:28 +04:00
shadcn
5234c46722 feat(shadcn): add support for TanStack Start (#6507)
* feat(shadcn): add TanStack Start support

* docs(www): add docs for TanStack Start

* chore: changeset
2025-02-06 22:34:16 +04:00
shadcn
f9037239af fix: v4 page 2025-02-06 20:42:21 +04:00
shadcn
14e4726400 feat: misc updates for icons and tooltip (#6584) 2025-02-06 16:44:40 +04:00
shadcn
9f4d65fc8f (9/n) shadcn: add warning for deprecated components (#6576)
* feat(shadcn): add deprecated components warning

* chore: changeset
2025-02-05 22:51:31 +04:00
shadcn
1e357cb20d (8/n) shadcn: default to new-york for v4 (#6574)
* feat(shadcn): handle radius for v4

* feat(shadcn): default to new-york for v4

* chore: changeset
2025-02-05 21:28:49 +04:00
shadcn
6339aaa315 (7/n) shadcn: improve focus-visible for all elements (#6573)
* feat: focus visible colors

* fix: lint

* fix: tabs
2025-02-05 17:49:20 +04:00
shadcn
e2caa3cd35 chore: add v4 to changeset ignore 2025-02-05 15:03:19 +04:00
shadcn
c74a094f14 (6/n) shadcn: handle registry for v4 (#6571)
* feat(shadcn): hotswap registry style for v4

* chore: changeset

* style(shadcn): remove unused import

* chore: registry

* fix(shadcn): cmon
2025-02-05 14:54:24 +04:00
shadcn
bfb5b6357e feat: add vercel analytics 2025-02-05 11:34:14 +04:00
shadcn
ac3d965c6e chore: remove unused code 2025-02-05 11:33:10 +04:00
shadcn
6e7cd11d68 fix(v4): charts 2025-02-05 11:31:50 +04:00
shadcn
a75b4ca80e fix: build 2025-02-05 11:17:48 +04:00
shadcn
475abbbb20 refactor: new-york-v4 2025-02-05 10:38:28 +04:00
shadcn
762cd73554 chore: build registry 2025-02-05 10:17:03 +04:00
shadcn
8c06932800 fix: debug build 2025-02-05 00:37:50 +04:00
shadcn
e516481394 (5/n) shadcn: migrate all components and blocks (#6567)
* feat: add v3

* feat: upgrade lucide-react

* feat: upgrade components to Tailwind v4

* feat(v3): add dark mode

* wip

* feat: components upgrade

* style: format

* feat: add select

* feat: more components

* feat: more components

* fix: shadow

* feat: upgrade sidebar

* refactor: registry

* refactor: internal

* fix: registry

* fix: charts

* chore: build registry

* fix: charts

* feat: form

* feat: clean up

* fix: icons

* fix: command demo

* chore: add new-york-v4 to registry

* fix: revert command in www registry

* fix

* chore: fix tests

* fix: types

* fix: lint errors

* chore: build v4 registry

* fix

* fix: types
2025-02-05 00:15:07 +04:00
shadcn
d1eb24e23a (4/n) shadcn: fix handling of sidebar colors (#6515)
* fix(shadcn): background, foreground and sidebar colors

* chore: changeset

* ci: update artifact
2025-01-30 22:57:57 +04:00
shadcn
9a14c1d092 (3/n) shadcn: tailwind config (#6490)
* feat(shadcn): add tailwind version detection

* chore: changeset

* feat(shadcn): css vars for tailwind v4

* fix(shadcn): handle color space

* fix(shadcn): add oklch support

* feat(shadcn): handle single quote

* feat(shadcn): update tailwind config

* feat(shadcn): add keyframes

* feat(shadcn): add animation vars

* fix: var name

* fix(shadcn): handle color values

* feat(shadcn): handle radius

* feat(shadcn): clean up formatting

* feat(shadcn): update next clean up function

* chore: changeset

* feat(shadcn): fix formatting for v3 and v4

* test(shadcn): update snapshots

* fix(shadcn): update tailwind version handling
2025-01-30 11:59:47 +04:00
shadcn
5ef2bc5f45 (2/n) shadcn: css vars for tailwind v4 (#6487)
* feat(shadcn): add tailwind version detection

* chore: changeset

* feat(shadcn): css vars for tailwind v4

* fix(shadcn): handle color space

* fix(shadcn): add oklch support

* feat(shadcn): handle single quote

* chore: add changeset
2025-01-30 11:55:22 +04:00
shadcn
8f6a64f176 (1/n) shadcn: add tailwind version detection (#6478)
* feat(shadcn): add tailwind version detection

* chore: changeset
2025-01-30 11:49:09 +04:00
Boby Tiwari
e85920c191 Update blocks.mdx (#6477) 2025-01-28 16:25:24 +04:00
github-actions[bot]
c8c4027b6b chore(release): version packages (#6349)
* chore(release): version packages

* chore(release): version packages

* chore: fix version

* deps: update lock file

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: shadcn <m@shadcn.com>
2025-01-28 15:26:05 +04:00
shadcn
699195ba77 deps(shadcn): remove unused lodash.template (#6475)
* deps(shadcn): remove unused lodash.template

* chore: changeset
2025-01-28 15:13:39 +04:00
shadcn
9643db42cf feat: add docs for registry (#6380)
* chore: 2.2.0-canary.2

* feat: add docs for registry

* docs(www): update registry docs

* fix: update dep

* docs(www): update docs

* docs(www): update registry docs

* feat: add new label

* fix: lint
2025-01-28 15:06:22 +04:00
JEM
dd71498762 feat(sidebar blocks): adds sidebar-16 block with components and styles (#5889)
Introduces a new sidebar-16 block with an inset sidebar and site header navigation.
Includes components for main navigation, projects, secondary navigation, and user menu.
Updates registry and scripts to include the new sidebar block.

Addresses: #5629

![image](https://github.com/user-attachments/assets/769148aa-ec83-46f4-b4f0-77947e253d28)
![image](https://github.com/user-attachments/assets/38532b4d-21f6-4ce1-9e92-08edc4f72e46)
2025-01-27 12:40:45 +00:00
Jordan Cruz-Correa
ddf761e802 feat: add oklch color format support for /colors (#6441) 2025-01-24 17:53:45 +04:00
shadcn
5f7957ab51 feat(shadcn): add new registry:file type (#6377)
* feat(shadcn): add new registry:file type

* chore: add changeset

* fix: file target
2025-01-16 15:46:33 +04:00
shadcn
f07c7ad5d0 feat(shadcn): handle nested paths for components (#6369)
* feat(shadcn): handle nested paths for components

* chore: add changeset
2025-01-15 18:01:44 +04:00
shadcn
d5aa527f0b docs(www): add block preview image 2025-01-15 15:14:12 +04:00
shadcn
5ec990a474 docs(www): add blocks contribution docs (#6354)
* docs(www): add blocks contrib docs

* docs(www): update intro

* feat: update banner

* docs: add link

* docs: update block types
2025-01-14 21:56:45 +04:00
shadcn
cb742e9825 feat: add new registry build command (#6350)
* feat: implement shadcn/registry

* feat: add schema field

* fix: import

* chore: add changeset

* chore: remove console

* fix: tests

* fix: diff command

* feat: move to schema/registy-item.json

* fix

* ci: switch to node 20

* ci: build packages

* fix: types

* chore: update schema

* chore: update build registry script

* feat(shadcn): add build command
2025-01-14 15:59:41 +04:00
shadcn
254198b4bf feat: add shadcn/registry (#6339)
* feat: implement shadcn/registry

* feat: add schema field

* fix: import

* chore: add changeset

* chore: remove console

* fix: tests

* fix: diff command

* feat: move to schema/registy-item.json

* fix

* ci: switch to node 20

* ci: build packages
2025-01-14 10:50:19 +04:00
shadcn
1081536246 fix: path 2024-12-20 19:12:22 +04:00
shadcn
811bb59a8f feat(shadcn): monorepo support (#6104) 2024-12-20 19:07:17 +04:00
shadcn
ea677cc74e fix: open in v0 2024-12-17 23:14:59 +04:00
github-actions[bot]
9253b43f29 chore(release): version packages (#6094)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-12-16 23:56:28 +04:00
shadcn
c8fda09a63 feat(cli): detect and use next version (#6093)
* feat: detect and use next version

* chore: changeset
2024-12-16 23:53:40 +04:00
shadcn
1ff01b1bb5 fix(www): adjust reading width 2024-12-16 12:43:11 +04:00
shadcn
f10d59fee9 fix(www): themes page 2024-12-16 12:26:48 +04:00
shadcn
2f869a2590 feat(www): code blocks package manager (#6075)
* feat(www): code blocks

* fix: code style
2024-12-14 19:13:06 +04:00
LiuJie
102b0b0c62 fix: calendar icon components' props not use. (#5222)
* fix: calendar icon components' props not use.

* fix: calendar icon components' props not use.

* style: revert the original className order

* fix: calendar icon components' props not use.

---------

Co-authored-by: shadcn <m@shadcn.com>
2024-12-14 15:16:52 +04:00
Ahad Rizvi
387756c4ab Remove unused imports from shadcn chart (#4391)
* Remove unused imports from shadcn chart

* chore: rebuild registry

---------

Co-authored-by: shadcn <m@shadcn.com>
2024-12-14 15:03:01 +04:00
shadcn
05145e66d3 feat: refactor registry (#6071)
* feat(www): add login blocks

* chore(www): restructure for blocks

* chore: build registry

* chore: clean up chunks

* fix(www): chart categories

* feat(www): big registry refactor

* feat(www): update blocks

* feat: complex blocks

* fix: update schema

* feat: sync new-york and default

* fix: lint

* feat: move charts

* fix(www): code

* fix: src path

* chore: rebuild registry

* fix: screenshot

* fix: set new-york as default
2024-12-14 14:52:55 +04:00
shadcn
704991247c fix: ui imports for open in v0 (#6029) 2024-12-10 14:20:50 +04:00
github-actions[bot]
729b9ec8ca chore(release): version packages (#5812)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-12-10 12:17:32 +04:00
Jens Astrup
a1bed464f3 chore(apps): Update lodash (#4397)
* chore(apps): Refactor usage of lodash.template to lodash to address security vulnerability

* chore(cli): Refactor usage of lodash.template to lodash to address security vulnerability

* deps: update lock

* chore: changesets

* style: fix format

* fix: import

* chore: build registry

---------

Co-authored-by: shadcn <m@shadcn.com>
2024-12-09 12:33:28 +04:00
shadcn
805ed4120a feat(www): refactor examples page (#5818) 2024-11-13 16:01:14 +04:00
shadcn
600a593c87 docs(www): update manual installation (#5817) 2024-11-13 15:42:52 +04:00
Braden Corbold
500dbe2664 fix(shadcn) arrays and nested deeply nested spread (#5711)
* fix: tailwind config updater parser

* fix: remove quote around spread element

* fix: specify deepmerge option for array

* fix(shadcn): Nested and spread array elements

* add test case for boolean primitive

---------

Co-authored-by: matsuyoshi30 <sfbgwm30@gmail.com>
Co-authored-by: shadcn <m@shadcn.com>
2024-11-13 15:19:21 +04:00
JEM
c577ee0666 docs(contributing): add CLI usage instructions (#5807)
Introduce a detailed workflow for running the CLI locally, ensuring contributors can easily test and develop enhancements in their local environment. This addition helps maintain consistency between CLI and component updates.
2024-11-13 15:17:11 +04:00
shadcn
d5bf0018fd fix(shadcn): handling of tsconfig aliases (#5813)
* fix(shadcn): handling of tsconfig aliases

* chore: add changeset
2024-11-13 15:15:22 +04:00
Tobbe Lundberg
fb36ca4159 fix(transform): Support aliases that are longer than one character (#5678)
* fix(transform): Support aliases that are longer than one character

* feat(shadcn): update handling of aliases

* chore: add changeset

---------

Co-authored-by: shadcn <m@shadcn.com>
2024-11-12 22:13:38 +04:00
JEM
824a35ada1 test(snapshots): update test snapshots for consistency (#5801)
Updated snapshot components to use `React.ComponentProps` instead of custom interfaces for `input` and `Dialog` components. This simplifies the code by leveraging built-in React types, ensuring consistency and reducing potential errors arising from custom definitions.
2024-11-12 12:54:31 +04:00
shadcn
8d520c8d49 fix(www): figma link (#5798) 2024-11-11 20:08:24 +04:00
Temkin Mengsitu
0873835339 fix: ensure block component name is copied in shadcn (#5790) 2024-11-11 12:10:04 +04:00
JEM
4a0d4cfdb9 refactor(components): remove redundant empty interfaces from Input, Command, and Textarea components (#5657)
* refactor(components): remove redundant empty interfaces from Input, Command, and Textarea components

Simplified props handling by removing redundant empty interfaces (`InputProps`, `CommandDialogProps`, `TextareaProps`). Directly extended native HTML attributes/lib for each component, enhancing code clarity and maintainability. This refactor ensures compliance with ESLint rules and streamlines future modifications without altering component functionality.

* updated the registry

* rerun build:registry with updated command

* Refactor input and textarea components to use `React.ComponentProps`

* update registry
2024-11-08 20:26:01 +04:00
shadcn
c4c5d8d419 feat(www): update next-themes 2024-11-08 15:18:32 +04:00
Wing Chung Ng
9253682b87 refactor(www): update ThemeProviderProps type extraction for next-themes v0.4.0 compatibility (#5701)
Since next-themes v0.4.0 no longer provides types in `dist`, replaced the import of `ThemeProviderProps` from `next-themes/dist/types` with a new type extraction using `React.ComponentProps`. This change ensures continued type safety and compatibility with the updated library version.

Co-authored-by: shadcn <m@shadcn.com>
2024-11-08 15:16:10 +04:00
shadcn
c7cd16a637 fix(www): interactive chart 2024-11-07 21:36:19 +04:00
shadcn
eff1918d41 fix: update file handling 2024-11-07 21:21:42 +04:00
shadcn
366d6b656b fix(www): file tracing 2024-11-07 21:05:44 +04:00
shadcn
e489c5e08e fix(www): file tracing 2024-11-07 20:54:26 +04:00
shadcn
d87003e0a4 fix(www): sidebar 2024-11-07 20:40:18 +04:00
shadcn
a8633075f7 fix(www): component source 2024-11-07 20:36:21 +04:00
shadcn
432d5e6e28 fix: code view for mdx 2024-11-07 17:45:34 +04:00
shadcn
8f0c26f22a feat(www): code for blocks (#5756)
* feat: update blocks

* fix: scrollbars

* fix: code viewer

* test(shadcn): fix
2024-11-07 17:09:41 +04:00
shadcn
149b321c1b fix: lint 2024-11-06 17:34:21 +04:00
shadcn
c1ae5a57cc feat(www): nav color 2024-11-06 17:29:53 +04:00
shadcn
b8ed303d8c Merge branch 'main' of github.com:shadcn-ui/ui 2024-11-06 17:19:41 +04:00
shadcn
13c97acf9f fix: font size 2024-11-06 17:16:21 +04:00
shadcn
bed277c54d feat( www): minor updates (#5749)
* feat(www): update mode switcher

* feat(www): update layout

* feat(www): update theme-color handling

* fix(www): docs pages
2024-11-06 17:03:28 +04:00
github-actions[bot]
f7c42169a6 chore(release): version packages (#5733)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-11-06 00:19:13 +04:00
shadcn
2c2fe97eb9 feat: move new-york to lucide-react (#5602)
* feat: move new-york to lucide-react

* fix: mail open

* chore: update registry

* chore: add test:dev

* chore: add changeset

* feat: build an icon registry

* chore: add missing registry icons

* feat: add an icons debug page

* feat: add an icon migration

* chore(www): migrate all radix icons to lucide

* feat: update migration script

* chore: update changeset

* feat(shadcn): implement icons transformer

* fix: missing registry icons

* fix(shadcn): handling of missing icons

* feat: add support for multiple libraries
2024-11-06 00:00:41 +04:00
shadcn
d64374d009 fix(www): chart colors 2024-11-04 23:06:14 +04:00
github-actions[bot]
e24e51a2fa chore(release): version packages (#5709)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-11-04 17:25:59 +04:00
shadcn
cdfecd1d97 Revert "feat: remove npm flags" (#5707)
* Revert "feat: remove npm flags (#5686)"

This reverts commit 4ff64ba818.

* chore: temporarily bring back flag
2024-11-04 17:14:08 +04:00
manfromexistence04
2c043e709f fix: svg props casing to camelCase (#5691) 2024-11-03 19:27:28 +04:00
Ghribi Ouassim
aed19aa911 docs(www): added --all option to add command in cli docs page (#5591) 2024-11-03 14:37:08 +04:00
shadcn
9e35d229ae feat: www updates (#5688)
* feat: www updates

* fix: lint
2024-11-03 14:35:04 +04:00
shadcn
db1975ef4d Merge branch 'main' of github.com:shadcn-ui/ui 2024-11-03 13:27:19 +04:00
shadcn
961e0b62d7 fix: a11y for card and calendar 2024-11-03 13:27:10 +04:00
github-actions[bot]
70c684c224 chore(release): version packages (#5687)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-11-03 12:17:58 +04:00
shadcn
4ff64ba818 feat: remove npm flags (#5686)
* docs: update status table and docs

* feat(shadcn): remove react-19 check

* chore: add changeset
2024-11-03 12:12:41 +04:00
shadcn
500a353816 feat: update home page (#5648) 2024-10-30 21:28:58 +04:00
Nicholas Lim
c830780d62 docs(www): fix diff for peerDependencies (#5640)
Co-authored-by: shadcn <m@shadcn.com>
2024-10-30 17:23:06 +04:00
Felix Lu
debd51a854 refactor(sidebar): improve setOpen callback logic (#5593)
* refactor(sidebar): improve setOpen callback logic
- Simplify state handling
- Ensure consistent cookie setting
- Added ability for users to persist sidebar state via cookies
- Update sidebar.mdx doc with SSR persistence instructions
- Run pnpm registry:build to update component

* docs: updates

---------

Co-authored-by: shadcn <m@shadcn.com>
2024-10-30 17:09:09 +04:00
shadcn
78426dd862 docs: fix diff 2024-10-30 16:31:44 +04:00
shadcn
6e47a94a8f chore: format 2024-10-30 15:40:46 +04:00
Barinderpreet Singh
ab6a856930 Docs: update vite installation docs (#4741)
* docs: update vite installation docs

* Update content config

---------

Co-authored-by: shadcn <m@shadcn.com>
2024-10-30 15:37:32 +04:00
shadcn
b33d3868e9 docs(www): update status for radix icons (#5638) 2024-10-30 11:29:48 +04:00
Maou
9e0a86122a fix(docs): resolve link issue in documentation pages (#5633)
Updated broken link in documentation routing file. 
This ensures that the correct content is loaded based on the slug.
2024-10-30 07:10:15 +00:00
github-actions[bot]
2b276de95a chore(release): version packages (#5625)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-10-29 21:39:58 +04:00
shadcn
64739f8399 feat: react 19 (#5621)
* feat(shadcn): add flag prompt for npm

* docs: add docs for react 19

* chore: add changeset

* test: update snapshots

* docs: add notes for recharts

* docs: fix

* fix

* fix: linting
2024-10-29 21:30:05 +04:00
shadcn
f0cff7e0eb fix: gaps in command and dropdown-menu (#5570)
* fix: gaps in command and dropdown-menu

* chore: update snapshots
2024-10-25 18:13:13 +04:00
github-actions[bot]
e242adaa9c chore(release): version packages (#5568)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-10-25 17:52:32 +04:00
Kiyan
986c00ee0e fix: shadcn init with correct packageManager (#5299)
* fix: shadcn init with correct  packageManager

* chore: changeset

---------

Co-authored-by: shadcn <m@shadcn.com>
2024-10-25 17:46:48 +04:00
shadcn
d0eece06d4 fix: update registry (#5530) 2024-10-23 17:35:26 +04:00
github-actions[bot]
0a0a566a4e chore(release): version packages (#5526)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-10-23 11:35:28 +04:00
Braden Corbold
bf5a79c4d4 fix(shadcn): fix transformRsc to account for ' (#5518)
* fix(shadcn): fix transformRsc to account for '

* chore: add changeset

---------

Co-authored-by: shadcn <m@shadcn.com>
2024-10-23 11:33:11 +04:00
shadcn
f02b412478 fix(shadcn): temporarily pin create-next-app (#5525)
* feat: temporarily pin create-next-app

* chore: add changeset
2024-10-23 11:30:29 +04:00
DADDOU Mohamed El Amine
0d31293c7b docs: add missing installation step in the sidebar documentation (#5467) 2024-10-22 12:15:03 +04:00
Jacob Paris
3d1d19fc1b fix(sidebar): only apply text-sidebar-foreground to sidebar (#5491)
The text-sidebar-foreground class was applied to the SidebarProvider, which is designed to wrap the whole app, therefore making the whole app inherit its font color. This has been removed. The correct place for it is in the Sidebar component

Of the 3 possible return cases in the Sidebar component, 2 already applied the text-sidebar-foreground and I have added it to the third
2024-10-22 08:02:20 +00:00
BNaushad
e5b56c84a9 docs(sidebar): fix typo in sidebar documentation (#5487) 2024-10-22 11:42:38 +04:00
shadcn
630afe836e fix: sidebar provider 2024-10-21 12:38:51 +04:00
Jakob Ortmann
52c12bc27a fix import in sidebar example (#5438) 2024-10-21 11:46:27 +04:00
JEM
182f2083cd docs(sidebar): Fix sidebar toggle text for consistent behavior (#5428) 2024-10-21 11:45:11 +04:00
Phaired
3febcdc523 docs: fix type in sidebar.mdx (#5452) 2024-10-21 11:43:35 +04:00
EdamAmex
ced2513137 docs: fix prop name (#5460) 2024-10-21 11:43:08 +04:00
shadcn
93ae8bd67f chore: deprecate next-template (#5478)
* chore: deprecate next-template

* chore: tests
2024-10-21 11:40:54 +04:00
shadcn
3259fb7ca1 fix: overflow on icon 2024-10-19 01:28:07 +04:00
shadcn
d8397d80a8 chore: rebuild registry 2024-10-18 23:25:49 +04:00
shadcn
06e74fce78 Merge branch 'shadcn/sidebar-tsx'
# Conflicts:
#	apps/www/public/r/styles/default/use-mobile.json
#	apps/www/public/r/styles/new-york/button.json
#	apps/www/public/r/styles/new-york/dropdown-menu.json
#	apps/www/public/r/styles/new-york/use-mobile.json
#	apps/www/registry/new-york/ui/button.tsx
#	apps/www/registry/new-york/ui/dropdown-menu.tsx
2024-10-18 23:14:27 +04:00
shadcn
bc9e5eaaab test: fix 2024-10-18 22:52:25 +04:00
shadcn
c9b69d0836 docs: remove beta 2024-10-18 22:22:25 +04:00
shadcn
539212c49e feat: add redirect 2024-10-18 15:29:28 +04:00
shadcn
123887c36c fix: blocks 2024-10-18 00:54:19 +04:00
shadcn
35c1ba57c2 feat: add login-01 to v0 2024-10-18 00:34:03 +04:00
shadcn
b34516f471 fix: lint 2024-10-17 22:59:23 +04:00
shadcn
66b95402c1 feat: add v0 registry blocks 2024-10-17 22:58:20 +04:00
shadcn
5460177a7a fix: sidebar-07 2024-10-17 19:49:28 +04:00
shadcn
b0049c2266 fix: data-name 2024-10-17 17:13:47 +04:00
shadcn
4e6e21f094 fix: dark mode blocks 2024-10-17 17:01:24 +04:00
shadcn
3b808c83be fix: lint 2024-10-17 16:33:34 +04:00
shadcn
0e6b37e99a feat: update preview for mobile 2024-10-17 16:28:09 +04:00
shadcn
9ec433838f fix: lint 2024-10-17 01:11:00 +04:00
shadcn
444ff70590 feat: add notice 2024-10-17 01:06:35 +04:00
shadcn
27bc5deff1 docs: beta testers 2024-10-17 00:55:28 +04:00
shadcn
cacd7c8798 fix: use-mobile 2024-10-17 00:36:50 +04:00
shadcn
f227f93742 fix: sidebar 2024-10-17 00:34:50 +04:00
shadcn
87e099a3d7 feat: update button and dropdown menu (#5408) 2024-10-17 00:21:03 +04:00
shadcn
303d65718c chore: registry updates 2024-10-16 23:58:44 +04:00
shadcn
909219df14 docs 2024-10-16 15:33:16 +04:00
shadcn
e8ada4e3c7 docs 2024-10-16 14:33:43 +04:00
shadcn
8fc80836ff docs 2024-10-16 12:54:21 +04:00
shadcn
d0a308cc64 fix: edit in v0 2024-10-16 11:12:58 +04:00
shadcn
e461c02389 feat: update open in v0 2024-10-15 21:28:38 +04:00
shadcn
50c2f6045a chore: fix lint 2024-10-15 12:52:40 +04:00
shadcn
14aca65eee docs: add sidebar docs 2024-10-15 12:49:22 +04:00
shadcn
14c952b594 chore: update registry 2024-10-15 12:41:20 +04:00
shadcn
1e9434e6f9 chore: build registry 2024-10-15 12:38:45 +04:00
shadcn
f3d14c48cb feat(www): update button and tooltip 2024-10-15 12:38:32 +04:00
shadcn
c668c35bb9 feat(www): add sidebar components 2024-10-15 12:37:47 +04:00
shadcn
1297abc882 fix: v0 2024-10-07 21:13:33 +04:00
shadcn
36ebbf26dc docs(www): update figma 2024-09-29 14:52:52 +04:00
github-actions[bot]
a2abc4ad95 chore(release): version packages (#4963)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-09-27 22:10:05 +04:00
Thomas Raffray
4b546bfb13 fix(cli): remix detection (#4972)
# What

Some remix templates doesn't package a `vite.config.*` file at their root.
It's the case for the recommended starter "stack" templates: blues-stack, indie-stack and grunge-stack.
As recommended in a TODO comment, it's more suitable to check for a `@remix-run/*` dependency in the package dependencies.

# How

- decouple vite and remix checks
- retrieve the `package.json`
- allow passing a `cwd` to the retrieval method
- remove the "empty config file list" that can be empty for a remix stack
- check that the `package.json` contains a `@remix-run/*` dependency

# Test

Added a fixture by running `npx create-remix@latest --template remix-run/indie-stack` in the [frameworks](/Fluf22/shadcn-ui/tree/fix/cli-remix-detection/packages/cli/test/fixtures/frameworks) folder and named it `remix-indie-stack`, if ever we want another stack as a fixture later

---

Fixes shadcn-ui/ui#4967
2024-09-27 17:58:46 +00:00
shadcn
5fc9ade413 chore(cli): add deprecation notice (#4988)
* feat(cli): add deprecation message

* chore: changesets
2024-09-27 17:43:16 +04:00
Nami Sunami
96880e7c9a fix: Fix the typo in error messages (component.jsoncomponents.json) (#4977)
* fix(prompt): fix error message, component.json -> components.json

* fix(config): fix the error message, component.json -> components.json

* chore: changeset

---------

Co-authored-by: shadcn <m@shadcn.com>
2024-09-27 16:01:04 +04:00
Artem Zakharchenko
7dfdb029e7 use "?url" suffix in "tailwind.css" import (Remix) (#4945)
Importing `tailwind.css` is incorrect as it will treat that file as a CSS Module, throwing that it has no `default` export. 

Instead, the tutorial relies on the import returning a _URL_ to the stylesheet. Adding `?url` to the import specifier is the way to go.
2024-09-26 12:42:49 +00:00
shadcn
28f34ed3c3 feat(shadcn): recursively resolve registry dependencies (#4961)
* feat(shadcn): recursively resolve registry dependencies

* chore: add changeset

* ci: update actions/upload-artifact
2024-09-25 22:56:39 +04:00
github-actions[bot]
bd54184e60 chore(release): version packages (#4938)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-09-23 15:21:55 +04:00
Braden Corbold
ce3adfa075 fix(shadcn): Support single quote formatted registry files (#4870) (#4871) 2024-09-23 11:47:37 +04:00
Shivang Rathore
674807c1b4 fix(shadcn): Ensure .scss files are considered in init command (#4866)
Co-authored-by: shadcn <m@shadcn.com>
2024-09-23 11:44:27 +04:00
sapenlei
c62167a449 fix(shadcn): default next styles are not completely cleared (#4922)
* fix(shadcn):  default next styles are not completely cleared

* chore: add changeset

* fix(shadcn): tests

---------

Co-authored-by: shadcn <m@shadcn.com>
2024-09-23 11:38:59 +04:00
3317 changed files with 162898 additions and 56663 deletions

View File

@@ -0,0 +1,5 @@
---
"shadcn": minor
---
add support for TanStack Start

View File

@@ -0,0 +1,5 @@
---
"shadcn": patch
---
support for version detection in monorepo

View File

@@ -7,5 +7,5 @@
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": ["www", "**-template"]
"ignore": ["www", "v4"]
}

View File

@@ -0,0 +1,5 @@
---
"shadcn": patch
---
upgrade @antfu/ni

View File

@@ -0,0 +1,5 @@
---
"shadcn": patch
---
allow silent mode with npm

View File

@@ -0,0 +1,5 @@
---
"shadcn": patch
---
do not add ring for v3

View File

@@ -0,0 +1,5 @@
---
"shadcn": minor
---
add theme vars support

View File

@@ -0,0 +1,5 @@
---
"shadcn": minor
---
add tailwind version detection

View File

@@ -0,0 +1,5 @@
---
"shadcn": minor
---
add --base-color flag

View File

@@ -0,0 +1,5 @@
---
"shadcn": minor
---
add support for tailwind v4

View File

@@ -0,0 +1,5 @@
---
"shadcn": minor
---
default to css vars. add --no-css-variables

View File

@@ -0,0 +1,5 @@
---
"shadcn": patch
---
cache registry calls

View File

@@ -0,0 +1,5 @@
---
"shadcn": minor
---
replace tailwindcss-animate with tw-animate-css

View File

@@ -0,0 +1,5 @@
---
"shadcn": patch
---
add --template flag

View File

@@ -0,0 +1,5 @@
---
"shadcn": minor
---
default for new-york for v4

View File

@@ -0,0 +1,5 @@
---
"shadcn": minor
---
fix handling of sidebar colors

View File

@@ -0,0 +1,5 @@
---
"shadcn": patch
---
do not overwrite user defined vars

View File

@@ -0,0 +1,5 @@
---
"shadcn": patch
---
fix cn import bug in monorepo

View File

@@ -0,0 +1,5 @@
---
"shadcn": patch
---
filter out deprecated from --all

View File

@@ -0,0 +1,5 @@
---
"shadcn": minor
---
add oklch base color

View File

@@ -0,0 +1,5 @@
---
"shadcn": minor
---
hotswap style for v4

View File

@@ -0,0 +1,5 @@
---
"shadcn": patch
---
check for empty css vars

View File

@@ -0,0 +1,5 @@
---
"shadcn": patch
---
only show deprecated for new projects

View File

@@ -0,0 +1,5 @@
---
"shadcn": minor
---
add warning for deprecated components

View File

@@ -0,0 +1,5 @@
---
"shadcn": patch
---
fix tanstack check

View File

@@ -0,0 +1,5 @@
---
"shadcn": minor
---
add support for route install for react-router and laravel

View File

@@ -0,0 +1,5 @@
---
"shadcn": minor
---
add theme prop to registry-item schema

8
.eslintignore Normal file
View File

@@ -0,0 +1,8 @@
node_modules/
target/
.next/
build/
dist/
/templates/
/fixtures/

3
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,3 @@
# These are supported funding model platforms
github: [shadcn]

View File

@@ -16,7 +16,7 @@ jobs:
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: 18
node-version: 20
- uses: pnpm/action-setup@v4
name: Install pnpm
@@ -52,7 +52,7 @@ jobs:
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: 18
node-version: 20
- uses: pnpm/action-setup@v4
name: Install pnpm
@@ -90,7 +90,7 @@ jobs:
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: 18
node-version: 20
- uses: pnpm/action-setup@v4
name: Install pnpm
@@ -113,4 +113,7 @@ jobs:
- name: Install dependencies
run: pnpm install
- name: Build packages
run: pnpm --filter=shadcn build
- run: pnpm typecheck

View File

@@ -1,5 +1,5 @@
# Adapted from vercel/next.js
name: Issue Stale
name: "Stale issue handler"
on:
workflow_dispatch:
schedule:
@@ -11,17 +11,35 @@ jobs:
runs-on: ubuntu-latest
if: github.repository_owner == 'shadcn-ui'
steps:
- uses: actions/stale@v4
id: stale-no-repro
name: "Close stale issues with no reproduction"
- uses: actions/stale@v9
id: issue-stale
name: "Mark stale issues, close stale issues"
with:
repo-token: ${{ secrets.STALE_TOKEN }}
close-issue-message: "This issue has been automatically closed because it received no activity for a while. If you think it was closed by accident, please reopen or leave a comment. Thank you.\n(This is an automated message.)"
ascending: true
days-before-issue-close: 7
days-before-issue-stale: 30
stale-pr-label: "stale?"
days-before-pr-close: 7
days-before-pr-stale: 15
only-pr-labels: "postpone: more info or changes requested,please add a reproduction"
days-before-issue-stale: 365 # ~2 years
days-before-pr-stale: -1
days-before-pr-close: -1
remove-issue-stale-when-updated: true
stale-issue-label: "stale?"
exempt-issue-labels: "roadmap,next,bug"
stale-issue-message: "This issue has been automatically marked as stale due to one year of inactivity. It will be closed in 7 days unless theres further input. If you believe this issue is still relevant, please leave a comment or provide updated details. Thank you."
close-issue-message: "This issue has been automatically closed due to one year of inactivity. If youre still experiencing a similar problem or have additional details to share, please open a new issue following our current issue template. Your updated report helps us investigate and address concerns more efficiently. Thank you for your understanding!"
operations-per-run: 300 # 1 operation per 100 issues, the rest is to label/comment/close
- uses: actions/stale@v9
id: pr-state
name: "Mark stale PRs, close stale PRs"
with:
repo-token: ${{ secrets.STALE_TOKEN }}
ascending: true
days-before-issue-close: -1
days-before-issue-stale: -1
days-before-pr-close: 7
days-before-pr-stale: 365 # PRs with no activity in over 90 days will be marked as stale
remove-pr-stale-when-updated: true
exempt-pr-labels: "roadmap,nex,awaiting-approval,work-in-progress"
stale-pr-label: "stale?"
stale-pr-message: "This PR has been automatically marked as stale due to one year of inactivity. It will be closed in 7 days unless theres further input. If you believe this PR is still relevant, please leave a comment or provide updated details. Thank you."
close-pr-message: "This PR has been automatically closed due to one year of inactivity. Thank you for your understanding!"
operations-per-run: 300 # 1 operation per 100 issues, the rest is to label/comment/close

View File

@@ -49,7 +49,7 @@ jobs:
A new prerelease is available for testing:
```sh
npx shadcn@${{ env.BETA_PACKAGE_VERSION }}
pnpm dlx shadcn@${{ env.BETA_PACKAGE_VERSION }}
```
- name: "Remove the autorelease label once published"

View File

@@ -54,7 +54,7 @@ jobs:
path: packages/shadcn
- name: Upload packaged artifact
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: npm-package-shadcn@${{ steps.package-version.outputs.current-version }}-pr-${{ github.event.number }} # encode the PR number into the artifact name
path: packages/shadcn/dist/index.js

7
.gitignore vendored
View File

@@ -33,4 +33,9 @@ yarn-error.log*
.turbo
.contentlayer
tsconfig.tsbuildinfo
tsconfig.tsbuildinfo
# ide
.idea
.fleet
.vscode

1
.npmrc
View File

@@ -1 +1,2 @@
auto-install-peers=true
link-workspace-packages=true

View File

@@ -4,8 +4,10 @@
{ "pattern": "packages/*/" }
],
"tailwindCSS.experimental.classRegex": [
["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"],
["cn\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"]
["cva\\(((?:[^()]|\\([^()]*\\))*)\\)", "[\"'`]?([^\"'`]+)[\"'`]?"],
["cn\\(((?:[^()]|\\([^()]*\\))*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"]
// "cva\\(([^)]*)\\)",
// "[\"'`]([^\"'`]*).*?[\"'`]"
],
"vitest.debugExclude": [
"<node_internals>/**",

View File

@@ -91,6 +91,42 @@ pnpm --filter=www dev
pnpm --filter=shadcn-ui dev
```
## Running the CLI Locally
To run the CLI locally, you can follow the workflow:
1. Start by running the registry (main site) to make sure the components are up to date:
```bash
pnpm www:dev
```
2. Run the development script for the CLI:
```bash
pnpm shadcn:dev
```
3. In another terminal tab, test the CLI by running:
```bash
pnpm shadcn
```
To test the CLI in a specific app, use a command like:
```bash
pnpm shadcn <init | add | ...> -c ~/Desktop/my-app
```
4. To run the tests for the CLI:
```bash
pnpm --filter=shadcn test
```
This workflow ensures that you are running the most recent version of the registry and testing the CLI properly in your local environment.
## Documentation
The documentation for this project is located in the `www` workspace. You can run the documentation locally by running the following command:

View File

@@ -3,7 +3,12 @@
# dependencies
/node_modules
/.pnp
.pnp.js
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions
# testing
/coverage
@@ -23,9 +28,10 @@
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# local env files
.env*.local
# env files (can opt-in for committing if needed)
.env*
# vercel
.vercel

6
apps/v4/.prettierignore Normal file
View File

@@ -0,0 +1,6 @@
dist
node_modules
.next
build
.contentlayer
registry/__index__.tsx

1
apps/v4/README.md Normal file
View File

@@ -0,0 +1 @@
This is a wip registry for the `shadcn` canary version. It has React 19 and Tailwind v4 components.

View File

@@ -0,0 +1 @@
// The content of this directory is autogenerated by the registry server.

View File

View File

@@ -0,0 +1 @@
> Files inside this directory is autogenerated by `./scripts/build-registry.ts`. **Do not edit them manually.** - shadcn

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,76 @@
export { ChartAreaDefault } from "@/registry/new-york-v4/charts/chart-area-default"
export { ChartAreaLinear } from "@/registry/new-york-v4/charts/chart-area-linear"
export { ChartAreaStep } from "@/registry/new-york-v4/charts/chart-area-step"
export { ChartAreaLegend } from "@/registry/new-york-v4/charts/chart-area-legend"
export { ChartAreaStacked } from "@/registry/new-york-v4/charts/chart-area-stacked"
export { ChartAreaStackedExpand } from "@/registry/new-york-v4/charts/chart-area-stacked-expand"
export { ChartAreaIcons } from "@/registry/new-york-v4/charts/chart-area-icons"
export { ChartAreaGradient } from "@/registry/new-york-v4/charts/chart-area-gradient"
export { ChartAreaAxes } from "@/registry/new-york-v4/charts/chart-area-axes"
export { ChartAreaInteractive } from "@/registry/new-york-v4/charts/chart-area-interactive"
export { ChartBarDefault } from "@/registry/new-york-v4/charts/chart-bar-default"
export { ChartBarHorizontal } from "@/registry/new-york-v4/charts/chart-bar-horizontal"
export { ChartBarMultiple } from "@/registry/new-york-v4/charts/chart-bar-multiple"
export { ChartBarStacked } from "@/registry/new-york-v4/charts/chart-bar-stacked"
export { ChartBarLabel } from "@/registry/new-york-v4/charts/chart-bar-label"
export { ChartBarLabelCustom } from "@/registry/new-york-v4/charts/chart-bar-label-custom"
export { ChartBarMixed } from "@/registry/new-york-v4/charts/chart-bar-mixed"
export { ChartBarActive } from "@/registry/new-york-v4/charts/chart-bar-active"
export { ChartBarNegative } from "@/registry/new-york-v4/charts/chart-bar-negative"
export { ChartBarInteractive } from "@/registry/new-york-v4/charts/chart-bar-interactive"
export { ChartLineDefault } from "@/registry/new-york-v4/charts/chart-line-default"
export { ChartLineLinear } from "@/registry/new-york-v4/charts/chart-line-linear"
export { ChartLineStep } from "@/registry/new-york-v4/charts/chart-line-step"
export { ChartLineMultiple } from "@/registry/new-york-v4/charts/chart-line-multiple"
export { ChartLineDots } from "@/registry/new-york-v4/charts/chart-line-dots"
export { ChartLineDotsCustom } from "@/registry/new-york-v4/charts/chart-line-dots-custom"
export { ChartLineDotsColors } from "@/registry/new-york-v4/charts/chart-line-dots-colors"
export { ChartLineLabel } from "@/registry/new-york-v4/charts/chart-line-label"
export { ChartLineLabelCustom } from "@/registry/new-york-v4/charts/chart-line-label-custom"
export { ChartLineInteractive } from "@/registry/new-york-v4/charts/chart-line-interactive"
export { ChartPieSimple } from "@/registry/new-york-v4/charts/chart-pie-simple"
export { ChartPieSeparatorNone } from "@/registry/new-york-v4/charts/chart-pie-separator-none"
export { ChartPieLabel } from "@/registry/new-york-v4/charts/chart-pie-label"
export { ChartPieLabelCustom } from "@/registry/new-york-v4/charts/chart-pie-label-custom"
export { ChartPieLabelList } from "@/registry/new-york-v4/charts/chart-pie-label-list"
export { ChartPieLegend } from "@/registry/new-york-v4/charts/chart-pie-legend"
export { ChartPieDonut } from "@/registry/new-york-v4/charts/chart-pie-donut"
export { ChartPieDonutActive } from "@/registry/new-york-v4/charts/chart-pie-donut-active"
export { ChartPieDonutText } from "@/registry/new-york-v4/charts/chart-pie-donut-text"
export { ChartPieStacked } from "@/registry/new-york-v4/charts/chart-pie-stacked"
export { ChartPieInteractive } from "@/registry/new-york-v4/charts/chart-pie-interactive"
export { ChartRadarDefault } from "@/registry/new-york-v4/charts/chart-radar-default"
export { ChartRadarDots } from "@/registry/new-york-v4/charts/chart-radar-dots"
export { ChartRadarLinesOnly } from "@/registry/new-york-v4/charts/chart-radar-lines-only"
export { ChartRadarLabelCustom } from "@/registry/new-york-v4/charts/chart-radar-label-custom"
export { ChartRadarGridCustom } from "@/registry/new-york-v4/charts/chart-radar-grid-custom"
export { ChartRadarGridNone } from "@/registry/new-york-v4/charts/chart-radar-grid-none"
export { ChartRadarGridCircle } from "@/registry/new-york-v4/charts/chart-radar-grid-circle"
export { ChartRadarGridCircleNoLines } from "@/registry/new-york-v4/charts/chart-radar-grid-circle-no-lines"
export { ChartRadarGridCircleFill } from "@/registry/new-york-v4/charts/chart-radar-grid-circle-fill"
export { ChartRadarGridFill } from "@/registry/new-york-v4/charts/chart-radar-grid-fill"
export { ChartRadarMultiple } from "@/registry/new-york-v4/charts/chart-radar-multiple"
export { ChartRadarLegend } from "@/registry/new-york-v4/charts/chart-radar-legend"
export { ChartRadarIcons } from "@/registry/new-york-v4/charts/chart-radar-icons"
export { ChartRadarRadius } from "@/registry/new-york-v4/charts/chart-radar-radius"
export { ChartRadialSimple } from "@/registry/new-york-v4/charts/chart-radial-simple"
export { ChartRadialLabel } from "@/registry/new-york-v4/charts/chart-radial-label"
export { ChartRadialGrid } from "@/registry/new-york-v4/charts/chart-radial-grid"
export { ChartRadialText } from "@/registry/new-york-v4/charts/chart-radial-text"
export { ChartRadialShape } from "@/registry/new-york-v4/charts/chart-radial-shape"
export { ChartRadialStacked } from "@/registry/new-york-v4/charts/chart-radial-stacked"
export { ChartTooltipDefault } from "@/registry/new-york-v4/charts/chart-tooltip-default"
export { ChartTooltipIndicatorLine } from "@/registry/new-york-v4/charts/chart-tooltip-indicator-line"
export { ChartTooltipIndicatorNone } from "@/registry/new-york-v4/charts/chart-tooltip-indicator-none"
export { ChartTooltipLabelCustom } from "@/registry/new-york-v4/charts/chart-tooltip-label-custom"
export { ChartTooltipLabelFormatter } from "@/registry/new-york-v4/charts/chart-tooltip-label-formatter"
export { ChartTooltipLabelNone } from "@/registry/new-york-v4/charts/chart-tooltip-label-none"
export { ChartTooltipFormatter } from "@/registry/new-york-v4/charts/chart-tooltip-formatter"
export { ChartTooltipIcons } from "@/registry/new-york-v4/charts/chart-tooltip-icons"
export { ChartTooltipAdvanced } from "@/registry/new-york-v4/charts/chart-tooltip-advanced"

View File

@@ -0,0 +1,20 @@
import { ComponentWrapper } from "@/components/component-wrapper"
import * as Charts from "@/app/(app)/charts/charts"
export default function ChartsPage() {
return (
<div className="grid flex-1 grid-cols-3 items-start gap-4 p-4 2xl:grid-cols-4">
{Object.entries(Charts)
.sort()
.map(([key, Component]) => (
<ComponentWrapper
key={key}
name={key}
className="w-auto data-[name=chartareainteractive]:col-span-3 data-[name=chartbarinteractive]:col-span-3 data-[name=chartlineinteractive]:col-span-3 **:data-[slot=card]:w-full"
>
<Component />
</ComponentWrapper>
))}
</div>
)
}

View File

@@ -0,0 +1,14 @@
import { FormsDemo } from "@/components/forms-demo"
export default function FormsPage() {
return (
<div className="flex flex-1 flex-col items-center justify-center gap-12 p-4 lg:flex-row">
<div className="">
<FormsDemo />
</div>
<div className="theme-scaled">
<FormsDemo />
</div>
</div>
)
}

View File

@@ -0,0 +1,44 @@
import { cookies } from "next/headers"
import { AppSidebar } from "@/components/app-sidebar"
import { ModeSwitcher } from "@/components/mode-switcher"
import { NavHeader } from "@/components/nav-header"
import { ThemeSelector } from "@/components/theme-selector"
import { Separator } from "@/registry/new-york-v4/ui/separator"
import {
SidebarInset,
SidebarProvider,
SidebarTrigger,
} from "@/registry/new-york-v4/ui/sidebar"
export default async function AppLayout({
children,
}: Readonly<{
children: React.ReactNode
}>) {
const cookieStore = await cookies()
const defaultOpen = cookieStore.get("sidebar_state")?.value === "true"
return (
<SidebarProvider defaultOpen={defaultOpen}>
<AppSidebar />
<SidebarInset>
<header className="bg-background sticky inset-x-0 top-0 isolate z-10 flex shrink-0 items-center gap-2 border-b">
<div className="flex h-14 w-full items-center gap-2 px-4">
<SidebarTrigger className="-ml-1.5" />
<Separator
orientation="vertical"
className="mr-2 data-[orientation=vertical]:h-4"
/>
<NavHeader />
<div className="ml-auto flex items-center gap-2">
<ThemeSelector />
<ModeSwitcher />
</div>
</div>
</header>
{children}
</SidebarInset>
</SidebarProvider>
)
}

View File

@@ -0,0 +1,40 @@
import {
Manrope as FontManrope,
Lexend as FontSans,
Newsreader as FontSerif,
} from "next/font/google"
import { cn } from "@/lib/utils"
import { LoginForm } from "@/components/login-form"
const fontSans = FontSans({ subsets: ["latin"], variable: "--font-sans" })
const fontSerif = FontSerif({ subsets: ["latin"], variable: "--font-serif" })
const fontManrope = FontManrope({
subsets: ["latin"],
variable: "--font-manrope",
})
export default function LoginPage() {
return (
<div
className={cn(
"bg-muted dark:bg-background flex flex-1 flex-col items-center justify-center gap-16 p-6 md:p-10",
fontSans.variable,
fontSerif.variable,
fontManrope.variable
)}
>
<div className="w-full max-w-sm md:max-w-3xl">
<LoginForm />
</div>
<div className="theme-login-one w-full max-w-sm md:max-w-3xl">
<LoginForm imageUrl="https://images.unsplash.com/photo-1482872376051-5ce74ebf0908?q=80&w=3050&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" />
</div>
<div className="theme-login-two w-full max-w-sm md:max-w-3xl">
<LoginForm imageUrl="https://images.unsplash.com/photo-1498758536662-35b82cd15e29?q=80&w=3088&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" />
</div>
<div className="theme-login-three w-full max-w-sm md:max-w-3xl">
<LoginForm imageUrl="https://images.unsplash.com/photo-1536147116438-62679a5e01f2?q=80&w=2688&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" />
</div>
</div>
)
}

196
apps/v4/app/(app)/page.tsx Normal file
View File

@@ -0,0 +1,196 @@
import { AccordionDemo } from "@/components/accordion-demo"
import { AlertDemo } from "@/components/alert-demo"
import { AlertDialogDemo } from "@/components/alert-dialog-demo"
import { AspectRatioDemo } from "@/components/aspect-ratio-demo"
import { AvatarDemo } from "@/components/avatar-demo"
import { BadgeDemo } from "@/components/badge-demo"
import { BreadcrumbDemo } from "@/components/breadcrumb-demo"
import { ButtonDemo } from "@/components/button-demo"
import { CalendarDemo } from "@/components/calendar-demo"
import { CardDemo } from "@/components/card-demo"
import { CarouselDemo } from "@/components/carousel-demo"
import { ChartDemo } from "@/components/chart-demo"
import { CheckboxDemo } from "@/components/checkbox-demo"
import { CollapsibleDemo } from "@/components/collapsible-demo"
import { ComboboxDemo } from "@/components/combobox-demo"
import { CommandDemo } from "@/components/command-demo"
import { ComponentWrapper } from "@/components/component-wrapper"
import { ContextMenuDemo } from "@/components/context-menu-demo"
import { DatePickerDemo } from "@/components/date-picker-demo"
import { DialogDemo } from "@/components/dialog-demo"
import { DrawerDemo } from "@/components/drawer-demo"
import { DropdownMenuDemo } from "@/components/dropdown-menu-demo"
import { FormDemo } from "@/components/form-demo"
import { HoverCardDemo } from "@/components/hover-card-demo"
import { InputDemo } from "@/components/input-demo"
import { InputOTPDemo } from "@/components/input-otp-demo"
import { LabelDemo } from "@/components/label-demo"
import { MenubarDemo } from "@/components/menubar-demo"
import { NavigationMenuDemo } from "@/components/navigation-menu-demo"
import { PaginationDemo } from "@/components/pagination-demo"
import { PopoverDemo } from "@/components/popover-demo"
import { ProgressDemo } from "@/components/progress-demo"
import { RadioGroupDemo } from "@/components/radio-group-demo"
import { ResizableDemo } from "@/components/resizable-demo"
import { ScrollAreaDemo } from "@/components/scroll-area-demo"
import { SelectDemo } from "@/components/select-demo"
import { SeparatorDemo } from "@/components/separator-demo"
import { SheetDemo } from "@/components/sheet-demo"
import { SkeletonDemo } from "@/components/skeleton-demo"
import { SliderDemo } from "@/components/slider-demo"
import { SonnerDemo } from "@/components/sonner-demo"
import { SwitchDemo } from "@/components/switch-demo"
import { TableDemo } from "@/components/table-demo"
import { TabsDemo } from "@/components/tabs-demo"
import { TextareaDemo } from "@/components/textarea-demo"
import { ToggleDemo } from "@/components/toggle-demo"
import { ToggleGroupDemo } from "@/components/toggle-group-demo"
import { TooltipDemo } from "@/components/tooltip-demo"
export default function SinkPage() {
return (
<div className="@container grid flex-1 gap-4 p-4">
<ComponentWrapper name="chart" className="w-full">
<ChartDemo />
</ComponentWrapper>
<ComponentWrapper name="accordion">
<AccordionDemo />
</ComponentWrapper>
<ComponentWrapper name="alert">
<AlertDemo />
</ComponentWrapper>
<ComponentWrapper name="alert-dialog">
<AlertDialogDemo />
</ComponentWrapper>
<ComponentWrapper name="aspect-ratio">
<AspectRatioDemo />
</ComponentWrapper>
<ComponentWrapper name="avatar">
<AvatarDemo />
</ComponentWrapper>
<ComponentWrapper name="badge">
<BadgeDemo />
</ComponentWrapper>
<ComponentWrapper name="breadcrumb">
<BreadcrumbDemo />
</ComponentWrapper>
<ComponentWrapper name="button">
<ButtonDemo />
</ComponentWrapper>
<ComponentWrapper name="calendar">
<CalendarDemo />
</ComponentWrapper>
<ComponentWrapper name="card">
<CardDemo />
</ComponentWrapper>
<ComponentWrapper name="carousel">
<CarouselDemo />
</ComponentWrapper>
<ComponentWrapper name="checkbox">
<CheckboxDemo />
</ComponentWrapper>
<ComponentWrapper name="collapsible">
<CollapsibleDemo />
</ComponentWrapper>
<ComponentWrapper name="combobox">
<ComboboxDemo />
</ComponentWrapper>
<ComponentWrapper name="command">
<CommandDemo />
</ComponentWrapper>
<ComponentWrapper name="context-menu">
<ContextMenuDemo />
</ComponentWrapper>
<ComponentWrapper name="date-picker">
<DatePickerDemo />
</ComponentWrapper>
<ComponentWrapper name="dialog">
<DialogDemo />
</ComponentWrapper>
<ComponentWrapper name="drawer">
<DrawerDemo />
</ComponentWrapper>
<ComponentWrapper name="dropdown-menu">
<DropdownMenuDemo />
</ComponentWrapper>
<ComponentWrapper name="form">
<FormDemo />
</ComponentWrapper>
<ComponentWrapper name="hover-card">
<HoverCardDemo />
</ComponentWrapper>
<ComponentWrapper name="input">
<InputDemo />
</ComponentWrapper>
<ComponentWrapper name="input-otp">
<InputOTPDemo />
</ComponentWrapper>
<ComponentWrapper name="label">
<LabelDemo />
</ComponentWrapper>
<ComponentWrapper name="menubar">
<MenubarDemo />
</ComponentWrapper>
<ComponentWrapper name="navigation-menu">
<NavigationMenuDemo />
</ComponentWrapper>
<ComponentWrapper name="pagination">
<PaginationDemo />
</ComponentWrapper>
<ComponentWrapper name="popover">
<PopoverDemo />
</ComponentWrapper>
<ComponentWrapper name="progress">
<ProgressDemo />
</ComponentWrapper>
<ComponentWrapper name="radio-group">
<RadioGroupDemo />
</ComponentWrapper>
<ComponentWrapper name="resizable">
<ResizableDemo />
</ComponentWrapper>
<ComponentWrapper name="scroll-area">
<ScrollAreaDemo />
</ComponentWrapper>
<ComponentWrapper name="select">
<SelectDemo />
</ComponentWrapper>
<ComponentWrapper name="separator">
<SeparatorDemo />
</ComponentWrapper>
<ComponentWrapper name="sheet">
<SheetDemo />
</ComponentWrapper>
<ComponentWrapper name="skeleton">
<SkeletonDemo />
</ComponentWrapper>
<ComponentWrapper name="slider">
<SliderDemo />
</ComponentWrapper>
<ComponentWrapper name="sonner">
<SonnerDemo />
</ComponentWrapper>
<ComponentWrapper name="switch">
<SwitchDemo />
</ComponentWrapper>
<ComponentWrapper name="table">
<TableDemo />
</ComponentWrapper>
<ComponentWrapper name="tabs">
<TabsDemo />
</ComponentWrapper>
<ComponentWrapper name="textarea">
<TextareaDemo />
</ComponentWrapper>
<ComponentWrapper name="toggle">
<ToggleDemo />
</ComponentWrapper>
<ComponentWrapper name="toggle-group">
<ToggleGroupDemo />
</ComponentWrapper>
<ComponentWrapper name="tooltip">
<TooltipDemo />
</ComponentWrapper>
</div>
)
}

View File

@@ -0,0 +1,61 @@
"use client"
import * as React from "react"
import { addDays, format } from "date-fns"
import { CalendarIcon } from "lucide-react"
import { DateRange } from "react-day-picker"
import { cn } from "@/lib/utils"
import { Button } from "@/registry/new-york-v4/ui/button"
import { Calendar } from "@/registry/new-york-v4/ui/calendar"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/registry/new-york-v4/ui/popover"
export function AnalyticsDatePicker() {
const [date, setDate] = React.useState<DateRange | undefined>({
from: new Date(new Date().getFullYear(), 0, 20),
to: addDays(new Date(new Date().getFullYear(), 0, 20), 20),
})
return (
<Popover>
<PopoverTrigger asChild>
<Button
id="date"
variant="outline"
className={cn(
"w-fit justify-start px-2 font-normal",
!date && "text-muted-foreground"
)}
>
<CalendarIcon className="text-muted-foreground" />
{date?.from ? (
date.to ? (
<>
{format(date.from, "LLL dd, y")} -{" "}
{format(date.to, "LLL dd, y")}
</>
) : (
format(date.from, "LLL dd, y")
)
) : (
<span>Pick a date</span>
)}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="end">
<Calendar
initialFocus
mode="range"
defaultMonth={date?.from}
selected={date}
onSelect={setDate}
numberOfMonths={2}
/>
</PopoverContent>
</Popover>
)
}

View File

@@ -0,0 +1,84 @@
"use client"
import * as React from "react"
import {
ChartLineIcon,
FileIcon,
HomeIcon,
LifeBuoy,
Send,
Settings2Icon,
ShoppingBagIcon,
ShoppingCartIcon,
UserIcon,
} from "lucide-react"
import { Sidebar, SidebarContent } from "@/registry/new-york-v4/ui/sidebar"
import { NavMain } from "@/app/(examples)/dashboard-03/components/nav-main"
import { NavSecondary } from "@/app/(examples)/dashboard-03/components/nav-secondary"
const data = {
navMain: [
{
title: "Dashboard",
url: "/dashboard",
icon: HomeIcon,
},
{
title: "Analytics",
url: "/dashboard/analytics",
icon: ChartLineIcon,
},
{
title: "Orders",
url: "/dashboard/orders",
icon: ShoppingBagIcon,
},
{
title: "Products",
url: "/dashboard/products",
icon: ShoppingCartIcon,
},
{
title: "Invoices",
url: "/dashboard/invoices",
icon: FileIcon,
},
{
title: "Customers",
url: "/dashboard/customers",
icon: UserIcon,
},
{
title: "Settings",
url: "/dashboard/settings",
icon: Settings2Icon,
},
],
navSecondary: [
{
title: "Support",
url: "#",
icon: LifeBuoy,
},
{
title: "Feedback",
url: "#",
icon: Send,
},
],
}
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
return (
<Sidebar
className="top-(--header-height) h-[calc(100svh-var(--header-height))]!"
{...props}
>
<SidebarContent>
<NavMain items={data.navMain} />
<NavSecondary items={data.navSecondary} className="mt-auto" />
</SidebarContent>
</Sidebar>
)
}

View File

@@ -0,0 +1,110 @@
"use client"
import { TrendingUp } from "lucide-react"
import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/new-york-v4/ui/card"
import {
ChartConfig,
ChartContainer,
ChartTooltip,
ChartTooltipContent,
} from "@/registry/new-york-v4/ui/chart"
const chartData = [
{ month: "January", desktop: 186, mobile: 80 },
{ month: "February", desktop: 305, mobile: 200 },
{ month: "March", desktop: 237, mobile: 120 },
{ month: "April", desktop: 73, mobile: 190 },
{ month: "May", desktop: 209, mobile: 130 },
{ month: "June", desktop: 346, mobile: 140 },
{ month: "July", desktop: 321, mobile: 275 },
{ month: "August", desktop: 132, mobile: 95 },
{ month: "September", desktop: 189, mobile: 225 },
{ month: "October", desktop: 302, mobile: 248 },
{ month: "November", desktop: 342, mobile: 285 },
{ month: "December", desktop: 328, mobile: 290 },
]
const chartConfig = {
desktop: {
label: "Desktop",
color: "var(--chart-1)",
},
mobile: {
label: "Mobile",
color: "var(--chart-2)",
},
} satisfies ChartConfig
export function ChartRevenue() {
return (
<Card>
<CardHeader>
<CardDescription>January - June 2024</CardDescription>
<CardTitle className="text-3xl font-bold tracking-tight">
$45,231.89
</CardTitle>
</CardHeader>
<CardContent>
<ChartContainer config={chartConfig} className="aspect-[3/1]">
<BarChart
accessibilityLayer
data={chartData}
margin={{
left: -16,
right: 0,
}}
>
<CartesianGrid vertical={false} />
<XAxis
dataKey="month"
tickLine={false}
tickMargin={10}
axisLine={false}
tickFormatter={(value) => value.slice(0, 3)}
/>
<YAxis
tickLine={false}
tickMargin={10}
axisLine={false}
tickFormatter={(value) => value.toLocaleString()}
domain={[0, "dataMax"]}
/>
<ChartTooltip
cursor={false}
content={<ChartTooltipContent hideIndicator />}
/>
<Bar
dataKey="desktop"
fill="var(--color-desktop)"
radius={[0, 0, 4, 4]}
stackId={1}
/>
<Bar
dataKey="mobile"
fill="var(--color-mobile)"
radius={[4, 4, 0, 0]}
stackId={1}
/>
</BarChart>
</ChartContainer>
</CardContent>
<CardFooter className="flex-col items-start gap-2 text-sm">
<div className="flex gap-2 leading-none font-medium">
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
</div>
<div className="text-muted-foreground leading-none">
Showing total visitors for the last 6 months
</div>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,189 @@
"use client"
import * as React from "react"
import { Label, Pie, PieChart, Sector } from "recharts"
import { PieSectorDataItem } from "recharts/types/polar/Pie"
import {
Card,
CardAction,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/registry/new-york-v4/ui/card"
import {
ChartConfig,
ChartContainer,
ChartStyle,
ChartTooltip,
ChartTooltipContent,
} from "@/registry/new-york-v4/ui/chart"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/registry/new-york-v4/ui/select"
const desktopData = [
{ month: "january", desktop: 186, fill: "var(--color-january)" },
{ month: "february", desktop: 305, fill: "var(--color-february)" },
{ month: "march", desktop: 237, fill: "var(--color-march)" },
{ month: "april", desktop: 173, fill: "var(--color-april)" },
{ month: "may", desktop: 209, fill: "var(--color-may)" },
]
const chartConfig = {
visitors: {
label: "Visitors",
},
desktop: {
label: "Desktop",
},
mobile: {
label: "Mobile",
},
january: {
label: "January",
color: "var(--chart-1)",
},
february: {
label: "February",
color: "var(--chart-2)",
},
march: {
label: "March",
color: "var(--chart-3)",
},
april: {
label: "April",
color: "var(--chart-4)",
},
may: {
label: "May",
color: "var(--chart-5)",
},
} satisfies ChartConfig
export function ChartVisitors() {
const id = "pie-interactive"
const [activeMonth, setActiveMonth] = React.useState(desktopData[0].month)
const activeIndex = React.useMemo(
() => desktopData.findIndex((item) => item.month === activeMonth),
[activeMonth]
)
const months = React.useMemo(() => desktopData.map((item) => item.month), [])
return (
<Card data-chart={id}>
<ChartStyle id={id} config={chartConfig} />
<CardHeader>
<CardDescription>January - June 2024</CardDescription>
<CardTitle className="text-2xl font-bold">1,234 visitors</CardTitle>
<CardAction>
<Select value={activeMonth} onValueChange={setActiveMonth}>
<SelectTrigger
className="ml-auto h-8 w-[120px]"
aria-label="Select a value"
>
<SelectValue placeholder="Select month" />
</SelectTrigger>
<SelectContent align="end">
{months.map((key) => {
const config = chartConfig[key as keyof typeof chartConfig]
if (!config) {
return null
}
const color = "color" in config ? config.color : undefined
return (
<SelectItem key={key} value={key}>
<div className="flex items-center gap-2 text-xs">
<span
className="flex h-3 w-3 shrink-0 rounded-sm"
style={{
backgroundColor: color,
}}
/>
{config?.label}
</div>
</SelectItem>
)
})}
</SelectContent>
</Select>
</CardAction>
</CardHeader>
<CardContent className="flex flex-1 justify-center pb-0">
<ChartContainer
id={id}
config={chartConfig}
className="mx-auto aspect-square w-full max-w-[300px]"
>
<PieChart>
<ChartTooltip
cursor={false}
content={<ChartTooltipContent hideLabel />}
/>
<Pie
data={desktopData}
dataKey="desktop"
nameKey="month"
innerRadius={60}
strokeWidth={5}
activeIndex={activeIndex}
activeShape={({
outerRadius = 0,
...props
}: PieSectorDataItem) => (
<g>
<Sector {...props} outerRadius={outerRadius + 10} />
<Sector
{...props}
outerRadius={outerRadius + 25}
innerRadius={outerRadius + 12}
/>
</g>
)}
>
<Label
content={({ viewBox }) => {
if (viewBox && "cx" in viewBox && "cy" in viewBox) {
return (
<text
x={viewBox.cx}
y={viewBox.cy}
textAnchor="middle"
dominantBaseline="middle"
>
<tspan
x={viewBox.cx}
y={viewBox.cy}
className="fill-foreground text-3xl font-bold"
>
{desktopData[activeIndex].desktop.toLocaleString()}
</tspan>
<tspan
x={viewBox.cx}
y={(viewBox.cy || 0) + 24}
className="fill-muted-foreground"
>
Visitors
</tspan>
</text>
)
}
}}
/>
</Pie>
</PieChart>
</ChartContainer>
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,28 @@
"use client"
import * as React from "react"
import { MoonIcon, SunIcon } from "lucide-react"
import { useTheme } from "next-themes"
import { Button } from "@/registry/new-york-v4/ui/button"
export function ModeToggle() {
const { setTheme, resolvedTheme } = useTheme()
const toggleTheme = React.useCallback(() => {
setTheme(resolvedTheme === "dark" ? "light" : "dark")
}, [resolvedTheme, setTheme])
return (
<Button
variant="secondary"
size="icon"
className="group/toggle size-8"
onClick={toggleTheme}
>
<SunIcon className="hidden [html.dark_&]:block" />
<MoonIcon className="hidden [html.light_&]:block" />
<span className="sr-only">Toggle theme</span>
</Button>
)
}

View File

@@ -0,0 +1,91 @@
"use client"
import { usePathname } from "next/navigation"
import { ChevronRight, type LucideIcon } from "lucide-react"
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/registry/new-york-v4/ui/collapsible"
import {
SidebarGroup,
SidebarGroupLabel,
SidebarMenu,
SidebarMenuAction,
SidebarMenuButton,
SidebarMenuItem,
SidebarMenuSub,
SidebarMenuSubButton,
SidebarMenuSubItem,
} from "@/registry/new-york-v4/ui/sidebar"
export function NavMain({
items,
}: {
items: {
title: string
url: string
icon: LucideIcon
isActive?: boolean
items?: {
title: string
url: string
}[]
disabled?: boolean
}[]
}) {
const pathname = usePathname()
return (
<SidebarGroup>
<SidebarGroupLabel>Dashboard</SidebarGroupLabel>
<SidebarMenu>
{items.map((item) => (
<Collapsible key={item.title} asChild defaultOpen={item.isActive}>
<SidebarMenuItem>
<SidebarMenuButton
asChild
tooltip={item.title}
isActive={pathname === item.url}
disabled={item.disabled}
>
<a
href={item.disabled ? "#" : item.url}
data-disabled={item.disabled}
className="data-[disabled=true]:opacity-50"
>
<item.icon className="text-muted-foreground" />
<span>{item.title}</span>
</a>
</SidebarMenuButton>
{item.items?.length ? (
<>
<CollapsibleTrigger asChild>
<SidebarMenuAction className="data-[state=open]:rotate-90">
<ChevronRight />
<span className="sr-only">Toggle</span>
</SidebarMenuAction>
</CollapsibleTrigger>
<CollapsibleContent>
<SidebarMenuSub>
{item.items?.map((subItem) => (
<SidebarMenuSubItem key={subItem.title}>
<SidebarMenuSubButton asChild>
<a href={subItem.url}>
<span>{subItem.title}</span>
</a>
</SidebarMenuSubButton>
</SidebarMenuSubItem>
))}
</SidebarMenuSub>
</CollapsibleContent>
</>
) : null}
</SidebarMenuItem>
</Collapsible>
))}
</SidebarMenu>
</SidebarGroup>
)
}

View File

@@ -0,0 +1,40 @@
import * as React from "react"
import { type LucideIcon } from "lucide-react"
import {
SidebarGroup,
SidebarGroupContent,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
} from "@/registry/new-york-v4/ui/sidebar"
export function NavSecondary({
items,
...props
}: {
items: {
title: string
url: string
icon: LucideIcon
}[]
} & React.ComponentPropsWithoutRef<typeof SidebarGroup>) {
return (
<SidebarGroup {...props}>
<SidebarGroupContent>
<SidebarMenu>
{items.map((item) => (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton asChild size="sm">
<a href={item.url}>
<item.icon />
<span>{item.title}</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
)
}

View File

@@ -0,0 +1,90 @@
"use client"
import { BadgeCheck, Bell, CreditCard, LogOut, Sparkles } from "lucide-react"
import {
Avatar,
AvatarFallback,
AvatarImage,
} from "@/registry/new-york-v4/ui/avatar"
import { Button } from "@/registry/new-york-v4/ui/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/registry/new-york-v4/ui/dropdown-menu"
export function NavUser({
user,
}: {
user: {
name: string
email: string
avatar: string
}
}) {
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon">
<Avatar className="size-8 rounded-md">
<AvatarImage src={user.avatar} alt={user.name} />
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
</Avatar>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
className="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg"
side="bottom"
align="end"
sideOffset={4}
>
<DropdownMenuLabel className="p-0 font-normal">
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
<Avatar className="h-8 w-8 rounded-lg">
<AvatarImage src={user.avatar} alt={user.name} />
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
</Avatar>
<div className="grid flex-1 text-left text-sm leading-tight">
<span className="truncate font-medium">{user.name}</span>
<span className="text-muted-foreground truncate text-xs">
{user.email}
</span>
</div>
</div>
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<Sparkles />
Upgrade to Pro
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<BadgeCheck />
Account
</DropdownMenuItem>
<DropdownMenuItem>
<CreditCard />
Billing
</DropdownMenuItem>
<DropdownMenuItem>
<Bell />
Notifications
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuItem>
<LogOut />
Log out
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
}

View File

@@ -0,0 +1,217 @@
import {
ArrowUpDownIcon,
EllipsisVerticalIcon,
ListFilterIcon,
PlusIcon,
} from "lucide-react"
import { Badge } from "@/registry/new-york-v4/ui/badge"
import { Button } from "@/registry/new-york-v4/ui/button"
import {
Card,
CardContent,
CardFooter,
CardHeader,
} from "@/registry/new-york-v4/ui/card"
import { Checkbox } from "@/registry/new-york-v4/ui/checkbox"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/registry/new-york-v4/ui/dropdown-menu"
import {
Pagination,
PaginationContent,
PaginationEllipsis,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious,
} from "@/registry/new-york-v4/ui/pagination"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/registry/new-york-v4/ui/select"
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/registry/new-york-v4/ui/table"
import { Tabs, TabsList, TabsTrigger } from "@/registry/new-york-v4/ui/tabs"
export function ProductsTable({
products,
}: {
products: {
id: string
name: string
price: number
stock: number
dateAdded: string
status: string
}[]
}) {
return (
<Card className="flex w-full flex-col gap-4">
<CardHeader className="flex flex-row items-center justify-between">
<Tabs defaultValue="all">
<TabsList className="w-full @3xl/page:w-fit">
<TabsTrigger value="all">All Products</TabsTrigger>
<TabsTrigger value="in-stock">In Stock</TabsTrigger>
<TabsTrigger value="low-stock">Low Stock</TabsTrigger>
<TabsTrigger value="add-product" asChild>
<button>
<PlusIcon />
</button>
</TabsTrigger>
</TabsList>
</Tabs>
<div className="hidden items-center gap-2 **:data-[slot=button]:size-8 **:data-[slot=select-trigger]:h-8 @3xl/page:flex">
<Select defaultValue="all">
<SelectTrigger>
<span className="text-muted-foreground text-sm">Category:</span>
<SelectValue placeholder="Select a product" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All</SelectItem>
<SelectItem value="in-stock">In Stock</SelectItem>
<SelectItem value="low-stock">Low Stock</SelectItem>
<SelectItem value="archived">Archived</SelectItem>
</SelectContent>
</Select>
<Select defaultValue="all">
<SelectTrigger>
<span className="text-muted-foreground text-sm">Price:</span>
<SelectValue placeholder="Select a product" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">$100-$200</SelectItem>
<SelectItem value="in-stock">$200-$300</SelectItem>
<SelectItem value="low-stock">$300-$400</SelectItem>
<SelectItem value="archived">$400-$500</SelectItem>
</SelectContent>
</Select>
<Select defaultValue="all">
<SelectTrigger>
<span className="text-muted-foreground text-sm">Status:</span>
<SelectValue placeholder="Select a product" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">In Stock</SelectItem>
<SelectItem value="in-stock">Low Stock</SelectItem>
<SelectItem value="low-stock">Archived</SelectItem>
<SelectItem value="archived">Archived</SelectItem>
</SelectContent>
</Select>
<Button variant="outline" size="icon">
<ListFilterIcon />
</Button>
<Button variant="outline" size="icon">
<ArrowUpDownIcon />
</Button>
</div>
</CardHeader>
<CardContent>
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-12 px-4">
<Checkbox />
</TableHead>
<TableHead>Product</TableHead>
<TableHead className="text-right">Price</TableHead>
<TableHead className="text-right">Stock</TableHead>
<TableHead>Status</TableHead>
<TableHead>Date Added</TableHead>
<TableHead />
</TableRow>
</TableHeader>
<TableBody className="**:data-[slot=table-cell]:py-2.5">
{products.map((product) => (
<TableRow key={product.id}>
<TableCell className="px-4">
<Checkbox />
</TableCell>
<TableCell className="font-medium">{product.name}</TableCell>
<TableCell className="text-right">
${product.price.toFixed(2)}
</TableCell>
<TableCell className="text-right">{product.stock}</TableCell>
<TableCell>
<Badge
variant="secondary"
className={
product.status === "Low Stock"
? "border-orange-700 bg-transparent text-orange-700 dark:border-orange-700 dark:bg-transparent dark:text-orange-700"
: "bg-green-100 text-green-800 dark:bg-green-950 dark:text-green-100"
}
>
{product.status}
</Badge>
</TableCell>
<TableCell>
{new Date(product.dateAdded).toLocaleDateString("en-US", {
month: "long",
day: "numeric",
year: "numeric",
})}
</TableCell>
<TableCell>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon" className="size-6">
<EllipsisVerticalIcon />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem>Edit</DropdownMenuItem>
<DropdownMenuItem variant="destructive">
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</CardContent>
<CardFooter className="flex flex-col items-center justify-between border-t pt-6 @3xl/page:flex-row">
<div className="text-muted-foreground hidden text-sm @3xl/page:block">
Showing 1-10 of 100 products
</div>
<Pagination className="mx-0 w-fit">
<PaginationContent>
<PaginationItem>
<PaginationPrevious href="#" />
</PaginationItem>
<PaginationItem>
<PaginationLink href="#">1</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href="#" isActive>
2
</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href="#">3</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationEllipsis />
</PaginationItem>
<PaginationItem>
<PaginationNext href="#" />
</PaginationItem>
</PaginationContent>
</Pagination>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,22 @@
import { Search } from "lucide-react"
import { Label } from "@/registry/new-york-v4/ui/label"
import { SidebarInput } from "@/registry/new-york-v4/ui/sidebar"
export function SearchForm({ ...props }: React.ComponentProps<"form">) {
return (
<form {...props}>
<div className="relative">
<Label htmlFor="search" className="sr-only">
Search
</Label>
<SidebarInput
id="search"
placeholder="Type to search..."
className="h-8 pl-7"
/>
<Search className="pointer-events-none absolute top-1/2 left-2 size-4 -translate-y-1/2 opacity-50 select-none" />
</div>
</form>
)
}

View File

@@ -0,0 +1,103 @@
"use client"
import { Fragment, useMemo } from "react"
import { usePathname } from "next/navigation"
import { SidebarIcon } from "lucide-react"
import { ThemeSelector } from "@/components/theme-selector"
import { SearchForm } from "@/registry/new-york-v4/blocks/sidebar-16/components/search-form"
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from "@/registry/new-york-v4/ui/breadcrumb"
import { Button } from "@/registry/new-york-v4/ui/button"
import { Separator } from "@/registry/new-york-v4/ui/separator"
import { useSidebar } from "@/registry/new-york-v4/ui/sidebar"
import { ModeToggle } from "@/app/(examples)/dashboard-03/components/mode-toggle"
import { NavUser } from "@/app/(examples)/dashboard-03/components/nav-user"
export function SiteHeader() {
const { toggleSidebar } = useSidebar()
const pathname = usePathname()
// Faux breadcrumbs for demo.
const breadcrumbs = useMemo(() => {
return pathname
.split("/")
.filter((path) => path !== "")
.map((path, index, array) => ({
label: path,
href: `/${array.slice(0, index + 1).join("/")}`,
}))
}, [pathname])
return (
<header
data-slot="site-header"
className="bg-background sticky top-0 z-50 flex w-full items-center border-b"
>
<div className="flex h-(--header-height) w-full items-center gap-2 px-2 pr-4">
<Button
variant="ghost"
size="sm"
onClick={toggleSidebar}
className="gap-2.5 has-[>svg]:px-2"
>
<SidebarIcon />
<span className="truncate font-medium">Acme Inc</span>
</Button>
<Separator
orientation="vertical"
className="mr-2 data-[orientation=vertical]:h-4"
/>
<Breadcrumb className="hidden sm:block">
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink href="/" className="capitalize">
Home
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
{breadcrumbs.map((breadcrumb, index) =>
index === breadcrumbs.length - 1 ? (
<BreadcrumbItem key={index}>
<BreadcrumbPage className="capitalize">
{breadcrumb.label}
</BreadcrumbPage>
</BreadcrumbItem>
) : (
<Fragment key={index}>
<BreadcrumbItem>
<BreadcrumbLink
href={breadcrumb.href}
className="capitalize"
>
{breadcrumb.label}
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
</Fragment>
)
)}
</BreadcrumbList>
</Breadcrumb>
<div className="ml-auto flex items-center gap-2">
<SearchForm className="w-fullsm:w-auto" />
<ThemeSelector />
<ModeToggle />
<NavUser
user={{
name: "shadcn",
email: "m@example.com",
avatar: "/avatars/shadcn.jpg",
}}
/>
</div>
</div>
</header>
)
}

View File

@@ -0,0 +1,8 @@
export default function CustomersPage() {
return (
<div className="p-6">
<div className="bg-input p-4">Input</div>
<div className="bg-input/30 p-4">Input 50</div>
</div>
)
}

View File

@@ -0,0 +1,31 @@
import { cookies } from "next/headers"
import {
SidebarInset,
SidebarProvider,
} from "@/registry/new-york-v4/ui/sidebar"
import { AppSidebar } from "@/app/(examples)/dashboard-03/components/app-sidebar"
import { SiteHeader } from "@/app/(examples)/dashboard-03/components/site-header"
import "../../themes.css"
export default async function DashboardLayout({
children,
}: {
children: React.ReactNode
}) {
const cookieStore = await cookies()
const defaultOpen = cookieStore.get("sidebar_state")?.value === "true"
return (
<main className="[--header-height:calc(theme(spacing.14))]">
<SidebarProvider defaultOpen={defaultOpen} className="flex flex-col">
<SiteHeader />
<div className="flex flex-1">
<AppSidebar />
<SidebarInset>{children}</SidebarInset>
</div>
</SidebarProvider>
</main>
)
}

View File

@@ -0,0 +1,206 @@
import { Metadata } from "next"
import {
DownloadIcon,
FilterIcon,
TrendingDownIcon,
TrendingUpIcon,
} from "lucide-react"
import { Badge } from "@/registry/new-york-v4/ui/badge"
import { Button } from "@/registry/new-york-v4/ui/button"
import {
Card,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/new-york-v4/ui/card"
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from "@/registry/new-york-v4/ui/tabs"
import { AnalyticsDatePicker } from "@/app/(examples)/dashboard-03/components/analytics-date-picker"
import { ChartRevenue } from "@/app/(examples)/dashboard-03/components/chart-revenue"
import { ChartVisitors } from "@/app/(examples)/dashboard-03/components/chart-visitors"
import { ProductsTable } from "@/app/(examples)/dashboard-03/components/products-table"
export const metadata: Metadata = {
title: "Dashboard",
description: "An example dashboard to test the new components.",
}
// Load from database.
const products = [
{
id: "1",
name: "BJÖRKSNÄS Dining Table",
price: 599.99,
stock: 12,
dateAdded: "2023-06-15",
status: "In Stock",
},
{
id: "2",
name: "POÄNG Armchair",
price: 249.99,
stock: 28,
dateAdded: "2023-07-22",
status: "In Stock",
},
{
id: "3",
name: "MALM Bed Frame",
price: 399.99,
stock: 15,
dateAdded: "2023-08-05",
status: "In Stock",
},
{
id: "4",
name: "KALLAX Shelf Unit",
price: 179.99,
stock: 32,
dateAdded: "2023-09-12",
status: "In Stock",
},
{
id: "5",
name: "STOCKHOLM Rug",
price: 299.99,
stock: 8,
dateAdded: "2023-10-18",
status: "Low Stock",
},
{
id: "6",
name: "KIVIK Sofa",
price: 899.99,
stock: 6,
dateAdded: "2023-11-02",
status: "Low Stock",
},
{
id: "7",
name: "LISABO Coffee Table",
price: 149.99,
stock: 22,
dateAdded: "2023-11-29",
status: "In Stock",
},
{
id: "8",
name: "HEMNES Bookcase",
price: 249.99,
stock: 17,
dateAdded: "2023-12-10",
status: "In Stock",
},
{
id: "9",
name: "EKEDALEN Dining Chairs (Set of 2)",
price: 199.99,
stock: 14,
dateAdded: "2024-01-05",
status: "In Stock",
},
{
id: "10",
name: "FRIHETEN Sleeper Sofa",
price: 799.99,
stock: 9,
dateAdded: "2024-01-18",
status: "Low Stock",
},
]
export default function DashboardPage() {
return (
<div className="@container/page flex flex-1 flex-col gap-8 p-6">
<Tabs defaultValue="overview" className="gap-6">
<div
data-slot="dashboard-header"
className="flex items-center justify-between"
>
<TabsList className="w-full @3xl/page:w-fit">
<TabsTrigger value="overview">Overview</TabsTrigger>
<TabsTrigger value="analytics">Analytics</TabsTrigger>
<TabsTrigger value="reports">Reports</TabsTrigger>
<TabsTrigger value="exports" disabled>
Exports
</TabsTrigger>
</TabsList>
<div className="hidden items-center gap-2 @3xl/page:flex">
<AnalyticsDatePicker />
<Button variant="outline">
<FilterIcon />
Filter
</Button>
<Button variant="outline">
<DownloadIcon />
Export
</Button>
</div>
</div>
<TabsContent value="overview" className="flex flex-col gap-4">
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-4">
<Card>
<CardHeader>
<CardTitle>Total Revenue</CardTitle>
<CardDescription>$1,250.00 in the last 30 days</CardDescription>
</CardHeader>
<CardFooter>
<Badge variant="outline">
<TrendingUpIcon />
+12.5%
</Badge>
</CardFooter>
</Card>
<Card>
<CardHeader>
<CardTitle>New Customers</CardTitle>
<CardDescription>-12 customers from last month</CardDescription>
</CardHeader>
<CardFooter>
<Badge variant="outline">
<TrendingDownIcon />
-20%
</Badge>
</CardFooter>
</Card>
<Card>
<CardHeader>
<CardTitle>Active Accounts</CardTitle>
<CardDescription>+2,345 users from last month</CardDescription>
</CardHeader>
<CardFooter>
<Badge variant="outline">
<TrendingUpIcon />
+12.5%
</Badge>
</CardFooter>
</Card>
<Card>
<CardHeader>
<CardTitle>Growth Rate</CardTitle>
<CardDescription>+12.5% increase per month</CardDescription>
</CardHeader>
<CardFooter>
<Badge variant="outline">
<TrendingUpIcon />
+4.5%
</Badge>
</CardFooter>
</Card>
</div>
<div className="grid grid-cols-1 gap-4 @4xl/page:grid-cols-[2fr_1fr]">
<ChartRevenue />
<ChartVisitors />
</div>
<ProductsTable products={products} />
</TabsContent>
</Tabs>
</div>
)
}

View File

@@ -0,0 +1,500 @@
import { Metadata } from "next"
import { cn } from "@/lib/utils"
import { Button } from "@/registry/new-york-v4/ui/button"
import {
Card,
CardAction,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/new-york-v4/ui/card"
import { Checkbox } from "@/registry/new-york-v4/ui/checkbox"
import { Input } from "@/registry/new-york-v4/ui/input"
import { Label } from "@/registry/new-york-v4/ui/label"
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectTrigger,
SelectValue,
} from "@/registry/new-york-v4/ui/select"
import { Switch } from "@/registry/new-york-v4/ui/switch"
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/registry/new-york-v4/ui/table"
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from "@/registry/new-york-v4/ui/tabs"
export const metadata: Metadata = {
title: "Settings",
description: "Manage your account settings",
}
const timezones = [
{
label: "Americas",
timezones: [
{ value: "America/New_York", label: "(GMT-5) New York" },
{ value: "America/Los_Angeles", label: "(GMT-8) Los Angeles" },
{ value: "America/Chicago", label: "(GMT-6) Chicago" },
{ value: "America/Toronto", label: "(GMT-5) Toronto" },
{ value: "America/Vancouver", label: "(GMT-8) Vancouver" },
{ value: "America/Sao_Paulo", label: "(GMT-3) São Paulo" },
],
},
{
label: "Europe",
timezones: [
{ value: "Europe/London", label: "(GMT+0) London" },
{ value: "Europe/Paris", label: "(GMT+1) Paris" },
{ value: "Europe/Berlin", label: "(GMT+1) Berlin" },
{ value: "Europe/Rome", label: "(GMT+1) Rome" },
{ value: "Europe/Madrid", label: "(GMT+1) Madrid" },
{ value: "Europe/Amsterdam", label: "(GMT+1) Amsterdam" },
],
},
{
label: "Asia/Pacific",
timezones: [
{ value: "Asia/Tokyo", label: "(GMT+9) Tokyo" },
{ value: "Asia/Shanghai", label: "(GMT+8) Shanghai" },
{ value: "Asia/Singapore", label: "(GMT+8) Singapore" },
{ value: "Asia/Dubai", label: "(GMT+4) Dubai" },
{ value: "Australia/Sydney", label: "(GMT+11) Sydney" },
{ value: "Asia/Seoul", label: "(GMT+9) Seoul" },
],
},
] as const
const loginHistory = [
{
date: "2024-01-01",
ip: "192.168.1.1",
location: "New York, USA",
},
{
date: "2023-12-29",
ip: "172.16.0.100",
location: "London, UK",
},
{
date: "2023-12-28",
ip: "10.0.0.50",
location: "Toronto, Canada",
},
{
date: "2023-12-25",
ip: "192.168.2.15",
location: "Sydney, Australia",
},
] as const
const activeSessions = [
{
device: "MacBook Pro",
browser: "Chrome",
os: "macOS",
},
{
device: "iPhone",
browser: "Safari",
os: "iOS",
},
{
device: "iPad",
browser: "Safari",
os: "iOS",
},
{
device: "Android Phone",
browser: "Chrome",
os: "Android",
},
] as const
export default function SettingsPage() {
return (
<div className="@container/page flex flex-1 flex-col gap-8 p-6">
<Tabs defaultValue="account" className="gap-6">
<div
data-slot="dashboard-header"
className="flex items-center justify-between"
>
<TabsList>
<TabsTrigger value="account">Account</TabsTrigger>
<TabsTrigger value="security">Security</TabsTrigger>
<TabsTrigger value="notifications">Notifications</TabsTrigger>
<TabsTrigger value="privacy">Privacy</TabsTrigger>
</TabsList>
</div>
<TabsContent value="account" className="grid gap-6">
<Card>
<CardHeader>
<CardTitle>Account Settings</CardTitle>
<CardDescription>
Make changes to your account here.
</CardDescription>
</CardHeader>
<CardContent>
<form id="form-account" className="@container">
<FieldGroup>
<Field>
<Label htmlFor="name">Name</Label>
<FieldControl>
<Input
id="name"
placeholder="First and last name"
required
/>
</FieldControl>
<FieldDescription>
This is your public display name.
</FieldDescription>
</Field>
<Field>
<Label htmlFor="email">Email</Label>
<FieldControl>
<Input
id="email"
placeholder="you@example.com"
required
/>
</FieldControl>
</Field>
<Field>
<Label htmlFor="timezone">Timezone</Label>
<FieldControl>
<Select>
<SelectTrigger id="timezone">
<SelectValue placeholder="Select a timezone" />
</SelectTrigger>
<SelectContent>
{timezones.map((timezone) => (
<SelectGroup key={timezone.label}>
<SelectLabel>{timezone.label}</SelectLabel>
{timezone.timezones.map((time) => (
<SelectItem key={time.value} value={time.value}>
{time.label}
</SelectItem>
))}
</SelectGroup>
))}
</SelectContent>
</Select>
</FieldControl>
</Field>
</FieldGroup>
</form>
</CardContent>
<CardFooter className="border-t">
<Button type="submit" form="form-account" variant="secondary">
Save changes
</Button>
</CardFooter>
</Card>
<Card>
<CardHeader>
<CardTitle>Notifications</CardTitle>
<CardDescription>
Manage how you receive notifications.
</CardDescription>
</CardHeader>
<CardContent>
<form id="form-notifications" className="@container">
<FieldGroup>
<Field>
<Label htmlFor="channels">Notification Channels</Label>
<FieldControl className="flex flex-col gap-2">
<div className="flex items-center gap-2">
<Checkbox id="notification-email" />
<Label htmlFor="notification-email">Email</Label>
</div>
<div className="flex items-center gap-2">
<Checkbox id="notification-sms" />
<Label htmlFor="notification-sms">SMS</Label>
</div>
<div className="flex items-center gap-2">
<Checkbox id="notification-push" />
<Label htmlFor="notification-push">Push</Label>
</div>
</FieldControl>
<FieldDescription>
Choose how you want to receive notifications.
</FieldDescription>
</Field>
<Field>
<Label htmlFor="types">Notification Types</Label>
<FieldControl className="flex flex-col gap-2">
<div className="flex items-center gap-2">
<Checkbox id="notification-account" />
<Label htmlFor="notification-account">
Account Activity
</Label>
</div>
<div className="flex items-center gap-2">
<Checkbox
id="notification-security"
defaultChecked
disabled
/>
<Label htmlFor="notification-security">
Security Alerts
</Label>
</div>
<div className="flex items-center gap-2">
<Checkbox id="notification-marketing" />
<Label htmlFor="notification-marketing">
Marketing & Promotions
</Label>
</div>
</FieldControl>
<FieldDescription>
Choose how you want to receive notifications.
</FieldDescription>
</Field>
</FieldGroup>
</form>
</CardContent>
<CardFooter className="border-t">
<Button
type="submit"
form="form-notifications"
variant="secondary"
>
Save changes
</Button>
</CardFooter>
</Card>
</TabsContent>
<TabsContent
value="security"
className="grid gap-6 @3xl/page:grid-cols-2"
>
<Card className="@3xl/page:col-span-2">
<CardHeader>
<CardTitle>Security Settings</CardTitle>
<CardDescription>
Make changes to your security settings here.
</CardDescription>
</CardHeader>
<CardContent className="@container">
<form id="form-security">
<FieldGroup>
<Field>
<Label htmlFor="current-password">Current Password</Label>
<FieldControl>
<Input
id="current-password"
placeholder="Current password"
required
/>
</FieldControl>
<FieldDescription>
This is your current password.
</FieldDescription>
</Field>
<Field>
<Label htmlFor="new-password">New Password</Label>
<FieldControl>
<Input
id="new-password"
placeholder="New password"
required
/>
</FieldControl>
</Field>
<Field>
<Label htmlFor="confirm-password">Confirm Password</Label>
<FieldControl>
<Input
id="confirm-password"
placeholder="Confirm password"
/>
</FieldControl>
</Field>
<Field>
<FieldControl>
<Switch
id="enable-two-factor-auth"
className="self-start"
/>
</FieldControl>
<Label htmlFor="enable-two-factor-auth">
Enable two-factor authentication
</Label>
<FieldDescription>
This will add an extra layer of security to your account.
Make this an extra long description to test the layout.
</FieldDescription>
</Field>
</FieldGroup>
</form>
</CardContent>
<CardFooter className="border-t">
<Button type="submit" form="form-security" variant="secondary">
Save changes
</Button>
</CardFooter>
</Card>
<Card>
<CardHeader>
<CardTitle>Login History</CardTitle>
<CardDescription>
Recent login activities on your account.
</CardDescription>
</CardHeader>
<CardContent>
<Table>
<TableHeader>
<TableRow>
<TableHead>Date</TableHead>
<TableHead className="hidden @md/page:table-cell">
IP
</TableHead>
<TableHead>Location</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{loginHistory.map((login) => (
<TableRow key={login.date}>
<TableCell>
<div className="flex flex-col gap-1">
{new Date(login.date).toLocaleDateString("en-US", {
year: "numeric",
month: "long",
day: "numeric",
})}
<span className="flex @md/page:hidden">
{login.ip}
</span>
</div>
</TableCell>
<TableCell className="hidden @md/page:table-cell">
{login.ip}
</TableCell>
<TableCell>{login.location}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Active Sessions</CardTitle>
<CardDescription>
Current active sessions on your account.
</CardDescription>
<CardAction>
<Button variant="outline" size="sm">
<span className="hidden @md/card-header:block">
Manage Sessions
</span>
<span className="block @md/card-header:hidden">Manage</span>
</Button>
</CardAction>
</CardHeader>
<CardContent>
<Table>
<TableHeader>
<TableRow>
<TableHead>Device</TableHead>
<TableHead>Browser</TableHead>
<TableHead>OS</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{activeSessions.map((session) => (
<TableRow key={session.device}>
<TableCell>{session.device}</TableCell>
<TableCell>{session.browser}</TableCell>
<TableCell>{session.os}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</CardContent>
</Card>
</TabsContent>
</Tabs>
</div>
)
}
function FieldGroup({ children }: React.ComponentProps<"div">) {
return (
<div
data-slot="field-group"
className="@container/field-group flex max-w-4xl min-w-0 flex-col gap-8 @3xl:gap-6"
>
{children}
</div>
)
}
function Field({ children, className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="field"
className={cn(
"grid auto-rows-min items-start gap-3 *:data-[slot=label]:col-start-1 *:data-[slot=label]:row-start-1 @3xl/field-group:grid-cols-2 @3xl/field-group:gap-6",
className
)}
{...props}
>
{children}
</div>
)
}
function FieldControl({
children,
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
data-slot="field-control"
className={cn(
"@3xl/field-group:col-start-2 @3xl/field-group:row-span-2 @3xl/field-group:row-start-1 @3xl/field-group:self-start",
className
)}
{...props}
>
{children}
</div>
)
}
function FieldDescription({
children,
className,
...props
}: React.ComponentProps<"p">) {
return (
<p
data-slot="field-description"
className={cn(
"text-muted-foreground text-sm @3xl/field-group:col-start-1 @3xl/field-group:row-start-1 @3xl/field-group:translate-y-6",
className
)}
{...props}
>
{children}
</p>
)
}

View File

@@ -0,0 +1,181 @@
"use client"
import * as React from "react"
import {
IconCamera,
IconChartBar,
IconDashboard,
IconDatabase,
IconFileAi,
IconFileDescription,
IconFileWord,
IconFolder,
IconHelp,
IconInnerShadowTop,
IconListDetails,
IconReport,
IconSearch,
IconSettings,
IconUsers,
} from "@tabler/icons-react"
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarHeader,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
} from "@/registry/new-york-v4/ui/sidebar"
import { NavDocuments } from "@/app/(examples)/dashboard/components/nav-documents"
import { NavMain } from "@/app/(examples)/dashboard/components/nav-main"
import { NavSecondary } from "@/app/(examples)/dashboard/components/nav-secondary"
import { NavUser } from "@/app/(examples)/dashboard/components/nav-user"
const data = {
user: {
name: "shadcn",
email: "m@example.com",
avatar: "/avatars/shadcn.jpg",
},
navMain: [
{
title: "Dashboard",
url: "#",
icon: IconDashboard,
},
{
title: "Lifecycle",
url: "#",
icon: IconListDetails,
},
{
title: "Analytics",
url: "#",
icon: IconChartBar,
},
{
title: "Projects",
url: "#",
icon: IconFolder,
},
{
title: "Team",
url: "#",
icon: IconUsers,
},
],
navClouds: [
{
title: "Capture",
icon: IconCamera,
isActive: true,
url: "#",
items: [
{
title: "Active Proposals",
url: "#",
},
{
title: "Archived",
url: "#",
},
],
},
{
title: "Proposal",
icon: IconFileDescription,
url: "#",
items: [
{
title: "Active Proposals",
url: "#",
},
{
title: "Archived",
url: "#",
},
],
},
{
title: "Prompts",
icon: IconFileAi,
url: "#",
items: [
{
title: "Active Proposals",
url: "#",
},
{
title: "Archived",
url: "#",
},
],
},
],
navSecondary: [
{
title: "Settings",
url: "#",
icon: IconSettings,
},
{
title: "Get Help",
url: "#",
icon: IconHelp,
},
{
title: "Search",
url: "#",
icon: IconSearch,
},
],
documents: [
{
name: "Data Library",
url: "#",
icon: IconDatabase,
},
{
name: "Reports",
url: "#",
icon: IconReport,
},
{
name: "Word Assistant",
url: "#",
icon: IconFileWord,
},
],
}
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
return (
<Sidebar collapsible="offcanvas" {...props}>
<SidebarHeader>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton
asChild
className="data-[slot=sidebar-menu-button]:!p-1.5"
>
<a href="#">
<IconInnerShadowTop className="!size-5" />
<span className="text-base font-semibold">Acme Inc.</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarHeader>
<SidebarContent>
<NavMain items={data.navMain} />
<NavDocuments items={data.documents} />
<NavSecondary items={data.navSecondary} className="mt-auto" />
</SidebarContent>
<SidebarFooter>
<NavUser user={data.user} />
</SidebarFooter>
</Sidebar>
)
}

View File

@@ -0,0 +1,292 @@
"use client"
import * as React from "react"
import { Area, AreaChart, CartesianGrid, XAxis } from "recharts"
import { useIsMobile } from "@/registry/new-york-v4/hooks/use-mobile"
import {
Card,
CardAction,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/registry/new-york-v4/ui/card"
import {
ChartConfig,
ChartContainer,
ChartTooltip,
ChartTooltipContent,
} from "@/registry/new-york-v4/ui/chart"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/registry/new-york-v4/ui/select"
import {
ToggleGroup,
ToggleGroupItem,
} from "@/registry/new-york-v4/ui/toggle-group"
export const description = "An interactive area chart"
const chartData = [
{ date: "2024-04-01", desktop: 222, mobile: 150 },
{ date: "2024-04-02", desktop: 97, mobile: 180 },
{ date: "2024-04-03", desktop: 167, mobile: 120 },
{ date: "2024-04-04", desktop: 242, mobile: 260 },
{ date: "2024-04-05", desktop: 373, mobile: 290 },
{ date: "2024-04-06", desktop: 301, mobile: 340 },
{ date: "2024-04-07", desktop: 245, mobile: 180 },
{ date: "2024-04-08", desktop: 409, mobile: 320 },
{ date: "2024-04-09", desktop: 59, mobile: 110 },
{ date: "2024-04-10", desktop: 261, mobile: 190 },
{ date: "2024-04-11", desktop: 327, mobile: 350 },
{ date: "2024-04-12", desktop: 292, mobile: 210 },
{ date: "2024-04-13", desktop: 342, mobile: 380 },
{ date: "2024-04-14", desktop: 137, mobile: 220 },
{ date: "2024-04-15", desktop: 120, mobile: 170 },
{ date: "2024-04-16", desktop: 138, mobile: 190 },
{ date: "2024-04-17", desktop: 446, mobile: 360 },
{ date: "2024-04-18", desktop: 364, mobile: 410 },
{ date: "2024-04-19", desktop: 243, mobile: 180 },
{ date: "2024-04-20", desktop: 89, mobile: 150 },
{ date: "2024-04-21", desktop: 137, mobile: 200 },
{ date: "2024-04-22", desktop: 224, mobile: 170 },
{ date: "2024-04-23", desktop: 138, mobile: 230 },
{ date: "2024-04-24", desktop: 387, mobile: 290 },
{ date: "2024-04-25", desktop: 215, mobile: 250 },
{ date: "2024-04-26", desktop: 75, mobile: 130 },
{ date: "2024-04-27", desktop: 383, mobile: 420 },
{ date: "2024-04-28", desktop: 122, mobile: 180 },
{ date: "2024-04-29", desktop: 315, mobile: 240 },
{ date: "2024-04-30", desktop: 454, mobile: 380 },
{ date: "2024-05-01", desktop: 165, mobile: 220 },
{ date: "2024-05-02", desktop: 293, mobile: 310 },
{ date: "2024-05-03", desktop: 247, mobile: 190 },
{ date: "2024-05-04", desktop: 385, mobile: 420 },
{ date: "2024-05-05", desktop: 481, mobile: 390 },
{ date: "2024-05-06", desktop: 498, mobile: 520 },
{ date: "2024-05-07", desktop: 388, mobile: 300 },
{ date: "2024-05-08", desktop: 149, mobile: 210 },
{ date: "2024-05-09", desktop: 227, mobile: 180 },
{ date: "2024-05-10", desktop: 293, mobile: 330 },
{ date: "2024-05-11", desktop: 335, mobile: 270 },
{ date: "2024-05-12", desktop: 197, mobile: 240 },
{ date: "2024-05-13", desktop: 197, mobile: 160 },
{ date: "2024-05-14", desktop: 448, mobile: 490 },
{ date: "2024-05-15", desktop: 473, mobile: 380 },
{ date: "2024-05-16", desktop: 338, mobile: 400 },
{ date: "2024-05-17", desktop: 499, mobile: 420 },
{ date: "2024-05-18", desktop: 315, mobile: 350 },
{ date: "2024-05-19", desktop: 235, mobile: 180 },
{ date: "2024-05-20", desktop: 177, mobile: 230 },
{ date: "2024-05-21", desktop: 82, mobile: 140 },
{ date: "2024-05-22", desktop: 81, mobile: 120 },
{ date: "2024-05-23", desktop: 252, mobile: 290 },
{ date: "2024-05-24", desktop: 294, mobile: 220 },
{ date: "2024-05-25", desktop: 201, mobile: 250 },
{ date: "2024-05-26", desktop: 213, mobile: 170 },
{ date: "2024-05-27", desktop: 420, mobile: 460 },
{ date: "2024-05-28", desktop: 233, mobile: 190 },
{ date: "2024-05-29", desktop: 78, mobile: 130 },
{ date: "2024-05-30", desktop: 340, mobile: 280 },
{ date: "2024-05-31", desktop: 178, mobile: 230 },
{ date: "2024-06-01", desktop: 178, mobile: 200 },
{ date: "2024-06-02", desktop: 470, mobile: 410 },
{ date: "2024-06-03", desktop: 103, mobile: 160 },
{ date: "2024-06-04", desktop: 439, mobile: 380 },
{ date: "2024-06-05", desktop: 88, mobile: 140 },
{ date: "2024-06-06", desktop: 294, mobile: 250 },
{ date: "2024-06-07", desktop: 323, mobile: 370 },
{ date: "2024-06-08", desktop: 385, mobile: 320 },
{ date: "2024-06-09", desktop: 438, mobile: 480 },
{ date: "2024-06-10", desktop: 155, mobile: 200 },
{ date: "2024-06-11", desktop: 92, mobile: 150 },
{ date: "2024-06-12", desktop: 492, mobile: 420 },
{ date: "2024-06-13", desktop: 81, mobile: 130 },
{ date: "2024-06-14", desktop: 426, mobile: 380 },
{ date: "2024-06-15", desktop: 307, mobile: 350 },
{ date: "2024-06-16", desktop: 371, mobile: 310 },
{ date: "2024-06-17", desktop: 475, mobile: 520 },
{ date: "2024-06-18", desktop: 107, mobile: 170 },
{ date: "2024-06-19", desktop: 341, mobile: 290 },
{ date: "2024-06-20", desktop: 408, mobile: 450 },
{ date: "2024-06-21", desktop: 169, mobile: 210 },
{ date: "2024-06-22", desktop: 317, mobile: 270 },
{ date: "2024-06-23", desktop: 480, mobile: 530 },
{ date: "2024-06-24", desktop: 132, mobile: 180 },
{ date: "2024-06-25", desktop: 141, mobile: 190 },
{ date: "2024-06-26", desktop: 434, mobile: 380 },
{ date: "2024-06-27", desktop: 448, mobile: 490 },
{ date: "2024-06-28", desktop: 149, mobile: 200 },
{ date: "2024-06-29", desktop: 103, mobile: 160 },
{ date: "2024-06-30", desktop: 446, mobile: 400 },
]
const chartConfig = {
visitors: {
label: "Visitors",
},
desktop: {
label: "Desktop",
color: "var(--primary)",
},
mobile: {
label: "Mobile",
color: "var(--primary)",
},
} satisfies ChartConfig
export function ChartAreaInteractive() {
const isMobile = useIsMobile()
const [timeRange, setTimeRange] = React.useState("90d")
React.useEffect(() => {
if (isMobile) {
setTimeRange("7d")
}
}, [isMobile])
const filteredData = chartData.filter((item) => {
const date = new Date(item.date)
const referenceDate = new Date("2024-06-30")
let daysToSubtract = 90
if (timeRange === "30d") {
daysToSubtract = 30
} else if (timeRange === "7d") {
daysToSubtract = 7
}
const startDate = new Date(referenceDate)
startDate.setDate(startDate.getDate() - daysToSubtract)
return date >= startDate
})
return (
<Card className="@container/card">
<CardHeader>
<CardTitle>Total Visitors</CardTitle>
<CardDescription>
<span className="hidden @[540px]/card:block">
Total for the last 3 months
</span>
<span className="@[540px]/card:hidden">Last 3 months</span>
</CardDescription>
<CardAction>
<ToggleGroup
type="single"
value={timeRange}
onValueChange={setTimeRange}
variant="outline"
className="hidden *:data-[slot=toggle-group-item]:!px-4 @[767px]/card:flex"
>
<ToggleGroupItem value="90d">Last 3 months</ToggleGroupItem>
<ToggleGroupItem value="30d">Last 30 days</ToggleGroupItem>
<ToggleGroupItem value="7d">Last 7 days</ToggleGroupItem>
</ToggleGroup>
<Select value={timeRange} onValueChange={setTimeRange}>
<SelectTrigger
className="flex w-40 **:data-[slot=select-value]:block **:data-[slot=select-value]:truncate @[767px]/card:hidden"
size="sm"
aria-label="Select a value"
>
<SelectValue placeholder="Last 3 months" />
</SelectTrigger>
<SelectContent className="rounded-xl">
<SelectItem value="90d" className="rounded-lg">
Last 3 months
</SelectItem>
<SelectItem value="30d" className="rounded-lg">
Last 30 days
</SelectItem>
<SelectItem value="7d" className="rounded-lg">
Last 7 days
</SelectItem>
</SelectContent>
</Select>
</CardAction>
</CardHeader>
<CardContent className="px-2 pt-4 sm:px-6 sm:pt-6">
<ChartContainer
config={chartConfig}
className="aspect-auto h-[250px] w-full"
>
<AreaChart data={filteredData}>
<defs>
<linearGradient id="fillDesktop" x1="0" y1="0" x2="0" y2="1">
<stop
offset="5%"
stopColor="var(--color-desktop)"
stopOpacity={1.0}
/>
<stop
offset="95%"
stopColor="var(--color-desktop)"
stopOpacity={0.1}
/>
</linearGradient>
<linearGradient id="fillMobile" x1="0" y1="0" x2="0" y2="1">
<stop
offset="5%"
stopColor="var(--color-mobile)"
stopOpacity={0.8}
/>
<stop
offset="95%"
stopColor="var(--color-mobile)"
stopOpacity={0.1}
/>
</linearGradient>
</defs>
<CartesianGrid vertical={false} />
<XAxis
dataKey="date"
tickLine={false}
axisLine={false}
tickMargin={8}
minTickGap={32}
tickFormatter={(value) => {
const date = new Date(value)
return date.toLocaleDateString("en-US", {
month: "short",
day: "numeric",
})
}}
/>
<ChartTooltip
cursor={false}
defaultIndex={isMobile ? -1 : 10}
content={
<ChartTooltipContent
labelFormatter={(value) => {
return new Date(value).toLocaleDateString("en-US", {
month: "short",
day: "numeric",
})
}}
indicator="dot"
/>
}
/>
<Area
dataKey="mobile"
type="natural"
fill="url(#fillMobile)"
stroke="var(--color-mobile)"
stackId="a"
/>
<Area
dataKey="desktop"
type="natural"
fill="url(#fillDesktop)"
stroke="var(--color-desktop)"
stackId="a"
/>
</AreaChart>
</ChartContainer>
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,807 @@
"use client"
import * as React from "react"
import {
DndContext,
KeyboardSensor,
MouseSensor,
TouchSensor,
closestCenter,
useSensor,
useSensors,
type DragEndEvent,
type UniqueIdentifier,
} from "@dnd-kit/core"
import { restrictToVerticalAxis } from "@dnd-kit/modifiers"
import {
SortableContext,
arrayMove,
useSortable,
verticalListSortingStrategy,
} from "@dnd-kit/sortable"
import { CSS } from "@dnd-kit/utilities"
import {
IconChevronDown,
IconChevronLeft,
IconChevronRight,
IconChevronsLeft,
IconChevronsRight,
IconCircleCheckFilled,
IconDotsVertical,
IconGripVertical,
IconLayoutColumns,
IconLoader,
IconPlus,
IconTrendingUp,
} from "@tabler/icons-react"
import {
ColumnDef,
ColumnFiltersState,
Row,
SortingState,
VisibilityState,
flexRender,
getCoreRowModel,
getFacetedRowModel,
getFacetedUniqueValues,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
} from "@tanstack/react-table"
import { Area, AreaChart, CartesianGrid, XAxis } from "recharts"
import { toast } from "sonner"
import { z } from "zod"
import { useIsMobile } from "@/registry/new-york-v4/hooks/use-mobile"
import { Badge } from "@/registry/new-york-v4/ui/badge"
import { Button } from "@/registry/new-york-v4/ui/button"
import {
ChartConfig,
ChartContainer,
ChartTooltip,
ChartTooltipContent,
} from "@/registry/new-york-v4/ui/chart"
import { Checkbox } from "@/registry/new-york-v4/ui/checkbox"
import {
Drawer,
DrawerClose,
DrawerContent,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerTitle,
DrawerTrigger,
} from "@/registry/new-york-v4/ui/drawer"
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/registry/new-york-v4/ui/dropdown-menu"
import { Input } from "@/registry/new-york-v4/ui/input"
import { Label } from "@/registry/new-york-v4/ui/label"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/registry/new-york-v4/ui/select"
import { Separator } from "@/registry/new-york-v4/ui/separator"
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/registry/new-york-v4/ui/table"
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from "@/registry/new-york-v4/ui/tabs"
export const schema = z.object({
id: z.number(),
header: z.string(),
type: z.string(),
status: z.string(),
target: z.string(),
limit: z.string(),
reviewer: z.string(),
})
// Create a separate component for the drag handle
function DragHandle({ id }: { id: number }) {
const { attributes, listeners } = useSortable({
id,
})
return (
<Button
{...attributes}
{...listeners}
variant="ghost"
size="icon"
className="text-muted-foreground size-7 hover:bg-transparent"
>
<IconGripVertical className="text-muted-foreground size-3" />
<span className="sr-only">Drag to reorder</span>
</Button>
)
}
const columns: ColumnDef<z.infer<typeof schema>>[] = [
{
id: "drag",
header: () => null,
cell: ({ row }) => <DragHandle id={row.original.id} />,
},
{
id: "select",
header: ({ table }) => (
<div className="flex items-center justify-center">
<Checkbox
checked={
table.getIsAllPageRowsSelected() ||
(table.getIsSomePageRowsSelected() && "indeterminate")
}
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
aria-label="Select all"
/>
</div>
),
cell: ({ row }) => (
<div className="flex items-center justify-center">
<Checkbox
checked={row.getIsSelected()}
onCheckedChange={(value) => row.toggleSelected(!!value)}
aria-label="Select row"
/>
</div>
),
enableSorting: false,
enableHiding: false,
},
{
accessorKey: "header",
header: "Header",
cell: ({ row }) => {
return <TableCellViewer item={row.original} />
},
enableHiding: false,
},
{
accessorKey: "type",
header: "Section Type",
cell: ({ row }) => (
<div className="w-32">
<Badge variant="outline" className="text-muted-foreground px-1.5">
{row.original.type}
</Badge>
</div>
),
},
{
accessorKey: "status",
header: "Status",
cell: ({ row }) => (
<Badge variant="outline" className="text-muted-foreground px-1.5">
{row.original.status === "Done" ? (
<IconCircleCheckFilled className="fill-green-500 dark:fill-green-400" />
) : (
<IconLoader />
)}
{row.original.status}
</Badge>
),
},
{
accessorKey: "target",
header: () => <div className="w-full text-right">Target</div>,
cell: ({ row }) => (
<form
onSubmit={(e) => {
e.preventDefault()
toast.promise(new Promise((resolve) => setTimeout(resolve, 1000)), {
loading: `Saving ${row.original.header}`,
success: "Done",
error: "Error",
})
}}
>
<Label htmlFor={`${row.original.id}-target`} className="sr-only">
Target
</Label>
<Input
className="hover:bg-input/30 focus-visible:bg-background dark:hover:bg-input/30 dark:focus-visible:bg-input/30 h-8 w-16 border-transparent bg-transparent text-right shadow-none focus-visible:border dark:bg-transparent"
defaultValue={row.original.target}
id={`${row.original.id}-target`}
/>
</form>
),
},
{
accessorKey: "limit",
header: () => <div className="w-full text-right">Limit</div>,
cell: ({ row }) => (
<form
onSubmit={(e) => {
e.preventDefault()
toast.promise(new Promise((resolve) => setTimeout(resolve, 1000)), {
loading: `Saving ${row.original.header}`,
success: "Done",
error: "Error",
})
}}
>
<Label htmlFor={`${row.original.id}-limit`} className="sr-only">
Limit
</Label>
<Input
className="hover:bg-input/30 focus-visible:bg-background dark:hover:bg-input/30 dark:focus-visible:bg-input/30 h-8 w-16 border-transparent bg-transparent text-right shadow-none focus-visible:border dark:bg-transparent"
defaultValue={row.original.limit}
id={`${row.original.id}-limit`}
/>
</form>
),
},
{
accessorKey: "reviewer",
header: "Reviewer",
cell: ({ row }) => {
const isAssigned = row.original.reviewer !== "Assign reviewer"
if (isAssigned) {
return row.original.reviewer
}
return (
<>
<Label htmlFor={`${row.original.id}-reviewer`} className="sr-only">
Reviewer
</Label>
<Select>
<SelectTrigger
className="w-38 **:data-[slot=select-value]:block **:data-[slot=select-value]:truncate"
size="sm"
id={`${row.original.id}-reviewer`}
>
<SelectValue placeholder="Assign reviewer" />
</SelectTrigger>
<SelectContent align="end">
<SelectItem value="Eddie Lake">Eddie Lake</SelectItem>
<SelectItem value="Jamik Tashpulatov">
Jamik Tashpulatov
</SelectItem>
</SelectContent>
</Select>
</>
)
},
},
{
id: "actions",
cell: () => (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
className="data-[state=open]:bg-muted text-muted-foreground flex size-8"
size="icon"
>
<IconDotsVertical />
<span className="sr-only">Open menu</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-32">
<DropdownMenuItem>Edit</DropdownMenuItem>
<DropdownMenuItem>Make a copy</DropdownMenuItem>
<DropdownMenuItem>Favorite</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem variant="destructive">Delete</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
),
},
]
function DraggableRow({ row }: { row: Row<z.infer<typeof schema>> }) {
const { transform, transition, setNodeRef, isDragging } = useSortable({
id: row.original.id,
})
return (
<TableRow
data-state={row.getIsSelected() && "selected"}
data-dragging={isDragging}
ref={setNodeRef}
className="relative z-0 data-[dragging=true]:z-10 data-[dragging=true]:opacity-80"
style={{
transform: CSS.Transform.toString(transform),
transition: transition,
}}
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
)
}
export function DataTable({
data: initialData,
}: {
data: z.infer<typeof schema>[]
}) {
const [data, setData] = React.useState(() => initialData)
const [rowSelection, setRowSelection] = React.useState({})
const [columnVisibility, setColumnVisibility] =
React.useState<VisibilityState>({})
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
[]
)
const [sorting, setSorting] = React.useState<SortingState>([])
const [pagination, setPagination] = React.useState({
pageIndex: 0,
pageSize: 10,
})
const sortableId = React.useId()
const sensors = useSensors(
useSensor(MouseSensor, {}),
useSensor(TouchSensor, {}),
useSensor(KeyboardSensor, {})
)
const dataIds = React.useMemo<UniqueIdentifier[]>(
() => data?.map(({ id }) => id) || [],
[data]
)
const table = useReactTable({
data,
columns,
state: {
sorting,
columnVisibility,
rowSelection,
columnFilters,
pagination,
},
getRowId: (row) => row.id.toString(),
enableRowSelection: true,
onRowSelectionChange: setRowSelection,
onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters,
onColumnVisibilityChange: setColumnVisibility,
onPaginationChange: setPagination,
getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
getFacetedRowModel: getFacetedRowModel(),
getFacetedUniqueValues: getFacetedUniqueValues(),
})
function handleDragEnd(event: DragEndEvent) {
const { active, over } = event
if (active && over && active.id !== over.id) {
setData((data) => {
const oldIndex = dataIds.indexOf(active.id)
const newIndex = dataIds.indexOf(over.id)
return arrayMove(data, oldIndex, newIndex)
})
}
}
return (
<Tabs
defaultValue="outline"
className="w-full flex-col justify-start gap-6"
>
<div className="flex items-center justify-between px-4 lg:px-6">
<Label htmlFor="view-selector" className="sr-only">
View
</Label>
<Select defaultValue="outline">
<SelectTrigger
className="flex w-fit @4xl/main:hidden"
size="sm"
id="view-selector"
>
<SelectValue placeholder="Select a view" />
</SelectTrigger>
<SelectContent>
<SelectItem value="outline">Outline</SelectItem>
<SelectItem value="past-performance">Past Performance</SelectItem>
<SelectItem value="key-personnel">Key Personnel</SelectItem>
<SelectItem value="focus-documents">Focus Documents</SelectItem>
</SelectContent>
</Select>
<TabsList className="**:data-[slot=badge]:bg-muted-foreground/30 hidden **:data-[slot=badge]:size-5 **:data-[slot=badge]:rounded-full **:data-[slot=badge]:px-1 @4xl/main:flex">
<TabsTrigger value="outline">Outline</TabsTrigger>
<TabsTrigger value="past-performance">
Past Performance <Badge variant="secondary">3</Badge>
</TabsTrigger>
<TabsTrigger value="key-personnel">
Key Personnel <Badge variant="secondary">2</Badge>
</TabsTrigger>
<TabsTrigger value="focus-documents">Focus Documents</TabsTrigger>
</TabsList>
<div className="flex items-center gap-2">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="sm">
<IconLayoutColumns />
<span className="hidden lg:inline">Customize Columns</span>
<span className="lg:hidden">Columns</span>
<IconChevronDown />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-56">
{table
.getAllColumns()
.filter(
(column) =>
typeof column.accessorFn !== "undefined" &&
column.getCanHide()
)
.map((column) => {
return (
<DropdownMenuCheckboxItem
key={column.id}
className="capitalize"
checked={column.getIsVisible()}
onCheckedChange={(value) =>
column.toggleVisibility(!!value)
}
>
{column.id}
</DropdownMenuCheckboxItem>
)
})}
</DropdownMenuContent>
</DropdownMenu>
<Button variant="outline" size="sm">
<IconPlus />
<span className="hidden lg:inline">Add Section</span>
</Button>
</div>
</div>
<TabsContent
value="outline"
className="relative flex flex-col gap-4 overflow-auto px-4 lg:px-6"
>
<div className="overflow-hidden rounded-lg border">
<DndContext
collisionDetection={closestCenter}
modifiers={[restrictToVerticalAxis]}
onDragEnd={handleDragEnd}
sensors={sensors}
id={sortableId}
>
<Table>
<TableHeader className="bg-muted sticky top-0 z-10">
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id} colSpan={header.colSpan}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext()
)}
</TableHead>
)
})}
</TableRow>
))}
</TableHeader>
<TableBody className="**:data-[slot=table-cell]:first:w-8">
{table.getRowModel().rows?.length ? (
<SortableContext
items={dataIds}
strategy={verticalListSortingStrategy}
>
{table.getRowModel().rows.map((row) => (
<DraggableRow key={row.id} row={row} />
))}
</SortableContext>
) : (
<TableRow>
<TableCell
colSpan={columns.length}
className="h-24 text-center"
>
No results.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</DndContext>
</div>
<div className="flex items-center justify-between px-4">
<div className="text-muted-foreground hidden flex-1 text-sm lg:flex">
{table.getFilteredSelectedRowModel().rows.length} of{" "}
{table.getFilteredRowModel().rows.length} row(s) selected.
</div>
<div className="flex w-full items-center gap-8 lg:w-fit">
<div className="hidden items-center gap-2 lg:flex">
<Label htmlFor="rows-per-page" className="text-sm font-medium">
Rows per page
</Label>
<Select
value={`${table.getState().pagination.pageSize}`}
onValueChange={(value) => {
table.setPageSize(Number(value))
}}
>
<SelectTrigger size="sm" className="w-20" id="rows-per-page">
<SelectValue
placeholder={table.getState().pagination.pageSize}
/>
</SelectTrigger>
<SelectContent side="top">
{[10, 20, 30, 40, 50].map((pageSize) => (
<SelectItem key={pageSize} value={`${pageSize}`}>
{pageSize}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="flex w-fit items-center justify-center text-sm font-medium">
Page {table.getState().pagination.pageIndex + 1} of{" "}
{table.getPageCount()}
</div>
<div className="ml-auto flex items-center gap-2 lg:ml-0">
<Button
variant="outline"
className="hidden h-8 w-8 p-0 lg:flex"
onClick={() => table.setPageIndex(0)}
disabled={!table.getCanPreviousPage()}
>
<span className="sr-only">Go to first page</span>
<IconChevronsLeft />
</Button>
<Button
variant="outline"
className="size-8"
size="icon"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
<span className="sr-only">Go to previous page</span>
<IconChevronLeft />
</Button>
<Button
variant="outline"
className="size-8"
size="icon"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
<span className="sr-only">Go to next page</span>
<IconChevronRight />
</Button>
<Button
variant="outline"
className="hidden size-8 lg:flex"
size="icon"
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
disabled={!table.getCanNextPage()}
>
<span className="sr-only">Go to last page</span>
<IconChevronsRight />
</Button>
</div>
</div>
</div>
</TabsContent>
<TabsContent
value="past-performance"
className="flex flex-col px-4 lg:px-6"
>
<div className="aspect-video w-full flex-1 rounded-lg border border-dashed"></div>
</TabsContent>
<TabsContent value="key-personnel" className="flex flex-col px-4 lg:px-6">
<div className="aspect-video w-full flex-1 rounded-lg border border-dashed"></div>
</TabsContent>
<TabsContent
value="focus-documents"
className="flex flex-col px-4 lg:px-6"
>
<div className="aspect-video w-full flex-1 rounded-lg border border-dashed"></div>
</TabsContent>
</Tabs>
)
}
const chartData = [
{ month: "January", desktop: 186, mobile: 80 },
{ month: "February", desktop: 305, mobile: 200 },
{ month: "March", desktop: 237, mobile: 120 },
{ month: "April", desktop: 73, mobile: 190 },
{ month: "May", desktop: 209, mobile: 130 },
{ month: "June", desktop: 214, mobile: 140 },
]
const chartConfig = {
desktop: {
label: "Desktop",
color: "var(--primary)",
},
mobile: {
label: "Mobile",
color: "var(--primary)",
},
} satisfies ChartConfig
function TableCellViewer({ item }: { item: z.infer<typeof schema> }) {
const isMobile = useIsMobile()
return (
<Drawer direction={isMobile ? "bottom" : "right"}>
<DrawerTrigger asChild>
<Button variant="link" className="text-foreground w-fit px-0 text-left">
{item.header}
</Button>
</DrawerTrigger>
<DrawerContent>
<DrawerHeader className="gap-1">
<DrawerTitle>{item.header}</DrawerTitle>
<DrawerDescription>
Showing total visitors for the last 6 months
</DrawerDescription>
</DrawerHeader>
<div className="flex flex-col gap-4 overflow-y-auto px-4 text-sm">
{!isMobile && (
<>
<ChartContainer config={chartConfig}>
<AreaChart
accessibilityLayer
data={chartData}
margin={{
left: 0,
right: 10,
}}
>
<CartesianGrid vertical={false} />
<XAxis
dataKey="month"
tickLine={false}
axisLine={false}
tickMargin={8}
tickFormatter={(value) => value.slice(0, 3)}
hide
/>
<ChartTooltip
cursor={false}
content={<ChartTooltipContent indicator="dot" />}
/>
<Area
dataKey="mobile"
type="natural"
fill="var(--color-mobile)"
fillOpacity={0.6}
stroke="var(--color-mobile)"
stackId="a"
/>
<Area
dataKey="desktop"
type="natural"
fill="var(--color-desktop)"
fillOpacity={0.4}
stroke="var(--color-desktop)"
stackId="a"
/>
</AreaChart>
</ChartContainer>
<Separator />
<div className="grid gap-2">
<div className="flex gap-2 leading-none font-medium">
Trending up by 5.2% this month{" "}
<IconTrendingUp className="size-4" />
</div>
<div className="text-muted-foreground">
Showing total visitors for the last 6 months. This is just
some random text to test the layout. It spans multiple lines
and should wrap around.
</div>
</div>
<Separator />
</>
)}
<form className="flex flex-col gap-4">
<div className="flex flex-col gap-3">
<Label htmlFor="header">Header</Label>
<Input id="header" defaultValue={item.header} />
</div>
<div className="grid grid-cols-2 gap-4">
<div className="flex flex-col gap-3">
<Label htmlFor="type">Type</Label>
<Select defaultValue={item.type}>
<SelectTrigger id="type" className="w-full">
<SelectValue placeholder="Select a type" />
</SelectTrigger>
<SelectContent>
<SelectItem value="Table of Contents">
Table of Contents
</SelectItem>
<SelectItem value="Executive Summary">
Executive Summary
</SelectItem>
<SelectItem value="Technical Approach">
Technical Approach
</SelectItem>
<SelectItem value="Design">Design</SelectItem>
<SelectItem value="Capabilities">Capabilities</SelectItem>
<SelectItem value="Focus Documents">
Focus Documents
</SelectItem>
<SelectItem value="Narrative">Narrative</SelectItem>
<SelectItem value="Cover Page">Cover Page</SelectItem>
</SelectContent>
</Select>
</div>
<div className="flex flex-col gap-3">
<Label htmlFor="status">Status</Label>
<Select defaultValue={item.status}>
<SelectTrigger id="status" className="w-full">
<SelectValue placeholder="Select a status" />
</SelectTrigger>
<SelectContent>
<SelectItem value="Done">Done</SelectItem>
<SelectItem value="In Progress">In Progress</SelectItem>
<SelectItem value="Not Started">Not Started</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="flex flex-col gap-3">
<Label htmlFor="target">Target</Label>
<Input id="target" defaultValue={item.target} />
</div>
<div className="flex flex-col gap-3">
<Label htmlFor="limit">Limit</Label>
<Input id="limit" defaultValue={item.limit} />
</div>
</div>
<div className="flex flex-col gap-3">
<Label htmlFor="reviewer">Reviewer</Label>
<Select defaultValue={item.reviewer}>
<SelectTrigger id="reviewer" className="w-full">
<SelectValue placeholder="Select a reviewer" />
</SelectTrigger>
<SelectContent>
<SelectItem value="Eddie Lake">Eddie Lake</SelectItem>
<SelectItem value="Jamik Tashpulatov">
Jamik Tashpulatov
</SelectItem>
<SelectItem value="Emily Whalen">Emily Whalen</SelectItem>
</SelectContent>
</Select>
</div>
</form>
</div>
<DrawerFooter>
<Button>Submit</Button>
<DrawerClose asChild>
<Button variant="outline">Done</Button>
</DrawerClose>
</DrawerFooter>
</DrawerContent>
</Drawer>
)
}

View File

@@ -0,0 +1,27 @@
"use client"
import * as React from "react"
import { IconBrightness } from "@tabler/icons-react"
import { useTheme } from "next-themes"
import { Button } from "@/registry/new-york-v4/ui/button"
export function ModeToggle() {
const { setTheme, resolvedTheme } = useTheme()
const toggleTheme = React.useCallback(() => {
setTheme(resolvedTheme === "dark" ? "light" : "dark")
}, [resolvedTheme, setTheme])
return (
<Button
variant="secondary"
size="icon"
className="group/toggle size-8"
onClick={toggleTheme}
>
<IconBrightness />
<span className="sr-only">Toggle theme</span>
</Button>
)
}

View File

@@ -0,0 +1,92 @@
"use client"
import {
IconDots,
IconFolder,
IconShare3,
IconTrash,
type Icon,
} from "@tabler/icons-react"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/registry/new-york-v4/ui/dropdown-menu"
import {
SidebarGroup,
SidebarGroupLabel,
SidebarMenu,
SidebarMenuAction,
SidebarMenuButton,
SidebarMenuItem,
useSidebar,
} from "@/registry/new-york-v4/ui/sidebar"
export function NavDocuments({
items,
}: {
items: {
name: string
url: string
icon: Icon
}[]
}) {
const { isMobile } = useSidebar()
return (
<SidebarGroup className="group-data-[collapsible=icon]:hidden">
<SidebarGroupLabel>Documents</SidebarGroupLabel>
<SidebarMenu>
{items.map((item) => (
<SidebarMenuItem key={item.name}>
<SidebarMenuButton asChild>
<a href={item.url}>
<item.icon />
<span>{item.name}</span>
</a>
</SidebarMenuButton>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<SidebarMenuAction
showOnHover
className="data-[state=open]:bg-accent rounded-sm"
>
<IconDots />
<span className="sr-only">More</span>
</SidebarMenuAction>
</DropdownMenuTrigger>
<DropdownMenuContent
className="w-24 rounded-lg"
side={isMobile ? "bottom" : "right"}
align={isMobile ? "end" : "start"}
>
<DropdownMenuItem>
<IconFolder />
<span>Open</span>
</DropdownMenuItem>
<DropdownMenuItem>
<IconShare3 />
<span>Share</span>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem variant="destructive">
<IconTrash />
<span>Delete</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
))}
<SidebarMenuItem>
<SidebarMenuButton className="text-sidebar-foreground/70">
<IconDots className="text-sidebar-foreground/70" />
<span>More</span>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroup>
)
}

View File

@@ -0,0 +1,58 @@
"use client"
import { IconCirclePlusFilled, IconMail, type Icon } from "@tabler/icons-react"
import { Button } from "@/registry/new-york-v4/ui/button"
import {
SidebarGroup,
SidebarGroupContent,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
} from "@/registry/new-york-v4/ui/sidebar"
export function NavMain({
items,
}: {
items: {
title: string
url: string
icon?: Icon
}[]
}) {
return (
<SidebarGroup>
<SidebarGroupContent className="flex flex-col gap-2">
<SidebarMenu>
<SidebarMenuItem className="flex items-center gap-2">
<SidebarMenuButton
tooltip="Quick Create"
className="bg-primary text-primary-foreground hover:bg-primary/90 hover:text-primary-foreground active:bg-primary/90 active:text-primary-foreground min-w-8 duration-200 ease-linear"
>
<IconCirclePlusFilled />
<span>Quick Create</span>
</SidebarMenuButton>
<Button
size="icon"
className="size-8 group-data-[collapsible=icon]:opacity-0"
variant="outline"
>
<IconMail />
<span className="sr-only">Inbox</span>
</Button>
</SidebarMenuItem>
</SidebarMenu>
<SidebarMenu>
{items.map((item) => (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton tooltip={item.title}>
{item.icon && <item.icon />}
<span>{item.title}</span>
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
)
}

View File

@@ -0,0 +1,71 @@
"use client"
import * as React from "react"
import { IconBrightness, type Icon } from "@tabler/icons-react"
import { useTheme } from "next-themes"
import {
SidebarGroup,
SidebarGroupContent,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
} from "@/registry/new-york-v4/ui/sidebar"
import { Skeleton } from "@/registry/new-york-v4/ui/skeleton"
import { Switch } from "@/registry/new-york-v4/ui/switch"
export function NavSecondary({
items,
...props
}: {
items: {
title: string
url: string
icon: Icon
}[]
} & React.ComponentPropsWithoutRef<typeof SidebarGroup>) {
const { resolvedTheme, setTheme } = useTheme()
const [mounted, setMounted] = React.useState(false)
React.useEffect(() => {
setMounted(true)
}, [])
return (
<SidebarGroup {...props}>
<SidebarGroupContent>
<SidebarMenu>
{items.map((item) => (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton asChild>
<a href={item.url}>
<item.icon />
<span>{item.title}</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
))}
<SidebarMenuItem className="group-data-[collapsible=icon]:hidden">
<SidebarMenuButton asChild>
<label>
<IconBrightness />
<span>Dark Mode</span>
{mounted ? (
<Switch
className="ml-auto"
checked={resolvedTheme !== "light"}
onCheckedChange={() =>
setTheme(resolvedTheme === "dark" ? "light" : "dark")
}
/>
) : (
<Skeleton className="ml-auto h-4 w-8 rounded-full" />
)}
</label>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
)
}

View File

@@ -0,0 +1,110 @@
"use client"
import {
IconCreditCard,
IconDotsVertical,
IconLogout,
IconNotification,
IconUserCircle,
} from "@tabler/icons-react"
import {
Avatar,
AvatarFallback,
AvatarImage,
} from "@/registry/new-york-v4/ui/avatar"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/registry/new-york-v4/ui/dropdown-menu"
import {
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
useSidebar,
} from "@/registry/new-york-v4/ui/sidebar"
export function NavUser({
user,
}: {
user: {
name: string
email: string
avatar: string
}
}) {
const { isMobile } = useSidebar()
return (
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<SidebarMenuButton
size="lg"
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
>
<Avatar className="h-8 w-8 rounded-lg grayscale">
<AvatarImage src={user.avatar} alt={user.name} />
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
</Avatar>
<div className="grid flex-1 text-left text-sm leading-tight">
<span className="truncate font-medium">{user.name}</span>
<span className="text-muted-foreground truncate text-xs">
{user.email}
</span>
</div>
<IconDotsVertical className="ml-auto size-4" />
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent
className="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg"
side={isMobile ? "bottom" : "right"}
align="end"
sideOffset={4}
>
<DropdownMenuLabel className="p-0 font-normal">
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
<Avatar className="h-8 w-8 rounded-lg">
<AvatarImage src={user.avatar} alt={user.name} />
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
</Avatar>
<div className="grid flex-1 text-left text-sm leading-tight">
<span className="truncate font-medium">{user.name}</span>
<span className="text-muted-foreground truncate text-xs">
{user.email}
</span>
</div>
</div>
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<IconUserCircle />
Account
</DropdownMenuItem>
<DropdownMenuItem>
<IconCreditCard />
Billing
</DropdownMenuItem>
<DropdownMenuItem>
<IconNotification />
Notifications
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuItem>
<IconLogout />
Log out
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
)
}

View File

@@ -0,0 +1,102 @@
import { IconTrendingDown, IconTrendingUp } from "@tabler/icons-react"
import { Badge } from "@/registry/new-york-v4/ui/badge"
import {
Card,
CardAction,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/new-york-v4/ui/card"
export function SectionCards() {
return (
<div className="*:data-[slot=card]:from-primary/5 *:data-[slot=card]:to-card dark:*:data-[slot=card]:bg-card grid grid-cols-1 gap-4 px-4 *:data-[slot=card]:bg-gradient-to-t *:data-[slot=card]:shadow-xs lg:px-6 @xl/main:grid-cols-2 @5xl/main:grid-cols-4">
<Card className="@container/card">
<CardHeader>
<CardDescription>Total Revenue</CardDescription>
<CardTitle className="text-2xl font-semibold tabular-nums @[250px]/card:text-3xl">
$1,250.00
</CardTitle>
<CardAction>
<Badge variant="outline">
<IconTrendingUp />
+12.5%
</Badge>
</CardAction>
</CardHeader>
<CardFooter className="flex-col items-start gap-1.5 text-sm">
<div className="line-clamp-1 flex gap-2 font-medium">
Trending up this month <IconTrendingUp className="size-4" />
</div>
<div className="text-muted-foreground">
Visitors for the last 6 months
</div>
</CardFooter>
</Card>
<Card className="@container/card">
<CardHeader>
<CardDescription>New Customers</CardDescription>
<CardTitle className="text-2xl font-semibold tabular-nums @[250px]/card:text-3xl">
1,234
</CardTitle>
<CardAction>
<Badge variant="outline">
<IconTrendingDown />
-20%
</Badge>
</CardAction>
</CardHeader>
<CardFooter className="flex-col items-start gap-1.5 text-sm">
<div className="line-clamp-1 flex gap-2 font-medium">
Down 20% this period <IconTrendingDown className="size-4" />
</div>
<div className="text-muted-foreground">
Acquisition needs attention
</div>
</CardFooter>
</Card>
<Card className="@container/card">
<CardHeader>
<CardDescription>Active Accounts</CardDescription>
<CardTitle className="text-2xl font-semibold tabular-nums @[250px]/card:text-3xl">
45,678
</CardTitle>
<CardAction>
<Badge variant="outline">
<IconTrendingUp />
+12.5%
</Badge>
</CardAction>
</CardHeader>
<CardFooter className="flex-col items-start gap-1.5 text-sm">
<div className="line-clamp-1 flex gap-2 font-medium">
Strong user retention <IconTrendingUp className="size-4" />
</div>
<div className="text-muted-foreground">Engagement exceed targets</div>
</CardFooter>
</Card>
<Card className="@container/card">
<CardHeader>
<CardDescription>Growth Rate</CardDescription>
<CardTitle className="text-2xl font-semibold tabular-nums @[250px]/card:text-3xl">
4.5%
</CardTitle>
<CardAction>
<Badge variant="outline">
<IconTrendingUp />
+4.5%
</Badge>
</CardAction>
</CardHeader>
<CardFooter className="flex-col items-start gap-1.5 text-sm">
<div className="line-clamp-1 flex gap-2 font-medium">
Steady performance increase <IconTrendingUp className="size-4" />
</div>
<div className="text-muted-foreground">Meets growth projections</div>
</CardFooter>
</Card>
</div>
)
}

View File

@@ -0,0 +1,34 @@
import { Button } from "@/registry/new-york-v4/ui/button"
import { Separator } from "@/registry/new-york-v4/ui/separator"
import { SidebarTrigger } from "@/registry/new-york-v4/ui/sidebar"
import { ModeToggle } from "@/app/(examples)/dashboard/components/mode-toggle"
import { ThemeSelector } from "@/app/(examples)/dashboard/components/theme-selector"
export function SiteHeader() {
return (
<header className="flex h-(--header-height) shrink-0 items-center gap-2 border-b transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-(--header-height)">
<div className="flex w-full items-center gap-1 px-4 lg:gap-2 lg:px-6">
<SidebarTrigger className="-ml-1" />
<Separator
orientation="vertical"
className="mx-2 data-[orientation=vertical]:h-4"
/>
<h1 className="text-base font-medium">Documents</h1>
<div className="ml-auto flex items-center gap-2">
<Button variant="ghost" asChild size="sm" className="hidden sm:flex">
<a
href="https://github.com/shadcn-ui/ui/tree/main/apps/v4/app/(examples)/dashboard"
rel="noopener noreferrer"
target="_blank"
className="dark:text-foreground"
>
GitHub
</a>
</Button>
<ThemeSelector />
<ModeToggle />
</div>
</div>
</header>
)
}

View File

@@ -0,0 +1,103 @@
"use client"
import { useThemeConfig } from "@/components/active-theme"
import { Label } from "@/registry/new-york-v4/ui/label"
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectSeparator,
SelectTrigger,
SelectValue,
} from "@/registry/new-york-v4/ui/select"
const DEFAULT_THEMES = [
{
name: "Default",
value: "default",
},
{
name: "Blue",
value: "blue",
},
{
name: "Green",
value: "green",
},
{
name: "Amber",
value: "amber",
},
]
const SCALED_THEMES = [
{
name: "Default",
value: "default-scaled",
},
{
name: "Blue",
value: "blue-scaled",
},
]
const MONO_THEMES = [
{
name: "Mono",
value: "mono-scaled",
},
]
export function ThemeSelector() {
const { activeTheme, setActiveTheme } = useThemeConfig()
return (
<div className="flex items-center gap-2">
<Label htmlFor="theme-selector" className="sr-only">
Theme
</Label>
<Select value={activeTheme} onValueChange={setActiveTheme}>
<SelectTrigger
id="theme-selector"
size="sm"
className="justify-start *:data-[slot=select-value]:w-12"
>
<span className="text-muted-foreground hidden sm:block">
Select a theme:
</span>
<span className="text-muted-foreground block sm:hidden">Theme</span>
<SelectValue placeholder="Select a theme" />
</SelectTrigger>
<SelectContent align="end">
<SelectGroup>
<SelectLabel>Default</SelectLabel>
{DEFAULT_THEMES.map((theme) => (
<SelectItem key={theme.name} value={theme.value}>
{theme.name}
</SelectItem>
))}
</SelectGroup>
<SelectSeparator />
<SelectGroup>
<SelectLabel>Scaled</SelectLabel>
{SCALED_THEMES.map((theme) => (
<SelectItem key={theme.name} value={theme.value}>
{theme.name}
</SelectItem>
))}
</SelectGroup>
<SelectGroup>
<SelectLabel>Monospaced</SelectLabel>
{MONO_THEMES.map((theme) => (
<SelectItem key={theme.name} value={theme.value}>
{theme.name}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</div>
)
}

View File

@@ -0,0 +1,614 @@
[
{
"id": 1,
"header": "Cover page",
"type": "Cover page",
"status": "In Process",
"target": "18",
"limit": "5",
"reviewer": "Eddie Lake"
},
{
"id": 2,
"header": "Table of contents",
"type": "Table of contents",
"status": "Done",
"target": "29",
"limit": "24",
"reviewer": "Eddie Lake"
},
{
"id": 3,
"header": "Executive summary",
"type": "Narrative",
"status": "Done",
"target": "10",
"limit": "13",
"reviewer": "Eddie Lake"
},
{
"id": 4,
"header": "Technical approach",
"type": "Narrative",
"status": "Done",
"target": "27",
"limit": "23",
"reviewer": "Jamik Tashpulatov"
},
{
"id": 5,
"header": "Design",
"type": "Narrative",
"status": "In Process",
"target": "2",
"limit": "16",
"reviewer": "Jamik Tashpulatov"
},
{
"id": 6,
"header": "Capabilities",
"type": "Narrative",
"status": "In Process",
"target": "20",
"limit": "8",
"reviewer": "Jamik Tashpulatov"
},
{
"id": 7,
"header": "Integration with existing systems",
"type": "Narrative",
"status": "In Process",
"target": "19",
"limit": "21",
"reviewer": "Jamik Tashpulatov"
},
{
"id": 8,
"header": "Innovation and Advantages",
"type": "Narrative",
"status": "Done",
"target": "25",
"limit": "26",
"reviewer": "Assign reviewer"
},
{
"id": 9,
"header": "Overview of EMR's Innovative Solutions",
"type": "Technical content",
"status": "Done",
"target": "7",
"limit": "23",
"reviewer": "Assign reviewer"
},
{
"id": 10,
"header": "Advanced Algorithms and Machine Learning",
"type": "Narrative",
"status": "Done",
"target": "30",
"limit": "28",
"reviewer": "Assign reviewer"
},
{
"id": 11,
"header": "Adaptive Communication Protocols",
"type": "Narrative",
"status": "Done",
"target": "9",
"limit": "31",
"reviewer": "Assign reviewer"
},
{
"id": 12,
"header": "Advantages Over Current Technologies",
"type": "Narrative",
"status": "Done",
"target": "12",
"limit": "0",
"reviewer": "Assign reviewer"
},
{
"id": 13,
"header": "Past Performance",
"type": "Narrative",
"status": "Done",
"target": "22",
"limit": "33",
"reviewer": "Assign reviewer"
},
{
"id": 14,
"header": "Customer Feedback and Satisfaction Levels",
"type": "Narrative",
"status": "Done",
"target": "15",
"limit": "34",
"reviewer": "Assign reviewer"
},
{
"id": 15,
"header": "Implementation Challenges and Solutions",
"type": "Narrative",
"status": "Done",
"target": "3",
"limit": "35",
"reviewer": "Assign reviewer"
},
{
"id": 16,
"header": "Security Measures and Data Protection Policies",
"type": "Narrative",
"status": "In Process",
"target": "6",
"limit": "36",
"reviewer": "Assign reviewer"
},
{
"id": 17,
"header": "Scalability and Future Proofing",
"type": "Narrative",
"status": "Done",
"target": "4",
"limit": "37",
"reviewer": "Assign reviewer"
},
{
"id": 18,
"header": "Cost-Benefit Analysis",
"type": "Plain language",
"status": "Done",
"target": "14",
"limit": "38",
"reviewer": "Assign reviewer"
},
{
"id": 19,
"header": "User Training and Onboarding Experience",
"type": "Narrative",
"status": "Done",
"target": "17",
"limit": "39",
"reviewer": "Assign reviewer"
},
{
"id": 20,
"header": "Future Development Roadmap",
"type": "Narrative",
"status": "Done",
"target": "11",
"limit": "40",
"reviewer": "Assign reviewer"
},
{
"id": 21,
"header": "System Architecture Overview",
"type": "Technical content",
"status": "In Process",
"target": "24",
"limit": "18",
"reviewer": "Maya Johnson"
},
{
"id": 22,
"header": "Risk Management Plan",
"type": "Narrative",
"status": "Done",
"target": "15",
"limit": "22",
"reviewer": "Carlos Rodriguez"
},
{
"id": 23,
"header": "Compliance Documentation",
"type": "Legal",
"status": "In Process",
"target": "31",
"limit": "27",
"reviewer": "Sarah Chen"
},
{
"id": 24,
"header": "API Documentation",
"type": "Technical content",
"status": "Done",
"target": "8",
"limit": "12",
"reviewer": "Raj Patel"
},
{
"id": 25,
"header": "User Interface Mockups",
"type": "Visual",
"status": "In Process",
"target": "19",
"limit": "25",
"reviewer": "Leila Ahmadi"
},
{
"id": 26,
"header": "Database Schema",
"type": "Technical content",
"status": "Done",
"target": "22",
"limit": "20",
"reviewer": "Thomas Wilson"
},
{
"id": 27,
"header": "Testing Methodology",
"type": "Technical content",
"status": "In Process",
"target": "17",
"limit": "14",
"reviewer": "Assign reviewer"
},
{
"id": 28,
"header": "Deployment Strategy",
"type": "Narrative",
"status": "Done",
"target": "26",
"limit": "30",
"reviewer": "Eddie Lake"
},
{
"id": 29,
"header": "Budget Breakdown",
"type": "Financial",
"status": "In Process",
"target": "13",
"limit": "16",
"reviewer": "Jamik Tashpulatov"
},
{
"id": 30,
"header": "Market Analysis",
"type": "Research",
"status": "Done",
"target": "29",
"limit": "32",
"reviewer": "Sophia Martinez"
},
{
"id": 31,
"header": "Competitor Comparison",
"type": "Research",
"status": "In Process",
"target": "21",
"limit": "19",
"reviewer": "Assign reviewer"
},
{
"id": 32,
"header": "Maintenance Plan",
"type": "Technical content",
"status": "Done",
"target": "16",
"limit": "23",
"reviewer": "Alex Thompson"
},
{
"id": 33,
"header": "User Personas",
"type": "Research",
"status": "In Process",
"target": "27",
"limit": "24",
"reviewer": "Nina Patel"
},
{
"id": 34,
"header": "Accessibility Compliance",
"type": "Legal",
"status": "Done",
"target": "18",
"limit": "21",
"reviewer": "Assign reviewer"
},
{
"id": 35,
"header": "Performance Metrics",
"type": "Technical content",
"status": "In Process",
"target": "23",
"limit": "26",
"reviewer": "David Kim"
},
{
"id": 36,
"header": "Disaster Recovery Plan",
"type": "Technical content",
"status": "Done",
"target": "14",
"limit": "17",
"reviewer": "Jamik Tashpulatov"
},
{
"id": 37,
"header": "Third-party Integrations",
"type": "Technical content",
"status": "In Process",
"target": "25",
"limit": "28",
"reviewer": "Eddie Lake"
},
{
"id": 38,
"header": "User Feedback Summary",
"type": "Research",
"status": "Done",
"target": "20",
"limit": "15",
"reviewer": "Assign reviewer"
},
{
"id": 39,
"header": "Localization Strategy",
"type": "Narrative",
"status": "In Process",
"target": "12",
"limit": "19",
"reviewer": "Maria Garcia"
},
{
"id": 40,
"header": "Mobile Compatibility",
"type": "Technical content",
"status": "Done",
"target": "28",
"limit": "31",
"reviewer": "James Wilson"
},
{
"id": 41,
"header": "Data Migration Plan",
"type": "Technical content",
"status": "In Process",
"target": "19",
"limit": "22",
"reviewer": "Assign reviewer"
},
{
"id": 42,
"header": "Quality Assurance Protocols",
"type": "Technical content",
"status": "Done",
"target": "30",
"limit": "33",
"reviewer": "Priya Singh"
},
{
"id": 43,
"header": "Stakeholder Analysis",
"type": "Research",
"status": "In Process",
"target": "11",
"limit": "14",
"reviewer": "Eddie Lake"
},
{
"id": 44,
"header": "Environmental Impact Assessment",
"type": "Research",
"status": "Done",
"target": "24",
"limit": "27",
"reviewer": "Assign reviewer"
},
{
"id": 45,
"header": "Intellectual Property Rights",
"type": "Legal",
"status": "In Process",
"target": "17",
"limit": "20",
"reviewer": "Sarah Johnson"
},
{
"id": 46,
"header": "Customer Support Framework",
"type": "Narrative",
"status": "Done",
"target": "22",
"limit": "25",
"reviewer": "Jamik Tashpulatov"
},
{
"id": 47,
"header": "Version Control Strategy",
"type": "Technical content",
"status": "In Process",
"target": "15",
"limit": "18",
"reviewer": "Assign reviewer"
},
{
"id": 48,
"header": "Continuous Integration Pipeline",
"type": "Technical content",
"status": "Done",
"target": "26",
"limit": "29",
"reviewer": "Michael Chen"
},
{
"id": 49,
"header": "Regulatory Compliance",
"type": "Legal",
"status": "In Process",
"target": "13",
"limit": "16",
"reviewer": "Assign reviewer"
},
{
"id": 50,
"header": "User Authentication System",
"type": "Technical content",
"status": "Done",
"target": "28",
"limit": "31",
"reviewer": "Eddie Lake"
},
{
"id": 51,
"header": "Data Analytics Framework",
"type": "Technical content",
"status": "In Process",
"target": "21",
"limit": "24",
"reviewer": "Jamik Tashpulatov"
},
{
"id": 52,
"header": "Cloud Infrastructure",
"type": "Technical content",
"status": "Done",
"target": "16",
"limit": "19",
"reviewer": "Assign reviewer"
},
{
"id": 53,
"header": "Network Security Measures",
"type": "Technical content",
"status": "In Process",
"target": "29",
"limit": "32",
"reviewer": "Lisa Wong"
},
{
"id": 54,
"header": "Project Timeline",
"type": "Planning",
"status": "Done",
"target": "14",
"limit": "17",
"reviewer": "Eddie Lake"
},
{
"id": 55,
"header": "Resource Allocation",
"type": "Planning",
"status": "In Process",
"target": "27",
"limit": "30",
"reviewer": "Assign reviewer"
},
{
"id": 56,
"header": "Team Structure and Roles",
"type": "Planning",
"status": "Done",
"target": "20",
"limit": "23",
"reviewer": "Jamik Tashpulatov"
},
{
"id": 57,
"header": "Communication Protocols",
"type": "Planning",
"status": "In Process",
"target": "15",
"limit": "18",
"reviewer": "Assign reviewer"
},
{
"id": 58,
"header": "Success Metrics",
"type": "Planning",
"status": "Done",
"target": "30",
"limit": "33",
"reviewer": "Eddie Lake"
},
{
"id": 59,
"header": "Internationalization Support",
"type": "Technical content",
"status": "In Process",
"target": "23",
"limit": "26",
"reviewer": "Jamik Tashpulatov"
},
{
"id": 60,
"header": "Backup and Recovery Procedures",
"type": "Technical content",
"status": "Done",
"target": "18",
"limit": "21",
"reviewer": "Assign reviewer"
},
{
"id": 61,
"header": "Monitoring and Alerting System",
"type": "Technical content",
"status": "In Process",
"target": "25",
"limit": "28",
"reviewer": "Daniel Park"
},
{
"id": 62,
"header": "Code Review Guidelines",
"type": "Technical content",
"status": "Done",
"target": "12",
"limit": "15",
"reviewer": "Eddie Lake"
},
{
"id": 63,
"header": "Documentation Standards",
"type": "Technical content",
"status": "In Process",
"target": "27",
"limit": "30",
"reviewer": "Jamik Tashpulatov"
},
{
"id": 64,
"header": "Release Management Process",
"type": "Planning",
"status": "Done",
"target": "22",
"limit": "25",
"reviewer": "Assign reviewer"
},
{
"id": 65,
"header": "Feature Prioritization Matrix",
"type": "Planning",
"status": "In Process",
"target": "19",
"limit": "22",
"reviewer": "Emma Davis"
},
{
"id": 66,
"header": "Technical Debt Assessment",
"type": "Technical content",
"status": "Done",
"target": "24",
"limit": "27",
"reviewer": "Eddie Lake"
},
{
"id": 67,
"header": "Capacity Planning",
"type": "Planning",
"status": "In Process",
"target": "21",
"limit": "24",
"reviewer": "Jamik Tashpulatov"
},
{
"id": 68,
"header": "Service Level Agreements",
"type": "Legal",
"status": "Done",
"target": "26",
"limit": "29",
"reviewer": "Assign reviewer"
}
]

View File

@@ -0,0 +1,36 @@
import { cookies } from "next/headers"
import {
SidebarInset,
SidebarProvider,
} from "@/registry/new-york-v4/ui/sidebar"
import { AppSidebar } from "@/app/(examples)/dashboard/components/app-sidebar"
import { SiteHeader } from "@/app/(examples)/dashboard/components/site-header"
import "@/app/(examples)/dashboard/theme.css"
export default async function DashboardLayout({
children,
}: {
children: React.ReactNode
}) {
const cookieStore = await cookies()
const defaultOpen = cookieStore.get("sidebar_state")?.value === "true"
return (
<SidebarProvider
defaultOpen={defaultOpen}
style={
{
"--sidebar-width": "calc(var(--spacing) * 72)",
} as React.CSSProperties
}
>
<AppSidebar variant="inset" />
<SidebarInset>
<SiteHeader />
<div className="flex flex-1 flex-col">{children}</div>
</SidebarInset>
</SidebarProvider>
)
}

View File

@@ -0,0 +1,18 @@
import { ChartAreaInteractive } from "@/app/(examples)/dashboard/components/chart-area-interactive"
import { DataTable } from "@/app/(examples)/dashboard/components/data-table"
import { SectionCards } from "@/app/(examples)/dashboard/components/section-cards"
import data from "@/app/(examples)/dashboard/data.json"
export default function Page() {
return (
<div className="@container/main flex flex-1 flex-col gap-2">
<div className="flex flex-col gap-4 py-4 md:gap-6 md:py-6">
<SectionCards />
<div className="px-4 lg:px-6">
<ChartAreaInteractive />
</div>
<DataTable data={data} />
</div>
</div>
)
}

View File

@@ -0,0 +1,105 @@
body {
@apply overscroll-none bg-transparent;
}
:root {
--font-sans: var(--font-inter);
--header-height: calc(var(--spacing) * 12 + 1px);
}
.theme-scaled {
@media (min-width: 1024px) {
--radius: 0.6rem;
--text-lg: 1.05rem;
--text-base: 0.85rem;
--text-sm: 0.8rem;
--spacing: 0.222222rem;
}
[data-slot="card"] {
--spacing: 0.16rem;
}
[data-slot="select-trigger"],
[data-slot="toggle-group-item"] {
--spacing: 0.222222rem;
}
}
.theme-default,
.theme-default-scaled {
--primary: var(--color-neutral-600);
--primary-foreground: var(--color-neutral-50);
@variant dark {
--primary: var(--color-neutral-500);
--primary-foreground: var(--color-neutral-50);
}
}
.theme-blue,
.theme-blue-scaled {
--primary: var(--color-blue-600);
--primary-foreground: var(--color-blue-50);
@variant dark {
--primary: var(--color-blue-500);
--primary-foreground: var(--color-blue-50);
}
}
.theme-green,
.theme-green-scaled {
--primary: var(--color-lime-600);
--primary-foreground: var(--color-lime-50);
@variant dark {
--primary: var(--color-lime-600);
--primary-foreground: var(--color-lime-50);
}
}
.theme-amber,
.theme-amber-scaled {
--primary: var(--color-amber-600);
--primary-foreground: var(--color-amber-50);
@variant dark {
--primary: var(--color-amber-500);
--primary-foreground: var(--color-amber-50);
}
}
.theme-mono,
.theme-mono-scaled {
--font-sans: var(--font-mono);
--primary: var(--color-neutral-600);
--primary-foreground: var(--color-neutral-50);
@variant dark {
--primary: var(--color-neutral-500);
--primary-foreground: var(--color-neutral-50);
}
.rounded-xs,
.rounded-sm,
.rounded-md,
.rounded-lg,
.rounded-xl {
@apply !rounded-none;
border-radius: 0;
}
.shadow-xs,
.shadow-sm,
.shadow-md,
.shadow-lg,
.shadow-xl {
@apply !shadow-none;
}
[data-slot="toggle-group"],
[data-slot="toggle-group-item"] {
@apply !rounded-none !shadow-none;
}
}

View File

@@ -0,0 +1,96 @@
import * as React from "react"
import { Metadata } from "next"
import { notFound } from "next/navigation"
import { registryItemSchema } from "shadcn/registry"
import { z } from "zod"
import { getRegistryComponent, getRegistryItem } from "@/lib/registry"
import { absoluteUrl, cn } from "@/lib/utils"
import { siteConfig } from "@/www/config/site"
const getCachedRegistryItem = React.cache(async (name: string) => {
return await getRegistryItem(name)
})
export async function generateMetadata({
params,
}: {
params: Promise<{
name: string
}>
}): Promise<Metadata> {
const { name } = await params
const item = await getCachedRegistryItem(name)
if (!item) {
return {}
}
const title = item.name
const description = item.description
return {
title: `${item.name}${item.description ? ` - ${item.description}` : ""}`,
description,
openGraph: {
title,
description,
type: "article",
url: absoluteUrl(`/blocks/${item.name}`),
images: [
{
url: siteConfig.ogImage,
width: 1200,
height: 630,
alt: siteConfig.name,
},
],
},
twitter: {
card: "summary_large_image",
title,
description,
images: [siteConfig.ogImage],
creator: "@shadcn",
},
}
}
export const dynamicParams = false
export async function generateStaticParams() {
const { Index } = await import("@/__registry__")
const index = z.record(registryItemSchema).parse(Index)
return Object.values(index)
.filter((block) =>
["registry:block", "registry:component"].includes(block.type)
)
.map((block) => ({
name: block.name,
}))
}
export default async function BlockPage({
params,
}: {
params: Promise<{
name: string
}>
}) {
const { name } = await params
const item = await getCachedRegistryItem(name)
const Component = getRegistryComponent(name)
if (!item || !Component) {
return notFound()
}
return (
<>
<div className={cn("themes-wrapper bg-background", item.meta?.container)}>
<Component />
</div>
</>
)
}

123
apps/v4/app/globals.css Normal file
View File

@@ -0,0 +1,123 @@
@import "tailwindcss";
@import "tw-animate-css";
@custom-variant dark (&:is(.dark *));
@import "./themes.css";
:root {
--radius: 0.625rem;
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0);
--primary: oklch(0.205 0 0);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.97 0 0);
--secondary-foreground: oklch(0.205 0 0);
--muted: oklch(0.97 0 0);
--muted-foreground: oklch(0.556 0 0);
--accent: oklch(0.97 0 0);
--accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.922 0 0);
--input: oklch(0.922 0 0);
--ring: oklch(0.708 0 0);
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.145 0 0);
--sidebar-primary: oklch(0.205 0 0);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.97 0 0);
--sidebar-accent-foreground: oklch(0.205 0 0);
--sidebar-border: oklch(0.922 0 0);
--sidebar-ring: oklch(0.708 0 0);
}
.dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--card: oklch(0.205 0 0);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.269 0 0);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.922 0 0);
--primary-foreground: oklch(0.205 0 0);
--secondary: oklch(0.269 0 0);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.269 0 0);
--muted-foreground: oklch(0.708 0 0);
--accent: oklch(0.371 0 0);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.556 0 0);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
--sidebar: oklch(0.205 0 0);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.269 0 0);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.439 0 0);
}
@theme inline {
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);
--color-sidebar: var(--sidebar);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
}
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}
}

123
apps/v4/app/layout.tsx Normal file
View File

@@ -0,0 +1,123 @@
import type { Metadata, Viewport } from "next"
import { cookies } from "next/headers"
import { fontVariables } from "@/lib/fonts"
import { Analytics } from "@/components/analytics"
import { ThemeProvider } from "@/components/theme-provider"
import { Toaster } from "@/registry/new-york-v4/ui/sonner"
import { siteConfig } from "@/www/config/site"
import "./globals.css"
import { cn } from "@/lib/utils"
import { ActiveThemeProvider } from "@/components/active-theme"
const META_THEME_COLORS = {
light: "#ffffff",
dark: "#09090b",
}
export const metadata: Metadata = {
title: {
default: siteConfig.name,
template: `%s - ${siteConfig.name}`,
},
metadataBase: new URL("https://v4.shadcn.com"),
description: siteConfig.description,
keywords: [
"Next.js",
"React",
"Tailwind CSS",
"Server Components",
"Radix UI",
],
authors: [
{
name: "shadcn",
url: "https://shadcn.com",
},
],
creator: "shadcn",
openGraph: {
type: "website",
locale: "en_US",
url: "https://v4.shadcn.com",
title: siteConfig.name,
description: siteConfig.description,
siteName: siteConfig.name,
images: [
{
url: "https://v4.shadcn.com/opengraph-image.png",
width: 1200,
height: 630,
alt: siteConfig.name,
},
],
},
twitter: {
card: "summary_large_image",
title: siteConfig.name,
description: siteConfig.description,
images: ["https://v4.shadcn.com/opengraph-image.png"],
creator: "@shadcn",
},
icons: {
icon: "/favicon.ico",
shortcut: "/favicon-16x16.png",
apple: "/apple-touch-icon.png",
},
manifest: `${siteConfig.url}/site.webmanifest`,
}
export const viewport: Viewport = {
themeColor: META_THEME_COLORS.light,
}
export default async function RootLayout({
children,
}: Readonly<{
children: React.ReactNode
}>) {
const cookieStore = await cookies()
const activeThemeValue = cookieStore.get("active_theme")?.value
const isScaled = activeThemeValue?.endsWith("-scaled")
return (
<html lang="en" suppressHydrationWarning>
<head>
<script
dangerouslySetInnerHTML={{
__html: `
try {
if (localStorage.theme === 'dark' || ((!('theme' in localStorage) || localStorage.theme === 'system') && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
document.querySelector('meta[name="theme-color"]').setAttribute('content', '${META_THEME_COLORS.dark}')
}
} catch (_) {}
`,
}}
/>
</head>
<body
className={cn(
"bg-background overscroll-none font-sans antialiased",
activeThemeValue ? `theme-${activeThemeValue}` : "",
isScaled ? "theme-scaled" : "",
fontVariables
)}
>
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
enableColorScheme
>
<ActiveThemeProvider initialTheme={activeThemeValue}>
{children}
<Toaster />
<Analytics />
</ActiveThemeProvider>
</ThemeProvider>
</body>
</html>
)
}

364
apps/v4/app/themes.css Normal file
View File

@@ -0,0 +1,364 @@
.theme-stone {
--radius: 0.625rem;
--background: oklch(1 0 0);
--foreground: oklch(0.147 0.004 49.25);
--card: oklch(1 0 0);
--card-foreground: oklch(0.147 0.004 49.25);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.147 0.004 49.25);
--primary: oklch(0.216 0.006 56.043);
--primary-foreground: oklch(0.985 0.001 106.423);
--secondary: oklch(0.97 0.001 106.424);
--secondary-foreground: oklch(0.216 0.006 56.043);
--muted: oklch(0.97 0.001 106.424);
--muted-foreground: oklch(0.553 0.013 58.071);
--accent: oklch(0.97 0.001 106.424);
--accent-foreground: oklch(0.216 0.006 56.043);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.923 0.003 48.717);
--input: oklch(0.923 0.003 48.717);
--ring: oklch(0.709 0.01 56.259);
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
--sidebar: oklch(0.985 0.001 106.423);
--sidebar-foreground: oklch(0.147 0.004 49.25);
--sidebar-primary: oklch(0.216 0.006 56.043);
--sidebar-primary-foreground: oklch(0.985 0.001 106.423);
--sidebar-accent: oklch(0.97 0.001 106.424);
--sidebar-accent-foreground: oklch(0.216 0.006 56.043);
--sidebar-border: oklch(0.923 0.003 48.717);
--sidebar-ring: oklch(0.709 0.01 56.259);
@variant dark {
--background: oklch(0.147 0.004 49.25);
--foreground: oklch(0.985 0.001 106.423);
--card: oklch(0.216 0.006 56.043);
--card-foreground: oklch(0.985 0.001 106.423);
--popover: oklch(0.216 0.006 56.043);
--popover-foreground: oklch(0.985 0.001 106.423);
--primary: oklch(0.923 0.003 48.717);
--primary-foreground: oklch(0.216 0.006 56.043);
--secondary: oklch(0.268 0.007 34.298);
--secondary-foreground: oklch(0.985 0.001 106.423);
--muted: oklch(0.268 0.007 34.298);
--muted-foreground: oklch(0.709 0.01 56.259);
--accent: oklch(0.268 0.007 34.298);
--accent-foreground: oklch(0.985 0.001 106.423);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.553 0.013 58.071);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
--sidebar: oklch(0.216 0.006 56.043);
--sidebar-foreground: oklch(0.985 0.001 106.423);
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.985 0.001 106.423);
--sidebar-accent: oklch(0.268 0.007 34.298);
--sidebar-accent-foreground: oklch(0.985 0.001 106.423);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.553 0.013 58.071);
}
}
.theme-zinc {
--radius: 0.625rem;
--background: oklch(1 0 0);
--foreground: oklch(0.141 0.005 285.823);
--card: oklch(1 0 0);
--card-foreground: oklch(0.141 0.005 285.823);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.141 0.005 285.823);
--primary: oklch(0.21 0.006 285.885);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.967 0.001 286.375);
--secondary-foreground: oklch(0.21 0.006 285.885);
--muted: oklch(0.967 0.001 286.375);
--muted-foreground: oklch(0.552 0.016 285.938);
--accent: oklch(0.967 0.001 286.375);
--accent-foreground: oklch(0.21 0.006 285.885);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.92 0.004 286.32);
--input: oklch(0.92 0.004 286.32);
--ring: oklch(0.705 0.015 286.067);
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.141 0.005 285.823);
--sidebar-primary: oklch(0.21 0.006 285.885);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.967 0.001 286.375);
--sidebar-accent-foreground: oklch(0.21 0.006 285.885);
--sidebar-border: oklch(0.92 0.004 286.32);
--sidebar-ring: oklch(0.705 0.015 286.067);
@variant dark {
--background: oklch(0.141 0.005 285.823);
--foreground: oklch(0.985 0 0);
--card: oklch(0.21 0.006 285.885);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.21 0.006 285.885);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.92 0.004 286.32);
--primary-foreground: oklch(0.21 0.006 285.885);
--secondary: oklch(0.274 0.006 286.033);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.274 0.006 286.033);
--muted-foreground: oklch(0.705 0.015 286.067);
--accent: oklch(0.274 0.006 286.033);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.552 0.016 285.938);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
--sidebar: oklch(0.21 0.006 285.885);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.274 0.006 286.033);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.552 0.016 285.938);
}
}
.theme-neutral {
--radius: 0.625rem;
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0);
--primary: oklch(0.205 0 0);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.97 0 0);
--secondary-foreground: oklch(0.205 0 0);
--muted: oklch(0.97 0 0);
--muted-foreground: oklch(0.556 0 0);
--accent: oklch(0.97 0 0);
--accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.922 0 0);
--input: oklch(0.922 0 0);
--ring: oklch(0.708 0 0);
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.145 0 0);
--sidebar-primary: oklch(0.205 0 0);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.97 0 0);
--sidebar-accent-foreground: oklch(0.205 0 0);
--sidebar-border: oklch(0.922 0 0);
--sidebar-ring: oklch(0.708 0 0);
@variant dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--card: oklch(0.205 0 0);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.205 0 0);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.922 0 0);
--primary-foreground: oklch(0.205 0 0);
--secondary: oklch(0.269 0 0);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.269 0 0);
--muted-foreground: oklch(0.708 0 0);
--accent: oklch(0.269 0 0);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.556 0 0);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
--sidebar: oklch(0.205 0 0);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.269 0 0);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.556 0 0);
}
}
.theme-gray {
--radius: 0.625rem;
--background: oklch(1 0 0);
--foreground: oklch(0.13 0.028 261.692);
--card: oklch(1 0 0);
--card-foreground: oklch(0.13 0.028 261.692);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.13 0.028 261.692);
--primary: oklch(0.21 0.034 264.665);
--primary-foreground: oklch(0.985 0.002 247.839);
--secondary: oklch(0.967 0.003 264.542);
--secondary-foreground: oklch(0.21 0.034 264.665);
--muted: oklch(0.967 0.003 264.542);
--muted-foreground: oklch(0.551 0.027 264.364);
--accent: oklch(0.967 0.003 264.542);
--accent-foreground: oklch(0.21 0.034 264.665);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.928 0.006 264.531);
--input: oklch(0.928 0.006 264.531);
--ring: oklch(0.707 0.022 261.325);
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
--sidebar: oklch(0.985 0.002 247.839);
--sidebar-foreground: oklch(0.13 0.028 261.692);
--sidebar-primary: oklch(0.21 0.034 264.665);
--sidebar-primary-foreground: oklch(0.985 0.002 247.839);
--sidebar-accent: oklch(0.967 0.003 264.542);
--sidebar-accent-foreground: oklch(0.21 0.034 264.665);
--sidebar-border: oklch(0.928 0.006 264.531);
--sidebar-ring: oklch(0.707 0.022 261.325);
@variant dark {
--background: oklch(0.13 0.028 261.692);
--foreground: oklch(0.985 0.002 247.839);
--card: oklch(0.21 0.034 264.665);
--card-foreground: oklch(0.985 0.002 247.839);
--popover: oklch(0.21 0.034 264.665);
--popover-foreground: oklch(0.985 0.002 247.839);
--primary: oklch(0.928 0.006 264.531);
--primary-foreground: oklch(0.21 0.034 264.665);
--secondary: oklch(0.278 0.033 256.848);
--secondary-foreground: oklch(0.985 0.002 247.839);
--muted: oklch(0.278 0.033 256.848);
--muted-foreground: oklch(0.707 0.022 261.325);
--accent: oklch(0.278 0.033 256.848);
--accent-foreground: oklch(0.985 0.002 247.839);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.551 0.027 264.364);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
--sidebar: oklch(0.21 0.034 264.665);
--sidebar-foreground: oklch(0.985 0.002 247.839);
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.985 0.002 247.839);
--sidebar-accent: oklch(0.278 0.033 256.848);
--sidebar-accent-foreground: oklch(0.985 0.002 247.839);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.551 0.027 264.364);
}
}
.theme-slate {
--radius: 0.625rem;
--background: oklch(1 0 0);
--foreground: oklch(0.129 0.042 264.695);
--card: oklch(1 0 0);
--card-foreground: oklch(0.129 0.042 264.695);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.129 0.042 264.695);
--primary: oklch(0.208 0.042 265.755);
--primary-foreground: oklch(0.984 0.003 247.858);
--secondary: oklch(0.968 0.007 247.896);
--secondary-foreground: oklch(0.208 0.042 265.755);
--muted: oklch(0.968 0.007 247.896);
--muted-foreground: oklch(0.554 0.046 257.417);
--accent: oklch(0.968 0.007 247.896);
--accent-foreground: oklch(0.208 0.042 265.755);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.929 0.013 255.508);
--input: oklch(0.929 0.013 255.508);
--ring: oklch(0.704 0.04 256.788);
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
--sidebar: oklch(0.984 0.003 247.858);
--sidebar-foreground: oklch(0.129 0.042 264.695);
--sidebar-primary: oklch(0.208 0.042 265.755);
--sidebar-primary-foreground: oklch(0.984 0.003 247.858);
--sidebar-accent: oklch(0.968 0.007 247.896);
--sidebar-accent-foreground: oklch(0.208 0.042 265.755);
--sidebar-border: oklch(0.929 0.013 255.508);
--sidebar-ring: oklch(0.704 0.04 256.788);
@variant dark {
--background: oklch(0.129 0.042 264.695);
--foreground: oklch(0.984 0.003 247.858);
--card: oklch(0.208 0.042 265.755);
--card-foreground: oklch(0.984 0.003 247.858);
--popover: oklch(0.208 0.042 265.755);
--popover-foreground: oklch(0.984 0.003 247.858);
--primary: oklch(0.929 0.013 255.508);
--primary-foreground: oklch(0.208 0.042 265.755);
--secondary: oklch(0.279 0.041 260.031);
--secondary-foreground: oklch(0.984 0.003 247.858);
--muted: oklch(0.279 0.041 260.031);
--muted-foreground: oklch(0.704 0.04 256.788);
--accent: oklch(0.279 0.041 260.031);
--accent-foreground: oklch(0.984 0.003 247.858);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.551 0.027 264.364);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
--sidebar: oklch(0.208 0.042 265.755);
--sidebar-foreground: oklch(0.984 0.003 247.858);
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.984 0.003 247.858);
--sidebar-accent: oklch(0.279 0.041 260.031);
--sidebar-accent-foreground: oklch(0.984 0.003 247.858);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.551 0.027 264.364);
}
}
.theme-scaled {
@media (min-width: 1024px) {
--radius: 0.6rem;
--text-lg: 1.05rem;
--text-base: 0.85rem;
--text-sm: 0.8rem;
--spacing: 0.222222rem;
}
[data-slot="card"] {
--spacing: 0.16rem;
}
[data-slot="card-header"] *,
[data-slot="card-content"] *,
[data-slot="card-footer"] * {
--spacing: 0.222222rem;
}
}

21
apps/v4/components.json Normal file
View File

@@ -0,0 +1,21 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "",
"css": "app/globals.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/registry/new-york-v4/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"iconLibrary": "lucide"
}

View File

@@ -0,0 +1,72 @@
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/registry/new-york-v4/ui/accordion"
export function AccordionDemo() {
return (
<div className="grid w-full max-w-xl gap-4">
<Accordion type="single" collapsible className="w-full">
<AccordionItem value="item-1">
<AccordionTrigger>Is it accessible?</AccordionTrigger>
<AccordionContent>
Yes. It adheres to the WAI-ARIA design pattern.
</AccordionContent>
</AccordionItem>
<AccordionItem value="item-2">
<AccordionTrigger>Is it styled?</AccordionTrigger>
<AccordionContent>
Yes. It comes with default styles that matches the other
components&apos; aesthetic.
</AccordionContent>
</AccordionItem>
<AccordionItem value="item-3">
<AccordionTrigger>Is it animated?</AccordionTrigger>
<AccordionContent>
Yes. It&apos;s animated by default, but you can disable it if you
prefer.
</AccordionContent>
</AccordionItem>
</Accordion>
<Accordion type="single" collapsible className="w-full">
<AccordionItem value="item-1">
<AccordionTrigger>
What are the key considerations when implementing a comprehensive
enterprise-level authentication system?
</AccordionTrigger>
<AccordionContent>
Implementing a robust enterprise authentication system requires
careful consideration of multiple factors. This includes secure
password hashing and storage, multi-factor authentication (MFA)
implementation, session management, OAuth2 and SSO integration,
regular security audits, rate limiting to prevent brute force
attacks, and maintaining detailed audit logs. Additionally,
you&apos;ll need to consider scalability, performance impact, and
compliance with relevant data protection regulations such as GDPR or
HIPAA.
</AccordionContent>
</AccordionItem>
<AccordionItem value="item-2">
<AccordionTrigger>
How does modern distributed system architecture handle eventual
consistency and data synchronization across multiple regions?
</AccordionTrigger>
<AccordionContent>
Modern distributed systems employ various strategies to maintain
data consistency across regions. This often involves using
techniques like CRDT (Conflict-Free Replicated Data Types), vector
clocks, and gossip protocols. Systems might implement event sourcing
patterns, utilize message queues for asynchronous updates, and
employ sophisticated conflict resolution strategies. Popular
solutions like Amazon&apos;s DynamoDB and Google&apos;s Spanner
demonstrate different approaches to solving these challenges,
balancing between consistency, availability, and partition tolerance
as described in the CAP theorem.
</AccordionContent>
</AccordionItem>
</Accordion>
</div>
)
}

View File

@@ -0,0 +1,65 @@
"use client"
import {
ReactNode,
createContext,
useContext,
useEffect,
useState,
} from "react"
const COOKIE_NAME = "active_theme"
const DEFAULT_THEME = "default"
function setThemeCookie(theme: string) {
if (typeof window === "undefined") return
document.cookie = `${COOKIE_NAME}=${theme}; path=/; max-age=31536000; SameSite=Lax; ${window.location.protocol === "https:" ? "Secure;" : ""}`
}
type ThemeContextType = {
activeTheme: string
setActiveTheme: (theme: string) => void
}
const ThemeContext = createContext<ThemeContextType | undefined>(undefined)
export function ActiveThemeProvider({
children,
initialTheme,
}: {
children: ReactNode
initialTheme?: string
}) {
const [activeTheme, setActiveTheme] = useState<string>(
() => initialTheme || DEFAULT_THEME
)
useEffect(() => {
setThemeCookie(activeTheme)
Array.from(document.body.classList)
.filter((className) => className.startsWith("theme-"))
.forEach((className) => {
document.body.classList.remove(className)
})
document.body.classList.add(`theme-${activeTheme}`)
if (activeTheme.endsWith("-scaled")) {
document.body.classList.add("theme-scaled")
}
}, [activeTheme])
return (
<ThemeContext.Provider value={{ activeTheme, setActiveTheme }}>
{children}
</ThemeContext.Provider>
)
}
export function useThemeConfig() {
const context = useContext(ThemeContext)
if (context === undefined) {
throw new Error("useThemeConfig must be used within an ActiveThemeProvider")
}
return context
}

View File

@@ -0,0 +1,115 @@
import {
AlertCircleIcon,
BookmarkCheckIcon,
CheckCircle2Icon,
GiftIcon,
PopcornIcon,
ShieldAlertIcon,
} from "lucide-react"
import {
Alert,
AlertDescription,
AlertTitle,
} from "@/registry/new-york-v4/ui/alert"
import { Button } from "@/registry/new-york-v4/ui/button"
export function AlertDemo() {
return (
<div className="grid max-w-xl items-start gap-4">
<Alert>
<CheckCircle2Icon />
<AlertTitle>Success! Your changes have been saved</AlertTitle>
<AlertDescription>
This is an alert with icon, title and description.
</AlertDescription>
</Alert>
<Alert>
<BookmarkCheckIcon>Heads up!</BookmarkCheckIcon>
<AlertDescription>
This one has an icon and a description only. No title.
</AlertDescription>
</Alert>
<Alert>
<AlertDescription>
This one has a description only. No title. No icon.
</AlertDescription>
</Alert>
<Alert>
<PopcornIcon />
<AlertTitle>Let&apos;s try one with icon and title.</AlertTitle>
</Alert>
<Alert>
<ShieldAlertIcon />
<AlertTitle>
This is a very long alert title that demonstrates how the component
handles extended text content and potentially wraps across multiple
lines
</AlertTitle>
</Alert>
<Alert>
<GiftIcon />
<AlertDescription>
This is a very long alert description that demonstrates how the
component handles extended text content and potentially wraps across
multiple lines
</AlertDescription>
</Alert>
<Alert>
<AlertCircleIcon />
<AlertTitle>
This is an extremely long alert title that spans multiple lines to
demonstrate how the component handles very lengthy headings while
maintaining readability and proper text wrapping behavior
</AlertTitle>
<AlertDescription>
This is an equally long description that contains detailed information
about the alert. It shows how the component can accommodate extensive
content while preserving proper spacing, alignment, and readability
across different screen sizes and viewport widths. This helps ensure
the user experience remains consistent regardless of the content
length.
</AlertDescription>
</Alert>
<Alert variant="destructive">
<AlertCircleIcon />
<AlertTitle>Something went wrong!</AlertTitle>
<AlertDescription>
Your session has expired. Please log in again.
</AlertDescription>
</Alert>
<Alert variant="destructive">
<AlertCircleIcon />
<AlertTitle>Unable to process your payment.</AlertTitle>
<AlertDescription>
<p>Please verify your billing information and try again.</p>
<ul className="list-inside list-disc text-sm">
<li>Check your card details</li>
<li>Ensure sufficient funds</li>
<li>Verify billing address</li>
</ul>
</AlertDescription>
</Alert>
<Alert>
<CheckCircle2Icon />
<AlertTitle className="max-w-[calc(100%-4rem)] overflow-ellipsis">
The selected emails have been marked as spam.
</AlertTitle>
<Button
size="sm"
variant="outline"
className="absolute top-2.5 right-3 h-6 shadow-none"
>
Undo
</Button>
</Alert>
<Alert className="border-amber-50 bg-amber-50 text-amber-900 dark:border-amber-950 dark:bg-amber-950 dark:text-amber-100">
<CheckCircle2Icon />
<AlertTitle>Plot Twist: This Alert is Actually Amber!</AlertTitle>
<AlertDescription>
This one has custom colors for light and dark mode.
</AlertDescription>
</Alert>
</div>
)
}

View File

@@ -0,0 +1,35 @@
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/registry/new-york-v4/ui/alert-dialog"
import { Button } from "@/registry/new-york-v4/ui/button"
export function AlertDialogDemo() {
return (
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="outline">Show Dialog</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone. This will permanently delete your
account and remove your data from our servers.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction>Continue</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
)
}

View File

@@ -0,0 +1,7 @@
"use client"
import { Analytics as VercelAnalytics } from "@vercel/analytics/react"
export function Analytics() {
return <VercelAnalytics />
}

View File

@@ -0,0 +1,248 @@
"use client"
import * as React from "react"
import { Index } from "@/__registry__"
import {
AudioWaveform,
BookOpen,
Bot,
ChevronRightIcon,
Command,
GalleryVerticalEnd,
Search,
Settings2,
SquareTerminal,
} from "lucide-react"
import { NavUser } from "@/registry/new-york-v4/blocks/sidebar-07/components/nav-user"
import { TeamSwitcher } from "@/registry/new-york-v4/blocks/sidebar-07/components/team-switcher"
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/registry/new-york-v4/ui/collapsible"
import { Label } from "@/registry/new-york-v4/ui/label"
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarHeader,
SidebarInput,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarMenuSub,
SidebarMenuSubButton,
SidebarMenuSubItem,
SidebarRail,
} from "@/registry/new-york-v4/ui/sidebar"
// This is sample data.
const data = {
user: {
name: "shadcn",
email: "m@example.com",
avatar: "/avatars/shadcn.jpg",
},
teams: [
{
name: "Acme Inc",
logo: GalleryVerticalEnd,
plan: "Enterprise",
},
{
name: "Acme Corp.",
logo: AudioWaveform,
plan: "Startup",
},
{
name: "Evil Corp.",
logo: Command,
plan: "Free",
},
],
navMain: [
{
title: "Playground",
url: "#",
icon: SquareTerminal,
isActive: true,
items: [
{
title: "History",
url: "#",
},
{
title: "Starred",
url: "#",
},
{
title: "Settings",
url: "#",
},
],
},
{
title: "Models",
url: "#",
icon: Bot,
items: [
{
title: "Genesis",
url: "#",
},
{
title: "Explorer",
url: "#",
},
{
title: "Quantum",
url: "#",
},
],
},
{
title: "Documentation",
url: "#",
icon: BookOpen,
items: [
{
title: "Introduction",
url: "#",
},
{
title: "Get Started",
url: "#",
},
{
title: "Tutorials",
url: "#",
},
{
title: "Changelog",
url: "#",
},
],
},
{
title: "Settings",
url: "#",
icon: Settings2,
items: [
{
title: "General",
url: "#",
},
{
title: "Team",
url: "#",
},
{
title: "Billing",
url: "#",
},
{
title: "Limits",
url: "#",
},
],
},
],
components: Object.values(Index)
.filter((item) => item.type === "registry:ui")
.concat([
{
name: "combobox",
},
])
.sort((a, b) => a.name.localeCompare(b.name)),
}
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
return (
<Sidebar collapsible="icon" {...props}>
<SidebarHeader>
<TeamSwitcher teams={data.teams} />
<SidebarGroup className="py-0 group-data-[collapsible=icon]:hidden">
<SidebarGroupContent>
<form className="relative">
<Label htmlFor="search" className="sr-only">
Search
</Label>
<SidebarInput
id="search"
placeholder="Search the docs..."
className="pl-8"
/>
<Search className="pointer-events-none absolute top-1/2 left-2 size-4 -translate-y-1/2 opacity-50 select-none" />
</form>
</SidebarGroupContent>
</SidebarGroup>
</SidebarHeader>
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel>Platform</SidebarGroupLabel>
<SidebarMenu>
{data.navMain.map((item) => (
<Collapsible
key={item.title}
asChild
defaultOpen={item.isActive}
className="group/collapsible"
>
<SidebarMenuItem>
<CollapsibleTrigger asChild>
<SidebarMenuButton tooltip={item.title}>
{item.icon && <item.icon />}
<span>{item.title}</span>
<ChevronRightIcon className="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" />
</SidebarMenuButton>
</CollapsibleTrigger>
<CollapsibleContent>
<SidebarMenuSub>
{item.items?.map((subItem) => (
<SidebarMenuSubItem key={subItem.title}>
<SidebarMenuSubButton asChild>
<a href={subItem.url}>
<span>{subItem.title}</span>
</a>
</SidebarMenuSubButton>
</SidebarMenuSubItem>
))}
</SidebarMenuSub>
</CollapsibleContent>
</SidebarMenuItem>
</Collapsible>
))}
</SidebarMenu>
</SidebarGroup>
<SidebarGroup className="group-data-[collapsible=icon]:hidden">
<SidebarGroupLabel>Components</SidebarGroupLabel>
<SidebarMenu>
{data.components.map((item) => (
<SidebarMenuItem key={item.name}>
<SidebarMenuButton asChild>
<a href={`/#${item.name}`}>
<span>{getComponentName(item.name)}</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroup>
</SidebarContent>
<SidebarFooter>
<NavUser user={data.user} />
</SidebarFooter>
<SidebarRail />
</Sidebar>
)
}
function getComponentName(name: string) {
// convert kebab-case to title case
return name.replace(/-/g, " ").replace(/\b\w/g, (char) => char.toUpperCase())
}

View File

@@ -0,0 +1,26 @@
import Image from "next/image"
import { AspectRatio } from "@/registry/new-york-v4/ui/aspect-ratio"
export function AspectRatioDemo() {
return (
<div className="grid w-full max-w-sm items-start gap-4">
<AspectRatio ratio={16 / 9} className="bg-muted rounded-lg">
<Image
src="https://images.unsplash.com/photo-1588345921523-c2dcdb7f1dcd?w=800&dpr=2&q=80"
alt="Photo by Drew Beamer"
fill
className="h-full w-full rounded-lg object-cover dark:brightness-[0.2] dark:grayscale"
/>
</AspectRatio>
<AspectRatio ratio={1 / 1} className="bg-muted rounded-lg">
<Image
src="https://images.unsplash.com/photo-1588345921523-c2dcdb7f1dcd?w=800&dpr=2&q=80"
alt="Photo by Drew Beamer"
fill
className="h-full w-full rounded-lg object-cover dark:brightness-[0.2] dark:grayscale"
/>
</AspectRatio>
</div>
)
}

View File

@@ -0,0 +1,81 @@
import {
Avatar,
AvatarFallback,
AvatarImage,
} from "@/registry/new-york-v4/ui/avatar"
export function AvatarDemo() {
return (
<div className="flex flex-row flex-wrap items-center gap-4">
<Avatar>
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar>
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar className="size-12">
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar className="rounded-lg">
<AvatarImage
src="https://github.com/evilrabbit.png"
alt="@evilrabbit"
/>
<AvatarFallback>ER</AvatarFallback>
</Avatar>
<div className="*:data-[slot=avatar]:ring-background flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:grayscale">
<Avatar>
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar>
<AvatarImage src="https://github.com/leerob.png" alt="@leerob" />
<AvatarFallback>LR</AvatarFallback>
</Avatar>
<Avatar>
<AvatarImage
src="https://github.com/evilrabbit.png"
alt="@evilrabbit"
/>
<AvatarFallback>ER</AvatarFallback>
</Avatar>
</div>
<div className="*:data-[slot=avatar]:ring-background flex -space-x-2 *:data-[slot=avatar]:size-12 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:grayscale">
<Avatar>
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar>
<AvatarImage src="https://github.com/leerob.png" alt="@leerob" />
<AvatarFallback>LR</AvatarFallback>
</Avatar>
<Avatar>
<AvatarImage
src="https://github.com/evilrabbit.png"
alt="@evilrabbit"
/>
<AvatarFallback>ER</AvatarFallback>
</Avatar>
</div>
<div className="*:data-[slot=avatar]:ring-background flex -space-x-2 hover:space-x-1 *:data-[slot=avatar]:size-12 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:grayscale *:data-[slot=avatar]:transition-all *:data-[slot=avatar]:duration-300 *:data-[slot=avatar]:ease-in-out">
<Avatar>
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar>
<AvatarImage src="https://github.com/leerob.png" alt="@leerob" />
<AvatarFallback>LR</AvatarFallback>
</Avatar>
<Avatar>
<AvatarImage
src="https://github.com/evilrabbit.png"
alt="@evilrabbit"
/>
<AvatarFallback>ER</AvatarFallback>
</Avatar>
</div>
</div>
)
}

View File

@@ -0,0 +1,61 @@
import { AlertCircleIcon, ArrowRightIcon, CheckIcon } from "lucide-react"
import { Badge } from "@/registry/new-york-v4/ui/badge"
export function BadgeDemo() {
return (
<div className="flex flex-col items-center gap-2">
<div className="flex w-full flex-wrap gap-2">
<Badge>Badge</Badge>
<Badge variant="secondary">Secondary</Badge>
<Badge variant="destructive">Destructive</Badge>
<Badge variant="outline">Outline</Badge>
<Badge variant="outline">
<CheckIcon />
Badge
</Badge>
<Badge variant="destructive">
<AlertCircleIcon />
Alert
</Badge>
<Badge className="h-5 min-w-5 rounded-full px-1 font-mono tabular-nums">
8
</Badge>
<Badge
className="h-5 min-w-5 rounded-full px-1 font-mono tabular-nums"
variant="destructive"
>
99
</Badge>
<Badge
className="h-5 min-w-5 rounded-full px-1 font-mono tabular-nums"
variant="outline"
>
20+
</Badge>
</div>
<div className="flex w-full flex-wrap gap-2">
<Badge asChild>
<a href="#">
Link <ArrowRightIcon />
</a>
</Badge>
<Badge asChild variant="secondary">
<a href="#">
Link <ArrowRightIcon />
</a>
</Badge>
<Badge asChild variant="destructive">
<a href="#">
Link <ArrowRightIcon />
</a>
</Badge>
<Badge asChild variant="outline">
<a href="#">
Link <ArrowRightIcon />
</a>
</Badge>
</div>
</div>
)
}

View File

@@ -0,0 +1,49 @@
import {
Breadcrumb,
BreadcrumbEllipsis,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from "@/registry/new-york-v4/ui/breadcrumb"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/registry/new-york-v4/ui/dropdown-menu"
export function BreadcrumbDemo() {
return (
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink href="/">Home</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<DropdownMenu>
<DropdownMenuTrigger className="flex items-center gap-1">
<BreadcrumbEllipsis className="h-4 w-4" />
<span className="sr-only">Toggle menu</span>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuItem>Documentation</DropdownMenuItem>
<DropdownMenuItem>Themes</DropdownMenuItem>
<DropdownMenuItem>GitHub</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbLink href="/docs/components">Components</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbPage>Breadcrumb</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
)
}

View File

@@ -0,0 +1,84 @@
import { ArrowRightIcon, Loader2Icon, SendIcon } from "lucide-react"
import { Button } from "@/registry/new-york-v4/ui/button"
export function ButtonDemo() {
return (
<div className="flex flex-col gap-6">
<div className="flex flex-wrap items-center gap-2 md:flex-row">
<Button>Button</Button>
<Button variant="outline">Outline</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="destructive">Destructive</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="link">Link</Button>
<Button variant="outline">
<SendIcon /> Send
</Button>
<Button variant="outline">
Learn More <ArrowRightIcon />
</Button>
<Button disabled variant="outline">
<Loader2Icon className="animate-spin" />
Please wait
</Button>
</div>
<div className="flex flex-wrap items-center gap-2 md:flex-row">
<Button size="sm">Small</Button>
<Button variant="outline" size="sm">
Outline
</Button>
<Button variant="ghost" size="sm">
Ghost
</Button>
<Button variant="destructive" size="sm">
Destructive
</Button>
<Button variant="secondary" size="sm">
Secondary
</Button>
<Button variant="link" size="sm">
Link
</Button>
<Button variant="outline" size="sm">
<SendIcon /> Send
</Button>
<Button variant="outline" size="sm">
Learn More <ArrowRightIcon />
</Button>
<Button disabled size="sm" variant="outline">
<Loader2Icon className="animate-spin" />
Please wait
</Button>
</div>
<div className="flex flex-wrap items-center gap-2 md:flex-row">
<Button size="lg">Large</Button>
<Button variant="outline" size="lg">
Outline
</Button>
<Button variant="ghost" size="lg">
Ghost
</Button>
<Button variant="destructive" size="lg">
Destructive
</Button>
<Button variant="secondary" size="lg">
Secondary
</Button>
<Button variant="link" size="lg">
Link
</Button>
<Button variant="outline" size="lg">
<SendIcon /> Send
</Button>
<Button variant="outline" size="lg">
Learn More <ArrowRightIcon />
</Button>
<Button disabled size="lg" variant="outline">
<Loader2Icon className="animate-spin" />
Please wait
</Button>
</div>
</div>
)
}

View File

@@ -0,0 +1,47 @@
"use client"
import * as React from "react"
import { addDays } from "date-fns"
import { type DateRange } from "react-day-picker"
import { Calendar } from "@/registry/new-york-v4/ui/calendar"
export function CalendarDemo() {
const [date, setDate] = React.useState<Date | undefined>(new Date())
const [dateRange, setDateRange] = React.useState<DateRange | undefined>({
from: new Date(new Date().getFullYear(), 0, 12),
to: addDays(new Date(new Date().getFullYear(), 0, 12), 30),
})
const [range, setRange] = React.useState<DateRange | undefined>({
from: new Date(new Date().getFullYear(), 0, 12),
to: addDays(new Date(new Date().getFullYear(), 0, 12), 50),
})
return (
<div className="flex flex-col flex-wrap items-start gap-2 @md:flex-row">
<Calendar
mode="single"
selected={date}
onSelect={setDate}
className="rounded-md border shadow-sm"
/>
<Calendar
mode="range"
defaultMonth={dateRange?.from}
selected={dateRange}
onSelect={setDateRange}
numberOfMonths={2}
disabled={(date) => date > new Date() || date < new Date("1900-01-01")}
className="rounded-md border shadow-sm"
/>
<Calendar
mode="range"
defaultMonth={range?.from}
selected={range}
onSelect={setRange}
numberOfMonths={3}
className="hidden rounded-md border shadow-sm @4xl:flex [&>div]:gap-5"
/>
</div>
)
}

View File

@@ -0,0 +1,191 @@
import Image from "next/image"
import { BathIcon, BedIcon, LandPlotIcon } from "lucide-react"
import {
Avatar,
AvatarFallback,
AvatarImage,
} from "@/registry/new-york-v4/ui/avatar"
import { Badge } from "@/registry/new-york-v4/ui/badge"
import { Button } from "@/registry/new-york-v4/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/new-york-v4/ui/card"
import { Input } from "@/registry/new-york-v4/ui/input"
import { Label } from "@/registry/new-york-v4/ui/label"
export function CardDemo() {
return (
<div className="flex flex-col items-start gap-4">
<Card className="w-full max-w-sm">
<CardHeader>
<CardTitle>Login to your account</CardTitle>
<CardDescription>
Enter your email below to login to your account
</CardDescription>
</CardHeader>
<CardContent>
<form>
<div className="flex flex-col gap-6">
<div className="grid gap-2">
<Label htmlFor="email">Email</Label>
<Input
id="email"
type="email"
placeholder="m@example.com"
required
/>
</div>
<div className="grid gap-2">
<div className="flex items-center">
<Label htmlFor="password">Password</Label>
<a
href="#"
className="ml-auto inline-block text-sm underline-offset-4 hover:underline"
>
Forgot your password?
</a>
</div>
<Input id="password" type="password" required />
</div>
</div>
</form>
</CardContent>
<CardFooter className="flex-col gap-2">
<Button type="submit" className="w-full">
Login
</Button>
<Button variant="outline" className="w-full">
Login with Google
</Button>
<div className="mt-4 text-center text-sm">
Don&apos;t have an account?{" "}
<a href="#" className="underline underline-offset-4">
Sign up
</a>
</div>
</CardFooter>
</Card>
<Card>
<CardHeader>
<CardTitle>Meeting Notes</CardTitle>
<CardDescription>
Transcript from the meeting with the client.
</CardDescription>
</CardHeader>
<CardContent className="text-sm">
<p>
Client requested dashboard redesign with focus on mobile
responsiveness.
</p>
<ol className="mt-4 flex list-decimal flex-col gap-2 pl-6">
<li>New analytics widgets for daily/weekly metrics</li>
<li>Simplified navigation menu</li>
<li>Dark mode support</li>
<li>Timeline: 6 weeks</li>
<li>Follow-up meeting scheduled for next Tuesday</li>
</ol>
</CardContent>
<CardFooter>
<div className="*:data-[slot=avatar]:ring-background flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:grayscale">
<Avatar>
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar>
<AvatarImage src="https://github.com/leerob.png" alt="@leerob" />
<AvatarFallback>LR</AvatarFallback>
</Avatar>
<Avatar>
<AvatarImage
src="https://github.com/evilrabbit.png"
alt="@evilrabbit"
/>
<AvatarFallback>ER</AvatarFallback>
</Avatar>
</div>
</CardFooter>
</Card>
<Card>
<CardHeader>
<CardTitle>Is this an image?</CardTitle>
<CardDescription>This is a card with an image.</CardDescription>
</CardHeader>
<CardContent className="px-0">
<Image
src="https://images.unsplash.com/photo-1588345921523-c2dcdb7f1dcd?w=800&dpr=2&q=80"
alt="Photo by Drew Beamer"
className="aspect-video object-cover"
width={500}
height={500}
/>
</CardContent>
<CardFooter className="flex items-center gap-2">
<Badge variant="outline">
<BedIcon /> 4
</Badge>
<Badge variant="outline">
<BathIcon /> 2
</Badge>
<Badge variant="outline">
<LandPlotIcon /> 350m²
</Badge>
<div className="ml-auto font-medium tabular-nums">$135,000</div>
</CardFooter>
</Card>
<div className="flex w-full flex-wrap items-start gap-8 md:*:data-[slot=card]:basis-1/4">
<Card>
<CardContent className="text-sm">Content Only</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Header Only</CardTitle>
<CardDescription>
This is a card with a header and a description.
</CardDescription>
</CardHeader>
</Card>
<Card>
<CardHeader>
<CardTitle>Header and Content</CardTitle>
<CardDescription>
This is a card with a header and a content.
</CardDescription>
</CardHeader>
<CardContent className="text-sm">Content</CardContent>
</Card>
<Card>
<CardFooter className="text-sm">Footer Only</CardFooter>
</Card>
<Card>
<CardHeader>
<CardTitle>Header + Footer</CardTitle>
<CardDescription>
This is a card with a header and a footer.
</CardDescription>
</CardHeader>
<CardFooter className="text-sm">Footer</CardFooter>
</Card>
<Card>
<CardContent className="text-sm">Content</CardContent>
<CardFooter className="text-sm">Footer</CardFooter>
</Card>
<Card>
<CardHeader>
<CardTitle>Header + Footer</CardTitle>
<CardDescription>
This is a card with a header and a footer.
</CardDescription>
</CardHeader>
<CardContent className="text-sm">Content</CardContent>
<CardFooter className="text-sm">Footer</CardFooter>
</Card>
</div>
</div>
)
}

View File

@@ -0,0 +1,73 @@
import * as React from "react"
import { Card, CardContent } from "@/registry/new-york-v4/ui/card"
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
} from "@/registry/new-york-v4/ui/carousel"
export function CarouselDemo() {
return (
<div className="hidden w-full flex-col items-center gap-4 @4xl:flex">
<Carousel className="max-w-sm *:data-[slot=carousel-next]:hidden *:data-[slot=carousel-previous]:hidden *:data-[slot=carousel-next]:md:inline-flex *:data-[slot=carousel-previous]:md:inline-flex">
<CarouselContent>
{Array.from({ length: 5 }).map((_, index) => (
<CarouselItem key={index}>
<div className="p-1">
<Card>
<CardContent className="flex aspect-square items-center justify-center p-6">
<span className="text-4xl font-semibold">{index + 1}</span>
</CardContent>
</Card>
</div>
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
<Carousel
className="max-w-sm *:data-[slot=carousel-next]:hidden *:data-[slot=carousel-previous]:hidden *:data-[slot=carousel-next]:md:inline-flex *:data-[slot=carousel-previous]:md:inline-flex"
opts={{
align: "start",
}}
>
<CarouselContent>
{Array.from({ length: 5 }).map((_, index) => (
<CarouselItem key={index} className="md:basis-1/2 lg:basis-1/3">
<div className="p-1">
<Card>
<CardContent className="flex aspect-square items-center justify-center p-6">
<span className="text-3xl font-semibold">{index + 1}</span>
</CardContent>
</Card>
</div>
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
<Carousel className="max-w-sm *:data-[slot=carousel-next]:hidden *:data-[slot=carousel-previous]:hidden *:data-[slot=carousel-next]:md:inline-flex *:data-[slot=carousel-previous]:md:inline-flex">
<CarouselContent className="-ml-1">
{Array.from({ length: 5 }).map((_, index) => (
<CarouselItem key={index} className="pl-1 md:basis-1/2">
<div className="p-1">
<Card>
<CardContent className="flex aspect-square items-center justify-center p-6">
<span className="text-2xl font-semibold">{index + 1}</span>
</CardContent>
</Card>
</div>
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
</div>
)
}

View File

@@ -0,0 +1,94 @@
"use client"
import { TrendingUp } from "lucide-react"
import { Area, AreaChart, CartesianGrid, XAxis } from "recharts"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/new-york-v4/ui/card"
import {
ChartConfig,
ChartContainer,
ChartTooltip,
ChartTooltipContent,
} from "@/registry/new-york-v4/ui/chart"
export const description = "A simple area chart"
const chartData = [
{ month: "January", desktop: 186 },
{ month: "February", desktop: 305 },
{ month: "March", desktop: 237 },
{ month: "April", desktop: 73 },
{ month: "May", desktop: 209 },
{ month: "June", desktop: 214 },
]
const chartConfig = {
desktop: {
label: "Desktop",
color: "var(--chart-1)",
},
} satisfies ChartConfig
export function ChartAreaDemo() {
return (
<Card>
<CardHeader>
<CardTitle>Area Chart</CardTitle>
<CardDescription>
Showing total visitors for the last 6 months
</CardDescription>
</CardHeader>
<CardContent>
<ChartContainer config={chartConfig}>
<AreaChart
accessibilityLayer
data={chartData}
margin={{
left: 12,
right: 12,
}}
>
<CartesianGrid vertical={false} />
<XAxis
dataKey="month"
tickLine={false}
axisLine={false}
tickMargin={8}
tickFormatter={(value) => value.slice(0, 3)}
/>
<ChartTooltip
cursor={false}
content={<ChartTooltipContent indicator="line" />}
/>
<Area
dataKey="desktop"
type="natural"
fill="var(--color-desktop)"
fillOpacity={0.4}
stroke="var(--color-desktop)"
/>
</AreaChart>
</ChartContainer>
</CardContent>
<CardFooter>
<div className="flex w-full items-start gap-2 text-sm">
<div className="grid gap-2">
<div className="flex items-center gap-2 leading-none font-medium">
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
</div>
<div className="text-muted-foreground flex items-center gap-2 leading-none">
January - June 2024
</div>
</div>
</div>
</CardFooter>
</Card>
)
}

Some files were not shown because too many files have changed in this diff Show More