Compare commits

...

262 Commits

Author SHA1 Message Date
shadcn
8392c7fa0c feat(cli): add css updates 2024-01-18 21:06:20 +04:00
Thomas Marrec
a465432a66 fix(carousel): adjust types for embla-carousel-react@8 (#2326)
Fixes #2322 , #2281 

Thanks to @akynau & @nianiam
2024-01-18 07:22:45 +00:00
Oguz Kazkayasi
7822e06904 Adds missing display names to Pagination Elements (#2178)
* fix: add missing pagination names

* chore: build registry

---------

Co-authored-by: shadcn <m@shadcn.com>
2024-01-18 11:08:25 +04:00
Takeshi Kriang
578d2c1823 fix(pagination): remove incorrect nested li from pagination link (#2190)
* feat: remove PaginationItem

* chore: build registry

---------

Co-authored-by: shadcn <m@shadcn.com>
2024-01-18 10:58:22 +04:00
github-actions[bot]
29de71d77f chore(release): version packages (#2458)
* chore(release): version packages

* Update CHANGELOG.md

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: shadcn <m@shadcn.com>
2024-01-16 23:56:19 +04:00
shadcn
0374ba874d feat(cli): add min-config support for Next.js (#2454)
* feat(cli): add zero-config support for Next.js

* chore: add changeset

* feat(cli): add preflight
2024-01-16 22:05:39 +04:00
github-actions[bot]
59b2cc8142 chore(release): version packages (#2420)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-01-14 16:06:43 +04:00
Vulcan-Coder
0e721be8dd Update models.ts (#2374)
Fixed the space at the start of the UUID
2024-01-14 11:27:26 +00:00
shadcn
7640ef7bbc feat: add support for adding devDependencies in the cli (#2419)
* feat(cli): add support for install devDependencies

* chore: add changeset
2024-01-14 15:21:57 +04:00
김태강
8f3b28f50f feat(components): add "use client" directive to carousel, resizable component (#2319)
[Reopen a PR that was accidentally closed.](https://github.com/shadcn-ui/ui/pull/2233)
carousel, resizable components to be used by the app router, a "use client" directive is required.
2024-01-07 09:24:23 +00:00
vinay
73be841162 [Docs] Update CLI options while configuring components.json (#2283)
New Line added to the docs.
Just for the consistency.

![image](https://github.com/shadcn-ui/ui/assets/94120295/e0da71c1-72d1-4e5d-bba9-54634a9d0e31)
2024-01-07 09:23:35 +00:00
Nuriman Quddus
ad32fdeb7d fix: typo on carousel import (#2216)
# What's changed

- [x] Fixed the typo on carousel component

before fix: "@/registry/new-york/ui/carousel"
after fix: "@/components/ui/carousel"

this is ease the user to copy paste without error
2024-01-07 08:04:54 +00:00
Clarence
2dd7864007 fix: Github case correction (#2268) 2024-01-07 07:59:20 +00:00
Andrew Qiao
5d37bae1b8 fix(www): incorrect toggle aria labels and values (#2163)
- some `Toggle` and `ToggleGroup` demos had incorrect `aria-label` or `value` props
2024-01-07 07:49:26 +00:00
Chase
4b59cb812e fix(docs): Resizable panel direction should be horizontal (#2295)
In the [resizable documentation](https://ui.shadcn.com/docs/components/resizable) the handle example shows a horizontal handle but the code example has `direction="vertical"` when it should be `direction="horizontal"`

<img width="745" alt="Screenshot 2024-01-05 at 9 34 43 AM" src="https://github.com/shadcn-ui/ui/assets/7241069/68c21241-e0c7-41b1-81d7-579306149520">
2024-01-07 07:43:51 +00:00
Anshul Kanwar
4b200ebf59 docs: typo in drawer, dialog and sheet (#2306) 2024-01-07 07:26:21 +00:00
Tilak Thapa
33795426dd Fix: Add e.preventDefault() to prevent page reload (#2278)
Fixes #2277

Add `e.preventDefault()` to prevent page reload in mail component

![image](https://github.com/shadcn-ui/ui/assets/106688422/d1373e8f-0c46-4f2e-8caf-2fcd2076b2fb)
2024-01-07 07:17:47 +00:00
github-actions[bot]
fb614ac292 chore(release): version packages (#2269)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-01-02 22:42:42 +04:00
shadcn
be580dbf76 feat(cli): add support for custom ui dir (#2266)
* feat(cli): add support for custom ui dir

* docs(www): update docs for aliases.ui

* chore: add changeset
2024-01-02 22:32:15 +04:00
shadcn
98859e7b1c feat(www): update keyboard handling for command menu (#2264) 2024-01-02 11:48:27 +04:00
kal07
6b523b60db Update pagination.mdx (#2191)
fix the Nextjs import example
2023-12-26 14:23:04 +00:00
Ghribi Ouassim
e3d5377a3e Added new label for mobile navigation (#2182)
- Update the `mobile-nav` component and added `new` label if `item.label` is present in the `docsConfig.sidebarNav`.

![image](https://github.com/shadcn-ui/ui/assets/70029024/e19eddf4-22bb-4afe-8158-ea795ea0c5c0)
2023-12-26 14:17:40 +00:00
shadcn
f60945c252 fix: add tailwind.prefix to schema (#2200)
* fix: add tailwind.prefix to schema

* fix(www): format
2023-12-26 18:00:26 +04:00
Rishabh
5eb33f7830 docs(pagination): wrong import path of components in pagination usage (#2180)
This PR:

Fixes: #2150
2023-12-26 13:35:19 +00:00
Rishabh
f6fef4a2ed docs(drawer): missing drawer footer import in drawer usage example (#2169)
This PR:

- Fixes:  #2168
2023-12-24 05:47:02 +00:00
arshad
f6f64ce773 Update: Rename 'ResizableGroup' to 'ResizablePanelGroup' in Documentation (#2166)
### Overview
This pull request updates the documentation to reflect the correct component name, changing `ResizableGroup` to `ResizablePanelGroup`. This change ensures consistency and correctness in the documentation, aiding developers in correctly implementing the component.

### Changes Made
- In the code examples within the documentation, `ResizableGroup` has been renamed to `ResizablePanelGroup`.
- This change is applied to both horizontal and vertical orientation examples.

### Additional Information
- These changes are confined to documentation and do not alter the actual implementation or functionality of the components in question.

Please review the changes for accuracy and merge if appropriate. Thanks!
2023-12-24 05:40:05 +00:00
Nader Ferjani
7ce4414445 docs: typo in drawer (#2164)
Co-authored-by: shadcn <m@shadcn.com>
2023-12-24 09:35:26 +04:00
shadcn
319c7c55cc fix(www): mail icon size (#2173) 2023-12-24 09:32:46 +04:00
shadcn
57d404b5d3 feat: new components (#2144) 2023-12-22 23:36:59 +04:00
github-actions[bot]
6145dd8118 chore(release): version packages (#1756)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-12-22 21:05:24 +04:00
Bereket Engida
4fb98d520f feat(cli): add support for custom Tailwind prefix transformer (#770)
* feat(cli): add support for custom Tailwind prefix

* fix(cli): add tw prefix on classes applied in the css file

* feat(cli): add support for custom tailwind prefix

* chore: add changeset

* style(shadcn-ui): code format

---------

Co-authored-by: shadcn <m@shadcn.com>
2023-12-22 01:42:40 +04:00
ocavue
1cf5fad881 fix(toast): replace MAX_VALUE with MAX_SAFE_INTEGER (#1982)
This PR replaces the maximum id from `Number.MAX_VALUE` to `Number.MAX_SAFE_INTEGER` in `use-toast.ts`. Considering how JS stores numbers, it's unsafe to plus one if the number is larger than `Number.MAX_SAFE_INTEGER`. Here is an example:

```js 
> let num

> num = Number.MAX_VALUE - 1
> num + 1 === num
true

> num = Number.MAX_SAFE_INTEGER - 1
> num + 1 === num
false
```
2023-11-21 12:13:44 +00:00
Js Park
6f3050248c docs(www): update installation of Astro (#1958)
- The latest version of Astro generates `tailwind.config.mjs` file instead of `tailwind.config.cjs`.
- The `index.astro` file is located in `/src/pages`.
2023-11-19 07:24:12 +00:00
Gravy59
1903eb94a8 fix(www): typo in metric cards (#1975) 2023-11-19 11:16:27 +04:00
Danilo Britto
9f3ae7746f docs(www): add indeterminate state checkboxes on all data table demos and code examples (#1959)
Co-authored-by: shadcn <m@shadcn.com>
2023-11-19 11:15:44 +04:00
Dimitri POSTOLOV
c579e9232c add missing return Update page.tsx (#1952) 2023-11-17 00:20:36 +04:00
兰天游
08018ed623 feat: fix for column grouping (#945) 2023-11-12 19:12:40 +00:00
Max Wiseman
1db90baaf2 feat(toggle-group): add toggle-group component (#1547)
* feat: added toggle-group component

* fix(components): ran build:registry script

* fix(components): fixed colors in toggle-group
- Dark mode border color is now consistent with the toggle component

* fix(components): fixed component.json toggle-group
- Added the content field to `components.json` for toggle-group
- Ran build:registry again

* feat(toggle-group): simplify implementation

---------

Co-authored-by: shadcn <m@shadcn.com>
2023-11-12 23:03:14 +04:00
shadcn
3dc6207e97 chore: build registry 2023-11-12 22:13:40 +04:00
Yahor Barkouski
5d2373fb7a fix(www): update faker version, reduce int() deprecation (#1832)
* fix: update faker version, reduce int() deprecation

* chore: pnpm format:write

---------

Co-authored-by: shadcn <m@shadcn.com>
2023-11-12 22:12:58 +04:00
Peeranat Danaidusadeekul
e67c0d4507 style(command): add import type for Command component (#1490)
* style: add import type for Command component

* style(code): format code

---------

Co-authored-by: shadcn <m@shadcn.com>
2023-11-12 22:12:16 +04:00
Mubin Ansari
147206c168 Remove Redundant md:w-full class in DialogPrimitive.Content in dialog and alert-dialog (#1640)
closes #1639
2023-11-12 11:09:14 +00:00
Thomas Alberola
e6e9a6772b Move className overwrite of AccordionContent to the children component (#1670)
In order to have a smooth opening of the accordion, moving the `AccordionContent` `className` overwrite to the children wrapper component allow Radix to calculate correctly the animation and execute a smooth animation in case of `className` on the `AccordionContent` component.

Possibly related to https://github.com/shadcn-ui/ui/issues/944
2023-11-12 10:56:52 +00:00
Chongyi Zheng
1ae9ffcf58 chore: upgrade @radix-ui/react-select to 2.0.0 (#1688) 2023-11-12 10:32:03 +00:00
shadcn
8fad64a854 feat(select): add scroll up and down button 2023-11-12 14:25:35 +04:00
shadcn
7527ff490a Merge branch 'main' into main 2023-11-12 13:49:12 +04:00
Tanish Baansal
3a279a2766 refactor(calendar): updated css so date doesn't show up selected twice in DatePickerComponent (#1852)
React Day Picker also has a unique styling distinction that designates the `day_outside` dates as unselected, and the selection of `day_outside` only highlights the dates in the next month. This PR will fix this issue - https://github.com/shadcn-ui/ui/issues/1762 and help with a cleaner UX 

  
<br/>

| Old Date Range Picker  | New Date Range Picker           |
| ---------------------- | ---------------------- |
| ![old](https://github.com/shadcn-ui/ui/assets/7449806/42e9448f-9e38-486c-b65c-cf00ec1ec7c7) | ![new](https://github.com/shadcn-ui/ui/assets/7449806/804f83d7-1b74-474c-8992-2d6d844dfb35) |

<br/>

#### React Day Picker
<img width="444" alt="image" src="https://github.com/shadcn-ui/ui/assets/7449806/aaeae160-b38c-4c16-bb2d-66898cf290d3">
2023-11-12 09:36:12 +00:00
Gravy59
51c8c3d798 fix(#1686): remove redundant children prop (#1717)
This pull request resolves #1686.

## Rationale for this PR

This PR affects the code for `RadioGroupItem` in both styles by removing the `children` prop from the component. The children prop is automatically passed in by the use of the spread operator (`...props`) and is redundant because it is never used in the component.

This PR shouldn't affect tests, representation, etc. and is merely a cosmetic change. There is no urgent need to merge this.
2023-11-12 09:28:07 +00:00
William Frank Monroy Mamani
53f211b043 docs: updated contributing guidelines to easily contribute (#1830)
## Explanation
Added detailed info to clone and setup the project to contribute.

Following the PR #1650 and suggestion of @shadcn.  Thanks!
2023-11-12 07:57:25 +00:00
Shubhdeep Chhabra
a2ed2883ac fix(alert-dialog): removed unused children prop (#1828) 2023-11-12 11:46:21 +04:00
Greg
66c7f6d73b fix(scroll-area): horizontal scroll bar not visible (#1829)
PRs #1515 and #1296 interfere with each other and cause the horizontal scroll bar to not be visible. This removes the conditional `flex-1`, however you could also remove `flex-col` to achieve the same result.

before:

https://github.com/shadcn-ui/ui/assets/9381099/6514de2e-e353-4d0b-bd24-aff79e0d5161

after:


https://github.com/shadcn-ui/ui/assets/9381099/3205baad-569b-4096-8dcd-9beb794de536
2023-11-12 07:36:35 +00:00
shadcn
fc3d8288f7 ci: update tasks name to make debug easier (#1932) 2023-11-12 11:30:00 +04:00
shadcn
6e399abdb4 fix(table): update style for table footer (#1931)
* fix(table): update table footer style

* chore: run registry

* style: fix docs
2023-11-12 11:20:18 +04:00
Martini
3c22784a98 docs(www): Fix typo (#1853) 2023-11-12 10:18:34 +04:00
Cole Cline
c82a6fab5f docs(www): Missing import statement (#1877)
Added missing import statement in fonts example in Next.js installation docs
2023-11-12 10:17:56 +04:00
Innei
3fccfeb301 fix(switch): change width unit to rem (#1891)
Signed-off-by: Innei <i@innei.in>
Co-authored-by: shadcn <m@shadcn.com>
2023-11-12 10:17:07 +04:00
Kevin Mok
42e8eaf7cb docs(www): add remix dark mode docs (#1920)
* docs(www): add remix dark mode docs

* docs(www): add modification to tailwindcss file
2023-11-12 10:16:22 +04:00
Michael Stramel
d250109cc4 fix(www): destructive contrast increase (#1899) 2023-11-06 18:30:44 +04:00
iaingymware
ef73e591c8 Merge branch 'main' into main 2023-10-24 19:50:21 +01:00
miquelvir
c6917799ce docs(card): remove unused line #1652 (#1798)
Fixes #1652
2023-10-24 12:27:21 +00:00
iaingymware
b4efc8aa4d Merge branch 'main' into main 2023-10-24 12:15:43 +01:00
miquelvir
35f776d38c Fix combobox examples using labels as value (#1788)
Fix #1785
2023-10-21 14:09:57 +00:00
Akshay Sharma
5cadc5e983 fix(www): update twitter icon to X (#1551)
* refactor(icon.tsx): twitter icon updated to latest version X icon

* refactor(icon.tsx): twitter icon updated to latest version X icon

* refactor(icons.tsx): added the same changes to the icon for X twitter icon

* refactor(icons.tsx): change the formating of the code

---------

Co-authored-by: shadcn <m@shadcn.com>
2023-10-21 18:03:31 +04:00
Greg
e0782b328b docs(scroll-area): add example for horizontal scroll area (#1515)
Adds documentation to the `<ScrollArea>` component for creating a scroll area with a horizontal scrollbar. There is also a commit to fix the improper sizing of a horizontal scroll bars.

Before:
https://github.com/shadcn-ui/ui/assets/9381099/a8b512f1-37f7-4107-a9fa-42a26e124696

After:
https://github.com/shadcn-ui/ui/assets/9381099/480f881c-b0fe-4b1b-9472-c533135e6769
2023-10-21 13:54:16 +00:00
Bumsik Kim
cf0dadafce fix(example): Prevent hydration error in music example (#1569)
<DisalogTrigger> should have asChild when a button used.
2023-10-21 12:58:30 +00:00
/raj
5877dcd21a docs(www): bunx scripts run using bun instead of node (#1590)
* fix: using bun to initialize project works now

* style(www): format write

---------

Co-authored-by: shadcn <m@shadcn.com>
2023-10-21 16:47:21 +04:00
Stefan Schulz
95be4835b1 fix(calendar): showOutsideDays=false (#1731)
showOutsideDays=false will shift the missing days of a month to the start of the row (cause of 'flex' in classnames: row), to fix it, we can use the same height and width in a cell as in a day

Co-authored-by: shadcn <m@shadcn.com>
2023-10-21 16:47:00 +04:00
Karan Janthe
5a13def46d fix: updated error msg for jsconfig in cli (#1696)
* fix: updated error msg for jsconfig in cli

* chore: add changeset

* style(cli): format

---------

Co-authored-by: shadcn <m@shadcn.com>
2023-10-21 16:35:55 +04:00
kevinmitch14
b8810caac7 fix: remove invalid collapsible prop (#496)
only available when type="single"

Co-authored-by: shadcn <m@shadcn.com>
2023-10-21 16:28:00 +04:00
Peeranat Danaidusadeekul
24ec36ee7b docs: add missing tailwind config in Astro install (#1264)
* docs: add missing tailwind config in Astro install

* style: prettier format

---------

Co-authored-by: Peeranat Danaidusadeekul <ppeeranat.d@skooldio.com>
Co-authored-by: shadcn <m@shadcn.com>
2023-10-21 16:26:28 +04:00
onurhan
dd94aa936f fix(docs): present useForm when copy form (#1486)
When I tried it after the attached issue, I realized;
For someone who's absolutely copying and pasting form the guide, this would give an error, as useForm is not imported in the guide. So I fixed that.

Related issue: https://github.com/shadcn-ui/ui/issues/1482

Fix #1482
2023-10-21 12:16:22 +00:00
IDRISSI HAMZA
44f35d55b0 fix(components): fix text wrapping issue in buttons (#1548)
## What does this PR do?

This pull request resolves an issue where the button text wraps onto the next line in specific screen sizes when the text contains two or more words. By applying the whitespace-nowrap utility class to the button element, the text now remains on a single line, even on screens with limited space. This enhancement ensures a consistent and visually pleasing user experience across various devices :

### Before (Get Started Button)
![before](https://github.com/shadcn-ui/ui/assets/97639117/50c8211d-2ed7-46d0-8ab2-d4c565d0d6f3)


### After  (Get Started Button)
![after](https://github.com/shadcn-ui/ui/assets/97639117/0f9f7e68-31e8-4011-a2ca-d7e59b3690ee)





This problem arises in other projects utilizing Shadcn, and this pull request addresses and resolves the issue, as demonstrated in the Cal.com project example:

### Before (Set Up Button)
![button-before](https://github.com/calcom/cal.com/assets/97639117/8f588a53-411a-4e9d-95df-76bd42d480d7)

### After  (Set Up Button)
![button-fixed](https://github.com/calcom/cal.com/assets/97639117/4c74e77a-a2cd-4b0c-87ee-a82dbefc23eb)
2023-10-21 12:09:10 +00:00
N8
958a0fdb18 docs: ✏️ Add defaultValues to input in form examples (#1610)
Fixes: https://github.com/shadcn-ui/ui/issues/1609
2023-10-21 11:57:26 +00:00
Lachlan Heywood
6b660033fb fix(components): remove className from dialog portals (#1606)
Fixes #1595, #1644

This PR changes the components that use the `DialogPortal` element to be aliases rather than components that pass a className prop.

The `DialogPortalProps` type from `@radix/react-dialog` recently had a patch update that probably should have been a minor or maybe a major update which is causing a few people to see the error `Property 'className' does not exist on type 'DialogPortalProps'`.

Since the `DialogPortal` component doesn't actually output any DOM elements, it never technically supported the `className` prop and the fact that it surfaced that prop was really a bug.

The `AlertDialog` and `Dialog` components were updated in #1603, but the `Sheet` component still references `className` which is resolved in this PR.
2023-10-21 11:56:31 +00:00
iaingymware
dac5a0bd2c Merge branch 'main' into main 2023-10-20 19:46:52 +01:00
Olle Månsson
648ddde3a2 Fix support rule in site-header.tsx (#1628)
Fixes https://github.com/shadcn-ui/ui/issues/1627 if that is desired.
2023-10-19 18:10:45 +00:00
Luka Hartwig
4ec8a67dab Support tailwind.config.ts (#1247)
Fixes #659
Fixes #633

Create Next App is using `tailwind.config.ts` in the TypeScript template. Since this is a very common use case it would be nice to preserve the type safety of the file.

I added new templates for TypeScript files. I see there is an issue #1073 which asks for ESM support as well. This is not included in this PR.

I also fixed the type error in the keyframes that is also handled in #636
2023-10-19 17:44:11 +00:00
CamTheGoblin
9091dcdc1b docs(install): Clarify & Match tsconfig Edits (#1642)
This is a small update to the installation instructions for some of the frameworks to make the instructions on editing the tsconfig file consistant across the frameworks, and remove some potentially confusing wording (if people read too fast...like me). 

Mainly applying the gatsby tsconfig instructions to vite and astro, as it is the most clear. 
Additionally changed the wording from:
```
 Add the code below to the compilerOptions...
```
to:
```
Add the following code to the compilerOptions...
```
to avoid people easily misreading it as "add the code **below the** compilerOptions".
2023-10-19 17:27:01 +00:00
Josiah Hawkins
33f89e9654 docs: remove unused imports (#1661)
- Remove unused import from Alert Default example
- Remove unused imports from Alert Destructive example
- Remove unused imports from Dropdown Menu Radio Group example
2023-10-19 17:20:34 +00:00
shadcn
545423c93b chore: add kodiak 2023-10-19 21:12:17 +04:00
shadcn
82528a62a0 Merge branch 'main' into main 2023-10-18 21:37:12 +04:00
Deveesh Shetty
14abbd94b5 fix(www): removes redundant class-name from H2 component (#1703)
* chore: removes redundant class from typography-h2

* chore: remove class from new york style

---------

Co-authored-by: shadcn <m@shadcn.com>
2023-10-18 21:33:24 +04:00
iaingymware
cf54b6fa71 Merge branch 'main' into main 2023-10-16 10:20:47 +01:00
Caíque de Castro Soares da Silva
46f247c47f style(shadcn-ui): use space instead of tab on config fixture (#1707)
* style: use space instead of tab on config fixture

* style: fix identation on template script

* chore(shadcn-ui): add changeset

---------

Co-authored-by: shadcn <m@shadcn.com>
2023-10-16 13:16:55 +04:00
iaingymware
beb0281ca2 Merge branch 'main' into main 2023-10-16 10:15:44 +01:00
Iain Wandless
a54ade1b98 Merge branch 'main' of https://github.com/iaingymware/ui 2023-10-16 10:09:31 +01:00
Iain Wandless
11c1bc2cb9 feat(select): update newyork to use radix icons 2023-10-16 10:09:24 +01:00
Robert Soriano
4083876e80 docs: update Remix config to use ESM (#1710)
* docs(remix): replace postcss config sample to use export default

* docs(remix): replace tailwind config sample to use export default

---------

Co-authored-by: shadcn <m@shadcn.com>
2023-10-16 12:56:08 +04:00
alex
0176754ff2 docs(www): add astro dark mode implementation (#1755)
* docs(www): add astro dark mode implementation

* docs(www): reduce redundancy in mode toggle
2023-10-16 12:37:25 +04:00
iaingymware
1be434bc64 Merge branch 'main' into main 2023-10-15 21:12:50 +01:00
shadcn
2a346ede51 feat(www): add custom close button example (#1753) 2023-10-15 16:28:57 +04:00
shadcn
82c56f9503 docs(www): add fonts docs (#1752) 2023-10-15 15:37:25 +04:00
Iain Wandless
f68798e50b Revert "feat(select): update registry"
This reverts commit b37fc17f04.
2023-10-10 14:15:08 +01:00
Iain Wandless
b37fc17f04 feat(select): update registry 2023-10-10 14:11:32 +01:00
Iain Wandless
d6063c5769 feat(select): scrollable with large datasets
newyork ui
2023-10-10 14:09:32 +01:00
Iain Wandless
ef9fa600a5 feat(select): scrollable with large datasets
update to default ui
2023-10-10 14:01:43 +01:00
shadcn
43c4023ed8 chore: rebuild registry 2023-10-03 18:47:47 +04:00
github-actions[bot]
c765635e13 chore(release): version packages (#1663)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-10-03 18:39:32 +04:00
shadcn
95a9673b1e minify cli (#1662)
* chore(shadcn-ui): minify build

* chore: add changeset
2023-10-03 18:20:58 +04:00
Oguz Kazkayasi
617cdd0e77 docs(www): framework is changed to language at language search example (#1646)
Co-authored-by: shadcn <m@shadcn.com>
2023-10-03 17:38:10 +04:00
shadcn
1536b7824e feat: export portal and overlay for alert-dialog, dialog and sheet (#1660) 2023-10-03 17:12:40 +04:00
Rohid
524e4b8b95 fix(alert-dialog): update portal component (#1603) 2023-10-03 17:02:31 +04:00
Diego Franchina
1f16cf4728 fix(scroll-area): added conditional flex-1 (#1296) 2023-09-25 15:29:05 +04:00
Rohan Godha
4f8d768e59 docs(www): add bun support for installation commands (#1445)
* feat(www): add bun support for commands

* chore: remove changeset

---------

Co-authored-by: shadcn <m@shadcn.com>
2023-09-21 17:30:29 +04:00
Shoaib Ahmed
c0deeac0d0 fix(table): add relative class to handle overflow issue (#1370) 2023-09-19 19:17:38 +04:00
github-actions[bot]
897376329b chore(release): version packages (#1556)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-09-19 17:14:39 +04:00
shadcn
d3d52fc687 docs(cli): update link to documentation (#1555)
* docs(cli): update link to documentation

* chore: add changeset
2023-09-19 16:52:45 +04:00
shadcn
4d0864a5c2 Merge branch 'main' of github.com:shadcn/ui 2023-09-19 16:36:06 +04:00
shadcn
e8f58932bd chore: update repo for changeset 2023-09-19 16:35:50 +04:00
shadcn
2f0dbca221 feat(cli): do not ask for confirmation (#1554)
* feat(cli): do not confirm

* chore: add changeset
2023-09-19 16:25:57 +04:00
Paul Ebose
58d012e342 feat(cli): add overwrite confirmation for existing components (#973)
* feat(cli):  add overwrite confirmation for existing components

* fix(cli): handle overwrite for multiple items

* chore: add changeset

---------

Co-authored-by: shadcn <m@shadcn.com>
2023-09-19 15:57:19 +04:00
Shishir
963114e118 feat(cli): support adding all components via CLI (#1033)
* feat: support adding all components via CLI

`shadcn-ui add --all`

This was manually tested to work as expected.

* chore: run prettier

* fix(cli): rename to all

* chore: add changeset

---------

Co-authored-by: shadcn <m@shadcn.com>
2023-09-19 07:37:35 +04:00
Ayhan
0a4286500e fix(cli): handle ts file extension (#1466)
Co-authored-by: shadcn <m@shadcn.com>
2023-09-19 07:23:07 +04:00
Santi Dalmasso
ae845788f6 fix(cli): deduplicate classNames in applyColorMapping (#1089)
* fix(cli): deduplicate classNames in applyColorMapping

* refactor: simplify applyColorMapping return

* chore: add changeset

---------

Co-authored-by: shadcn <m@shadcn.com>
2023-09-19 07:04:57 +04:00
oasisy
ccb2d695a7 fix(www): enable input editing in the dialog demo (#1428)
Co-authored-by: shadcn <m@shadcn.com>
2023-09-18 17:35:27 +04:00
Abderrahim Guerfi
b838ffe8cc docs(www): add missing prop to ThemeProvider (#1447)
Co-authored-by: shadcn <m@shadcn.com>
2023-09-18 16:47:58 +04:00
Robert Soriano
7ce6c495bd fix(www): rename DataTableFacetedFilter interface (#1438)
Co-authored-by: shadcn <m@shadcn.com>
2023-09-18 16:31:21 +04:00
MD
c9ca64d2b9 docs(www): update manual.mdx (#1471)
Fix file name

Co-authored-by: shadcn <m@shadcn.com>
2023-09-18 16:25:26 +04:00
Arjun Raj
4bb9e9de53 docs(www): update cli add options (#1484) 2023-09-18 16:23:47 +04:00
Nikhar Pandya
c21ecfb665 docs(www): update contrubuting.md and next.mdx (#1343)
* docs: Update CONTRIBUTING.md

added commit convention section to the contribution docs.

* docs: Update next.mdx for dark mode

---------

Co-authored-by: shadcn <m@shadcn.com>
2023-08-31 17:01:36 +04:00
sanka
613ec3583f docs(www): update dark-mode for vite (#1118)
Co-authored-by: sanka <sanka.s@fidenz.com>
Co-authored-by: shadcn <m@shadcn.com>
2023-08-26 14:45:34 +04:00
Diego Franchina
170d3c087c fix(scroll-area): missing key prop error (#1295)
Co-authored-by: shadcn <m@shadcn.com>
2023-08-25 07:24:04 +04:00
kevinmitch14
4b0fbe27fa fix(www): don't allow empty chat messages (#1137)
Co-authored-by: shadcn <m@shadcn.com>
2023-08-25 06:51:19 +04:00
rcrosbourne
c34193cd34 docs(www): add Laravel install docs (#1279)
Co-authored-by: shadcn <m@shadcn.com>
2023-08-24 20:01:29 +04:00
Deveesh Shetty
88fdc989e9 chore(www): remove unneccesary imports (#1311)
* chore: remove uneccesary imports

* chore: remove from new-york style

---------

Co-authored-by: shadcn <m@shadcn.com>
2023-08-24 09:26:12 +04:00
K-Sato
4506822389 chore: remove a duplicate call in init.test (#1319)
* chore: remove duplicate call in init.test

* chore: add changeset

---------

Co-authored-by: shadcn <m@shadcn.com>
2023-08-24 09:14:20 +04:00
jspark
33a5fc7966 docs(www): fix typo (#1270) 2023-08-17 21:39:49 +04:00
Dani
33b77e2f31 fix(toast): toast add missing text color class (#1162)
Co-authored-by: shadcn <m@shadcn.com>
2023-08-17 15:27:23 +04:00
shadcn
e3769277d8 docs: update CONTRIBUTING.md 2023-08-17 15:00:08 +04:00
shadcn
3992a7b19c docs: add contributing guide (#1266) 2023-08-17 14:59:22 +04:00
shadcn
52c23746bc fix(www): issue with border radius in component preview (#1265) 2023-08-17 14:14:48 +04:00
vinay
f68976b667 docs(www): remove unused Link import in form docs. (#1251)
Co-authored-by: shadcn <m@shadcn.com>
2023-08-17 13:50:34 +04:00
Edgaras Benediktavicius
7a1f80af2c docs(www): @hookform/resolvers installation missing (#1257)
Co-authored-by: shadcn <m@shadcn.com>
2023-08-17 13:43:32 +04:00
我不是张硕
646f715388 fix(www): error in color values for slaet (#1224)
Co-authored-by: shadcn <m@shadcn.com>
2023-08-17 13:35:06 +04:00
Alonso Ureña
9441130f05 fix(www): payment method :has selector not working on Firefox (#1209)
Co-authored-by: shadcn <m@shadcn.com>
2023-08-16 21:11:49 +04:00
Daniele Luisetto
48e3a4a326 fix(docs): add missing files in toast manual installation (#1243)
Co-authored-by: shadcn <m@shadcn.com>
2023-08-16 07:32:09 +04:00
Daniele Luisetto
98078fbe01 fix(docs): missing form manual installation and aspect-ratio typo (#1242)
* fix(docs): add form manual installation

* fix(docs): typo in aspect-ratio

---------

Co-authored-by: shadcn <m@shadcn.com>
2023-08-16 07:26:49 +04:00
shadcn
8be9e5d966 fix(www): extra semi 2023-08-15 19:25:11 +04:00
Nguyen Long Nhat
a8b1ea7e55 fix: remove semicolon duplicated in theme.css (#1146) 2023-08-15 19:24:37 +04:00
shadcn
3c9f7ca0e2 feat: themes (#1135) 2023-08-07 22:39:16 +04:00
shadcn
c598f19845 docs(www): fix typo 2023-08-04 20:54:46 +04:00
Paul Ebose
7962cee384 docs(www): add api reference for components.json (#982)
* docs: add api reference for component.json file

* docs: use Link for internal links in component.json

* docs(www): update docs for components.json

---------

Co-authored-by: shadcn <m@shadcn.com>
2023-08-04 17:03:41 +04:00
Guten Ye
de3c34845b docs(www): update import in remix installation (#974)
Co-authored-by: shadcn <m@shadcn.com>
2023-08-03 22:27:39 +04:00
vinay
6a1354e52d fix(examples): update keyboard shortcut for opening dialog on Windows (#1004) 2023-08-03 22:22:38 +04:00
Rudy Orre
1532a15894 docs(www): add missing "command" component to imports (#550)
Co-authored-by: shadcn <m@shadcn.com>
2023-08-03 21:57:39 +04:00
Alan Daniel
8e5d080900 docs(www): add dark mode for Vite. (#814)
Co-authored-by: shadcn <m@shadcn.com>
2023-08-03 19:41:33 +04:00
GrungeElFz
cf95943446 docs(www): update import path for button (#839)
Co-authored-by: shadcn <m@shadcn.com>
2023-08-03 19:03:44 +04:00
vinay
3210bed755 fix(next-template): remove experimental appdir (#979)
Co-authored-by: shadcn <m@shadcn.com>
2023-08-01 19:21:18 +04:00
Bernardo Ferrari
eaa91d43df style(card): remove extra spacing (#1082) 2023-08-01 19:07:03 +04:00
Antsiferov Maxim
8cf0c7f3ba fix(alert): padding on Firefox (#1059)
Co-authored-by: shadcn <m@shadcn.com>
2023-07-30 15:50:11 +04:00
Abdulelah
da7729644c fix(combobox): search language by label in examples (#989)
Co-authored-by: shadcn <m@shadcn.com>
2023-07-30 15:33:18 +04:00
shadcn
aca3ef97e3 fix(www): responsive cards (#1066) 2023-07-28 23:14:38 +04:00
Deveesh Shetty
c9fecd4cdf fix(www): overflow issue in documentation (#1055)
Co-authored-by: shadcn <m@shadcn.com>
2023-07-28 16:03:43 +04:00
munan56
6cf598d47f docs(www): update import path next.mdx (#1062) 2023-07-28 15:56:19 +04:00
shadcn
91727ec460 ci: rename repo owner (#1056) 2023-07-27 13:12:13 +04:00
Pedro Vítor Ribeiro Martins
5e172fc34f docs(www): add typescript configuration question to cli docs (#898)
Co-authored-by: shadcn <m@shadcn.com>
2023-07-12 20:34:05 +04:00
Drew Greene
f461ab0910 docs(www): update label in card demo (#799)
Co-authored-by: shadcn <m@shadcn.com>
2023-07-12 20:22:35 +04:00
Lennart Gastler
26c8d0f662 docs(www): improve astro guide (#858) 2023-07-12 20:17:25 +04:00
vinay
ac5c727fc9 docs(www): add links to frameworks (#744)
Co-authored-by: shadcn <m@shadcn.com>
2023-07-05 23:29:14 +04:00
shadcn
54b1f5b661 feat: update next-template 2023-07-05 23:00:03 +04:00
github-actions[bot]
25a41cfe2a chore(release): version packages (#835)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-07-04 23:26:32 +04:00
shadcn
edc653c01e feat(shadcn-ui): add support for jsx (#834) 2023-07-04 23:22:20 +04:00
shadcn
f78a4aaa28 chore(www): site updates (#829)
* chore(www): update to latest Next.js

* feat(www): replace lucide icons
2023-07-04 17:04:22 +04:00
shadcn
b4dda36cc9 docs(www): add radix-ui deps install (#827) 2023-07-04 12:01:50 +04:00
Lennart
cbe0f1959c docs(www): improve Gatsby guide (#826) 2023-07-04 10:55:01 +04:00
Daniele Luisetto
dbd3b8f066 docs(www): add astro installation guide (#824) 2023-07-04 10:02:34 +04:00
Moinul Moin
7abb4019c3 docs(www): add installation guide for Gatsby (#822)
* docs: add installation guide for Gatsby

* docs: add  PR review changes
2023-07-03 22:23:52 +04:00
shadcn
b14081631f docs(www): fix link 2023-06-30 23:32:17 +04:00
github-actions[bot]
0857bfe889 chore(release): version packages (#737)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-06-30 22:56:01 +04:00
shadcn
ea6699adbf feat: restructure installation docs 2023-06-30 22:37:43 +04:00
Samuel Adebayo
1f004243d4 docs(www): add react with vite installation guide (#714)
* docs: add react with vite installation guide

* refactor(docs): move Ract with vite page into installation.mdx as tab

* fix(docs): remove classnames and wrapper divs

* fix(docs): update tsconfig file path to default @

* docs(www): fix code style

---------

Co-authored-by: Samuel Adebayo <samuel.adebayo@engagetech.com>
Co-authored-by: shadcn <m@shadcn.com>
2023-06-30 21:21:10 +04:00
Christoffer Hallas
eee7ce6879 docs(www): add remix and tabbed installation docs (#753)
* docs: add react with vite installation guide

* refactor(docs): move Ract with vite page into installation.mdx as tab

* fix(docs): remove classnames and wrapper divs

* fix(docs): update tsconfig file path to default @

* feat: added remix installation docs

* feat: added tabbed installation docs

* fix: remove log statement

* fix: cleaned up, restored usage notice, removed vite for now

* fix: moved installation.mdx into folder

---------

Co-authored-by: Samuel Adebayo <samuel.adebayo@engagetech.com>
Co-authored-by: shadcn <m@shadcn.com>
2023-06-29 17:27:03 +04:00
Mark Lyck
c3377530f4 feat(shadcn-cli): add support for bun install (#707)
* add support for bun install

* fix(cli): add changeset for bun install

---------

Co-authored-by: shadcn <m@shadcn.com>
2023-06-27 11:48:51 +04:00
Fady Makram
a8bb2ef737 fix(accordion): prevent accordion chevron from shrinking on smaller viewports (#717)
* Prevent accordion chevron from shrinking on smaller viewports

* fix: apply feedback
2023-06-27 11:39:54 +04:00
sean-brydon
ab836d1ab3 fix(www): update data-table-toolbar to correctly handle isFiltered (#702)
* Update data-table-toolbar.tsx

* style(www): run format:write

---------

Co-authored-by: shadcn <m@shadcn.com>
2023-06-26 16:16:03 +04:00
Daichi Ninomiya
77d6f5676e chore(www): pnpx to pnpm dlx (#350)
* fix(npm-commands): pnpx to pnpm dlx

* style(www): fix

---------

Co-authored-by: shadcn <m@shadcn.com>
2023-06-26 15:50:53 +04:00
github-actions[bot]
2846b2ea9a chore(release): version packages (#696)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-06-24 21:12:15 +04:00
Samuel Corsi-House
0f84973b4d fix(shadcn-ui): use @antfu/ni to detect package manager (#677)
* fix(cli): use @antfu/ni to detect package manager

* chore(cli): cleanup imports

* Create cyan-houses-dress.md

---------

Co-authored-by: shadcn <m@shadcn.com>
2023-06-24 21:01:22 +04:00
Caio Borghi
f8348621f4 docs(www): fix TypeScript typo (#645)
Co-authored-by: shadcn <m@shadcn.com>
2023-06-24 20:38:10 +04:00
Daniele Luisetto
fbed50f4e8 fix(shadcn-ui): cssVars template typo causing missing value (#682)
Co-authored-by: shadcn <m@shadcn.com>
2023-06-24 20:10:01 +04:00
Daniele Luisetto
1971fa7511 fix(shadcn-ui): use slash for alpha values in colors (#681)
Co-authored-by: shadcn <m@shadcn.com>
2023-06-24 20:09:22 +04:00
Daniele Luisetto
7b5582e5d0 fix(docs): remove duplicate install command (#679)
Co-authored-by: shadcn <m@shadcn.com>
2023-06-24 15:51:23 +04:00
Bryce Kalow
2ca7476c9b docs(www): directory -> directive (#685) 2023-06-24 15:49:47 +04:00
Reda
aea12e9762 docs(www): missing RSC anchor link (#664)
adda a missing # for the RSC section anchor link, resulting in 404.

Co-authored-by: shadcn <m@shadcn.com>
2023-06-23 20:08:18 +04:00
shadcn
343acb3a51 docs: update toast.mdx (#670) 2023-06-23 19:13:29 +04:00
shadcn
cf139e5fa1 feat: add manual installation for components (#666) 2023-06-23 14:28:34 +04:00
Ahmed Abdelbaset
38fb9693d0 style(shadcn-ui): remove unused variable in index.ts (#654) 2023-06-23 10:58:33 +04:00
Amruth Pillai
f1de3401a2 fix: update schema.json (#651)
Co-authored-by: shadcn <m@shadcn.com>
2023-06-23 00:27:28 +04:00
shadcn
379d1560c3 docs: add latest to cli (#649) 2023-06-23 00:07:19 +04:00
github-actions[bot]
d604b82fb3 chore(release): version packages (#644)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-06-22 23:08:57 +04:00
shadcn
658c710bce fix: schema keys validation (#641)
* fix: schema keys validation

* chore: add changeset

* fix: update registry base url
2023-06-22 23:05:54 +04:00
github-actions[bot]
4ca9619efa chore(release): version packages (#640)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-06-22 22:49:27 +04:00
shadcn
eeb17545a1 feat: new CLI, Styles and more (#637) 2023-06-22 22:44:52 +04:00
Tat Tran
3d717f992b fix: class name typo (#398)
* fix: class name

* fix: class names order

---------

Co-authored-by: shadcn <m@shadcn.com>
2023-05-29 11:26:44 +04:00
moshyfawn
042554ad07 fix(www): register field array input once (#466)
Closes: #465

Co-authored-by: shadcn <m@shadcn.com>
2023-05-29 11:20:43 +04:00
Hajime Nakagawa
71f496d41f fix(command): modify incorrect className (#473)
text-foreground-muted => text-muted-foreground

Co-authored-by: shadcn <m@shadcn.com>
2023-05-29 11:16:04 +04:00
puneet-sarhali
0a5df3ac85 docs(www): broken link to React Hook Form's useController hook (#479)
Co-authored-by: shadcn <m@shadcn.com>
2023-05-29 11:10:34 +04:00
shadcn
eb27529f50 feat(next-template): update template and dependencies (#483)
* feat(next-template): update template and deps

* docs(next-template): update README

* fix(www): update deps

* chore: remove console.log
2023-05-29 11:02:02 +04:00
Prince Hernandez
dffbe89f7d feat(cli): add support for config file (#245)
* feat(cli): add config files and examples in docs

* docs(www): remove cli advanced options

---------

Co-authored-by: shadcn <m@shadcn.com>
2023-05-28 10:21:11 +04:00
Alexander Kachkaev
22f23b7db3 chore: update pnpm version in package.json (#314)
* Update pnpm version in package.json

* Update package.json

* 8.5.1

---------

Co-authored-by: shadcn <m@shadcn.com>
2023-05-25 22:05:55 +04:00
Brad Adams
d6d4017b95 fix: tailwind-indicator classnames (#453)
+ Remove unnecessary `hidden` breakpoint classnames

Co-authored-by: shadcn <m@shadcn.com>
2023-05-25 21:05:41 +04:00
Sammy Hass
00ecdfbb17 fix(docs): command menu unresponsive after no results (#455) 2023-05-25 20:57:49 +04:00
Oliver Schneider
065ba02ae5 feat(sheet): expose close trigger for Sheet (#438)
Co-authored-by: shadcn <m@shadcn.com>
2023-05-25 16:10:22 +04:00
Spastic
5dfc2020aa fix typings (#287)
* fix typings

* change Toast interface to type

* ran prettier
2023-05-25 15:51:13 +04:00
Daniel Rotärmel
5aecccc586 feat(cli): use https_proxy and consider previous install locations (#430)
* feat(cli): use system proxy

* feat(cli): adds default installation directory

* style: formatting

* feat: update lockfile

---------

Co-authored-by: shadcn <m@shadcn.com>
2023-05-25 14:37:37 +04:00
shadcn
97a444b210 feat(www): add link to view code examples 2023-05-25 13:32:05 +04:00
Raí Siqueira
060e896183 fix(www): fix language placeholder (#433)
Signed-off-by: Rai Siqueira <rai93siqueira@gmail.com>
2023-05-24 18:21:27 +04:00
Dev
c4da22ffe9 docs(www): Fix unmatched closing brace typo (#423)
Co-authored-by: shadcn <m@shadcn.com>
2023-05-23 11:31:32 +04:00
dong.huo
f6b2d0c5dd fix(www): add key to list map components (#413)
Co-authored-by: shadcn <m@shadcn.com>
2023-05-23 11:22:35 +04:00
Zwyx
3f01388389 docs(www): fix inconsistency in forms docs + add note about uncontrolled components (#417)
* fix(forms): correct inconsistency in forms example

* doc(forms): add note about uncontrolled components
2023-05-22 17:00:59 +04:00
shadcn
c584f01163 docs(www): update defaultValues for useForm 2023-05-22 13:43:29 +04:00
shadcn
588ebd79e4 fix(www): filter out disabled links 2023-05-20 21:44:26 +04:00
shadcn
36881682cf feat: switch to stable next 2023-05-20 20:02:42 +04:00
jeremy
d1363515eb fix: incorrect example in form docs (#387) 2023-05-20 12:13:06 +04:00
Usman S. (Max Programming)
5afb8d530f docs(www): fix typo (#375)
Co-authored-by: shadcn <m@shadcn.com>
2023-05-20 10:09:42 +04:00
Piros
6a5195498f fix(radio-group): add aspect-square on radio button (#379)
Co-authored-by: shadcn <m@shadcn.com>
2023-05-20 10:03:52 +04:00
Piros
7d8be94a01 docs(www): update react-hook-form.mdx (#378)
Co-authored-by: shadcn <m@shadcn.com>
2023-05-20 09:58:25 +04:00
moshyfawn
a3c904dcc9 fix(www): refactor controlled inputs API examples (#385)
* refactor: remove usage of uncontrolled input apis within controlled inputs

Closes: #384

* docs: display FormField controlled input usage example

---------

Co-authored-by: shadcn <m@shadcn.com>
2023-05-20 08:58:44 +04:00
moshyfawn
11447c9bff fix(form): avoid unnecessary state subscriptions (#383)
* perf: avoid unnecessary state subscriptions

Closes: #382

* fix(form): code format

---------

Co-authored-by: shadcn <m@shadcn.com>
2023-05-20 08:52:53 +04:00
moshyfawn
f9d399172c fix(www): display account form name input error (#381)
* docs: display account form name input error

Closes: #380

* style: remove callback return statement
2023-05-20 08:40:01 +04:00
shadcn
4ccff13f9c feat: react-hook-form (#377)
* feat(form): add form component

* feat(www): update site styles

* feat: add form examples

* docs(www): add docs for forms

* docs(www): hide tabs for docs demo
2023-05-19 22:56:49 +04:00
Quentin
dbbdbe618f fix(www): missing ']' in className (#358)
* fix(code-block-wrapper): missing ']' in className

* fix(code-block-wrapper): re-order tailwind className

---------

Co-authored-by: shadcn <m@shadcn.com>
2023-05-16 15:41:35 +04:00
Yiyang
b09cff40ae fix(textarea): replace h-20 with min-h-[80px] (#344)
Co-authored-by: shadcn <m@shadcn.com>
2023-05-16 15:04:17 +04:00
github-actions[bot]
df9369762a chore(release): version packages (#352)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-05-13 13:35:46 +04:00
Nirmalya Ghosh
87ad14cb2a fix(shadcn-ui): add missing deps for Button (#259)
* fix: add missing deps for Button

* fix: use correct dep

Co-authored-by: Pablo <its.monotype@gmail.com>

* chore: add changeset

---------

Co-authored-by: Pablo <its.monotype@gmail.com>
Co-authored-by: shadcn <m@shadcn.com>
2023-05-13 13:07:39 +04:00
Jack
5a2ce61e2e fix(data-table): uncontrolled input error on data-table (#335) 2023-05-11 10:07:39 +04:00
Bilal Afzal
4a7c07e754 fix(next-template): remove unused import Laptop in icons.tsx (#310)
Remove unused import 'Laptop' in icons.tsx (nextjs-example)

Co-authored-by: shadcn <m@shadcn.com>
2023-05-10 21:48:46 +04:00
shadcn
8eb3e1e160 fix(www): update padding for row actions 2023-05-10 20:51:00 +04:00
shadcn
5bc68894b8 fix(www): filter display for lg 2023-05-10 16:32:57 +04:00
Connor Stevens
21890afa80 docs(www): fix typo
Co-authored-by: shadcn <m@shadcn.com>
2023-05-10 07:49:17 +04:00
Ben Jacobson
84b7200178 docs(www): fix link to DataTable (#322) 2023-05-10 00:09:15 +04:00
shadcn
f8272baf07 feat: add table and data table (#321) 2023-05-09 23:25:26 +04:00
Mike Robinson
9a6b934421 feat(table): add table component (#248)
* Add table.tsx

* Update table.tsx

---------

Co-authored-by: shadcn <m@shadcn.com>
2023-05-08 15:49:19 +04:00
Mike Robinson
b3247d90a6 fix(checkbox,radio-group): Increase contrast ratio of checkboxes and radio buttons (#224)
* Update checkbox.tsx

* Update radio-group.tsx

* fix(radio-group): update colors for contrast

* fix(checkbox): update colors for contrast

---------

Co-authored-by: shadcn <m@shadcn.com>
2023-05-08 15:43:10 +04:00
Danazumi
0c31f60bb0 fix(next-template): strict type check
Co-authored-by: Danazumi <granzin98@gmail.com>
Co-authored-by: shadcn <m@shadcn.com>
2023-05-03 18:47:49 +04:00
James
be64c55901 fix(dropdown-menu): dropdown sub menu using wrong text color (#285) 2023-05-03 17:20:18 +04:00
shadcn
b19199a35d docs: move combobox and date picker to their own page (#283) 2023-05-03 15:59:00 +04:00
shadcn
6e67107170 fix(www): update to canary with scrolling fix (#279) 2023-05-03 10:53:59 +04:00
github-actions[bot]
71c631891f chore(release): version packages (#272)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-05-02 19:25:02 +04:00
shadcn
888a5ad6f6 fix(shadcn-ui): update color of cards in dark mode (#271) 2023-05-02 15:39:16 +04:00
Evan Rosenfeld
d99b992e27 feat(button): support render delegation with asChild parameter (#174) 2023-04-30 12:01:41 +04:00
Elliot Anderson
7dc67be1ad fix(www): set icon fill to current currentColor (#239) 2023-04-29 18:52:25 +04:00
Aron Hafner
d528e9a45e docs(www): set correct colors for card in darkmode (#213) 2023-04-20 16:36:38 +04:00
Jack B
2a61b54096 fix(www): component selection in copy-button (#220)
Co-authored-by: shadcn <m@shadcn.com>
2023-04-20 16:20:14 +04:00
Esteban De la Rosa
5b004f7565 docs(www): Fix typo in alert component usage (#196)
Co-authored-by: shadcn <m@shadcn.com>
2023-04-20 12:49:15 +04:00
shadcn
5e915756d5 feat(www): implement event tracking (#218)
* feat(www): implement event tracking

* fix(www): always show copy button

* fix(www): update align props for copy button
2023-04-20 12:21:50 +04:00
Elia Maino
bfc66148ea fix(badge): update destructive bg color on hover (#189) 2023-04-20 09:34:32 +04:00
Vincent Taneri
2fa78133c8 docs(www): fix a typo in FAQ (#198) 2023-04-19 19:56:04 +04:00
Seo Jun Hyung
3a60500d54 fix(www): remove styles for preventing scroll when modal is open (#209) 2023-04-19 19:43:33 +04:00
shadcn
a5b313ab13 chore: update template 2023-04-19 15:01:29 +04:00
github-actions[bot]
c37c087c65 chore(release): version packages (#206)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-04-19 13:47:25 +04:00
shadcn
dc573c7e9e fix: repo name in changeset config (#205) 2023-04-19 13:38:58 +04:00
shadcn
68e1c5624a chore(www): update deps and fix CI errors (#204) 2023-04-19 13:22:53 +04:00
shadcn
acf4ae79cb docs(shadcn-ui): update README (#203) 2023-04-19 09:48:47 +04:00
shadcn
4a794a354f feat(shadcn-ui): update init message (#197) 2023-04-18 19:12:38 +04:00
shadcn
2953c6cb0a fix(shadcn-ui): support for array of dependencies (#195) 2023-04-18 18:34:25 +04:00
shadcn
3812e119c0 fix(www): color of link 2023-04-18 10:18:13 +04:00
shadcn
4c9a706204 fix(switch): update background color for dark mode 2023-04-18 10:17:00 +04:00
The Command Runner
a589df7bca docs(www): add type keyword to ClassValue (#184)
Add `type` keyword because Typescript complains, since ClassValue is only imported as a type.
2023-04-18 10:10:19 +04:00
Gimel Dick
eb36e53057 fix: typo in badge component (#186) 2023-04-18 05:45:45 +04:00
shadcn
7a87fbfa77 fix(www): background color for auth form 2023-04-17 19:41:45 +04:00
shadcn
a703c6fb58 feat: CSS variables and CLI (#180) 2023-04-17 19:19:40 +04:00
1091 changed files with 71100 additions and 29232 deletions

View File

@@ -1,11 +1,11 @@
{
"$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json",
"changelog": ["@changesets/changelog-github", { "repo": "shadcn/ui" }],
"changelog": ["@changesets/changelog-github", { "repo": "shadcn-ui/ui" }],
"commit": false,
"fixed": [],
"linked": [],
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": ["www", "playground", "**-template"]
"ignore": ["www", "**-template"]
}

View File

@@ -10,16 +10,22 @@
"plugins": ["tailwindcss"],
"rules": {
"@next/next/no-html-link-for-pages": "off",
"react/jsx-key": "off",
"tailwindcss/no-custom-classname": "off",
"tailwindcss/classnames-order": "error"
},
"settings": {
"tailwindcss": {
"callees": ["cn"]
"callees": ["cn", "cva"],
"config": "tailwind.config.cjs"
},
"next": {
"rootDir": ["apps/*/"]
}
}
},
"overrides": [
{
"files": ["*.ts", "*.tsx"],
"parser": "@typescript-eslint/parser"
}
]
}

View File

@@ -7,7 +7,7 @@ on:
jobs:
lint:
runs-on: ubuntu-latest
name: Lint
name: pnpm lint
steps:
- uses: actions/checkout@v3
with:
@@ -16,12 +16,13 @@ jobs:
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: 16
node-version: 18
- uses: pnpm/action-setup@v2.2.4
name: Install pnpm
id: pnpm-install
with:
version: 8.6.1
run_install: false
- name: Get pnpm store directory
@@ -42,7 +43,7 @@ jobs:
format:
runs-on: ubuntu-latest
name: Format
name: pnpm format:check
steps:
- uses: actions/checkout@v3
with:
@@ -51,12 +52,13 @@ jobs:
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: 16
node-version: 18
- uses: pnpm/action-setup@v2.2.4
name: Install pnpm
id: pnpm-install
with:
version: 8.6.1
run_install: false
- name: Get pnpm store directory
@@ -79,7 +81,7 @@ jobs:
tsc:
runs-on: ubuntu-latest
name: TypeScript
name: pnpm typecheck
steps:
- uses: actions/checkout@v3
with:
@@ -88,12 +90,13 @@ jobs:
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: 16
node-version: 18
- uses: pnpm/action-setup@v2.2.4
name: Install pnpm
id: pnpm-install
with:
version: 8.6.1
run_install: false
- name: Get pnpm store directory

View File

@@ -10,7 +10,7 @@ on:
jobs:
comment:
if: |
github.repository_owner == 'shadcn' &&
github.repository_owner == 'shadcn-ui' &&
${{ github.event.workflow_run.conclusion == 'success' }}
runs-on: ubuntu-latest
name: Write comment to the PR
@@ -60,6 +60,6 @@ jobs:
github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: ${{ env.WORKFLOW_RUN_PR }},
issue_number: '${{ env.WORKFLOW_RUN_PR }}',
name: '🚀 autorelease',
});

View File

@@ -10,7 +10,7 @@ on:
jobs:
prerelease:
if: |
github.repository_owner == 'shadcn' &&
github.repository_owner == 'shadcn-ui' &&
contains(github.event.pull_request.labels.*.name, '🚀 autorelease')
name: Build & Publish a beta release to NPM
runs-on: ubuntu-latest
@@ -24,6 +24,8 @@ jobs:
- name: Use PNPM
uses: pnpm/action-setup@v2.2.4
with:
version: 8.6.1
- name: Use Node.js 18
uses: actions/setup-node@v3

View File

@@ -9,7 +9,7 @@ on:
jobs:
release:
if: ${{ github.repository_owner == 'shadcn' }}
if: ${{ github.repository_owner == 'shadcn-ui' }}
name: Create a PR for release workflow
runs-on: ubuntu-latest
steps:
@@ -20,10 +20,13 @@ jobs:
- name: Use PNPM
uses: pnpm/action-setup@v2.2.4
with:
version: 8.6.1
- name: Use Node.js 18
uses: actions/setup-node@v3
with:
version: 8.6.1
node-version: 18
cache: "pnpm"

42
.github/workflows/test.yml vendored Normal file
View File

@@ -0,0 +1,42 @@
name: Test
on:
pull_request:
branches: ["*"]
jobs:
test:
runs-on: ubuntu-latest
name: pnpm test
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: 18
- uses: pnpm/action-setup@v2.2.4
name: Install pnpm
id: pnpm-install
with:
version: 8.6.1
run_install: false
- name: Get pnpm store directory
id: pnpm-cache
run: |
echo "pnpm_cache_dir=$(pnpm store path)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
name: Setup pnpm cache
with:
path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Install dependencies
run: pnpm install
- run: pnpm test

View File

@@ -1,4 +0,0 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx pretty-quick --staged

18
.kodiak.toml Normal file
View File

@@ -0,0 +1,18 @@
# .kodiak.toml
version = 1
[merge]
automerge_label = "automerge"
require_automerge_label = true
method = "squash"
delete_branch_on_merge = true
optimistic_updates = false
prioritize_ready_to_merge = true
notify_on_conflict = true
[merge.message]
title = "pull_request_title"
body = "pull_request_body"
include_pr_number = true
body_type = "markdown"
strip_html_comments = true

2
.nvmrc
View File

@@ -1 +1 @@
v16.18.0
v18.17.0

View File

@@ -3,4 +3,4 @@ node_modules
.next
build
.contentlayer
apps/www/pages/api/components.json
apps/www/pages/api/registry.json

View File

@@ -2,5 +2,9 @@
"eslint.workingDirectories": [
{ "pattern": "apps/*/" },
{ "pattern": "packages/*/" }
],
"tailwindCSS.experimental.classRegex": [
["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"],
["cn\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"]
]
}

175
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,175 @@
# Contributing
Thanks for your interest in contributing to ui.shadcn.com. We're happy to have you here.
Please take a moment to review this document before submitting your first pull request. We also strongly recommend that you check for open issues and pull requests to see if someone else is working on something similar.
If you need any help, feel free to reach out to [@shadcn](https://twitter.com/shadcn).
## About this repository
This repository is a monorepo.
- We use [pnpm](https://pnpm.io) and [`workspaces`](https://pnpm.io/workspaces) for development.
- We use [Turborepo](https://turbo.build/repo) as our build system.
- We use [changesets](https://github.com/changesets/changesets) for managing releases.
## Structure
This repository is structured as follows:
```
apps
└── www
├── app
├── components
├── content
└── registry
├── default
│ ├── example
│ └── ui
└── new-york
├── example
└── ui
packages
└── cli
```
| Path | Description |
| --------------------- | ---------------------------------------- |
| `apps/www/app` | The Next.js application for the website. |
| `apps/www/components` | The React components for the website. |
| `apps/www/content` | The content for the website. |
| `apps/www/registry` | The registry for the components. |
| `packages/cli` | The `shadcn-ui` package. |
## Development
### Fork this repo
You can fork this repo by clicking the fork button in the top right corner of this page.
### Clone on your local machine
```bash
git clone https://github.com/your-username/ui.git
```
### Navigate to project directory
```bash
cd ui
```
### Create a new Branch
```bash
git checkout -b my-new-branch
```
### Install dependencies
```bash
pnpm install
```
### Run a workspace
You can use the `pnpm --filter=[WORKSPACE]` command to start the development process for a workspace.
#### Examples
1. To run the `ui.shadcn.com` website:
```bash
pnpm --filter=www dev
```
2. To run the `shadcn-ui` package:
```bash
pnpm --filter=shadcn-ui dev
```
## Documentation
The documentation for this project is located in the `www` workspace. You can run the documentation locally by running the following command:
```bash
pnpm --filter=www dev
```
Documentation is written using [MDX](https://mdxjs.com). You can find the documentation files in the `apps/www/content/docs` directory.
## Components
We use a registry system for developing components. You can find the source code for the components under `apps/www/registry`. The components are organized by styles.
```bash
apps
└── www
└── registry
├── default
│ ├── example
│ └── ui
└── new-york
├── example
└── ui
```
When adding or modifying components, please ensure that:
1. You make the changes for every style.
2. You update the documentation.
3. You run `pnpm build:registry` to update the registry.
## Commit Convention
Before you create a Pull Request, please check whether your commits comply with
the commit conventions used in this repository.
When you create a commit we kindly ask you to follow the convention
`category(scope or module): message` in your commit message while using one of
the following categories:
- `feat / feature`: all changes that introduce completely new code or new
features
- `fix`: changes that fix a bug (ideally you will additionally reference an
issue if present)
- `refactor`: any code related change that is not a fix nor a feature
- `docs`: changing existing or creating new documentation (i.e. README, docs for
usage of a lib or cli usage)
- `build`: all changes regarding the build of the software, changes to
dependencies or the addition of new dependencies
- `test`: all changes regarding tests (adding new tests or changing existing
ones)
- `ci`: all changes regarding the configuration of continuous integration (i.e.
github actions, ci system)
- `chore`: all changes to the repository that do not fit into any of the above
categories
e.g. `feat(components): add new prop to the avatar component`
If you are interested in the detailed specification you can visit
https://www.conventionalcommits.org/ or check out the
[Angular Commit Message Guidelines](https://github.com/angular/angular/blob/22b96b9/CONTRIBUTING.md#-commit-message-guidelines).
## Requests for new components
If you have a request for a new component, please open a discussion on GitHub. We'll be happy to help you out.
## CLI
The `shadcn-ui` package is a CLI for adding components to your project. You can find the documentation for the CLI [here](https://ui.shadcn.com/docs/cli).
Any changes to the CLI should be made in the `packages/cli` directory. If you can, it would be great if you could add tests for your changes.
## Testing
Tests are written using [Vitest](https://vitest.dev). You can run all the tests from the root of the repository.
```bash
pnpm test
```
Please ensure that the tests are passing when submitting a pull request. If you're adding new features, please include tests.

View File

@@ -4,42 +4,13 @@ Accessible and customizable components that you can copy and paste into your app
![hero](apps/www/public/og.jpg)
## Roadmap
## Documentation
> **Warning**
> This is work in progress. I'm building this in public. You can follow the progress on Twitter [@shadcn](https://twitter.com/shadcn).
Visit http://ui.shadcn.com/docs to view the documentation.
- [ ] Toggle Group
- [ ] Toolbar
- [x] ~Toast~
- [x] ~Toggle~
- [x] ~Navigation Menu~
- [x] ~Figma~
## Contributing
## Get Started
Starting a new project? Check out the Next.js template.
```bash
npx create-next-app -e https://github.com/shadcn/next-template
```
### Features
- Radix UI Primitives
- Tailwind CSS
- Fonts with `@next/font`
- Icons from [Lucide](https://lucide.dev)
- Dark mode with `next-themes`
- Automatic import sorting with `@ianvs/prettier-plugin-sort-imports`
### Tailwind CSS Features
- Class merging with `tailwind-merge`
- Animation with `tailwindcss-animate`
- Conditional classes with `clsx`
- Variants with `class-variance-authority`
- Automatic class sorting with `eslint-plugin-tailwindcss`
Please read the [contributing guide](/CONTRIBUTING.md).
## License

View File

@@ -1,4 +1,4 @@
# -----------------------------------------------------------------------------
# App
# -----------------------------------------------------------------------------
NEXT_PUBLIC_APP_URL=http://localhost:3000
NEXT_PUBLIC_APP_URL=http://localhost:3001

View File

@@ -2,4 +2,6 @@ dist
node_modules
.next
build
.contentlayer
.contentlayer
__registry__/index.tsx
app/examples/mail/components/mail.tsx

View File

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

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

@@ -1,25 +0,0 @@
import { allDocs } from "contentlayer/generated"
import MdxHead from "@/components/mdx-head"
interface HeadProps {
params: {
slug: string[]
}
}
export default function Head({ params }: HeadProps) {
const slug = params?.slug?.join("/") || ""
const doc = allDocs.find((doc) => doc.slugAsParams === slug)
if (!doc) {
return null
}
return (
<MdxHead
params={params}
og={{ heading: doc.title, type: doc.title, mode: "light" }}
/>
)
}

View File

@@ -2,15 +2,19 @@ import { notFound } from "next/navigation"
import { allDocs } from "contentlayer/generated"
import "@/styles/mdx.css"
import type { Metadata } from "next"
import Link from "next/link"
import { ChevronRightIcon, ExternalLinkIcon } from "@radix-ui/react-icons"
import Balancer from "react-wrap-balancer"
import { siteConfig } from "@/config/site"
import { getTableOfContents } from "@/lib/toc"
import { Icons } from "@/components/icons"
import { Mdx } from "@/components/mdx"
import { DocsPageHeader } from "@/components/page-header"
import { absoluteUrl, cn } from "@/lib/utils"
import { Mdx } from "@/components/mdx-components"
import { DocsPager } from "@/components/pager"
import { DashboardTableOfContents } from "@/components/toc"
import { Separator } from "@/components/ui/separator"
import { badgeVariants } from "@/registry/new-york/ui/badge"
import { ScrollArea } from "@/registry/new-york/ui/scroll-area"
interface DocPageProps {
params: {
@@ -18,6 +22,53 @@ interface DocPageProps {
}
}
async function getDocFromParams({ params }: DocPageProps) {
const slug = params.slug?.join("/") || ""
const doc = allDocs.find((doc) => doc.slugAsParams === slug)
if (!doc) {
return null
}
return doc
}
export async function generateMetadata({
params,
}: DocPageProps): Promise<Metadata> {
const doc = await getDocFromParams({ params })
if (!doc) {
return {}
}
return {
title: doc.title,
description: doc.description,
openGraph: {
title: doc.title,
description: doc.description,
type: "article",
url: absoluteUrl(doc.slug),
images: [
{
url: siteConfig.ogImage,
width: 1200,
height: 630,
alt: siteConfig.name,
},
],
},
twitter: {
card: "summary_large_image",
title: doc.title,
description: doc.description,
images: [siteConfig.ogImage],
creator: "@shadcn",
},
}
}
export async function generateStaticParams(): Promise<
DocPageProps["params"][]
> {
@@ -27,8 +78,7 @@ export async function generateStaticParams(): Promise<
}
export default async function DocPage({ params }: DocPageProps) {
const slug = params?.slug?.join("/") || ""
const doc = allDocs.find((doc) => doc.slugAsParams === slug)
const doc = await getDocFromParams({ params })
if (!doc) {
notFound()
@@ -37,44 +87,67 @@ export default async function DocPage({ params }: DocPageProps) {
const toc = await getTableOfContents(doc.body.raw)
return (
<main className="relative py-6 lg:gap-10 lg:py-10 xl:grid xl:grid-cols-[1fr_300px]">
<main className="relative py-6 lg:gap-10 lg:py-8 xl:grid xl:grid-cols-[1fr_300px]">
<div className="mx-auto w-full min-w-0">
<DocsPageHeader heading={doc.title} text={doc.description}>
{doc.radix ? (
<div className="flex items-center space-x-2 pt-4">
{doc.radix?.link && (
<Link
href={doc.radix.link}
target="_blank"
rel="noreferrer"
className="inline-flex items-center rounded-full bg-slate-100 px-2.5 py-1 text-xs font-semibold text-slate-900 transition-colors hover:bg-slate-700 hover:text-slate-50"
>
<Icons.radix className="mr-1 h-3 w-3" />
Radix UI
</Link>
)}
{doc.radix?.api && (
<Link
href={doc.radix.api}
target="_blank"
rel="noreferrer"
className="inline-flex items-center rounded-full bg-slate-100 px-2.5 py-1 text-xs font-semibold text-slate-900 transition-colors hover:bg-slate-700 hover:text-slate-50"
>
API Reference
</Link>
)}
</div>
) : null}
</DocsPageHeader>
<Mdx code={doc.body.code} />
<Separator className="my-4 md:my-6" />
<div className="mb-4 flex items-center space-x-1 text-sm text-muted-foreground">
<div className="overflow-hidden text-ellipsis whitespace-nowrap">
Docs
</div>
<ChevronRightIcon className="h-4 w-4" />
<div className="font-medium text-foreground">{doc.title}</div>
</div>
<div className="space-y-2">
<h1 className={cn("scroll-m-20 text-4xl font-bold tracking-tight")}>
{doc.title}
</h1>
{doc.description && (
<p className="text-lg text-muted-foreground">
<Balancer>{doc.description}</Balancer>
</p>
)}
</div>
{doc.links ? (
<div className="flex items-center space-x-2 pt-4">
{doc.links?.doc && (
<Link
href={doc.links.doc}
target="_blank"
rel="noreferrer"
className={cn(badgeVariants({ variant: "secondary" }), "gap-1")}
>
Docs
<ExternalLinkIcon className="h-3 w-3" />
</Link>
)}
{doc.links?.api && (
<Link
href={doc.links.api}
target="_blank"
rel="noreferrer"
className={cn(badgeVariants({ variant: "secondary" }), "gap-1")}
>
API Reference
<ExternalLinkIcon className="h-3 w-3" />
</Link>
)}
</div>
) : null}
<div className="pb-12 pt-8">
<Mdx code={doc.body.code} />
</div>
<DocsPager doc={doc} />
</div>
<div className="hidden text-sm xl:block">
<div className="sticky top-16 -mt-10 max-h-[calc(var(--vh)-4rem)] overflow-y-auto pt-10">
<DashboardTableOfContents toc={toc} />
{doc.toc && (
<div className="hidden text-sm xl:block">
<div className="sticky top-16 -mt-10 pt-4">
<ScrollArea className="pb-10">
<div className="sticky top-16 -mt-10 h-[calc(100vh-3.5rem)] py-12">
<DashboardTableOfContents toc={toc} />
</div>
</ScrollArea>
</div>
</div>
</div>
)}
</main>
)
}

View File

@@ -1,6 +1,6 @@
import { docsConfig } from "@/config/docs"
import { DocsSidebarNav } from "@/components/sidebar-nav"
import { ScrollArea } from "@/components/ui/scroll-area"
import { ScrollArea } from "@/registry/new-york/ui/scroll-area"
interface DocsLayoutProps {
children: React.ReactNode
@@ -8,13 +8,15 @@ interface DocsLayoutProps {
export default function DocsLayout({ children }: DocsLayoutProps) {
return (
<div className="flex-1 items-start md:grid md:grid-cols-[220px_minmax(0,1fr)] md:gap-6 lg:grid-cols-[240px_minmax(0,1fr)] lg:gap-10">
<aside className="fixed top-14 z-30 hidden h-[calc(100vh-3.5rem)] w-full shrink-0 overflow-y-auto border-r border-r-slate-100 dark:border-r-slate-700 md:sticky md:block">
<ScrollArea className="pr-6 lg:py-10">
<DocsSidebarNav items={docsConfig.sidebarNav} />
</ScrollArea>
</aside>
{children}
<div className="border-b">
<div className="container flex-1 items-start md:grid md:grid-cols-[220px_minmax(0,1fr)] md:gap-6 lg:grid-cols-[240px_minmax(0,1fr)] lg:gap-10">
<aside className="fixed top-14 z-30 -ml-2 hidden h-[calc(100vh-3.5rem)] w-full shrink-0 md:sticky md:block">
<ScrollArea className="h-full py-6 pr-6 lg:py-8">
<DocsSidebarNav items={docsConfig.sidebarNav} />
</ScrollArea>
</aside>
{children}
</div>
</div>
)
}

View File

@@ -0,0 +1,71 @@
"use client"
import * as React from "react"
import { cn } from "@/lib/utils"
import { Icons } from "@/components/icons"
import { Button } from "@/registry/new-york/ui/button"
import { Input } from "@/registry/new-york/ui/input"
import { Label } from "@/registry/new-york/ui/label"
interface UserAuthFormProps extends React.HTMLAttributes<HTMLDivElement> {}
export function UserAuthForm({ className, ...props }: UserAuthFormProps) {
const [isLoading, setIsLoading] = React.useState<boolean>(false)
async function onSubmit(event: React.SyntheticEvent) {
event.preventDefault()
setIsLoading(true)
setTimeout(() => {
setIsLoading(false)
}, 3000)
}
return (
<div className={cn("grid gap-6", className)} {...props}>
<form onSubmit={onSubmit}>
<div className="grid gap-2">
<div className="grid gap-1">
<Label className="sr-only" htmlFor="email">
Email
</Label>
<Input
id="email"
placeholder="name@example.com"
type="email"
autoCapitalize="none"
autoComplete="email"
autoCorrect="off"
disabled={isLoading}
/>
</div>
<Button disabled={isLoading}>
{isLoading && (
<Icons.spinner className="mr-2 h-4 w-4 animate-spin" />
)}
Sign In with Email
</Button>
</div>
</form>
<div className="relative">
<div className="absolute inset-0 flex items-center">
<span className="w-full border-t" />
</div>
<div className="relative flex justify-center text-xs uppercase">
<span className="bg-background px-2 text-muted-foreground">
Or continue with
</span>
</div>
</div>
<Button variant="outline" type="button" disabled={isLoading}>
{isLoading ? (
<Icons.spinner className="mr-2 h-4 w-4 animate-spin" />
) : (
<Icons.gitHub className="mr-2 h-4 w-4" />
)}{" "}
GitHub
</Button>
</div>
)
}

View File

@@ -0,0 +1,104 @@
import { Metadata } from "next"
import Image from "next/image"
import Link from "next/link"
import { cn } from "@/lib/utils"
import { buttonVariants } from "@/registry/new-york/ui/button"
import { UserAuthForm } from "@/app/examples/authentication/components/user-auth-form"
export const metadata: Metadata = {
title: "Authentication",
description: "Authentication forms built using the components.",
}
export default function AuthenticationPage() {
return (
<>
<div className="md:hidden">
<Image
src="/examples/authentication-light.png"
width={1280}
height={843}
alt="Authentication"
className="block dark:hidden"
/>
<Image
src="/examples/authentication-dark.png"
width={1280}
height={843}
alt="Authentication"
className="hidden dark:block"
/>
</div>
<div className="container relative hidden h-[800px] flex-col items-center justify-center md:grid lg:max-w-none lg:grid-cols-2 lg:px-0">
<Link
href="/examples/authentication"
className={cn(
buttonVariants({ variant: "ghost" }),
"absolute right-4 top-4 md:right-8 md:top-8"
)}
>
Login
</Link>
<div className="relative hidden h-full flex-col bg-muted p-10 text-white lg:flex dark:border-r">
<div className="absolute inset-0 bg-zinc-900" />
<div className="relative z-20 flex items-center text-lg font-medium">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="mr-2 h-6 w-6"
>
<path d="M15 6v12a3 3 0 1 0 3-3H6a3 3 0 1 0 3 3V6a3 3 0 1 0-3 3h12a3 3 0 1 0-3-3" />
</svg>
Acme Inc
</div>
<div className="relative z-20 mt-auto">
<blockquote className="space-y-2">
<p className="text-lg">
&ldquo;This library has saved me countless hours of work and
helped me deliver stunning designs to my clients faster than
ever before.&rdquo;
</p>
<footer className="text-sm">Sofia Davis</footer>
</blockquote>
</div>
</div>
<div className="lg:p-8">
<div className="mx-auto flex w-full flex-col justify-center space-y-6 sm:w-[350px]">
<div className="flex flex-col space-y-2 text-center">
<h1 className="text-2xl font-semibold tracking-tight">
Create an account
</h1>
<p className="text-sm text-muted-foreground">
Enter your email below to create your account
</p>
</div>
<UserAuthForm />
<p className="px-8 text-center text-sm text-muted-foreground">
By clicking continue, you agree to our{" "}
<Link
href="/terms"
className="underline underline-offset-4 hover:text-primary"
>
Terms of Service
</Link>{" "}
and{" "}
<Link
href="/privacy"
className="underline underline-offset-4 hover:text-primary"
>
Privacy Policy
</Link>
.
</p>
</div>
</div>
</div>
</>
)
}

View File

@@ -0,0 +1,60 @@
"use client"
import { Button } from "@/registry/new-york/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/new-york/ui/card"
import { Label } from "@/registry/new-york/ui/label"
import { Switch } from "@/registry/new-york/ui/switch"
export function DemoCookieSettings() {
return (
<Card>
<CardHeader>
<CardTitle>Cookie Settings</CardTitle>
<CardDescription>Manage your cookie settings here.</CardDescription>
</CardHeader>
<CardContent className="grid gap-6">
<div className="flex items-center justify-between space-x-2">
<Label htmlFor="necessary" className="flex flex-col space-y-1">
<span>Strictly Necessary</span>
<span className="font-normal leading-snug text-muted-foreground">
These cookies are essential in order to use the website and use
its features.
</span>
</Label>
<Switch id="necessary" defaultChecked />
</div>
<div className="flex items-center justify-between space-x-2">
<Label htmlFor="functional" className="flex flex-col space-y-1">
<span>Functional Cookies</span>
<span className="font-normal leading-snug text-muted-foreground">
These cookies allow the website to provide personalized
functionality.
</span>
</Label>
<Switch id="functional" />
</div>
<div className="flex items-center justify-between space-x-2">
<Label htmlFor="performance" className="flex flex-col space-y-1">
<span>Performance Cookies</span>
<span className="font-normal leading-snug text-muted-foreground">
These cookies help to improve the performance of the website.
</span>
</Label>
<Switch id="performance" />
</div>
</CardContent>
<CardFooter>
<Button variant="outline" className="w-full">
Save preferences
</Button>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,60 @@
"use client"
import { Icons } from "@/components/icons"
import { Button } from "@/registry/new-york/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/new-york/ui/card"
import { Input } from "@/registry/new-york/ui/input"
import { Label } from "@/registry/new-york/ui/label"
export function DemoCreateAccount() {
return (
<Card>
<CardHeader className="space-y-1">
<CardTitle className="text-2xl">Create an account</CardTitle>
<CardDescription>
Enter your email below to create your account
</CardDescription>
</CardHeader>
<CardContent className="grid gap-4">
<div className="grid grid-cols-2 gap-6">
<Button variant="outline">
<Icons.gitHub className="mr-2 h-4 w-4" />
Github
</Button>
<Button variant="outline">
<Icons.google className="mr-2 h-4 w-4" />
Google
</Button>
</div>
<div className="relative">
<div className="absolute inset-0 flex items-center">
<span className="w-full border-t" />
</div>
<div className="relative flex justify-center text-xs uppercase">
<span className="bg-background px-2 text-muted-foreground">
Or continue with
</span>
</div>
</div>
<div className="grid gap-2">
<Label htmlFor="email">Email</Label>
<Input id="email" type="email" placeholder="m@example.com" />
</div>
<div className="grid gap-2">
<Label htmlFor="password">Password</Label>
<Input id="password" type="password" />
</div>
</CardContent>
<CardFooter>
<Button className="w-full">Create account</Button>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,18 @@
import DatePickerWithRange from "@/registry/default/example/date-picker-with-range"
import { Card, CardContent } from "@/registry/new-york/ui/card"
import { Label } from "@/registry/new-york/ui/label"
export function DemoDatePicker() {
return (
<Card>
<CardContent className="pt-6">
<div className="space-y-2">
<Label htmlFor="date" className="shrink-0">
Pick a date
</Label>
<DatePickerWithRange className="[&>button]:w-[260px]" />
</div>
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,86 @@
import {
ChevronDownIcon,
CircleIcon,
PlusIcon,
StarIcon,
} from "@radix-ui/react-icons"
import { Button } from "@/registry/new-york/ui/button"
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/registry/new-york/ui/card"
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/registry/new-york/ui/dropdown-menu"
import { Separator } from "@/registry/new-york/ui/separator"
export function DemoGithub() {
return (
<Card>
<CardHeader className="grid grid-cols-[1fr_110px] items-start gap-4 space-y-0">
<div className="space-y-1">
<CardTitle>shadcn/ui</CardTitle>
<CardDescription>
Beautifully designed components that you can copy and paste into
your apps. Accessible. Customizable. Open Source.
</CardDescription>
</div>
<div className="flex items-center space-x-1 rounded-md bg-secondary text-secondary-foreground">
<Button variant="secondary" className="px-3 shadow-none">
<StarIcon className="mr-2 h-4 w-4" />
Star
</Button>
<Separator orientation="vertical" className="h-[20px]" />
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="secondary" className="px-2 shadow-none">
<ChevronDownIcon className="h-4 w-4 text-secondary-foreground" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
align="end"
alignOffset={-5}
className="w-[200px]"
forceMount
>
<DropdownMenuLabel>Suggested Lists</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuCheckboxItem checked>
Future Ideas
</DropdownMenuCheckboxItem>
<DropdownMenuCheckboxItem>My Stack</DropdownMenuCheckboxItem>
<DropdownMenuCheckboxItem>Inspiration</DropdownMenuCheckboxItem>
<DropdownMenuSeparator />
<DropdownMenuItem>
<PlusIcon className="mr-2 h-4 w-4" /> Create List
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</CardHeader>
<CardContent>
<div className="flex space-x-4 text-sm text-muted-foreground">
<div className="flex items-center">
<CircleIcon className="mr-1 h-3 w-3 fill-sky-400 text-sky-400" />
TypeScript
</div>
<div className="flex items-center">
<StarIcon className="mr-1 h-3 w-3" />
20k
</div>
<div>Updated April 2023</div>
</div>
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,51 @@
import { BellIcon, EyeNoneIcon, PersonIcon } from "@radix-ui/react-icons"
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/registry/new-york/ui/card"
export function DemoNotifications() {
return (
<Card>
<CardHeader className="pb-3">
<CardTitle>Notifications</CardTitle>
<CardDescription>
Choose what you want to be notified about.
</CardDescription>
</CardHeader>
<CardContent className="grid gap-1">
<div className="-mx-2 flex items-start space-x-4 rounded-md p-2 transition-all hover:bg-accent hover:text-accent-foreground">
<BellIcon className="mt-px h-5 w-5" />
<div className="space-y-1">
<p className="text-sm font-medium leading-none">Everything</p>
<p className="text-sm text-muted-foreground">
Email digest, mentions & all activity.
</p>
</div>
</div>
<div className="-mx-2 flex items-start space-x-4 rounded-md bg-accent p-2 text-accent-foreground transition-all">
<PersonIcon className="mt-px h-5 w-5" />
<div className="space-y-1">
<p className="text-sm font-medium leading-none">Available</p>
<p className="text-sm text-muted-foreground">
Only mentions and comments.
</p>
</div>
</div>
<div className="-mx-2 flex items-start space-x-4 rounded-md p-2 transition-all hover:bg-accent hover:text-accent-foreground">
<EyeNoneIcon className="mt-px h-5 w-5" />
<div className="space-y-1">
<p className="text-sm font-medium leading-none">Ignoring</p>
<p className="text-sm text-muted-foreground">
Turn off all notifications.
</p>
</div>
</div>
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,137 @@
import { Icons } from "@/components/icons"
import { Button } from "@/registry/new-york/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/new-york/ui/card"
import { Input } from "@/registry/new-york/ui/input"
import { Label } from "@/registry/new-york/ui/label"
import { RadioGroup, RadioGroupItem } from "@/registry/new-york/ui/radio-group"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/registry/new-york/ui/select"
export function DemoPaymentMethod() {
return (
<Card>
<CardHeader>
<CardTitle>Payment Method</CardTitle>
<CardDescription>
Add a new payment method to your account.
</CardDescription>
</CardHeader>
<CardContent className="grid gap-6">
<RadioGroup defaultValue="card" className="grid grid-cols-3 gap-4">
<div>
<RadioGroupItem value="card" id="card" className="peer sr-only" />
<Label
htmlFor="card"
className="flex flex-col items-center justify-between rounded-md border-2 border-muted bg-popover p-4 hover:bg-accent hover:text-accent-foreground peer-data-[state=checked]:border-primary [&:has([data-state=checked])]:border-primary"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
className="mb-3 h-6 w-6"
>
<rect width="20" height="14" x="2" y="5" rx="2" />
<path d="M2 10h20" />
</svg>
Card
</Label>
</div>
<div>
<RadioGroupItem
value="paypal"
id="paypal"
className="peer sr-only"
/>
<Label
htmlFor="paypal"
className="flex flex-col items-center justify-between rounded-md border-2 border-muted bg-popover p-4 hover:bg-accent hover:text-accent-foreground peer-data-[state=checked]:border-primary [&:has([data-state=checked])]:border-primary"
>
<Icons.paypal className="mb-3 h-6 w-6" />
Paypal
</Label>
</div>
<div>
<RadioGroupItem value="apple" id="apple" className="peer sr-only" />
<Label
htmlFor="apple"
className="flex flex-col items-center justify-between rounded-md border-2 border-muted bg-popover p-4 hover:bg-accent hover:text-accent-foreground peer-data-[state=checked]:border-primary [&:has([data-state=checked])]:border-primary"
>
<Icons.apple className="mb-3 h-6 w-6" />
Apple
</Label>
</div>
</RadioGroup>
<div className="grid gap-2">
<Label htmlFor="name">Name</Label>
<Input id="name" placeholder="First Last" />
</div>
<div className="grid gap-2">
<Label htmlFor="number">Card number</Label>
<Input id="number" placeholder="" />
</div>
<div className="grid grid-cols-3 gap-4">
<div className="grid gap-2">
<Label htmlFor="month">Expires</Label>
<Select>
<SelectTrigger id="month">
<SelectValue placeholder="Month" />
</SelectTrigger>
<SelectContent>
<SelectItem value="1">January</SelectItem>
<SelectItem value="2">February</SelectItem>
<SelectItem value="3">March</SelectItem>
<SelectItem value="4">April</SelectItem>
<SelectItem value="5">May</SelectItem>
<SelectItem value="6">June</SelectItem>
<SelectItem value="7">July</SelectItem>
<SelectItem value="8">August</SelectItem>
<SelectItem value="9">September</SelectItem>
<SelectItem value="10">October</SelectItem>
<SelectItem value="11">November</SelectItem>
<SelectItem value="12">December</SelectItem>
</SelectContent>
</Select>
</div>
<div className="grid gap-2">
<Label htmlFor="year">Year</Label>
<Select>
<SelectTrigger id="year">
<SelectValue placeholder="Year" />
</SelectTrigger>
<SelectContent>
{Array.from({ length: 10 }, (_, i) => (
<SelectItem key={i} value={`${new Date().getFullYear() + i}`}>
{new Date().getFullYear() + i}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="grid gap-2">
<Label htmlFor="cvc">CVC</Label>
<Input id="cvc" placeholder="CVC" />
</div>
</div>
</CardContent>
<CardFooter>
<Button className="w-full">Continue</Button>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,85 @@
"use client"
import { Button } from "@/registry/new-york/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/new-york/ui/card"
import { Input } from "@/registry/new-york/ui/input"
import { Label } from "@/registry/new-york/ui/label"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/registry/new-york/ui/select"
import { Textarea } from "@/registry/new-york/ui/textarea"
export function DemoReportAnIssue() {
return (
<Card>
<CardHeader>
<CardTitle>Report an issue</CardTitle>
<CardDescription>
What area are you having problems with?
</CardDescription>
</CardHeader>
<CardContent className="grid gap-6">
<div className="grid grid-cols-2 gap-4">
<div className="grid gap-2">
<Label htmlFor="area">Area</Label>
<Select defaultValue="billing">
<SelectTrigger id="area">
<SelectValue placeholder="Select" />
</SelectTrigger>
<SelectContent>
<SelectItem value="team">Team</SelectItem>
<SelectItem value="billing">Billing</SelectItem>
<SelectItem value="account">Account</SelectItem>
<SelectItem value="deployments">Deployments</SelectItem>
<SelectItem value="support">Support</SelectItem>
</SelectContent>
</Select>
</div>
<div className="grid gap-2">
<Label htmlFor="security-level">Security Level</Label>
<Select defaultValue="2">
<SelectTrigger
id="security-level"
className="line-clamp-1 w-[160px] truncate"
>
<SelectValue placeholder="Select level" />
</SelectTrigger>
<SelectContent>
<SelectItem value="1">Severity 1 (Highest)</SelectItem>
<SelectItem value="2">Severity 2</SelectItem>
<SelectItem value="3">Severity 3</SelectItem>
<SelectItem value="4">Severity 4 (Lowest)</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="grid gap-2">
<Label htmlFor="subject">Subject</Label>
<Input id="subject" placeholder="I need help with..." />
</div>
<div className="grid gap-2">
<Label htmlFor="description">Description</Label>
<Textarea
id="description"
placeholder="Please include all information relevant to your issue."
/>
</div>
</CardContent>
<CardFooter className="justify-between space-x-2">
<Button variant="ghost">Cancel</Button>
<Button>Submit</Button>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,120 @@
"use client"
import {
Avatar,
AvatarFallback,
AvatarImage,
} from "@/registry/new-york/ui/avatar"
import { Button } from "@/registry/new-york/ui/button"
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/registry/new-york/ui/card"
import { Input } from "@/registry/new-york/ui/input"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/registry/new-york/ui/select"
import { Separator } from "@/registry/new-york/ui/separator"
export function DemoShareDocument() {
return (
<Card>
<CardHeader>
<CardTitle>Share this document</CardTitle>
<CardDescription>
Anyone with the link can view this document.
</CardDescription>
</CardHeader>
<CardContent>
<div className="flex space-x-2">
<Input value="http://example.com/link/to/document" readOnly />
<Button variant="secondary" className="shrink-0">
Copy Link
</Button>
</div>
<Separator className="my-4" />
<div className="space-y-4">
<h4 className="text-sm font-medium">People with access</h4>
<div className="grid gap-6">
<div className="flex items-center justify-between space-x-4">
<div className="flex items-center space-x-4">
<Avatar>
<AvatarImage src="/avatars/03.png" />
<AvatarFallback>OM</AvatarFallback>
</Avatar>
<div>
<p className="text-sm font-medium leading-none">
Olivia Martin
</p>
<p className="text-sm text-muted-foreground">m@example.com</p>
</div>
</div>
<Select defaultValue="edit">
<SelectTrigger className="ml-auto w-[110px]">
<SelectValue placeholder="Select" />
</SelectTrigger>
<SelectContent>
<SelectItem value="edit">Can edit</SelectItem>
<SelectItem value="view">Can view</SelectItem>
</SelectContent>
</Select>
</div>
<div className="flex items-center justify-between space-x-4">
<div className="flex items-center space-x-4">
<Avatar>
<AvatarImage src="/avatars/05.png" />
<AvatarFallback>IN</AvatarFallback>
</Avatar>
<div>
<p className="text-sm font-medium leading-none">
Isabella Nguyen
</p>
<p className="text-sm text-muted-foreground">b@example.com</p>
</div>
</div>
<Select defaultValue="view">
<SelectTrigger className="ml-auto w-[110px]">
<SelectValue placeholder="Select" />
</SelectTrigger>
<SelectContent>
<SelectItem value="edit">Can edit</SelectItem>
<SelectItem value="view">Can view</SelectItem>
</SelectContent>
</Select>
</div>
<div className="flex items-center justify-between space-x-4">
<div className="flex items-center space-x-4">
<Avatar>
<AvatarImage src="/avatars/01.png" />
<AvatarFallback>SD</AvatarFallback>
</Avatar>
<div>
<p className="text-sm font-medium leading-none">
Sofia Davis
</p>
<p className="text-sm text-muted-foreground">p@example.com</p>
</div>
</div>
<Select defaultValue="view">
<SelectTrigger className="ml-auto w-[110px]">
<SelectValue placeholder="Select" />
</SelectTrigger>
<SelectContent>
<SelectItem value="edit">Can edit</SelectItem>
<SelectItem value="view">Can view</SelectItem>
</SelectContent>
</Select>
</div>
</div>
</div>
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,151 @@
import { ChevronDownIcon } from "@radix-ui/react-icons"
import {
Avatar,
AvatarFallback,
AvatarImage,
} from "@/registry/new-york/ui/avatar"
import { Button } from "@/registry/new-york/ui/button"
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/registry/new-york/ui/card"
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@/registry/new-york/ui/command"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/registry/new-york/ui/popover"
export function DemoTeamMembers() {
return (
<Card>
<CardHeader>
<CardTitle>Team Members</CardTitle>
<CardDescription>
Invite your team members to collaborate.
</CardDescription>
</CardHeader>
<CardContent className="grid gap-6">
<div className="flex items-center justify-between space-x-4">
<div className="flex items-center space-x-4">
<Avatar>
<AvatarImage src="/avatars/01.png" />
<AvatarFallback>OM</AvatarFallback>
</Avatar>
<div>
<p className="text-sm font-medium leading-none">Sofia Davis</p>
<p className="text-sm text-muted-foreground">m@example.com</p>
</div>
</div>
<Popover>
<PopoverTrigger asChild>
<Button variant="outline" className="ml-auto">
Owner{" "}
<ChevronDownIcon className="ml-2 h-4 w-4 text-muted-foreground" />
</Button>
</PopoverTrigger>
<PopoverContent className="p-0" align="end">
<Command>
<CommandInput placeholder="Select new role..." />
<CommandList>
<CommandEmpty>No roles found.</CommandEmpty>
<CommandGroup>
<CommandItem className="teamaspace-y-1 flex flex-col items-start px-4 py-2">
<p>Viewer</p>
<p className="text-sm text-muted-foreground">
Can view and comment.
</p>
</CommandItem>
<CommandItem className="teamaspace-y-1 flex flex-col items-start px-4 py-2">
<p>Developer</p>
<p className="text-sm text-muted-foreground">
Can view, comment and edit.
</p>
</CommandItem>
<CommandItem className="teamaspace-y-1 flex flex-col items-start px-4 py-2">
<p>Billing</p>
<p className="text-sm text-muted-foreground">
Can view, comment and manage billing.
</p>
</CommandItem>
<CommandItem className="teamaspace-y-1 flex flex-col items-start px-4 py-2">
<p>Owner</p>
<p className="text-sm text-muted-foreground">
Admin-level access to all resources.
</p>
</CommandItem>
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
</div>
<div className="flex items-center justify-between space-x-4">
<div className="flex items-center space-x-4">
<Avatar>
<AvatarImage src="/avatars/02.png" />
<AvatarFallback>JL</AvatarFallback>
</Avatar>
<div>
<p className="text-sm font-medium leading-none">Jackson Lee</p>
<p className="text-sm text-muted-foreground">p@example.com</p>
</div>
</div>
<Popover>
<PopoverTrigger asChild>
<Button variant="outline" className="ml-auto">
Member{" "}
<ChevronDownIcon className="ml-2 h-4 w-4 text-muted-foreground" />
</Button>
</PopoverTrigger>
<PopoverContent className="p-0" align="end">
<Command>
<CommandInput placeholder="Select new role..." />
<CommandList>
<CommandEmpty>No roles found.</CommandEmpty>
<CommandGroup className="p-1.5">
<CommandItem className="teamaspace-y-1 flex flex-col items-start px-4 py-2">
<p>Viewer</p>
<p className="text-sm text-muted-foreground">
Can view and comment.
</p>
</CommandItem>
<CommandItem className="teamaspace-y-1 flex flex-col items-start px-4 py-2">
<p>Developer</p>
<p className="text-sm text-muted-foreground">
Can view, comment and edit.
</p>
</CommandItem>
<CommandItem className="teamaspace-y-1 flex flex-col items-start px-4 py-2">
<p>Billing</p>
<p className="text-sm text-muted-foreground">
Can view, comment and manage billing.
</p>
</CommandItem>
<CommandItem className="teamaspace-y-1 flex flex-col items-start px-4 py-2">
<p>Owner</p>
<p className="text-sm text-muted-foreground">
Admin-level access to all resources.
</p>
</CommandItem>
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
</div>
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,92 @@
import { Metadata } from "next"
import Image from "next/image"
import { cn } from "@/lib/utils"
import { DemoCookieSettings } from "./components/cookie-settings"
import { DemoCreateAccount } from "./components/create-account"
import { DemoDatePicker } from "./components/date-picker"
import { DemoGithub } from "./components/github-card"
import { DemoNotifications } from "./components/notifications"
import { DemoPaymentMethod } from "./components/payment-method"
import { DemoReportAnIssue } from "./components/report-an-issue"
import { DemoShareDocument } from "./components/share-document"
import { DemoTeamMembers } from "./components/team-members"
export const metadata: Metadata = {
title: "Cards",
description: "Examples of cards built using the components.",
}
function DemoContainer({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) {
return (
<div
className={cn(
"flex items-center justify-center [&>div]:w-full",
className
)}
{...props}
/>
)
}
export default function CardsPage() {
return (
<>
<div className="md:hidden">
<Image
src="/examples/cards-light.png"
width={1280}
height={1214}
alt="Cards"
className="block dark:hidden"
/>
<Image
src="/examples/cards-dark.png"
width={1280}
height={1214}
alt="Cards"
className="hidden dark:block"
/>
</div>
<div className="hidden items-start justify-center gap-6 rounded-lg p-8 md:grid lg:grid-cols-2 xl:grid-cols-3">
<div className="col-span-2 grid items-start gap-6 lg:col-span-1">
<DemoContainer>
<DemoCreateAccount />
</DemoContainer>
<DemoContainer>
<DemoPaymentMethod />
</DemoContainer>
</div>
<div className="col-span-2 grid items-start gap-6 lg:col-span-1">
<DemoContainer>
<DemoTeamMembers />
</DemoContainer>
<DemoContainer>
<DemoShareDocument />
</DemoContainer>
<DemoContainer>
<DemoDatePicker />
</DemoContainer>
<DemoContainer>
<DemoNotifications />
</DemoContainer>
</div>
<div className="col-span-2 grid items-start gap-6 lg:col-span-2 lg:grid-cols-2 xl:col-span-1 xl:grid-cols-1">
<DemoContainer>
<DemoReportAnIssue />
</DemoContainer>
<DemoContainer>
<DemoGithub />
</DemoContainer>
<DemoContainer>
<DemoCookieSettings />
</DemoContainer>
</div>
</div>
</>
)
}

View File

@@ -0,0 +1,65 @@
"use client"
import * as React from "react"
import { CalendarIcon } from "@radix-ui/react-icons"
import { addDays, format } from "date-fns"
import { DateRange } from "react-day-picker"
import { cn } from "@/lib/utils"
import { Button } from "@/registry/new-york/ui/button"
import { Calendar } from "@/registry/new-york/ui/calendar"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/registry/new-york/ui/popover"
export function CalendarDateRangePicker({
className,
}: React.HTMLAttributes<HTMLDivElement>) {
const [date, setDate] = React.useState<DateRange | undefined>({
from: new Date(2023, 0, 20),
to: addDays(new Date(2023, 0, 20), 20),
})
return (
<div className={cn("grid gap-2", className)}>
<Popover>
<PopoverTrigger asChild>
<Button
id="date"
variant={"outline"}
className={cn(
"w-[260px] justify-start text-left font-normal",
!date && "text-muted-foreground"
)}
>
<CalendarIcon className="mr-2 h-4 w-4" />
{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>
</div>
)
}

View File

@@ -0,0 +1,40 @@
import Link from "next/link"
import { cn } from "@/lib/utils"
export function MainNav({
className,
...props
}: React.HTMLAttributes<HTMLElement>) {
return (
<nav
className={cn("flex items-center space-x-4 lg:space-x-6", className)}
{...props}
>
<Link
href="/examples/dashboard"
className="text-sm font-medium transition-colors hover:text-primary"
>
Overview
</Link>
<Link
href="/examples/dashboard"
className="text-sm font-medium text-muted-foreground transition-colors hover:text-primary"
>
Customers
</Link>
<Link
href="/examples/dashboard"
className="text-sm font-medium text-muted-foreground transition-colors hover:text-primary"
>
Products
</Link>
<Link
href="/examples/dashboard"
className="text-sm font-medium text-muted-foreground transition-colors hover:text-primary"
>
Settings
</Link>
</nav>
)
}

View File

@@ -0,0 +1,83 @@
"use client"
import { Bar, BarChart, ResponsiveContainer, XAxis, YAxis } from "recharts"
const data = [
{
name: "Jan",
total: Math.floor(Math.random() * 5000) + 1000,
},
{
name: "Feb",
total: Math.floor(Math.random() * 5000) + 1000,
},
{
name: "Mar",
total: Math.floor(Math.random() * 5000) + 1000,
},
{
name: "Apr",
total: Math.floor(Math.random() * 5000) + 1000,
},
{
name: "May",
total: Math.floor(Math.random() * 5000) + 1000,
},
{
name: "Jun",
total: Math.floor(Math.random() * 5000) + 1000,
},
{
name: "Jul",
total: Math.floor(Math.random() * 5000) + 1000,
},
{
name: "Aug",
total: Math.floor(Math.random() * 5000) + 1000,
},
{
name: "Sep",
total: Math.floor(Math.random() * 5000) + 1000,
},
{
name: "Oct",
total: Math.floor(Math.random() * 5000) + 1000,
},
{
name: "Nov",
total: Math.floor(Math.random() * 5000) + 1000,
},
{
name: "Dec",
total: Math.floor(Math.random() * 5000) + 1000,
},
]
export function Overview() {
return (
<ResponsiveContainer width="100%" height={350}>
<BarChart data={data}>
<XAxis
dataKey="name"
stroke="#888888"
fontSize={12}
tickLine={false}
axisLine={false}
/>
<YAxis
stroke="#888888"
fontSize={12}
tickLine={false}
axisLine={false}
tickFormatter={(value) => `$${value}`}
/>
<Bar
dataKey="total"
fill="currentColor"
radius={[4, 4, 0, 0]}
className="fill-primary"
/>
</BarChart>
</ResponsiveContainer>
)
}

View File

@@ -0,0 +1,71 @@
import {
Avatar,
AvatarFallback,
AvatarImage,
} from "@/registry/new-york/ui/avatar"
export function RecentSales() {
return (
<div className="space-y-8">
<div className="flex items-center">
<Avatar className="h-9 w-9">
<AvatarImage src="/avatars/01.png" alt="Avatar" />
<AvatarFallback>OM</AvatarFallback>
</Avatar>
<div className="ml-4 space-y-1">
<p className="text-sm font-medium leading-none">Olivia Martin</p>
<p className="text-sm text-muted-foreground">
olivia.martin@email.com
</p>
</div>
<div className="ml-auto font-medium">+$1,999.00</div>
</div>
<div className="flex items-center">
<Avatar className="flex h-9 w-9 items-center justify-center space-y-0 border">
<AvatarImage src="/avatars/02.png" alt="Avatar" />
<AvatarFallback>JL</AvatarFallback>
</Avatar>
<div className="ml-4 space-y-1">
<p className="text-sm font-medium leading-none">Jackson Lee</p>
<p className="text-sm text-muted-foreground">jackson.lee@email.com</p>
</div>
<div className="ml-auto font-medium">+$39.00</div>
</div>
<div className="flex items-center">
<Avatar className="h-9 w-9">
<AvatarImage src="/avatars/03.png" alt="Avatar" />
<AvatarFallback>IN</AvatarFallback>
</Avatar>
<div className="ml-4 space-y-1">
<p className="text-sm font-medium leading-none">Isabella Nguyen</p>
<p className="text-sm text-muted-foreground">
isabella.nguyen@email.com
</p>
</div>
<div className="ml-auto font-medium">+$299.00</div>
</div>
<div className="flex items-center">
<Avatar className="h-9 w-9">
<AvatarImage src="/avatars/04.png" alt="Avatar" />
<AvatarFallback>WK</AvatarFallback>
</Avatar>
<div className="ml-4 space-y-1">
<p className="text-sm font-medium leading-none">William Kim</p>
<p className="text-sm text-muted-foreground">will@email.com</p>
</div>
<div className="ml-auto font-medium">+$99.00</div>
</div>
<div className="flex items-center">
<Avatar className="h-9 w-9">
<AvatarImage src="/avatars/05.png" alt="Avatar" />
<AvatarFallback>SD</AvatarFallback>
</Avatar>
<div className="ml-4 space-y-1">
<p className="text-sm font-medium leading-none">Sofia Davis</p>
<p className="text-sm text-muted-foreground">sofia.davis@email.com</p>
</div>
<div className="ml-auto font-medium">+$39.00</div>
</div>
</div>
)
}

View File

@@ -0,0 +1,13 @@
import { Input } from "@/registry/new-york/ui/input"
export function Search() {
return (
<div>
<Input
type="search"
placeholder="Search..."
className="md:w-[100px] lg:w-[300px]"
/>
</div>
)
}

View File

@@ -0,0 +1,214 @@
"use client"
import * as React from "react"
import {
CaretSortIcon,
CheckIcon,
PlusCircledIcon,
} from "@radix-ui/react-icons"
import { cn } from "@/lib/utils"
import {
Avatar,
AvatarFallback,
AvatarImage,
} from "@/registry/new-york/ui/avatar"
import { Button } from "@/registry/new-york/ui/button"
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandSeparator,
} from "@/registry/new-york/ui/command"
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/registry/new-york/ui/dialog"
import { Input } from "@/registry/new-york/ui/input"
import { Label } from "@/registry/new-york/ui/label"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/registry/new-york/ui/popover"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/registry/new-york/ui/select"
const groups = [
{
label: "Personal Account",
teams: [
{
label: "Alicia Koch",
value: "personal",
},
],
},
{
label: "Teams",
teams: [
{
label: "Acme Inc.",
value: "acme-inc",
},
{
label: "Monsters Inc.",
value: "monsters",
},
],
},
]
type Team = (typeof groups)[number]["teams"][number]
type PopoverTriggerProps = React.ComponentPropsWithoutRef<typeof PopoverTrigger>
interface TeamSwitcherProps extends PopoverTriggerProps {}
export default function TeamSwitcher({ className }: TeamSwitcherProps) {
const [open, setOpen] = React.useState(false)
const [showNewTeamDialog, setShowNewTeamDialog] = React.useState(false)
const [selectedTeam, setSelectedTeam] = React.useState<Team>(
groups[0].teams[0]
)
return (
<Dialog open={showNewTeamDialog} onOpenChange={setShowNewTeamDialog}>
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={open}
aria-label="Select a team"
className={cn("w-[200px] justify-between", className)}
>
<Avatar className="mr-2 h-5 w-5">
<AvatarImage
src={`https://avatar.vercel.sh/${selectedTeam.value}.png`}
alt={selectedTeam.label}
className="grayscale"
/>
<AvatarFallback>SC</AvatarFallback>
</Avatar>
{selectedTeam.label}
<CaretSortIcon className="ml-auto h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-[200px] p-0">
<Command>
<CommandList>
<CommandInput placeholder="Search team..." />
<CommandEmpty>No team found.</CommandEmpty>
{groups.map((group) => (
<CommandGroup key={group.label} heading={group.label}>
{group.teams.map((team) => (
<CommandItem
key={team.value}
onSelect={() => {
setSelectedTeam(team)
setOpen(false)
}}
className="text-sm"
>
<Avatar className="mr-2 h-5 w-5">
<AvatarImage
src={`https://avatar.vercel.sh/${team.value}.png`}
alt={team.label}
className="grayscale"
/>
<AvatarFallback>SC</AvatarFallback>
</Avatar>
{team.label}
<CheckIcon
className={cn(
"ml-auto h-4 w-4",
selectedTeam.value === team.value
? "opacity-100"
: "opacity-0"
)}
/>
</CommandItem>
))}
</CommandGroup>
))}
</CommandList>
<CommandSeparator />
<CommandList>
<CommandGroup>
<DialogTrigger asChild>
<CommandItem
onSelect={() => {
setOpen(false)
setShowNewTeamDialog(true)
}}
>
<PlusCircledIcon className="mr-2 h-5 w-5" />
Create Team
</CommandItem>
</DialogTrigger>
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
<DialogContent>
<DialogHeader>
<DialogTitle>Create team</DialogTitle>
<DialogDescription>
Add a new team to manage products and customers.
</DialogDescription>
</DialogHeader>
<div>
<div className="space-y-4 py-2 pb-4">
<div className="space-y-2">
<Label htmlFor="name">Team name</Label>
<Input id="name" placeholder="Acme Inc." />
</div>
<div className="space-y-2">
<Label htmlFor="plan">Subscription plan</Label>
<Select>
<SelectTrigger>
<SelectValue placeholder="Select a plan" />
</SelectTrigger>
<SelectContent>
<SelectItem value="free">
<span className="font-medium">Free</span> -{" "}
<span className="text-muted-foreground">
Trial for two weeks
</span>
</SelectItem>
<SelectItem value="pro">
<span className="font-medium">Pro</span> -{" "}
<span className="text-muted-foreground">
$9/month per user
</span>
</SelectItem>
</SelectContent>
</Select>
</div>
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => setShowNewTeamDialog(false)}>
Cancel
</Button>
<Button type="submit">Continue</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)
}

View File

@@ -0,0 +1,62 @@
import {
Avatar,
AvatarFallback,
AvatarImage,
} from "@/registry/new-york/ui/avatar"
import { Button } from "@/registry/new-york/ui/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuTrigger,
} from "@/registry/new-york/ui/dropdown-menu"
export function UserNav() {
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="relative h-8 w-8 rounded-full">
<Avatar className="h-8 w-8">
<AvatarImage src="/avatars/01.png" alt="@shadcn" />
<AvatarFallback>SC</AvatarFallback>
</Avatar>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56" align="end" forceMount>
<DropdownMenuLabel className="font-normal">
<div className="flex flex-col space-y-1">
<p className="text-sm font-medium leading-none">shadcn</p>
<p className="text-xs leading-none text-muted-foreground">
m@example.com
</p>
</div>
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
Profile
<DropdownMenuShortcut>P</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem>
Billing
<DropdownMenuShortcut>B</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem>
Settings
<DropdownMenuShortcut>S</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem>New Team</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuItem>
Log out
<DropdownMenuShortcut>Q</DropdownMenuShortcut>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
}

View File

@@ -0,0 +1,213 @@
import { Metadata } from "next"
import Image from "next/image"
import { Button } from "@/registry/new-york/ui/button"
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/registry/new-york/ui/card"
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from "@/registry/new-york/ui/tabs"
import { CalendarDateRangePicker } from "@/app/examples/dashboard/components/date-range-picker"
import { MainNav } from "@/app/examples/dashboard/components/main-nav"
import { Overview } from "@/app/examples/dashboard/components/overview"
import { RecentSales } from "@/app/examples/dashboard/components/recent-sales"
import { Search } from "@/app/examples/dashboard/components/search"
import TeamSwitcher from "@/app/examples/dashboard/components/team-switcher"
import { UserNav } from "@/app/examples/dashboard/components/user-nav"
export const metadata: Metadata = {
title: "Dashboard",
description: "Example dashboard app built using the components.",
}
export default function DashboardPage() {
return (
<>
<div className="md:hidden">
<Image
src="/examples/dashboard-light.png"
width={1280}
height={866}
alt="Dashboard"
className="block dark:hidden"
/>
<Image
src="/examples/dashboard-dark.png"
width={1280}
height={866}
alt="Dashboard"
className="hidden dark:block"
/>
</div>
<div className="hidden flex-col md:flex">
<div className="border-b">
<div className="flex h-16 items-center px-4">
<TeamSwitcher />
<MainNav className="mx-6" />
<div className="ml-auto flex items-center space-x-4">
<Search />
<UserNav />
</div>
</div>
</div>
<div className="flex-1 space-y-4 p-8 pt-6">
<div className="flex items-center justify-between space-y-2">
<h2 className="text-3xl font-bold tracking-tight">Dashboard</h2>
<div className="flex items-center space-x-2">
<CalendarDateRangePicker />
<Button>Download</Button>
</div>
</div>
<Tabs defaultValue="overview" className="space-y-4">
<TabsList>
<TabsTrigger value="overview">Overview</TabsTrigger>
<TabsTrigger value="analytics" disabled>
Analytics
</TabsTrigger>
<TabsTrigger value="reports" disabled>
Reports
</TabsTrigger>
<TabsTrigger value="notifications" disabled>
Notifications
</TabsTrigger>
</TabsList>
<TabsContent value="overview" className="space-y-4">
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">
Total Revenue
</CardTitle>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
className="h-4 w-4 text-muted-foreground"
>
<path d="M12 2v20M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6" />
</svg>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">$45,231.89</div>
<p className="text-xs text-muted-foreground">
+20.1% from last month
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">
Subscriptions
</CardTitle>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
className="h-4 w-4 text-muted-foreground"
>
<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" />
<circle cx="9" cy="7" r="4" />
<path d="M22 21v-2a4 4 0 0 0-3-3.87M16 3.13a4 4 0 0 1 0 7.75" />
</svg>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">+2350</div>
<p className="text-xs text-muted-foreground">
+180.1% from last month
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Sales</CardTitle>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
className="h-4 w-4 text-muted-foreground"
>
<rect width="20" height="14" x="2" y="5" rx="2" />
<path d="M2 10h20" />
</svg>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">+12,234</div>
<p className="text-xs text-muted-foreground">
+19% from last month
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">
Active Now
</CardTitle>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
className="h-4 w-4 text-muted-foreground"
>
<path d="M22 12h-4l-3 9L9 3l-3 9H2" />
</svg>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">+573</div>
<p className="text-xs text-muted-foreground">
+201 since last hour
</p>
</CardContent>
</Card>
</div>
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-7">
<Card className="col-span-4">
<CardHeader>
<CardTitle>Overview</CardTitle>
</CardHeader>
<CardContent className="pl-2">
<Overview />
</CardContent>
</Card>
<Card className="col-span-3">
<CardHeader>
<CardTitle>Recent Sales</CardTitle>
<CardDescription>
You made 265 sales this month.
</CardDescription>
</CardHeader>
<CardContent>
<RecentSales />
</CardContent>
</Card>
</div>
</TabsContent>
</Tabs>
</div>
</div>
</>
)
}

View File

@@ -0,0 +1,219 @@
"use client"
import { zodResolver } from "@hookform/resolvers/zod"
import { CalendarIcon, CaretSortIcon, CheckIcon } from "@radix-ui/react-icons"
import { format } from "date-fns"
import { useForm } from "react-hook-form"
import * as z from "zod"
import { cn } from "@/lib/utils"
import { Button } from "@/registry/new-york/ui/button"
import { Calendar } from "@/registry/new-york/ui/calendar"
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
} from "@/registry/new-york/ui/command"
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/registry/new-york/ui/form"
import { Input } from "@/registry/new-york/ui/input"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/registry/new-york/ui/popover"
import { toast } from "@/registry/new-york/ui/use-toast"
const languages = [
{ label: "English", value: "en" },
{ label: "French", value: "fr" },
{ label: "German", value: "de" },
{ label: "Spanish", value: "es" },
{ label: "Portuguese", value: "pt" },
{ label: "Russian", value: "ru" },
{ label: "Japanese", value: "ja" },
{ label: "Korean", value: "ko" },
{ label: "Chinese", value: "zh" },
] as const
const accountFormSchema = z.object({
name: z
.string()
.min(2, {
message: "Name must be at least 2 characters.",
})
.max(30, {
message: "Name must not be longer than 30 characters.",
}),
dob: z.date({
required_error: "A date of birth is required.",
}),
language: z.string({
required_error: "Please select a language.",
}),
})
type AccountFormValues = z.infer<typeof accountFormSchema>
// This can come from your database or API.
const defaultValues: Partial<AccountFormValues> = {
// name: "Your name",
// dob: new Date("2023-01-23"),
}
export function AccountForm() {
const form = useForm<AccountFormValues>({
resolver: zodResolver(accountFormSchema),
defaultValues,
})
function onSubmit(data: AccountFormValues) {
toast({
title: "You submitted the following values:",
description: (
<pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4">
<code className="text-white">{JSON.stringify(data, null, 2)}</code>
</pre>
),
})
}
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Name</FormLabel>
<FormControl>
<Input placeholder="Your name" {...field} />
</FormControl>
<FormDescription>
This is the name that will be displayed on your profile and in
emails.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="dob"
render={({ field }) => (
<FormItem className="flex flex-col">
<FormLabel>Date of birth</FormLabel>
<Popover>
<PopoverTrigger asChild>
<FormControl>
<Button
variant={"outline"}
className={cn(
"w-[240px] pl-3 text-left font-normal",
!field.value && "text-muted-foreground"
)}
>
{field.value ? (
format(field.value, "PPP")
) : (
<span>Pick a date</span>
)}
<CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
</Button>
</FormControl>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<Calendar
mode="single"
selected={field.value}
onSelect={field.onChange}
disabled={(date) =>
date > new Date() || date < new Date("1900-01-01")
}
initialFocus
/>
</PopoverContent>
</Popover>
<FormDescription>
Your date of birth is used to calculate your age.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="language"
render={({ field }) => (
<FormItem className="flex flex-col">
<FormLabel>Language</FormLabel>
<Popover>
<PopoverTrigger asChild>
<FormControl>
<Button
variant="outline"
role="combobox"
className={cn(
"w-[200px] justify-between",
!field.value && "text-muted-foreground"
)}
>
{field.value
? languages.find(
(language) => language.value === field.value
)?.label
: "Select language"}
<CaretSortIcon className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</FormControl>
</PopoverTrigger>
<PopoverContent className="w-[200px] p-0">
<Command>
<CommandInput placeholder="Search language..." />
<CommandEmpty>No language found.</CommandEmpty>
<CommandGroup>
{languages.map((language) => (
<CommandItem
value={language.label}
key={language.value}
onSelect={() => {
form.setValue("language", language.value)
}}
>
<CheckIcon
className={cn(
"mr-2 h-4 w-4",
language.value === field.value
? "opacity-100"
: "opacity-0"
)}
/>
{language.label}
</CommandItem>
))}
</CommandGroup>
</Command>
</PopoverContent>
</Popover>
<FormDescription>
This is the language that will be used in the dashboard.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">Update account</Button>
</form>
</Form>
)
}

View File

@@ -0,0 +1,18 @@
import { Separator } from "@/registry/new-york/ui/separator"
import { AccountForm } from "@/app/examples/forms/account/account-form"
export default function SettingsAccountPage() {
return (
<div className="space-y-6">
<div>
<h3 className="text-lg font-medium">Account</h3>
<p className="text-sm text-muted-foreground">
Update your account settings. Set your preferred language and
timezone.
</p>
</div>
<Separator />
<AccountForm />
</div>
)
}

View File

@@ -0,0 +1,164 @@
"use client"
import { zodResolver } from "@hookform/resolvers/zod"
import { ChevronDownIcon } from "@radix-ui/react-icons"
import { useForm } from "react-hook-form"
import * as z from "zod"
import { cn } from "@/lib/utils"
import { Button, buttonVariants } from "@/registry/new-york/ui/button"
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/registry/new-york/ui/form"
import { RadioGroup, RadioGroupItem } from "@/registry/new-york/ui/radio-group"
import { toast } from "@/registry/new-york/ui/use-toast"
const appearanceFormSchema = z.object({
theme: z.enum(["light", "dark"], {
required_error: "Please select a theme.",
}),
font: z.enum(["inter", "manrope", "system"], {
invalid_type_error: "Select a font",
required_error: "Please select a font.",
}),
})
type AppearanceFormValues = z.infer<typeof appearanceFormSchema>
// This can come from your database or API.
const defaultValues: Partial<AppearanceFormValues> = {
theme: "light",
}
export function AppearanceForm() {
const form = useForm<AppearanceFormValues>({
resolver: zodResolver(appearanceFormSchema),
defaultValues,
})
function onSubmit(data: AppearanceFormValues) {
toast({
title: "You submitted the following values:",
description: (
<pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4">
<code className="text-white">{JSON.stringify(data, null, 2)}</code>
</pre>
),
})
}
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
<FormField
control={form.control}
name="font"
render={({ field }) => (
<FormItem>
<FormLabel>Font</FormLabel>
<div className="relative w-max">
<FormControl>
<select
className={cn(
buttonVariants({ variant: "outline" }),
"w-[200px] appearance-none bg-transparent font-normal"
)}
{...field}
>
<option value="inter">Inter</option>
<option value="manrope">Manrope</option>
<option value="system">System</option>
</select>
</FormControl>
<ChevronDownIcon className="absolute right-3 top-2.5 h-4 w-4 opacity-50" />
</div>
<FormDescription>
Set the font you want to use in the dashboard.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="theme"
render={({ field }) => (
<FormItem className="space-y-1">
<FormLabel>Theme</FormLabel>
<FormDescription>
Select the theme for the dashboard.
</FormDescription>
<FormMessage />
<RadioGroup
onValueChange={field.onChange}
defaultValue={field.value}
className="grid max-w-md grid-cols-2 gap-8 pt-2"
>
<FormItem>
<FormLabel className="[&:has([data-state=checked])>div]:border-primary">
<FormControl>
<RadioGroupItem value="light" className="sr-only" />
</FormControl>
<div className="items-center rounded-md border-2 border-muted p-1 hover:border-accent">
<div className="space-y-2 rounded-sm bg-[#ecedef] p-2">
<div className="space-y-2 rounded-md bg-white p-2 shadow-sm">
<div className="h-2 w-[80px] rounded-lg bg-[#ecedef]" />
<div className="h-2 w-[100px] rounded-lg bg-[#ecedef]" />
</div>
<div className="flex items-center space-x-2 rounded-md bg-white p-2 shadow-sm">
<div className="h-4 w-4 rounded-full bg-[#ecedef]" />
<div className="h-2 w-[100px] rounded-lg bg-[#ecedef]" />
</div>
<div className="flex items-center space-x-2 rounded-md bg-white p-2 shadow-sm">
<div className="h-4 w-4 rounded-full bg-[#ecedef]" />
<div className="h-2 w-[100px] rounded-lg bg-[#ecedef]" />
</div>
</div>
</div>
<span className="block w-full p-2 text-center font-normal">
Light
</span>
</FormLabel>
</FormItem>
<FormItem>
<FormLabel className="[&:has([data-state=checked])>div]:border-primary">
<FormControl>
<RadioGroupItem value="dark" className="sr-only" />
</FormControl>
<div className="items-center rounded-md border-2 border-muted bg-popover p-1 hover:bg-accent hover:text-accent-foreground">
<div className="space-y-2 rounded-sm bg-slate-950 p-2">
<div className="space-y-2 rounded-md bg-slate-800 p-2 shadow-sm">
<div className="h-2 w-[80px] rounded-lg bg-slate-400" />
<div className="h-2 w-[100px] rounded-lg bg-slate-400" />
</div>
<div className="flex items-center space-x-2 rounded-md bg-slate-800 p-2 shadow-sm">
<div className="h-4 w-4 rounded-full bg-slate-400" />
<div className="h-2 w-[100px] rounded-lg bg-slate-400" />
</div>
<div className="flex items-center space-x-2 rounded-md bg-slate-800 p-2 shadow-sm">
<div className="h-4 w-4 rounded-full bg-slate-400" />
<div className="h-2 w-[100px] rounded-lg bg-slate-400" />
</div>
</div>
</div>
<span className="block w-full p-2 text-center font-normal">
Dark
</span>
</FormLabel>
</FormItem>
</RadioGroup>
</FormItem>
)}
/>
<Button type="submit">Update preferences</Button>
</form>
</Form>
)
}

View File

@@ -0,0 +1,18 @@
import { Separator } from "@/registry/new-york/ui/separator"
import { AppearanceForm } from "@/app/examples/forms/appearance/appearance-form"
export default function SettingsAppearancePage() {
return (
<div className="space-y-6">
<div>
<h3 className="text-lg font-medium">Appearance</h3>
<p className="text-sm text-muted-foreground">
Customize the appearance of the app. Automatically switch between day
and night themes.
</p>
</div>
<Separator />
<AppearanceForm />
</div>
)
}

View File

@@ -0,0 +1,44 @@
"use client"
import Link from "next/link"
import { usePathname } from "next/navigation"
import { cn } from "@/lib/utils"
import { buttonVariants } from "@/registry/new-york/ui/button"
interface SidebarNavProps extends React.HTMLAttributes<HTMLElement> {
items: {
href: string
title: string
}[]
}
export function SidebarNav({ className, items, ...props }: SidebarNavProps) {
const pathname = usePathname()
return (
<nav
className={cn(
"flex space-x-2 lg:flex-col lg:space-x-0 lg:space-y-1",
className
)}
{...props}
>
{items.map((item) => (
<Link
key={item.href}
href={item.href}
className={cn(
buttonVariants({ variant: "ghost" }),
pathname === item.href
? "bg-muted hover:bg-muted"
: "hover:bg-transparent hover:underline",
"justify-start"
)}
>
{item.title}
</Link>
))}
</nav>
)
}

View File

@@ -0,0 +1,132 @@
"use client"
import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form"
import * as z from "zod"
import { Button } from "@/registry/new-york/ui/button"
import { Checkbox } from "@/registry/new-york/ui/checkbox"
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/registry/new-york/ui/form"
import { toast } from "@/registry/new-york/ui/use-toast"
const items = [
{
id: "recents",
label: "Recents",
},
{
id: "home",
label: "Home",
},
{
id: "applications",
label: "Applications",
},
{
id: "desktop",
label: "Desktop",
},
{
id: "downloads",
label: "Downloads",
},
{
id: "documents",
label: "Documents",
},
] as const
const displayFormSchema = z.object({
items: z.array(z.string()).refine((value) => value.some((item) => item), {
message: "You have to select at least one item.",
}),
})
type DisplayFormValues = z.infer<typeof displayFormSchema>
// This can come from your database or API.
const defaultValues: Partial<DisplayFormValues> = {
items: ["recents", "home"],
}
export function DisplayForm() {
const form = useForm<DisplayFormValues>({
resolver: zodResolver(displayFormSchema),
defaultValues,
})
function onSubmit(data: DisplayFormValues) {
toast({
title: "You submitted the following values:",
description: (
<pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4">
<code className="text-white">{JSON.stringify(data, null, 2)}</code>
</pre>
),
})
}
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
<FormField
control={form.control}
name="items"
render={() => (
<FormItem>
<div className="mb-4">
<FormLabel className="text-base">Sidebar</FormLabel>
<FormDescription>
Select the items you want to display in the sidebar.
</FormDescription>
</div>
{items.map((item) => (
<FormField
key={item.id}
control={form.control}
name="items"
render={({ field }) => {
return (
<FormItem
key={item.id}
className="flex flex-row items-start space-x-3 space-y-0"
>
<FormControl>
<Checkbox
checked={field.value?.includes(item.id)}
onCheckedChange={(checked) => {
return checked
? field.onChange([...field.value, item.id])
: field.onChange(
field.value?.filter(
(value) => value !== item.id
)
)
}}
/>
</FormControl>
<FormLabel className="font-normal">
{item.label}
</FormLabel>
</FormItem>
)
}}
/>
))}
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">Update display</Button>
</form>
</Form>
)
}

View File

@@ -0,0 +1,17 @@
import { Separator } from "@/registry/new-york/ui/separator"
import { DisplayForm } from "@/app/examples/forms/display/display-form"
export default function SettingsDisplayPage() {
return (
<div className="space-y-6">
<div>
<h3 className="text-lg font-medium">Display</h3>
<p className="text-sm text-muted-foreground">
Turn items on or off to control what&apos;s displayed in the app.
</p>
</div>
<Separator />
<DisplayForm />
</div>
)
}

View File

@@ -0,0 +1,75 @@
import { Metadata } from "next"
import Image from "next/image"
import { Separator } from "@/registry/new-york/ui/separator"
import { SidebarNav } from "@/app/examples/forms/components/sidebar-nav"
export const metadata: Metadata = {
title: "Forms",
description: "Advanced form example using react-hook-form and Zod.",
}
const sidebarNavItems = [
{
title: "Profile",
href: "/examples/forms",
},
{
title: "Account",
href: "/examples/forms/account",
},
{
title: "Appearance",
href: "/examples/forms/appearance",
},
{
title: "Notifications",
href: "/examples/forms/notifications",
},
{
title: "Display",
href: "/examples/forms/display",
},
]
interface SettingsLayoutProps {
children: React.ReactNode
}
export default function SettingsLayout({ children }: SettingsLayoutProps) {
return (
<>
<div className="md:hidden">
<Image
src="/examples/forms-light.png"
width={1280}
height={791}
alt="Forms"
className="block dark:hidden"
/>
<Image
src="/examples/forms-dark.png"
width={1280}
height={791}
alt="Forms"
className="hidden dark:block"
/>
</div>
<div className="hidden space-y-6 p-10 pb-16 md:block">
<div className="space-y-0.5">
<h2 className="text-2xl font-bold tracking-tight">Settings</h2>
<p className="text-muted-foreground">
Manage your account settings and set e-mail preferences.
</p>
</div>
<Separator className="my-6" />
<div className="flex flex-col space-y-8 lg:flex-row lg:space-x-12 lg:space-y-0">
<aside className="-mx-4 lg:w-1/5">
<SidebarNav items={sidebarNavItems} />
</aside>
<div className="flex-1 lg:max-w-2xl">{children}</div>
</div>
</div>
</>
)
}

View File

@@ -0,0 +1,222 @@
"use client"
import Link from "next/link"
import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form"
import * as z from "zod"
import { Button } from "@/registry/new-york/ui/button"
import { Checkbox } from "@/registry/new-york/ui/checkbox"
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/registry/new-york/ui/form"
import { RadioGroup, RadioGroupItem } from "@/registry/new-york/ui/radio-group"
import { Switch } from "@/registry/new-york/ui/switch"
import { toast } from "@/registry/new-york/ui/use-toast"
const notificationsFormSchema = z.object({
type: z.enum(["all", "mentions", "none"], {
required_error: "You need to select a notification type.",
}),
mobile: z.boolean().default(false).optional(),
communication_emails: z.boolean().default(false).optional(),
social_emails: z.boolean().default(false).optional(),
marketing_emails: z.boolean().default(false).optional(),
security_emails: z.boolean(),
})
type NotificationsFormValues = z.infer<typeof notificationsFormSchema>
// This can come from your database or API.
const defaultValues: Partial<NotificationsFormValues> = {
communication_emails: false,
marketing_emails: false,
social_emails: true,
security_emails: true,
}
export function NotificationsForm() {
const form = useForm<NotificationsFormValues>({
resolver: zodResolver(notificationsFormSchema),
defaultValues,
})
function onSubmit(data: NotificationsFormValues) {
toast({
title: "You submitted the following values:",
description: (
<pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4">
<code className="text-white">{JSON.stringify(data, null, 2)}</code>
</pre>
),
})
}
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
<FormField
control={form.control}
name="type"
render={({ field }) => (
<FormItem className="space-y-3">
<FormLabel>Notify me about...</FormLabel>
<FormControl>
<RadioGroup
onValueChange={field.onChange}
defaultValue={field.value}
className="flex flex-col space-y-1"
>
<FormItem className="flex items-center space-x-3 space-y-0">
<FormControl>
<RadioGroupItem value="all" />
</FormControl>
<FormLabel className="font-normal">
All new messages
</FormLabel>
</FormItem>
<FormItem className="flex items-center space-x-3 space-y-0">
<FormControl>
<RadioGroupItem value="mentions" />
</FormControl>
<FormLabel className="font-normal">
Direct messages and mentions
</FormLabel>
</FormItem>
<FormItem className="flex items-center space-x-3 space-y-0">
<FormControl>
<RadioGroupItem value="none" />
</FormControl>
<FormLabel className="font-normal">Nothing</FormLabel>
</FormItem>
</RadioGroup>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div>
<h3 className="mb-4 text-lg font-medium">Email Notifications</h3>
<div className="space-y-4">
<FormField
control={form.control}
name="communication_emails"
render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-4">
<div className="space-y-0.5">
<FormLabel className="text-base">
Communication emails
</FormLabel>
<FormDescription>
Receive emails about your account activity.
</FormDescription>
</div>
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
</FormItem>
)}
/>
<FormField
control={form.control}
name="marketing_emails"
render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-4">
<div className="space-y-0.5">
<FormLabel className="text-base">
Marketing emails
</FormLabel>
<FormDescription>
Receive emails about new products, features, and more.
</FormDescription>
</div>
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
</FormItem>
)}
/>
<FormField
control={form.control}
name="social_emails"
render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-4">
<div className="space-y-0.5">
<FormLabel className="text-base">Social emails</FormLabel>
<FormDescription>
Receive emails for friend requests, follows, and more.
</FormDescription>
</div>
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
</FormItem>
)}
/>
<FormField
control={form.control}
name="security_emails"
render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-4">
<div className="space-y-0.5">
<FormLabel className="text-base">Security emails</FormLabel>
<FormDescription>
Receive emails about your account activity and security.
</FormDescription>
</div>
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
disabled
aria-readonly
/>
</FormControl>
</FormItem>
)}
/>
</div>
</div>
<FormField
control={form.control}
name="mobile"
render={({ field }) => (
<FormItem className="flex flex-row items-start space-x-3 space-y-0">
<FormControl>
<Checkbox
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
<div className="space-y-1 leading-none">
<FormLabel>
Use different settings for my mobile devices
</FormLabel>
<FormDescription>
You can manage your mobile notifications in the{" "}
<Link href="/examples/forms">mobile settings</Link> page.
</FormDescription>
</div>
</FormItem>
)}
/>
<Button type="submit">Update notifications</Button>
</form>
</Form>
)
}

View File

@@ -0,0 +1,17 @@
import { Separator } from "@/registry/new-york/ui/separator"
import { NotificationsForm } from "@/app/examples/forms/notifications/notifications-form"
export default function SettingsNotificationsPage() {
return (
<div className="space-y-6">
<div>
<h3 className="text-lg font-medium">Notifications</h3>
<p className="text-sm text-muted-foreground">
Configure how you receive notifications.
</p>
</div>
<Separator />
<NotificationsForm />
</div>
)
}

View File

@@ -0,0 +1,17 @@
import { Separator } from "@/registry/new-york/ui/separator"
import { ProfileForm } from "@/app/examples/forms/profile-form"
export default function SettingsProfilePage() {
return (
<div className="space-y-6">
<div>
<h3 className="text-lg font-medium">Profile</h3>
<p className="text-sm text-muted-foreground">
This is how others will see you on the site.
</p>
</div>
<Separator />
<ProfileForm />
</div>
)
}

View File

@@ -0,0 +1,191 @@
"use client"
import Link from "next/link"
import { zodResolver } from "@hookform/resolvers/zod"
import { useFieldArray, useForm } from "react-hook-form"
import * as z from "zod"
import { cn } from "@/lib/utils"
import { Button } from "@/registry/new-york/ui/button"
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/registry/new-york/ui/form"
import { Input } from "@/registry/new-york/ui/input"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/registry/new-york/ui/select"
import { Textarea } from "@/registry/new-york/ui/textarea"
import { toast } from "@/registry/new-york/ui/use-toast"
const profileFormSchema = z.object({
username: z
.string()
.min(2, {
message: "Username must be at least 2 characters.",
})
.max(30, {
message: "Username must not be longer than 30 characters.",
}),
email: z
.string({
required_error: "Please select an email to display.",
})
.email(),
bio: z.string().max(160).min(4),
urls: z
.array(
z.object({
value: z.string().url({ message: "Please enter a valid URL." }),
})
)
.optional(),
})
type ProfileFormValues = z.infer<typeof profileFormSchema>
// This can come from your database or API.
const defaultValues: Partial<ProfileFormValues> = {
bio: "I own a computer.",
urls: [
{ value: "https://shadcn.com" },
{ value: "http://twitter.com/shadcn" },
],
}
export function ProfileForm() {
const form = useForm<ProfileFormValues>({
resolver: zodResolver(profileFormSchema),
defaultValues,
mode: "onChange",
})
const { fields, append } = useFieldArray({
name: "urls",
control: form.control,
})
function onSubmit(data: ProfileFormValues) {
toast({
title: "You submitted the following values:",
description: (
<pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4">
<code className="text-white">{JSON.stringify(data, null, 2)}</code>
</pre>
),
})
}
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
<FormField
control={form.control}
name="username"
render={({ field }) => (
<FormItem>
<FormLabel>Username</FormLabel>
<FormControl>
<Input placeholder="shadcn" {...field} />
</FormControl>
<FormDescription>
This is your public display name. It can be your real name or a
pseudonym. You can only change this once every 30 days.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select a verified email to display" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="m@example.com">m@example.com</SelectItem>
<SelectItem value="m@google.com">m@google.com</SelectItem>
<SelectItem value="m@support.com">m@support.com</SelectItem>
</SelectContent>
</Select>
<FormDescription>
You can manage verified email addresses in your{" "}
<Link href="/examples/forms">email settings</Link>.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="bio"
render={({ field }) => (
<FormItem>
<FormLabel>Bio</FormLabel>
<FormControl>
<Textarea
placeholder="Tell us a little bit about yourself"
className="resize-none"
{...field}
/>
</FormControl>
<FormDescription>
You can <span>@mention</span> other users and organizations to
link to them.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<div>
{fields.map((field, index) => (
<FormField
control={form.control}
key={field.id}
name={`urls.${index}.value`}
render={({ field }) => (
<FormItem>
<FormLabel className={cn(index !== 0 && "sr-only")}>
URLs
</FormLabel>
<FormDescription className={cn(index !== 0 && "sr-only")}>
Add links to your website, blog, or social media profiles.
</FormDescription>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
))}
<Button
type="button"
variant="outline"
size="sm"
className="mt-2"
onClick={() => append({ value: "" })}
>
Add URL
</Button>
</div>
<Button type="submit">Update profile</Button>
</form>
</Form>
)
}

View File

@@ -0,0 +1,65 @@
import { Metadata } from "next"
import Link from "next/link"
import { cn } from "@/lib/utils"
import { Announcement } from "@/components/announcement"
import { ExamplesNav } from "@/components/examples-nav"
import {
PageActions,
PageHeader,
PageHeaderDescription,
PageHeaderHeading,
} from "@/components/page-header"
import { buttonVariants } from "@/registry/new-york/ui/button"
export const metadata: Metadata = {
title: "Examples",
description: "Check out some examples app built using the components.",
}
interface ExamplesLayoutProps {
children: React.ReactNode
}
export default function ExamplesLayout({ children }: ExamplesLayoutProps) {
return (
<>
<div className="container relative">
<PageHeader>
<Announcement />
<PageHeaderHeading className="hidden md:block">
Check out some examples
</PageHeaderHeading>
<PageHeaderHeading className="md:hidden">Examples</PageHeaderHeading>
<PageHeaderDescription>
Dashboard, cards, authentication. Some examples built using the
components. Use this as a guide to build your own.
</PageHeaderDescription>
<PageActions>
<Link
href="/docs"
className={cn(buttonVariants(), "rounded-[6px]")}
>
Get Started
</Link>
<Link
href="/components"
className={cn(
buttonVariants({ variant: "outline" }),
"rounded-[6px]"
)}
>
Components
</Link>
</PageActions>
</PageHeader>
<section>
<ExamplesNav />
<div className="overflow-hidden rounded-[0.5rem] border bg-background shadow-md md:shadow-xl">
{children}
</div>
</section>
</div>
</>
)
}

View File

@@ -0,0 +1,63 @@
"use client"
import * as React from "react"
import { cn } from "@/lib/utils"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/registry/new-york/ui/select"
interface AccountSwitcherProps {
isCollapsed: boolean
accounts: {
label: string
email: string
icon: React.ReactNode
}[]
}
export function AccountSwitcher({
isCollapsed,
accounts,
}: AccountSwitcherProps) {
const [selectedAccount, setSelectedAccount] = React.useState<string>(
accounts[0].email
)
return (
<Select defaultValue={selectedAccount} onValueChange={setSelectedAccount}>
<SelectTrigger
className={cn(
"flex items-center gap-2 [&>span]:line-clamp-1 [&>span]:flex [&>span]:w-full [&>span]:items-center [&>span]:gap-1 [&>span]:truncate [&_svg]:h-4 [&_svg]:w-4 [&_svg]:shrink-0",
isCollapsed &&
"flex h-9 w-9 shrink-0 items-center justify-center p-0 [&>span]:w-auto [&>svg]:hidden"
)}
aria-label="Select account"
>
<SelectValue placeholder="Select an account">
{accounts.find((account) => account.email === selectedAccount)?.icon}
<span className={cn("ml-2", isCollapsed && "hidden")}>
{
accounts.find((account) => account.email === selectedAccount)
?.label
}
</span>
</SelectValue>
</SelectTrigger>
<SelectContent>
{accounts.map((account) => (
<SelectItem key={account.email} value={account.email}>
<div className="flex items-center gap-3 [&_svg]:h-4 [&_svg]:w-4 [&_svg]:shrink-0 [&_svg]:text-foreground">
{account.icon}
{account.email}
</div>
</SelectItem>
))}
</SelectContent>
</Select>
)
}

View File

@@ -0,0 +1,258 @@
import addDays from "date-fns/addDays"
import addHours from "date-fns/addHours"
import format from "date-fns/format"
import nextSaturday from "date-fns/nextSaturday"
import {
Archive,
ArchiveX,
Clock,
Forward,
MoreVertical,
Reply,
ReplyAll,
Trash2,
} from "lucide-react"
import {
DropdownMenuContent,
DropdownMenuItem,
} from "@/registry/default/ui/dropdown-menu"
import {
Avatar,
AvatarFallback,
AvatarImage,
} from "@/registry/new-york/ui/avatar"
import { Button } from "@/registry/new-york/ui/button"
import { Calendar } from "@/registry/new-york/ui/calendar"
import {
DropdownMenu,
DropdownMenuTrigger,
} from "@/registry/new-york/ui/dropdown-menu"
import { Label } from "@/registry/new-york/ui/label"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/registry/new-york/ui/popover"
import { Separator } from "@/registry/new-york/ui/separator"
import { Switch } from "@/registry/new-york/ui/switch"
import { Textarea } from "@/registry/new-york/ui/textarea"
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/registry/new-york/ui/tooltip"
import { Mail } from "@/app/examples/mail/data"
interface MailDisplayProps {
mail: Mail | null
}
export function MailDisplay({ mail }: MailDisplayProps) {
const today = new Date()
return (
<div className="flex h-full flex-col">
<div className="flex items-center p-2">
<div className="flex items-center gap-2">
<Tooltip>
<TooltipTrigger asChild>
<Button variant="ghost" size="icon" disabled={!mail}>
<Archive className="h-4 w-4" />
<span className="sr-only">Archive</span>
</Button>
</TooltipTrigger>
<TooltipContent>Archive</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button variant="ghost" size="icon" disabled={!mail}>
<ArchiveX className="h-4 w-4" />
<span className="sr-only">Move to junk</span>
</Button>
</TooltipTrigger>
<TooltipContent>Move to junk</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button variant="ghost" size="icon" disabled={!mail}>
<Trash2 className="h-4 w-4" />
<span className="sr-only">Move to trash</span>
</Button>
</TooltipTrigger>
<TooltipContent>Move to trash</TooltipContent>
</Tooltip>
<Separator orientation="vertical" className="mx-1 h-6" />
<Tooltip>
<Popover>
<PopoverTrigger asChild>
<TooltipTrigger asChild>
<Button variant="ghost" size="icon" disabled={!mail}>
<Clock className="h-4 w-4" />
<span className="sr-only">Snooze</span>
</Button>
</TooltipTrigger>
</PopoverTrigger>
<PopoverContent className="flex w-[535px] p-0">
<div className="flex flex-col gap-2 border-r px-2 py-4">
<div className="px-4 text-sm font-medium">Snooze until</div>
<div className="grid min-w-[250px] gap-1">
<Button
variant="ghost"
className="justify-start font-normal"
>
Later today{" "}
<span className="ml-auto text-muted-foreground">
{format(addHours(today, 4), "E, h:m b")}
</span>
</Button>
<Button
variant="ghost"
className="justify-start font-normal"
>
Tomorrow
<span className="ml-auto text-muted-foreground">
{format(addDays(today, 1), "E, h:m b")}
</span>
</Button>
<Button
variant="ghost"
className="justify-start font-normal"
>
This weekend
<span className="ml-auto text-muted-foreground">
{format(nextSaturday(today), "E, h:m b")}
</span>
</Button>
<Button
variant="ghost"
className="justify-start font-normal"
>
Next week
<span className="ml-auto text-muted-foreground">
{format(addDays(today, 7), "E, h:m b")}
</span>
</Button>
</div>
</div>
<div className="p-2">
<Calendar />
</div>
</PopoverContent>
</Popover>
<TooltipContent>Snooze</TooltipContent>
</Tooltip>
</div>
<div className="ml-auto flex items-center gap-2">
<Tooltip>
<TooltipTrigger asChild>
<Button variant="ghost" size="icon" disabled={!mail}>
<Reply className="h-4 w-4" />
<span className="sr-only">Reply</span>
</Button>
</TooltipTrigger>
<TooltipContent>Reply</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button variant="ghost" size="icon" disabled={!mail}>
<ReplyAll className="h-4 w-4" />
<span className="sr-only">Reply all</span>
</Button>
</TooltipTrigger>
<TooltipContent>Reply all</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button variant="ghost" size="icon" disabled={!mail}>
<Forward className="h-4 w-4" />
<span className="sr-only">Forward</span>
</Button>
</TooltipTrigger>
<TooltipContent>Forward</TooltipContent>
</Tooltip>
</div>
<Separator orientation="vertical" className="mx-2 h-6" />
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon" disabled={!mail}>
<MoreVertical className="h-4 w-4" />
<span className="sr-only">More</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem>Mark as unread</DropdownMenuItem>
<DropdownMenuItem>Star thread</DropdownMenuItem>
<DropdownMenuItem>Add label</DropdownMenuItem>
<DropdownMenuItem>Mute thread</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
<Separator />
{mail ? (
<div className="flex flex-1 flex-col">
<div className="flex items-start p-4">
<div className="flex items-start gap-4 text-sm">
<Avatar>
<AvatarImage alt={mail.name} />
<AvatarFallback>
{mail.name
.split(" ")
.map((chunk) => chunk[0])
.join("")}
</AvatarFallback>
</Avatar>
<div className="grid gap-1">
<div className="font-semibold">{mail.name}</div>
<div className="line-clamp-1 text-xs">{mail.subject}</div>
<div className="line-clamp-1 text-xs">
<span className="font-medium">Reply-To:</span> {mail.email}
</div>
</div>
</div>
{mail.date && (
<div className="ml-auto text-xs text-muted-foreground">
{format(new Date(mail.date), "PPpp")}
</div>
)}
</div>
<Separator />
<div className="flex-1 whitespace-pre-wrap p-4 text-sm">
{mail.text}
</div>
<Separator className="mt-auto" />
<div className="p-4">
<form>
<div className="grid gap-4">
<Textarea
className="p-4"
placeholder={`Reply ${mail.name}...`}
/>
<div className="flex items-center">
<Label
htmlFor="mute"
className="flex items-center gap-2 text-xs font-normal"
>
<Switch id="mute" aria-label="Mute thread" /> Mute this
thread
</Label>
<Button
onClick={(e) => e.preventDefault()}
size="sm"
className="ml-auto"
>
Send
</Button>
</div>
</div>
</form>
</div>
</div>
) : (
<div className="p-8 text-center text-muted-foreground">
No message selected
</div>
)}
</div>
)
}

View File

@@ -0,0 +1,89 @@
import { ComponentProps } from "react"
import formatDistanceToNow from "date-fns/formatDistanceToNow"
import { cn } from "@/lib/utils"
import { Badge } from "@/registry/new-york/ui/badge"
import { ScrollArea } from "@/registry/new-york/ui/scroll-area"
import { Separator } from "@/registry/new-york/ui/separator"
import { Mail } from "@/app/examples/mail/data"
import { useMail } from "@/app/examples/mail/use-mail"
interface MailListProps {
items: Mail[]
}
export function MailList({ items }: MailListProps) {
const [mail, setMail] = useMail()
return (
<ScrollArea className="h-screen">
<div className="flex flex-col gap-2 p-4 pt-0">
{items.map((item) => (
<button
key={item.id}
className={cn(
"flex flex-col items-start gap-2 rounded-lg border p-3 text-left text-sm transition-all hover:bg-accent",
mail.selected === item.id && "bg-muted"
)}
onClick={() =>
setMail({
...mail,
selected: item.id,
})
}
>
<div className="flex w-full flex-col gap-1">
<div className="flex items-center">
<div className="flex items-center gap-2">
<div className="font-semibold">{item.name}</div>
{!item.read && (
<span className="flex h-2 w-2 rounded-full bg-blue-600" />
)}
</div>
<div
className={cn(
"ml-auto text-xs",
mail.selected === item.id
? "text-foreground"
: "text-muted-foreground"
)}
>
{formatDistanceToNow(new Date(item.date), {
addSuffix: true,
})}
</div>
</div>
<div className="text-xs font-medium">{item.subject}</div>
</div>
<div className="line-clamp-2 text-xs text-muted-foreground">
{item.text.substring(0, 300)}
</div>
{item.labels.length ? (
<div className="flex items-center gap-2">
{item.labels.map((label) => (
<Badge key={label} variant={getBadgeVariantFromLabel(label)}>
{label}
</Badge>
))}
</div>
) : null}
</button>
))}
</div>
</ScrollArea>
)
}
function getBadgeVariantFromLabel(
label: string
): ComponentProps<typeof Badge>["variant"] {
if (["work"].includes(label.toLowerCase())) {
return "default"
}
if (["personal"].includes(label.toLowerCase())) {
return "outline"
}
return "secondary"
}

View File

@@ -0,0 +1,201 @@
"use client"
import * as React from "react"
import {
AlertCircle,
Archive,
ArchiveX,
File,
Inbox,
MessagesSquare,
PenBox,
Search,
Send,
ShoppingCart,
Trash2,
Users2,
} from "lucide-react"
import { AccountSwitcher } from "@/app/examples/mail/components/account-switcher"
import { MailDisplay } from "@/app/examples/mail/components/mail-display"
import { MailList } from "@/app/examples/mail/components/mail-list"
import { Nav } from "@/app/examples/mail/components/nav"
import { Mail } from "@/app/examples/mail/data"
import { useMail } from "@/app/examples/mail/use-mail"
import { cn } from "@/lib/utils"
import { Separator } from "@/registry/new-york/ui/separator"
import { Input } from "@/registry/new-york/ui/input"
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from "@/registry/new-york/ui/tabs"
import { TooltipProvider } from "@/registry/new-york/ui/tooltip"
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/registry/new-york/ui/resizable"
interface MailProps {
accounts: {
label: string
email: string
icon: React.ReactNode
}[]
mails: Mail[]
defaultLayout: number[] | undefined
defaultCollapsed?: boolean
navCollapsedSize: number
}
export function Mail({
accounts,
mails,
defaultLayout = [265, 440, 655],
defaultCollapsed = false,
navCollapsedSize,
}: MailProps) {
const [isCollapsed, setIsCollapsed] = React.useState(defaultCollapsed)
const [mail] = useMail()
return (
<TooltipProvider delayDuration={0}>
<ResizablePanelGroup
direction="horizontal"
onLayout={(sizes: number[]) => {
document.cookie = `react-resizable-panels:layout=${JSON.stringify(
sizes
)}`
}}
className="h-full max-h-[800px] items-stretch"
>
<ResizablePanel
defaultSize={defaultLayout[0]}
collapsedSize={navCollapsedSize}
collapsible={true}
minSize={15}
maxSize={20}
onCollapse={(collapsed) => {
setIsCollapsed(collapsed)
document.cookie = `react-resizable-panels:collapsed=${JSON.stringify(
collapsed
)}`
}}
className={cn(isCollapsed && "min-w-[50px] transition-all duration-300 ease-in-out")}
>
<div className={cn("flex h-[52px] items-center justify-center", isCollapsed ? 'h-[52px]': 'px-2')}>
<AccountSwitcher isCollapsed={isCollapsed} accounts={accounts} />
</div>
<Separator />
<Nav
isCollapsed={isCollapsed}
links={[
{
title: "Inbox",
label: "128",
icon: Inbox,
variant: "default",
},
{
title: "Drafts",
label: "9",
icon: File,
variant: "ghost",
},
{
title: "Sent",
label: "",
icon: Send,
variant: "ghost",
},
{
title: "Junk",
label: "23",
icon: ArchiveX,
variant: "ghost",
},
{
title: "Trash",
label: "",
icon: Trash2,
variant: "ghost",
},
{
title: "Archive",
label: "",
icon: Archive,
variant: "ghost",
},
]}
/>
<Separator />
<Nav
isCollapsed={isCollapsed}
links={[
{
title: "Social",
label: "972",
icon: Users2,
variant: "ghost",
},
{
title: "Updates",
label: "342",
icon: AlertCircle,
variant: "ghost",
},
{
title: "Forums",
label: "128",
icon: MessagesSquare,
variant: "ghost",
},
{
title: "Shopping",
label: "8",
icon: ShoppingCart,
variant: "ghost",
},
{
title: "Promotions",
label: "21",
icon: Archive,
variant: "ghost",
},
]}
/>
</ResizablePanel>
<ResizableHandle withHandle />
<ResizablePanel defaultSize={defaultLayout[1]} minSize={30}>
<Tabs defaultValue="all">
<div className="flex items-center px-4 py-2">
<h1 className="text-xl font-bold">Inbox</h1>
<TabsList className="ml-auto">
<TabsTrigger value="all" className="text-zinc-600 dark:text-zinc-200">All mail</TabsTrigger>
<TabsTrigger value="unread" className="text-zinc-600 dark:text-zinc-200">Unread</TabsTrigger>
</TabsList>
</div>
<Separator />
<div className="bg-background/95 p-4 backdrop-blur supports-[backdrop-filter]:bg-background/60">
<form>
<div className="relative">
<Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
<Input placeholder="Search" className="pl-8" />
</div>
</form>
</div>
<TabsContent value="all" className="m-0">
<MailList items={mails} />
</TabsContent>
<TabsContent value="unread" className="m-0">
<MailList items={mails.filter((item) => !item.read)} />
</TabsContent>
</Tabs>
</ResizablePanel>
<ResizableHandle withHandle />
<ResizablePanel defaultSize={defaultLayout[2]}>
<MailDisplay
mail={mails.find((item) => item.id === mail.selected) || null}
/>
</ResizablePanel>
</ResizablePanelGroup>
</TooltipProvider>
)
}

View File

@@ -0,0 +1,87 @@
"use client"
import Link from "next/link"
import { LucideIcon } from "lucide-react"
import { cn } from "@/lib/utils"
import { buttonVariants } from "@/registry/default/ui/button"
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/registry/new-york/ui/tooltip"
interface NavProps {
isCollapsed: boolean
links: {
title: string
label?: string
icon: LucideIcon
variant: "default" | "ghost"
}[]
}
export function Nav({ links, isCollapsed }: NavProps) {
return (
<div
data-collapsed={isCollapsed}
className="group flex flex-col gap-4 py-2 data-[collapsed=true]:py-2"
>
<nav className="grid gap-1 px-2 group-[[data-collapsed=true]]:justify-center group-[[data-collapsed=true]]:px-2">
{links.map((link, index) =>
isCollapsed ? (
<Tooltip key={index} delayDuration={0}>
<TooltipTrigger asChild>
<Link
href="#"
className={cn(
buttonVariants({ variant: link.variant, size: "icon" }),
"h-9 w-9",
link.variant === "default" &&
"dark:bg-muted dark:text-muted-foreground dark:hover:bg-muted dark:hover:text-white"
)}
>
<link.icon className="h-4 w-4" />
<span className="sr-only">{link.title}</span>
</Link>
</TooltipTrigger>
<TooltipContent side="right" className="flex items-center gap-4">
{link.title}
{link.label && (
<span className="ml-auto text-muted-foreground">
{link.label}
</span>
)}
</TooltipContent>
</Tooltip>
) : (
<Link
key={index}
href="#"
className={cn(
buttonVariants({ variant: link.variant, size: "sm" }),
link.variant === "default" &&
"dark:bg-muted dark:text-white dark:hover:bg-muted dark:hover:text-white",
"justify-start"
)}
>
<link.icon className="mr-2 h-4 w-4" />
{link.title}
{link.label && (
<span
className={cn(
"ml-auto",
link.variant === "default" &&
"text-background dark:text-white"
)}
>
{link.label}
</span>
)}
</Link>
)
)}
</nav>
</div>
)
}

View File

@@ -0,0 +1,300 @@
export const mails = [
{
id: "6c84fb90-12c4-11e1-840d-7b25c5ee775a",
name: "William Smith",
email: "williamsmith@example.com",
subject: "Meeting Tomorrow",
text: "Hi, let's have a meeting tomorrow to discuss the project. I've been reviewing the project details and have some ideas I'd like to share. It's crucial that we align on our next steps to ensure the project's success.\n\nPlease come prepared with any questions or insights you may have. Looking forward to our meeting!\n\nBest regards, William",
date: "2023-10-22T09:00:00",
read: true,
labels: ["meeting", "work", "important"],
},
{
id: "110e8400-e29b-11d4-a716-446655440000",
name: "Alice Smith",
email: "alicesmith@example.com",
subject: "Re: Project Update",
text: "Thank you for the project update. It looks great! I've gone through the report, and the progress is impressive. The team has done a fantastic job, and I appreciate the hard work everyone has put in.\n\nI have a few minor suggestions that I'll include in the attached document.\n\nLet's discuss these during our next meeting. Keep up the excellent work!\n\nBest regards, Alice",
date: "2023-10-22T10:30:00",
read: true,
labels: ["work", "important"],
},
{
id: "3e7c3f6d-bdf5-46ae-8d90-171300f27ae2",
name: "Bob Johnson",
email: "bobjohnson@example.com",
subject: "Weekend Plans",
text: "Any plans for the weekend? I was thinking of going hiking in the nearby mountains. It's been a while since we had some outdoor fun.\n\nIf you're interested, let me know, and we can plan the details. It'll be a great way to unwind and enjoy nature.\n\nLooking forward to your response!\n\nBest, Bob",
date: "2023-04-10T11:45:00",
read: true,
labels: ["personal"],
},
{
id: "61c35085-72d7-42b4-8d62-738f700d4b92",
name: "Emily Davis",
email: "emilydavis@example.com",
subject: "Re: Question about Budget",
text: "I have a question about the budget for the upcoming project. It seems like there's a discrepancy in the allocation of resources.\n\nI've reviewed the budget report and identified a few areas where we might be able to optimize our spending without compromising the project's quality.\n\nI've attached a detailed analysis for your reference. Let's discuss this further in our next meeting.\n\nThanks, Emily",
date: "2023-03-25T13:15:00",
read: false,
labels: ["work", "budget"],
},
{
id: "8f7b5db9-d935-4e42-8e05-1f1d0a3dfb97",
name: "Michael Wilson",
email: "michaelwilson@example.com",
subject: "Important Announcement",
text: "I have an important announcement to make during our team meeting. It pertains to a strategic shift in our approach to the upcoming product launch. We've received valuable feedback from our beta testers, and I believe it's time to make some adjustments to better meet our customers' needs.\n\nThis change is crucial to our success, and I look forward to discussing it with the team. Please be prepared to share your insights during the meeting.\n\nRegards, Michael",
date: "2023-03-10T15:00:00",
read: false,
labels: ["meeting", "work", "important"],
},
{
id: "1f0f2c02-e299-40de-9b1d-86ef9e42126b",
name: "Sarah Brown",
email: "sarahbrown@example.com",
subject: "Re: Feedback on Proposal",
text: "Thank you for your feedback on the proposal. It looks great! I'm pleased to hear that you found it promising. The team worked diligently to address all the key points you raised, and I believe we now have a strong foundation for the project.\n\nI've attached the revised proposal for your review.\n\nPlease let me know if you have any further comments or suggestions. Looking forward to your response.\n\nBest regards, Sarah",
date: "2023-02-15T16:30:00",
read: true,
labels: ["work"],
},
{
id: "17c0a96d-4415-42b1-8b4f-764efab57f66",
name: "David Lee",
email: "davidlee@example.com",
subject: "New Project Idea",
text: "I have an exciting new project idea to discuss with you. It involves expanding our services to target a niche market that has shown considerable growth in recent months.\n\nI've prepared a detailed proposal outlining the potential benefits and the strategy for execution.\n\nThis project has the potential to significantly impact our business positively. Let's set up a meeting to dive into the details and determine if it aligns with our current goals.\n\nBest regards, David",
date: "2023-01-28T17:45:00",
read: false,
labels: ["meeting", "work", "important"],
},
{
id: "2f0130cb-39fc-44c4-bb3c-0a4337edaaab",
name: "Olivia Wilson",
email: "oliviawilson@example.com",
subject: "Vacation Plans",
text: "Let's plan our vacation for next month. What do you think? I've been thinking of visiting a tropical paradise, and I've put together some destination options.\n\nI believe it's time for us to unwind and recharge. Please take a look at the options and let me know your preferences.\n\nWe can start making arrangements to ensure a smooth and enjoyable trip.\n\nExcited to hear your thoughts! Olivia",
date: "2022-12-20T18:30:00",
read: true,
labels: ["personal"],
},
{
id: "de305d54-75b4-431b-adb2-eb6b9e546014",
name: "James Martin",
email: "jamesmartin@example.com",
subject: "Re: Conference Registration",
text: "I've completed the registration for the conference next month. The event promises to be a great networking opportunity, and I'm looking forward to attending the various sessions and connecting with industry experts.\n\nI've also attached the conference schedule for your reference.\n\nIf there are any specific topics or sessions you'd like me to explore, please let me know. It's an exciting event, and I'll make the most of it.\n\nBest regards, James",
date: "2022-11-30T19:15:00",
read: true,
labels: ["work", "conference"],
},
{
id: "7dd90c63-00f6-40f3-bd87-5060a24e8ee7",
name: "Sophia White",
email: "sophiawhite@example.com",
subject: "Team Dinner",
text: "Let's have a team dinner next week to celebrate our success. We've achieved some significant milestones, and it's time to acknowledge our hard work and dedication.\n\nI've made reservations at a lovely restaurant, and I'm sure it'll be an enjoyable evening.\n\nPlease confirm your availability and any dietary preferences. Looking forward to a fun and memorable dinner with the team!\n\nBest, Sophia",
date: "2022-11-05T20:30:00",
read: false,
labels: ["meeting", "work"],
},
{
id: "99a88f78-3eb4-4d87-87b7-7b15a49a0a05",
name: "Daniel Johnson",
email: "danieljohnson@example.com",
subject: "Feedback Request",
text: "I'd like your feedback on the latest project deliverables. We've made significant progress, and I value your input to ensure we're on the right track.\n\nI've attached the deliverables for your review, and I'm particularly interested in any areas where you think we can further enhance the quality or efficiency.\n\nYour feedback is invaluable, and I appreciate your time and expertise. Let's work together to make this project a success.\n\nRegards, Daniel",
date: "2022-10-22T09:30:00",
read: false,
labels: ["work"],
},
{
id: "f47ac10b-58cc-4372-a567-0e02b2c3d479",
name: "Ava Taylor",
email: "avataylor@example.com",
subject: "Re: Meeting Agenda",
text: "Here's the agenda for our meeting next week. I've included all the topics we need to cover, as well as time allocations for each.\n\nIf you have any additional items to discuss or any specific points to address, please let me know, and we can integrate them into the agenda.\n\nIt's essential that our meeting is productive and addresses all relevant matters.\n\nLooking forward to our meeting! Ava",
date: "2022-10-10T10:45:00",
read: true,
labels: ["meeting", "work"],
},
{
id: "c1a0ecb4-2540-49c5-86f8-21e5ce79e4e6",
name: "William Anderson",
email: "williamanderson@example.com",
subject: "Product Launch Update",
text: "The product launch is on track. I'll provide an update during our call. We've made substantial progress in the development and marketing of our new product.\n\nI'm excited to share the latest updates with you during our upcoming call. It's crucial that we coordinate our efforts to ensure a successful launch. Please come prepared with any questions or insights you may have.\n\nLet's make this product launch a resounding success!\n\nBest regards, William",
date: "2022-09-20T12:00:00",
read: false,
labels: ["meeting", "work", "important"],
},
{
id: "ba54eefd-4097-4949-99f2-2a9ae4d1a836",
name: "Mia Harris",
email: "miaharris@example.com",
subject: "Re: Travel Itinerary",
text: "I've received the travel itinerary. It looks great! Thank you for your prompt assistance in arranging the details. I've reviewed the schedule and the accommodations, and everything seems to be in order. I'm looking forward to the trip, and I'm confident it'll be a smooth and enjoyable experience.\n\nIf there are any specific activities or attractions you recommend at our destination, please feel free to share your suggestions.\n\nExcited for the trip! Mia",
date: "2022-09-10T13:15:00",
read: true,
labels: ["personal", "travel"],
},
{
id: "df09b6ed-28bd-4e0c-85a9-9320ec5179aa",
name: "Ethan Clark",
email: "ethanclark@example.com",
subject: "Team Building Event",
text: "Let's plan a team-building event for our department. Team cohesion and morale are vital to our success, and I believe a well-organized team-building event can be incredibly beneficial. I've done some research and have a few ideas for fun and engaging activities.\n\nPlease let me know your thoughts and availability. We want this event to be both enjoyable and productive.\n\nTogether, we'll strengthen our team and boost our performance.\n\nRegards, Ethan",
date: "2022-08-25T15:30:00",
read: false,
labels: ["meeting", "work"],
},
{
id: "d67c1842-7f8b-4b4b-9be1-1b3b1ab4611d",
name: "Chloe Hall",
email: "chloehall@example.com",
subject: "Re: Budget Approval",
text: "The budget has been approved. We can proceed with the project. I'm delighted to inform you that our budget proposal has received the green light from the finance department. This is a significant milestone, and it means we can move forward with the project as planned.\n\nI've attached the finalized budget for your reference. Let's ensure that we stay on track and deliver the project on time and within budget.\n\nIt's an exciting time for us! Chloe",
date: "2022-08-10T16:45:00",
read: true,
labels: ["work", "budget"],
},
{
id: "6c9a7f94-8329-4d70-95d3-51f68c186ae1",
name: "Samuel Turner",
email: "samuelturner@example.com",
subject: "Weekend Hike",
text: "Who's up for a weekend hike in the mountains? I've been craving some outdoor adventure, and a hike in the mountains sounds like the perfect escape. If you're up for the challenge, we can explore some scenic trails and enjoy the beauty of nature.\n\nI've done some research and have a few routes in mind.\n\nLet me know if you're interested, and we can plan the details.\n\nIt's sure to be a memorable experience! Samuel",
date: "2022-07-28T17:30:00",
read: false,
labels: ["personal"],
},
]
export type Mail = (typeof mails)[number]
export const accounts = [
{
label: "Alicia Koch",
email: "alicia@example.com",
icon: (
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<title>Vercel</title>
<path d="M24 22.525H0l12-21.05 12 21.05z" fill="currentColor" />
</svg>
),
},
{
label: "Alicia Koch",
email: "alicia@gmail.com",
icon: (
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<title>Gmail</title>
<path
d="M24 5.457v13.909c0 .904-.732 1.636-1.636 1.636h-3.819V11.73L12 16.64l-6.545-4.91v9.273H1.636A1.636 1.636 0 0 1 0 19.366V5.457c0-2.023 2.309-3.178 3.927-1.964L5.455 4.64 12 9.548l6.545-4.91 1.528-1.145C21.69 2.28 24 3.434 24 5.457z"
fill="currentColor"
/>
</svg>
),
},
{
label: "Alicia Koch",
email: "alicia@me.com",
icon: (
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<title>iCloud</title>
<path
d="M13.762 4.29a6.51 6.51 0 0 0-5.669 3.332 3.571 3.571 0 0 0-1.558-.36 3.571 3.571 0 0 0-3.516 3A4.918 4.918 0 0 0 0 14.796a4.918 4.918 0 0 0 4.92 4.914 4.93 4.93 0 0 0 .617-.045h14.42c2.305-.272 4.041-2.258 4.043-4.589v-.009a4.594 4.594 0 0 0-3.727-4.508 6.51 6.51 0 0 0-6.511-6.27z"
fill="currentColor"
/>
</svg>
),
},
]
export type Account = (typeof accounts)[number]
export const contacts = [
{
name: "Emma Johnson",
email: "emma.johnson@example.com",
},
{
name: "Liam Wilson",
email: "liam.wilson@example.com",
},
{
name: "Olivia Davis",
email: "olivia.davis@example.com",
},
{
name: "Noah Martinez",
email: "noah.martinez@example.com",
},
{
name: "Ava Taylor",
email: "ava.taylor@example.com",
},
{
name: "Lucas Brown",
email: "lucas.brown@example.com",
},
{
name: "Sophia Smith",
email: "sophia.smith@example.com",
},
{
name: "Ethan Wilson",
email: "ethan.wilson@example.com",
},
{
name: "Isabella Jackson",
email: "isabella.jackson@example.com",
},
{
name: "Mia Clark",
email: "mia.clark@example.com",
},
{
name: "Mason Lee",
email: "mason.lee@example.com",
},
{
name: "Layla Harris",
email: "layla.harris@example.com",
},
{
name: "William Anderson",
email: "william.anderson@example.com",
},
{
name: "Ella White",
email: "ella.white@example.com",
},
{
name: "James Thomas",
email: "james.thomas@example.com",
},
{
name: "Harper Lewis",
email: "harper.lewis@example.com",
},
{
name: "Benjamin Moore",
email: "benjamin.moore@example.com",
},
{
name: "Aria Hall",
email: "aria.hall@example.com",
},
{
name: "Henry Turner",
email: "henry.turner@example.com",
},
{
name: "Scarlett Adams",
email: "scarlett.adams@example.com",
},
]
export type Contact = (typeof contacts)[number]

View File

@@ -0,0 +1,43 @@
import { cookies } from "next/headers"
import Image from "next/image"
import { Mail } from "@/app/examples/mail/components/mail"
import { accounts, mails } from "@/app/examples/mail/data"
export default function MailPage() {
const layout = cookies().get("react-resizable-panels:layout")
const collapsed = cookies().get("react-resizable-panels:collapsed")
const defaultLayout = layout ? JSON.parse(layout.value) : undefined
const defaultCollapsed = collapsed ? JSON.parse(collapsed.value) : undefined
return (
<>
<div className="md:hidden">
<Image
src="/examples/mail-dark.png"
width={1280}
height={727}
alt="Mail"
className="hidden dark:block"
/>
<Image
src="/examples/mail-light.png"
width={1280}
height={727}
alt="Mail"
className="block dark:hidden"
/>
</div>
<div className="hidden flex-col md:flex">
<Mail
accounts={accounts}
mails={mails}
defaultLayout={defaultLayout}
defaultCollapsed={defaultCollapsed}
navCollapsedSize={4}
/>
</div>
</>
)
}

View File

@@ -0,0 +1,15 @@
import { atom, useAtom } from "jotai"
import { Mail, mails } from "@/app/examples/mail/data"
type Config = {
selected: Mail["id"] | null
}
const configAtom = atom<Config>({
selected: mails[0].id,
})
export function useMail() {
return useAtom(configAtom)
}

View File

@@ -0,0 +1,95 @@
import Image from "next/image"
import { PlusCircledIcon } from "@radix-ui/react-icons"
import { cn } from "@/lib/utils"
import {
ContextMenu,
ContextMenuContent,
ContextMenuItem,
ContextMenuSeparator,
ContextMenuSub,
ContextMenuSubContent,
ContextMenuSubTrigger,
ContextMenuTrigger,
} from "@/registry/new-york/ui/context-menu"
import { Album } from "../data/albums"
import { playlists } from "../data/playlists"
interface AlbumArtworkProps extends React.HTMLAttributes<HTMLDivElement> {
album: Album
aspectRatio?: "portrait" | "square"
width?: number
height?: number
}
export function AlbumArtwork({
album,
aspectRatio = "portrait",
width,
height,
className,
...props
}: AlbumArtworkProps) {
return (
<div className={cn("space-y-3", className)} {...props}>
<ContextMenu>
<ContextMenuTrigger>
<div className="overflow-hidden rounded-md">
<Image
src={album.cover}
alt={album.name}
width={width}
height={height}
className={cn(
"h-auto w-auto object-cover transition-all hover:scale-105",
aspectRatio === "portrait" ? "aspect-[3/4]" : "aspect-square"
)}
/>
</div>
</ContextMenuTrigger>
<ContextMenuContent className="w-40">
<ContextMenuItem>Add to Library</ContextMenuItem>
<ContextMenuSub>
<ContextMenuSubTrigger>Add to Playlist</ContextMenuSubTrigger>
<ContextMenuSubContent className="w-48">
<ContextMenuItem>
<PlusCircledIcon className="mr-2 h-4 w-4" />
New Playlist
</ContextMenuItem>
<ContextMenuSeparator />
{playlists.map((playlist) => (
<ContextMenuItem key={playlist}>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
className="mr-2 h-4 w-4"
viewBox="0 0 24 24"
>
<path d="M21 15V6M18.5 18a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5ZM12 12H3M16 6H3M12 18H3" />
</svg>
{playlist}
</ContextMenuItem>
))}
</ContextMenuSubContent>
</ContextMenuSub>
<ContextMenuSeparator />
<ContextMenuItem>Play Next</ContextMenuItem>
<ContextMenuItem>Play Later</ContextMenuItem>
<ContextMenuItem>Create Station</ContextMenuItem>
<ContextMenuSeparator />
<ContextMenuItem>Like</ContextMenuItem>
<ContextMenuItem>Share</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>
<div className="space-y-1 text-sm">
<h3 className="font-medium leading-none">{album.name}</h3>
<p className="text-xs text-muted-foreground">{album.artist}</p>
</div>
</div>
)
}

View File

@@ -0,0 +1,200 @@
import {
Menubar,
MenubarCheckboxItem,
MenubarContent,
MenubarItem,
MenubarLabel,
MenubarMenu,
MenubarRadioGroup,
MenubarRadioItem,
MenubarSeparator,
MenubarShortcut,
MenubarSub,
MenubarSubContent,
MenubarSubTrigger,
MenubarTrigger,
} from "@/registry/new-york/ui/menubar"
export function Menu() {
return (
<Menubar className="rounded-none border-b border-none px-2 lg:px-4">
<MenubarMenu>
<MenubarTrigger className="font-bold">Music</MenubarTrigger>
<MenubarContent>
<MenubarItem>About Music</MenubarItem>
<MenubarSeparator />
<MenubarItem>
Preferences... <MenubarShortcut>,</MenubarShortcut>
</MenubarItem>
<MenubarSeparator />
<MenubarItem>
Hide Music... <MenubarShortcut>H</MenubarShortcut>
</MenubarItem>
<MenubarItem>
Hide Others... <MenubarShortcut>H</MenubarShortcut>
</MenubarItem>
<MenubarShortcut />
<MenubarItem>
Quit Music <MenubarShortcut>Q</MenubarShortcut>
</MenubarItem>
</MenubarContent>
</MenubarMenu>
<MenubarMenu>
<MenubarTrigger className="relative">File</MenubarTrigger>
<MenubarContent>
<MenubarSub>
<MenubarSubTrigger>New</MenubarSubTrigger>
<MenubarSubContent className="w-[230px]">
<MenubarItem>
Playlist <MenubarShortcut>N</MenubarShortcut>
</MenubarItem>
<MenubarItem disabled>
Playlist from Selection <MenubarShortcut>N</MenubarShortcut>
</MenubarItem>
<MenubarItem>
Smart Playlist... <MenubarShortcut>N</MenubarShortcut>
</MenubarItem>
<MenubarItem>Playlist Folder</MenubarItem>
<MenubarItem disabled>Genius Playlist</MenubarItem>
</MenubarSubContent>
</MenubarSub>
<MenubarItem>
Open Stream URL... <MenubarShortcut>U</MenubarShortcut>
</MenubarItem>
<MenubarItem>
Close Window <MenubarShortcut>W</MenubarShortcut>
</MenubarItem>
<MenubarSeparator />
<MenubarSub>
<MenubarSubTrigger>Library</MenubarSubTrigger>
<MenubarSubContent>
<MenubarItem>Update Cloud Library</MenubarItem>
<MenubarItem>Update Genius</MenubarItem>
<MenubarSeparator />
<MenubarItem>Organize Library...</MenubarItem>
<MenubarItem>Export Library...</MenubarItem>
<MenubarSeparator />
<MenubarItem>Import Playlist...</MenubarItem>
<MenubarItem disabled>Export Playlist...</MenubarItem>
<MenubarItem>Show Duplicate Items</MenubarItem>
<MenubarSeparator />
<MenubarItem>Get Album Artwork</MenubarItem>
<MenubarItem disabled>Get Track Names</MenubarItem>
</MenubarSubContent>
</MenubarSub>
<MenubarItem>
Import... <MenubarShortcut>O</MenubarShortcut>
</MenubarItem>
<MenubarItem disabled>Burn Playlist to Disc...</MenubarItem>
<MenubarSeparator />
<MenubarItem>
Show in Finder <MenubarShortcut>R</MenubarShortcut>{" "}
</MenubarItem>
<MenubarItem>Convert</MenubarItem>
<MenubarSeparator />
<MenubarItem>Page Setup...</MenubarItem>
<MenubarItem disabled>
Print... <MenubarShortcut>P</MenubarShortcut>
</MenubarItem>
</MenubarContent>
</MenubarMenu>
<MenubarMenu>
<MenubarTrigger>Edit</MenubarTrigger>
<MenubarContent>
<MenubarItem disabled>
Undo <MenubarShortcut>Z</MenubarShortcut>
</MenubarItem>
<MenubarItem disabled>
Redo <MenubarShortcut>Z</MenubarShortcut>
</MenubarItem>
<MenubarSeparator />
<MenubarItem disabled>
Cut <MenubarShortcut>X</MenubarShortcut>
</MenubarItem>
<MenubarItem disabled>
Copy <MenubarShortcut>C</MenubarShortcut>
</MenubarItem>
<MenubarItem disabled>
Paste <MenubarShortcut>V</MenubarShortcut>
</MenubarItem>
<MenubarSeparator />
<MenubarItem>
Select All <MenubarShortcut>A</MenubarShortcut>
</MenubarItem>
<MenubarItem disabled>
Deselect All <MenubarShortcut>A</MenubarShortcut>
</MenubarItem>
<MenubarSeparator />
<MenubarItem>
Smart Dictation...{" "}
<MenubarShortcut>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
className="h-4 w-4"
viewBox="0 0 24 24"
>
<path d="m12 8-9.04 9.06a2.82 2.82 0 1 0 3.98 3.98L16 12" />
<circle cx="17" cy="7" r="5" />
</svg>
</MenubarShortcut>
</MenubarItem>
<MenubarItem>
Emoji & Symbols{" "}
<MenubarShortcut>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
className="h-4 w-4"
viewBox="0 0 24 24"
>
<circle cx="12" cy="12" r="10" />
<path d="M2 12h20M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" />
</svg>
</MenubarShortcut>
</MenubarItem>
</MenubarContent>
</MenubarMenu>
<MenubarMenu>
<MenubarTrigger>View</MenubarTrigger>
<MenubarContent>
<MenubarCheckboxItem>Show Playing Next</MenubarCheckboxItem>
<MenubarCheckboxItem checked>Show Lyrics</MenubarCheckboxItem>
<MenubarSeparator />
<MenubarItem inset disabled>
Show Status Bar
</MenubarItem>
<MenubarSeparator />
<MenubarItem inset>Hide Sidebar</MenubarItem>
<MenubarItem disabled inset>
Enter Full Screen
</MenubarItem>
</MenubarContent>
</MenubarMenu>
<MenubarMenu>
<MenubarTrigger className="hidden md:block">Account</MenubarTrigger>
<MenubarContent forceMount>
<MenubarLabel inset>Switch Account</MenubarLabel>
<MenubarSeparator />
<MenubarRadioGroup value="benoit">
<MenubarRadioItem value="andy">Andy</MenubarRadioItem>
<MenubarRadioItem value="benoit">Benoit</MenubarRadioItem>
<MenubarRadioItem value="Luis">Luis</MenubarRadioItem>
</MenubarRadioGroup>
<MenubarSeparator />
<MenubarItem inset>Manage Family...</MenubarItem>
<MenubarSeparator />
<MenubarItem inset>Add Account...</MenubarItem>
</MenubarContent>
</MenubarMenu>
</Menubar>
)
}

View File

@@ -0,0 +1,64 @@
import { Button } from "@/registry/new-york/ui/button"
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/registry/new-york/ui/dialog"
import { Input } from "@/registry/new-york/ui/input"
import { Label } from "@/registry/new-york/ui/label"
export function PodcastEmptyPlaceholder() {
return (
<div className="flex h-[450px] shrink-0 items-center justify-center rounded-md border border-dashed">
<div className="mx-auto flex max-w-[420px] flex-col items-center justify-center text-center">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
className="h-10 w-10 text-muted-foreground"
viewBox="0 0 24 24"
>
<circle cx="12" cy="11" r="1" />
<path d="M11 17a1 1 0 0 1 2 0c0 .5-.34 3-.5 4.5a.5.5 0 0 1-1 0c-.16-1.5-.5-4-.5-4.5ZM8 14a5 5 0 1 1 8 0" />
<path d="M17 18.5a9 9 0 1 0-10 0" />
</svg>
<h3 className="mt-4 text-lg font-semibold">No episodes added</h3>
<p className="mb-4 mt-2 text-sm text-muted-foreground">
You have not added any podcasts. Add one below.
</p>
<Dialog>
<DialogTrigger asChild>
<Button size="sm" className="relative">
Add Podcast
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Add Podcast</DialogTitle>
<DialogDescription>
Copy and paste the podcast feed URL to import.
</DialogDescription>
</DialogHeader>
<div className="grid gap-4 py-4">
<div className="grid gap-2">
<Label htmlFor="url">Podcast URL</Label>
<Input id="url" placeholder="https://example.com/feed.xml" />
</div>
</div>
<DialogFooter>
<Button>Import Podcast</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
</div>
)
}

View File

@@ -0,0 +1,204 @@
import { cn } from "@/lib/utils"
import { Button } from "@/registry/new-york/ui/button"
import { ScrollArea } from "@/registry/new-york/ui/scroll-area"
import { Playlist } from "../data/playlists"
interface SidebarProps extends React.HTMLAttributes<HTMLDivElement> {
playlists: Playlist[]
}
export function Sidebar({ className, playlists }: SidebarProps) {
return (
<div className={cn("pb-12", className)}>
<div className="space-y-4 py-4">
<div className="px-3 py-2">
<h2 className="mb-2 px-4 text-lg font-semibold tracking-tight">
Discover
</h2>
<div className="space-y-1">
<Button variant="secondary" className="w-full justify-start">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="mr-2 h-4 w-4"
>
<circle cx="12" cy="12" r="10" />
<polygon points="10 8 16 12 10 16 10 8" />
</svg>
Listen Now
</Button>
<Button variant="ghost" className="w-full justify-start">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="mr-2 h-4 w-4"
>
<rect width="7" height="7" x="3" y="3" rx="1" />
<rect width="7" height="7" x="14" y="3" rx="1" />
<rect width="7" height="7" x="14" y="14" rx="1" />
<rect width="7" height="7" x="3" y="14" rx="1" />
</svg>
Browse
</Button>
<Button variant="ghost" className="w-full justify-start">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="mr-2 h-4 w-4"
>
<path d="M4.9 19.1C1 15.2 1 8.8 4.9 4.9" />
<path d="M7.8 16.2c-2.3-2.3-2.3-6.1 0-8.5" />
<circle cx="12" cy="12" r="2" />
<path d="M16.2 7.8c2.3 2.3 2.3 6.1 0 8.5" />
<path d="M19.1 4.9C23 8.8 23 15.1 19.1 19" />
</svg>
Radio
</Button>
</div>
</div>
<div className="px-3 py-2">
<h2 className="mb-2 px-4 text-lg font-semibold tracking-tight">
Library
</h2>
<div className="space-y-1">
<Button variant="ghost" className="w-full justify-start">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="mr-2 h-4 w-4"
>
<path d="M21 15V6" />
<path d="M18.5 18a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5Z" />
<path d="M12 12H3" />
<path d="M16 6H3" />
<path d="M12 18H3" />
</svg>
Playlists
</Button>
<Button variant="ghost" className="w-full justify-start">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="mr-2 h-4 w-4"
>
<circle cx="8" cy="18" r="4" />
<path d="M12 18V2l7 4" />
</svg>
Songs
</Button>
<Button variant="ghost" className="w-full justify-start">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="mr-2 h-4 w-4"
>
<path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" />
<circle cx="12" cy="7" r="4" />
</svg>
Made for You
</Button>
<Button variant="ghost" className="w-full justify-start">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="mr-2 h-4 w-4"
>
<path d="m12 8-9.04 9.06a2.82 2.82 0 1 0 3.98 3.98L16 12" />
<circle cx="17" cy="7" r="5" />
</svg>
Artists
</Button>
<Button variant="ghost" className="w-full justify-start">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="mr-2 h-4 w-4"
>
<path d="m16 6 4 14" />
<path d="M12 6v14" />
<path d="M8 8v12" />
<path d="M4 4v16" />
</svg>
Albums
</Button>
</div>
</div>
<div className="py-2">
<h2 className="relative px-7 text-lg font-semibold tracking-tight">
Playlists
</h2>
<ScrollArea className="h-[300px] px-1">
<div className="space-y-1 p-2">
{playlists?.map((playlist, i) => (
<Button
key={`${playlist}-${i}`}
variant="ghost"
className="w-full justify-start font-normal"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="mr-2 h-4 w-4"
>
<path d="M21 15V6" />
<path d="M18.5 18a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5Z" />
<path d="M12 12H3" />
<path d="M16 6H3" />
<path d="M12 18H3" />
</svg>
{playlist}
</Button>
))}
</div>
</ScrollArea>
</div>
</div>
</div>
)
}

View File

@@ -0,0 +1,71 @@
export interface Album {
name: string
artist: string
cover: string
}
export const listenNowAlbums: Album[] = [
{
name: "React Rendezvous",
artist: "Ethan Byte",
cover:
"https://images.unsplash.com/photo-1611348586804-61bf6c080437?w=300&dpr=2&q=80",
},
{
name: "Async Awakenings",
artist: "Nina Netcode",
cover:
"https://images.unsplash.com/photo-1468817814611-b7edf94b5d60?w=300&dpr=2&q=80",
},
{
name: "The Art of Reusability",
artist: "Lena Logic",
cover:
"https://images.unsplash.com/photo-1528143358888-6d3c7f67bd5d?w=300&dpr=2&q=80",
},
{
name: "Stateful Symphony",
artist: "Beth Binary",
cover:
"https://images.unsplash.com/photo-1490300472339-79e4adc6be4a?w=300&dpr=2&q=80",
},
]
export const madeForYouAlbums: Album[] = [
{
name: "Thinking Components",
artist: "Lena Logic",
cover:
"https://images.unsplash.com/photo-1615247001958-f4bc92fa6a4a?w=300&dpr=2&q=80",
},
{
name: "Functional Fury",
artist: "Beth Binary",
cover:
"https://images.unsplash.com/photo-1513745405825-efaf9a49315f?w=300&dpr=2&q=80",
},
{
name: "React Rendezvous",
artist: "Ethan Byte",
cover:
"https://images.unsplash.com/photo-1614113489855-66422ad300a4?w=300&dpr=2&q=80",
},
{
name: "Stateful Symphony",
artist: "Beth Binary",
cover:
"https://images.unsplash.com/photo-1446185250204-f94591f7d702?w=300&dpr=2&q=80",
},
{
name: "Async Awakenings",
artist: "Nina Netcode",
cover:
"https://images.unsplash.com/photo-1468817814611-b7edf94b5d60?w=300&dpr=2&q=80",
},
{
name: "The Art of Reusability",
artist: "Lena Logic",
cover:
"https://images.unsplash.com/photo-1490300472339-79e4adc6be4a?w=300&dpr=2&q=80",
},
]

View File

@@ -0,0 +1,16 @@
export type Playlist = (typeof playlists)[number]
export const playlists = [
"Recently Added",
"Recently Played",
"Top Songs",
"Top Albums",
"Top Artists",
"Logic Discography",
"Bedtime Beats",
"Feeling Happy",
"I miss Y2K Pop",
"Runtober",
"Mellow Days",
"Eminem Essentials",
]

View File

@@ -0,0 +1,157 @@
import { Metadata } from "next"
import Image from "next/image"
import { PlusCircledIcon } from "@radix-ui/react-icons"
import { Button } from "@/registry/new-york/ui/button"
import { ScrollArea, ScrollBar } from "@/registry/new-york/ui/scroll-area"
import { Separator } from "@/registry/new-york/ui/separator"
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from "@/registry/new-york/ui/tabs"
import { AlbumArtwork } from "./components/album-artwork"
import { Menu } from "./components/menu"
import { PodcastEmptyPlaceholder } from "./components/podcast-empty-placeholder"
import { Sidebar } from "./components/sidebar"
import { listenNowAlbums, madeForYouAlbums } from "./data/albums"
import { playlists } from "./data/playlists"
export const metadata: Metadata = {
title: "Music App",
description: "Example music app using the components.",
}
export default function MusicPage() {
return (
<>
<div className="md:hidden">
<Image
src="/examples/music-light.png"
width={1280}
height={1114}
alt="Music"
className="block dark:hidden"
/>
<Image
src="/examples/music-dark.png"
width={1280}
height={1114}
alt="Music"
className="hidden dark:block"
/>
</div>
<div className="hidden md:block">
<Menu />
<div className="border-t">
<div className="bg-background">
<div className="grid lg:grid-cols-5">
<Sidebar playlists={playlists} className="hidden lg:block" />
<div className="col-span-3 lg:col-span-4 lg:border-l">
<div className="h-full px-4 py-6 lg:px-8">
<Tabs defaultValue="music" className="h-full space-y-6">
<div className="space-between flex items-center">
<TabsList>
<TabsTrigger value="music" className="relative">
Music
</TabsTrigger>
<TabsTrigger value="podcasts">Podcasts</TabsTrigger>
<TabsTrigger value="live" disabled>
Live
</TabsTrigger>
</TabsList>
<div className="ml-auto mr-4">
<Button>
<PlusCircledIcon className="mr-2 h-4 w-4" />
Add music
</Button>
</div>
</div>
<TabsContent
value="music"
className="border-none p-0 outline-none"
>
<div className="flex items-center justify-between">
<div className="space-y-1">
<h2 className="text-2xl font-semibold tracking-tight">
Listen Now
</h2>
<p className="text-sm text-muted-foreground">
Top picks for you. Updated daily.
</p>
</div>
</div>
<Separator className="my-4" />
<div className="relative">
<ScrollArea>
<div className="flex space-x-4 pb-4">
{listenNowAlbums.map((album) => (
<AlbumArtwork
key={album.name}
album={album}
className="w-[250px]"
aspectRatio="portrait"
width={250}
height={330}
/>
))}
</div>
<ScrollBar orientation="horizontal" />
</ScrollArea>
</div>
<div className="mt-6 space-y-1">
<h2 className="text-2xl font-semibold tracking-tight">
Made for You
</h2>
<p className="text-sm text-muted-foreground">
Your personal playlists. Updated daily.
</p>
</div>
<Separator className="my-4" />
<div className="relative">
<ScrollArea>
<div className="flex space-x-4 pb-4">
{madeForYouAlbums.map((album) => (
<AlbumArtwork
key={album.name}
album={album}
className="w-[150px]"
aspectRatio="square"
width={150}
height={150}
/>
))}
</div>
<ScrollBar orientation="horizontal" />
</ScrollArea>
</div>
</TabsContent>
<TabsContent
value="podcasts"
className="h-full flex-col border-none p-0 data-[state=active]:flex"
>
<div className="flex items-center justify-between">
<div className="space-y-1">
<h2 className="text-2xl font-semibold tracking-tight">
New Episodes
</h2>
<p className="text-sm text-muted-foreground">
Your favorite podcasts. Updated daily.
</p>
</div>
</div>
<Separator className="my-4" />
<PodcastEmptyPlaceholder />
</TabsContent>
</Tabs>
</div>
</div>
</div>
</div>
</div>
</div>
</>
)
}

View File

@@ -1,6 +1,4 @@
"use client"
import { Button } from "@/components/ui/button"
import { Button } from "@/registry/new-york/ui/button"
import {
Dialog,
DialogContent,
@@ -8,13 +6,13 @@ import {
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
} from "@/registry/new-york/ui/dialog"
export function CodeViewer() {
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="subtle">View code</Button>
<Button variant="secondary">View code</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[625px]">
<DialogHeader>
@@ -27,7 +25,7 @@ export function CodeViewer() {
<div className="grid gap-4">
<div className="rounded-md bg-black p-6">
<pre>
<code className="grid gap-1 text-sm text-slate-50 [&_span]:h-4">
<code className="grid gap-1 text-sm text-muted-foreground [&_span]:h-4">
<span>
<span className="text-sky-300">import</span> os
</span>
@@ -78,7 +76,7 @@ export function CodeViewer() {
</pre>
</div>
<div>
<p className="text-sm text-slate-500 dark:text-slate-400">
<p className="text-sm text-muted-foreground">
Your API Key can be found here. You should use environment
variables or a secret management tool to expose your key to your
applications.

View File

@@ -7,9 +7,9 @@ import {
HoverCard,
HoverCardContent,
HoverCardTrigger,
} from "@/components/ui/hover-card"
import { Label } from "@/components/ui/label"
import { Slider } from "@/components/ui/slider"
} from "@/registry/new-york/ui/hover-card"
import { Label } from "@/registry/new-york/ui/label"
import { Slider } from "@/registry/new-york/ui/slider"
interface MaxLengthSelectorProps {
defaultValue: SliderProps["defaultValue"]
@@ -25,7 +25,7 @@ export function MaxLengthSelector({ defaultValue }: MaxLengthSelectorProps) {
<div className="grid gap-4">
<div className="flex items-center justify-between">
<Label htmlFor="maxlength">Maximum Length</Label>
<span className="w-12 rounded-md border border-transparent px-2 py-0.5 text-right text-sm text-slate-600 hover:border-slate-100 dark:text-slate-400 dark:hover:border-slate-800">
<span className="w-12 rounded-md border border-transparent px-2 py-0.5 text-right text-sm text-muted-foreground hover:border-border">
{value}
</span>
</div>

View File

@@ -1,14 +1,12 @@
"use client"
import * as React from "react"
import { useMutationObserver } from "@/hooks/use-mutation-observer"
import { Model } from "@/types"
import { CaretSortIcon, CheckIcon } from "@radix-ui/react-icons"
import { PopoverProps } from "@radix-ui/react-popover"
import { Check, ChevronsUpDown } from "lucide-react"
import { ModelType } from "@/data/models"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { useMutationObserver } from "@/hooks/use-mutation-observer"
import { Button } from "@/registry/new-york/ui/button"
import {
Command,
CommandEmpty,
@@ -16,18 +14,20 @@ import {
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command"
} from "@/registry/new-york/ui/command"
import {
HoverCard,
HoverCardContent,
HoverCardTrigger,
} from "@/components/ui/hover-card"
import { Label } from "@/components/ui/label"
} from "@/registry/new-york/ui/hover-card"
import { Label } from "@/registry/new-york/ui/label"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover"
} from "@/registry/new-york/ui/popover"
import { Model, ModelType } from "../data/models"
interface ModelSelectorProps extends PopoverProps {
types: readonly ModelType[]
@@ -64,7 +64,7 @@ export function ModelSelector({ models, types, ...props }: ModelSelectorProps) {
className="w-full justify-between"
>
{selectedModel ? selectedModel.name : "Select a model..."}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
<CaretSortIcon className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent align="end" className="w-[250px] p-0">
@@ -77,7 +77,7 @@ export function ModelSelector({ models, types, ...props }: ModelSelectorProps) {
>
<div className="grid gap-2">
<h4 className="font-medium leading-none">{peekedModel.name}</h4>
<div className="text-sm text-slate-500 dark:text-slate-400">
<div className="text-sm text-muted-foreground">
{peekedModel.description}
</div>
{peekedModel.strengths ? (
@@ -85,7 +85,7 @@ export function ModelSelector({ models, types, ...props }: ModelSelectorProps) {
<h5 className="text-sm font-medium leading-none">
Strengths
</h5>
<ul className="text-sm text-slate-500 dark:text-slate-400">
<ul className="text-sm text-muted-foreground">
{peekedModel.strengths}
</ul>
</div>
@@ -135,12 +135,12 @@ function ModelItem({ model, isSelected, onSelect, onPeek }: ModelItemProps) {
const ref = React.useRef<HTMLDivElement>(null)
useMutationObserver(ref, (mutations) => {
const mutation = mutations.find(
(mutation) => mutation.attributeName === "aria-selected"
)
if (mutation && mutation.target?.getAttribute("aria-selected")) {
onPeek(model)
for (const mutation of mutations) {
if (mutation.type === "attributes") {
if (mutation.attributeName === "aria-selected") {
onPeek(model)
}
}
}
})
@@ -149,10 +149,10 @@ function ModelItem({ model, isSelected, onSelect, onPeek }: ModelItemProps) {
key={model.id}
onSelect={onSelect}
ref={ref}
className="aria-selected:bg-slate-900 aria-selected:text-slate-50"
className="aria-selected:bg-primary aria-selected:text-primary-foreground"
>
{model.name}
<Check
<CheckIcon
className={cn(
"ml-auto h-4 w-4",
isSelected ? "opacity-100" : "opacity-0"

View File

@@ -1,9 +1,8 @@
"use client"
import * as React from "react"
import { toast } from "@/hooks/use-toast"
import { Dialog } from "@radix-ui/react-dialog"
import { Flag, MoreHorizontal, Trash } from "lucide-react"
import { DotsHorizontalIcon } from "@radix-ui/react-icons"
import {
AlertDialog,
@@ -13,24 +12,25 @@ import {
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog"
import { Button } from "@/components/ui/button"
} from "@/registry/new-york/ui/alert-dialog"
import { Button } from "@/registry/new-york/ui/button"
import {
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog"
} from "@/registry/new-york/ui/dialog"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { Label } from "@/components/ui/label"
import { Switch } from "@/components/ui/switch"
} from "@/registry/new-york/ui/dropdown-menu"
import { Label } from "@/registry/new-york/ui/label"
import { Switch } from "@/registry/new-york/ui/switch"
import { toast } from "@/registry/new-york/ui/use-toast"
export function PresetActions() {
const [open, setIsOpen] = React.useState(false)
@@ -40,14 +40,13 @@ export function PresetActions() {
<>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="subtle">
<Button variant="secondary">
<span className="sr-only">Actions</span>
<MoreHorizontal className="h-4 w-4" />
<DotsHorizontalIcon className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onSelect={() => setIsOpen(true)}>
<Flag className="mr-2 h-4 w-4" />
Content filter preferences
</DropdownMenuItem>
<DropdownMenuSeparator />
@@ -55,7 +54,6 @@ export function PresetActions() {
onSelect={() => setShowDeleteDialog(true)}
className="text-red-600"
>
<Trash className="mr-2 h-4 w-4" />
Delete preset
</DropdownMenuItem>
</DropdownMenuContent>
@@ -71,7 +69,7 @@ export function PresetActions() {
</DialogDescription>
</DialogHeader>
<div className="py-6">
<h4 className="text-sm text-slate-500 dark:text-slate-400">
<h4 className="text-sm text-muted-foreground">
Playground Warnings
</h4>
<div className="flex items-start justify-between space-x-4 pt-3">
@@ -80,7 +78,7 @@ export function PresetActions() {
<span className="font-semibold">
Show a warning when content is flagged
</span>
<span className="text-sm text-slate-500 dark:text-slate-400">
<span className="text-sm text-muted-foreground">
A warning will be shown when sexual, hateful, violent or
self-harm content is detected.
</span>
@@ -88,7 +86,7 @@ export function PresetActions() {
</div>
</div>
<DialogFooter>
<Button variant="subtle" onClick={() => setIsOpen(false)}>
<Button variant="secondary" onClick={() => setIsOpen(false)}>
Close
</Button>
</DialogFooter>
@@ -97,7 +95,7 @@ export function PresetActions() {
<AlertDialog open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you sure absolutely sure?</AlertDialogTitle>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone. This preset will no longer be
accessible by you or others you&apos;ve shared it with.

View File

@@ -1,6 +1,4 @@
"use client"
import { Button } from "@/components/ui/button"
import { Button } from "@/registry/new-york/ui/button"
import {
Dialog,
DialogContent,
@@ -9,15 +7,15 @@ import {
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
} from "@/registry/new-york/ui/dialog"
import { Input } from "@/registry/new-york/ui/input"
import { Label } from "@/registry/new-york/ui/label"
export function PresetSave() {
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="subtle">Save</Button>
<Button variant="secondary">Save</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[475px]">
<DialogHeader>

View File

@@ -1,26 +1,26 @@
"use client"
import * as React from "react"
import Link from "next/link"
import { useRouter } from "next/navigation"
import { Preset } from "@/types"
import { CaretSortIcon, CheckIcon } from "@radix-ui/react-icons"
import { PopoverProps } from "@radix-ui/react-popover"
import { Check, ChevronsUpDown } from "lucide-react"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { Button } from "@/registry/new-york/ui/button"
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
} from "@/components/ui/command"
} from "@/registry/new-york/ui/command"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover"
} from "@/registry/new-york/ui/popover"
import { Preset } from "../data/presets"
interface PresetSelectorProps extends PopoverProps {
presets: Preset[]
@@ -39,10 +39,10 @@ export function PresetSelector({ presets, ...props }: PresetSelectorProps) {
role="combobox"
aria-label="Load a preset..."
aria-expanded={open}
className="max-w-[300px] flex-1 justify-between"
className="flex-1 justify-between md:max-w-[200px] lg:max-w-[300px]"
>
{selectedPreset ? selectedPreset.name : "Load a preset..."}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
<CaretSortIcon className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-[300px] p-0">
@@ -59,7 +59,7 @@ export function PresetSelector({ presets, ...props }: PresetSelectorProps) {
}}
>
{preset.name}
<Check
<CheckIcon
className={cn(
"ml-auto h-4 w-4",
selectedPreset?.id === preset.id

View File

@@ -1,38 +1,24 @@
"use client"
import { CopyIcon } from "@radix-ui/react-icons"
import { PopoverProps } from "@radix-ui/react-popover"
import { Clipboard, Copy } from "lucide-react"
import { Button } from "@/components/ui/button"
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Button } from "@/registry/new-york/ui/button"
import { Input } from "@/registry/new-york/ui/input"
import { Label } from "@/registry/new-york/ui/label"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover"
} from "@/registry/new-york/ui/popover"
export function PresetShare() {
return (
<Popover>
<PopoverTrigger asChild>
<Button variant="subtle">Share</Button>
<Button variant="secondary">Share</Button>
</PopoverTrigger>
<PopoverContent align="end" className="w-[520px]">
<div className="flex flex-col space-y-2 text-center sm:text-left">
<h3 className="text-lg font-semibold text-slate-900 dark:text-slate-50">
Share preset
</h3>
<p className="text-sm text-slate-500 dark:text-slate-400">
<h3 className="text-lg font-semibold">Share preset</h3>
<p className="text-sm text-muted-foreground">
Anyone who has this link and an OpenAI account will be able to view
this.
</p>
@@ -51,7 +37,7 @@ export function PresetShare() {
</div>
<Button type="submit" size="sm" className="px-3">
<span className="sr-only">Copy</span>
<Copy className="h-4 w-4" />
<CopyIcon className="h-4 w-4" />
</Button>
</div>
</PopoverContent>

View File

@@ -7,9 +7,9 @@ import {
HoverCard,
HoverCardContent,
HoverCardTrigger,
} from "@/components/ui/hover-card"
import { Label } from "@/components/ui/label"
import { Slider } from "@/components/ui/slider"
} from "@/registry/new-york/ui/hover-card"
import { Label } from "@/registry/new-york/ui/label"
import { Slider } from "@/registry/new-york/ui/slider"
interface TemperatureSelectorProps {
defaultValue: SliderProps["defaultValue"]
@@ -27,7 +27,7 @@ export function TemperatureSelector({
<div className="grid gap-4">
<div className="flex items-center justify-between">
<Label htmlFor="temperature">Temperature</Label>
<span className="w-12 rounded-md border border-transparent px-2 py-0.5 text-right text-sm text-slate-600 hover:border-slate-100 dark:text-slate-400 dark:hover:border-slate-800">
<span className="w-12 rounded-md border border-transparent px-2 py-0.5 text-right text-sm text-muted-foreground hover:border-border">
{value}
</span>
</div>

View File

@@ -7,9 +7,9 @@ import {
HoverCard,
HoverCardContent,
HoverCardTrigger,
} from "@/components/ui/hover-card"
import { Label } from "@/components/ui/label"
import { Slider } from "@/components/ui/slider"
} from "@/registry/new-york/ui/hover-card"
import { Label } from "@/registry/new-york/ui/label"
import { Slider } from "@/registry/new-york/ui/slider"
interface TopPSelectorProps {
defaultValue: SliderProps["defaultValue"]
@@ -25,7 +25,7 @@ export function TopPSelector({ defaultValue }: TopPSelectorProps) {
<div className="grid gap-4">
<div className="flex items-center justify-between">
<Label htmlFor="top-p">Top P</Label>
<span className="w-12 rounded-md border border-transparent px-2 py-0.5 text-right text-sm text-slate-600 hover:border-slate-100 dark:text-slate-400 dark:hover:border-slate-800">
<span className="w-12 rounded-md border border-transparent px-2 py-0.5 text-right text-sm text-muted-foreground hover:border-border">
{value}
</span>
</div>

View File

@@ -1,8 +1,14 @@
import { Model } from "@/types"
export const types = ["GPT-3", "Codex"] as const
export type ModelType = typeof types[number]
export type ModelType = (typeof types)[number]
export interface Model<Type = string> {
id: string
name: string
description: string
strengths?: string
type: Type
}
export const models: Model<ModelType>[] = [
{
@@ -30,7 +36,7 @@ export const models: Model<ModelType>[] = [
strengths: "Moderate classification, semantic search",
},
{
id: " be638fb1-973b-4471-a49c-290325085802",
id: "be638fb1-973b-4471-a49c-290325085802",
name: "text-ada-001",
description:
"Capable of very simple tasks, usually the fastest model in the GPT-3 series, and lowest cost.",

View File

@@ -1,4 +1,7 @@
import { Preset } from "@/types"
export interface Preset {
id: string
name: string
}
export const presets: Preset[] = [
{

View File

@@ -0,0 +1,329 @@
import { Metadata } from "next"
import Image from "next/image"
import { CounterClockwiseClockIcon } from "@radix-ui/react-icons"
import { Button } from "@/registry/new-york/ui/button"
import {
HoverCard,
HoverCardContent,
HoverCardTrigger,
} from "@/registry/new-york/ui/hover-card"
import { Label } from "@/registry/new-york/ui/label"
import { Separator } from "@/registry/new-york/ui/separator"
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from "@/registry/new-york/ui/tabs"
import { Textarea } from "@/registry/new-york/ui/textarea"
import { CodeViewer } from "./components/code-viewer"
import { MaxLengthSelector } from "./components/maxlength-selector"
import { ModelSelector } from "./components/model-selector"
import { PresetActions } from "./components/preset-actions"
import { PresetSave } from "./components/preset-save"
import { PresetSelector } from "./components/preset-selector"
import { PresetShare } from "./components/preset-share"
import { TemperatureSelector } from "./components/temperature-selector"
import { TopPSelector } from "./components/top-p-selector"
import { models, types } from "./data/models"
import { presets } from "./data/presets"
export const metadata: Metadata = {
title: "Playground",
description: "The OpenAI Playground built using the components.",
}
export default function PlaygroundPage() {
return (
<>
<div className="md:hidden">
<Image
src="/examples/playground-light.png"
width={1280}
height={916}
alt="Playground"
className="block dark:hidden"
/>
<Image
src="/examples/playground-dark.png"
width={1280}
height={916}
alt="Playground"
className="hidden dark:block"
/>
</div>
<div className="hidden h-full flex-col md:flex">
<div className="container flex flex-col items-start justify-between space-y-2 py-4 sm:flex-row sm:items-center sm:space-y-0 md:h-16">
<h2 className="text-lg font-semibold">Playground</h2>
<div className="ml-auto flex w-full space-x-2 sm:justify-end">
<PresetSelector presets={presets} />
<PresetSave />
<div className="hidden space-x-2 md:flex">
<CodeViewer />
<PresetShare />
</div>
<PresetActions />
</div>
</div>
<Separator />
<Tabs defaultValue="complete" className="flex-1">
<div className="container h-full py-6">
<div className="grid h-full items-stretch gap-6 md:grid-cols-[1fr_200px]">
<div className="hidden flex-col space-y-4 sm:flex md:order-2">
<div className="grid gap-2">
<HoverCard openDelay={200}>
<HoverCardTrigger asChild>
<span className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">
Mode
</span>
</HoverCardTrigger>
<HoverCardContent className="w-[320px] text-sm" side="left">
Choose the interface that best suits your task. You can
provide: a simple prompt to complete, starting and ending
text to insert a completion within, or some text with
instructions to edit it.
</HoverCardContent>
</HoverCard>
<TabsList className="grid grid-cols-3">
<TabsTrigger value="complete">
<span className="sr-only">Complete</span>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="none"
className="h-5 w-5"
>
<rect
x="4"
y="3"
width="12"
height="2"
rx="1"
fill="currentColor"
></rect>
<rect
x="4"
y="7"
width="12"
height="2"
rx="1"
fill="currentColor"
></rect>
<rect
x="4"
y="11"
width="3"
height="2"
rx="1"
fill="currentColor"
></rect>
<rect
x="4"
y="15"
width="3"
height="2"
rx="1"
fill="currentColor"
></rect>
<rect
x="8.5"
y="11"
width="3"
height="2"
rx="1"
fill="currentColor"
></rect>
<rect
x="8.5"
y="15"
width="3"
height="2"
rx="1"
fill="currentColor"
></rect>
<rect
x="13"
y="11"
width="3"
height="2"
rx="1"
fill="currentColor"
></rect>
</svg>
</TabsTrigger>
<TabsTrigger value="insert">
<span className="sr-only">Insert</span>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="none"
className="h-5 w-5"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M14.491 7.769a.888.888 0 0 1 .287.648.888.888 0 0 1-.287.648l-3.916 3.667a1.013 1.013 0 0 1-.692.268c-.26 0-.509-.097-.692-.268L5.275 9.065A.886.886 0 0 1 5 8.42a.889.889 0 0 1 .287-.64c.181-.17.427-.267.683-.269.257-.002.504.09.69.258L8.903 9.87V3.917c0-.243.103-.477.287-.649.183-.171.432-.268.692-.268.26 0 .509.097.692.268a.888.888 0 0 1 .287.649V9.87l2.245-2.102c.183-.172.432-.269.692-.269.26 0 .508.097.692.269Z"
fill="currentColor"
></path>
<rect
x="4"
y="15"
width="3"
height="2"
rx="1"
fill="currentColor"
></rect>
<rect
x="8.5"
y="15"
width="3"
height="2"
rx="1"
fill="currentColor"
></rect>
<rect
x="13"
y="15"
width="3"
height="2"
rx="1"
fill="currentColor"
></rect>
</svg>
</TabsTrigger>
<TabsTrigger value="edit">
<span className="sr-only">Edit</span>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="none"
className="h-5 w-5"
>
<rect
x="4"
y="3"
width="12"
height="2"
rx="1"
fill="currentColor"
></rect>
<rect
x="4"
y="7"
width="12"
height="2"
rx="1"
fill="currentColor"
></rect>
<rect
x="4"
y="11"
width="3"
height="2"
rx="1"
fill="currentColor"
></rect>
<rect
x="4"
y="15"
width="4"
height="2"
rx="1"
fill="currentColor"
></rect>
<rect
x="8.5"
y="11"
width="3"
height="2"
rx="1"
fill="currentColor"
></rect>
<path
d="M17.154 11.346a1.182 1.182 0 0 0-1.671 0L11 15.829V17.5h1.671l4.483-4.483a1.182 1.182 0 0 0 0-1.671Z"
fill="currentColor"
></path>
</svg>
</TabsTrigger>
</TabsList>
</div>
<ModelSelector types={types} models={models} />
<TemperatureSelector defaultValue={[0.56]} />
<MaxLengthSelector defaultValue={[256]} />
<TopPSelector defaultValue={[0.9]} />
</div>
<div className="md:order-1">
<TabsContent value="complete" className="mt-0 border-0 p-0">
<div className="flex h-full flex-col space-y-4">
<Textarea
placeholder="Write a tagline for an ice cream shop"
className="min-h-[400px] flex-1 p-4 md:min-h-[700px] lg:min-h-[700px]"
/>
<div className="flex items-center space-x-2">
<Button>Submit</Button>
<Button variant="secondary">
<span className="sr-only">Show history</span>
<CounterClockwiseClockIcon className="h-4 w-4" />
</Button>
</div>
</div>
</TabsContent>
<TabsContent value="insert" className="mt-0 border-0 p-0">
<div className="flex flex-col space-y-4">
<div className="grid h-full grid-rows-2 gap-6 lg:grid-cols-2 lg:grid-rows-1">
<Textarea
placeholder="We're writing to [inset]. Congrats from OpenAI!"
className="h-full min-h-[300px] lg:min-h-[700px] xl:min-h-[700px]"
/>
<div className="rounded-md border bg-muted"></div>
</div>
<div className="flex items-center space-x-2">
<Button>Submit</Button>
<Button variant="secondary">
<span className="sr-only">Show history</span>
<CounterClockwiseClockIcon className="h-4 w-4" />
</Button>
</div>
</div>
</TabsContent>
<TabsContent value="edit" className="mt-0 border-0 p-0">
<div className="flex flex-col space-y-4">
<div className="grid h-full gap-6 lg:grid-cols-2">
<div className="flex flex-col space-y-4">
<div className="flex flex-1 flex-col space-y-2">
<Label htmlFor="input">Input</Label>
<Textarea
id="input"
placeholder="We is going to the market."
className="flex-1 lg:min-h-[580px]"
/>
</div>
<div className="flex flex-col space-y-2">
<Label htmlFor="instructions">Instructions</Label>
<Textarea
id="instructions"
placeholder="Fix the grammar."
/>
</div>
</div>
<div className="mt-[21px] min-h-[400px] rounded-md border bg-muted lg:min-h-[700px]" />
</div>
<div className="flex items-center space-x-2">
<Button>Submit</Button>
<Button variant="secondary">
<span className="sr-only">Show history</span>
<CounterClockwiseClockIcon className="h-4 w-4" />
</Button>
</div>
</div>
</TabsContent>
</div>
</div>
</div>
</Tabs>
</div>
</>
)
}

View File

@@ -0,0 +1,123 @@
"use client"
import { ColumnDef } from "@tanstack/react-table"
import { Badge } from "@/registry/new-york/ui/badge"
import { Checkbox } from "@/registry/new-york/ui/checkbox"
import { labels, priorities, statuses } from "../data/data"
import { Task } from "../data/schema"
import { DataTableColumnHeader } from "./data-table-column-header"
import { DataTableRowActions } from "./data-table-row-actions"
export const columns: ColumnDef<Task>[] = [
{
id: "select",
header: ({ table }) => (
<Checkbox
checked={
table.getIsAllPageRowsSelected() ||
(table.getIsSomePageRowsSelected() && "indeterminate")
}
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
aria-label="Select all"
className="translate-y-[2px]"
/>
),
cell: ({ row }) => (
<Checkbox
checked={row.getIsSelected()}
onCheckedChange={(value) => row.toggleSelected(!!value)}
aria-label="Select row"
className="translate-y-[2px]"
/>
),
enableSorting: false,
enableHiding: false,
},
{
accessorKey: "id",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Task" />
),
cell: ({ row }) => <div className="w-[80px]">{row.getValue("id")}</div>,
enableSorting: false,
enableHiding: false,
},
{
accessorKey: "title",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Title" />
),
cell: ({ row }) => {
const label = labels.find((label) => label.value === row.original.label)
return (
<div className="flex space-x-2">
{label && <Badge variant="outline">{label.label}</Badge>}
<span className="max-w-[500px] truncate font-medium">
{row.getValue("title")}
</span>
</div>
)
},
},
{
accessorKey: "status",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Status" />
),
cell: ({ row }) => {
const status = statuses.find(
(status) => status.value === row.getValue("status")
)
if (!status) {
return null
}
return (
<div className="flex w-[100px] items-center">
{status.icon && (
<status.icon className="mr-2 h-4 w-4 text-muted-foreground" />
)}
<span>{status.label}</span>
</div>
)
},
filterFn: (row, id, value) => {
return value.includes(row.getValue(id))
},
},
{
accessorKey: "priority",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Priority" />
),
cell: ({ row }) => {
const priority = priorities.find(
(priority) => priority.value === row.getValue("priority")
)
if (!priority) {
return null
}
return (
<div className="flex items-center">
{priority.icon && (
<priority.icon className="mr-2 h-4 w-4 text-muted-foreground" />
)}
<span>{priority.label}</span>
</div>
)
},
filterFn: (row, id, value) => {
return value.includes(row.getValue(id))
},
},
{
id: "actions",
cell: ({ row }) => <DataTableRowActions row={row} />,
},
]

View File

@@ -0,0 +1,71 @@
import {
ArrowDownIcon,
ArrowUpIcon,
CaretSortIcon,
EyeNoneIcon,
} from "@radix-ui/react-icons"
import { Column } from "@tanstack/react-table"
import { cn } from "@/lib/utils"
import { Button } from "@/registry/new-york/ui/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/registry/new-york/ui/dropdown-menu"
interface DataTableColumnHeaderProps<TData, TValue>
extends React.HTMLAttributes<HTMLDivElement> {
column: Column<TData, TValue>
title: string
}
export function DataTableColumnHeader<TData, TValue>({
column,
title,
className,
}: DataTableColumnHeaderProps<TData, TValue>) {
if (!column.getCanSort()) {
return <div className={cn(className)}>{title}</div>
}
return (
<div className={cn("flex items-center space-x-2", className)}>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="sm"
className="-ml-3 h-8 data-[state=open]:bg-accent"
>
<span>{title}</span>
{column.getIsSorted() === "desc" ? (
<ArrowDownIcon className="ml-2 h-4 w-4" />
) : column.getIsSorted() === "asc" ? (
<ArrowUpIcon className="ml-2 h-4 w-4" />
) : (
<CaretSortIcon className="ml-2 h-4 w-4" />
)}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuItem onClick={() => column.toggleSorting(false)}>
<ArrowUpIcon className="mr-2 h-3.5 w-3.5 text-muted-foreground/70" />
Asc
</DropdownMenuItem>
<DropdownMenuItem onClick={() => column.toggleSorting(true)}>
<ArrowDownIcon className="mr-2 h-3.5 w-3.5 text-muted-foreground/70" />
Desc
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={() => column.toggleVisibility(false)}>
<EyeNoneIcon className="mr-2 h-3.5 w-3.5 text-muted-foreground/70" />
Hide
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
)
}

View File

@@ -0,0 +1,147 @@
import * as React from "react"
import { CheckIcon, PlusCircledIcon } from "@radix-ui/react-icons"
import { Column } from "@tanstack/react-table"
import { cn } from "@/lib/utils"
import { Badge } from "@/registry/new-york/ui/badge"
import { Button } from "@/registry/new-york/ui/button"
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandSeparator,
} from "@/registry/new-york/ui/command"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/registry/new-york/ui/popover"
import { Separator } from "@/registry/new-york/ui/separator"
interface DataTableFacetedFilterProps<TData, TValue> {
column?: Column<TData, TValue>
title?: string
options: {
label: string
value: string
icon?: React.ComponentType<{ className?: string }>
}[]
}
export function DataTableFacetedFilter<TData, TValue>({
column,
title,
options,
}: DataTableFacetedFilterProps<TData, TValue>) {
const facets = column?.getFacetedUniqueValues()
const selectedValues = new Set(column?.getFilterValue() as string[])
return (
<Popover>
<PopoverTrigger asChild>
<Button variant="outline" size="sm" className="h-8 border-dashed">
<PlusCircledIcon className="mr-2 h-4 w-4" />
{title}
{selectedValues?.size > 0 && (
<>
<Separator orientation="vertical" className="mx-2 h-4" />
<Badge
variant="secondary"
className="rounded-sm px-1 font-normal lg:hidden"
>
{selectedValues.size}
</Badge>
<div className="hidden space-x-1 lg:flex">
{selectedValues.size > 2 ? (
<Badge
variant="secondary"
className="rounded-sm px-1 font-normal"
>
{selectedValues.size} selected
</Badge>
) : (
options
.filter((option) => selectedValues.has(option.value))
.map((option) => (
<Badge
variant="secondary"
key={option.value}
className="rounded-sm px-1 font-normal"
>
{option.label}
</Badge>
))
)}
</div>
</>
)}
</Button>
</PopoverTrigger>
<PopoverContent className="w-[200px] p-0" align="start">
<Command>
<CommandInput placeholder={title} />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup>
{options.map((option) => {
const isSelected = selectedValues.has(option.value)
return (
<CommandItem
key={option.value}
onSelect={() => {
if (isSelected) {
selectedValues.delete(option.value)
} else {
selectedValues.add(option.value)
}
const filterValues = Array.from(selectedValues)
column?.setFilterValue(
filterValues.length ? filterValues : undefined
)
}}
>
<div
className={cn(
"mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary",
isSelected
? "bg-primary text-primary-foreground"
: "opacity-50 [&_svg]:invisible"
)}
>
<CheckIcon className={cn("h-4 w-4")} />
</div>
{option.icon && (
<option.icon className="mr-2 h-4 w-4 text-muted-foreground" />
)}
<span>{option.label}</span>
{facets?.get(option.value) && (
<span className="ml-auto flex h-4 w-4 items-center justify-center font-mono text-xs">
{facets.get(option.value)}
</span>
)}
</CommandItem>
)
})}
</CommandGroup>
{selectedValues.size > 0 && (
<>
<CommandSeparator />
<CommandGroup>
<CommandItem
onSelect={() => column?.setFilterValue(undefined)}
className="justify-center text-center"
>
Clear filters
</CommandItem>
</CommandGroup>
</>
)}
</CommandList>
</Command>
</PopoverContent>
</Popover>
)
}

View File

@@ -0,0 +1,97 @@
import {
ChevronLeftIcon,
ChevronRightIcon,
DoubleArrowLeftIcon,
DoubleArrowRightIcon,
} from "@radix-ui/react-icons"
import { Table } from "@tanstack/react-table"
import { Button } from "@/registry/new-york/ui/button"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/registry/new-york/ui/select"
interface DataTablePaginationProps<TData> {
table: Table<TData>
}
export function DataTablePagination<TData>({
table,
}: DataTablePaginationProps<TData>) {
return (
<div className="flex items-center justify-between px-2">
<div className="flex-1 text-sm text-muted-foreground">
{table.getFilteredSelectedRowModel().rows.length} of{" "}
{table.getFilteredRowModel().rows.length} row(s) selected.
</div>
<div className="flex items-center space-x-6 lg:space-x-8">
<div className="flex items-center space-x-2">
<p className="text-sm font-medium">Rows per page</p>
<Select
value={`${table.getState().pagination.pageSize}`}
onValueChange={(value) => {
table.setPageSize(Number(value))
}}
>
<SelectTrigger className="h-8 w-[70px]">
<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-[100px] items-center justify-center text-sm font-medium">
Page {table.getState().pagination.pageIndex + 1} of{" "}
{table.getPageCount()}
</div>
<div className="flex items-center space-x-2">
<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>
<DoubleArrowLeftIcon className="h-4 w-4" />
</Button>
<Button
variant="outline"
className="h-8 w-8 p-0"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
<span className="sr-only">Go to previous page</span>
<ChevronLeftIcon className="h-4 w-4" />
</Button>
<Button
variant="outline"
className="h-8 w-8 p-0"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
<span className="sr-only">Go to next page</span>
<ChevronRightIcon className="h-4 w-4" />
</Button>
<Button
variant="outline"
className="hidden h-8 w-8 p-0 lg:flex"
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
disabled={!table.getCanNextPage()}
>
<span className="sr-only">Go to last page</span>
<DoubleArrowRightIcon className="h-4 w-4" />
</Button>
</div>
</div>
</div>
)
}

View File

@@ -0,0 +1,69 @@
"use client"
import { DotsHorizontalIcon } from "@radix-ui/react-icons"
import { Row } from "@tanstack/react-table"
import { Button } from "@/registry/new-york/ui/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from "@/registry/new-york/ui/dropdown-menu"
import { labels } from "../data/data"
import { taskSchema } from "../data/schema"
interface DataTableRowActionsProps<TData> {
row: Row<TData>
}
export function DataTableRowActions<TData>({
row,
}: DataTableRowActionsProps<TData>) {
const task = taskSchema.parse(row.original)
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
className="flex h-8 w-8 p-0 data-[state=open]:bg-muted"
>
<DotsHorizontalIcon className="h-4 w-4" />
<span className="sr-only">Open menu</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-[160px]">
<DropdownMenuItem>Edit</DropdownMenuItem>
<DropdownMenuItem>Make a copy</DropdownMenuItem>
<DropdownMenuItem>Favorite</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuSub>
<DropdownMenuSubTrigger>Labels</DropdownMenuSubTrigger>
<DropdownMenuSubContent>
<DropdownMenuRadioGroup value={task.label}>
{labels.map((label) => (
<DropdownMenuRadioItem key={label.value} value={label.value}>
{label.label}
</DropdownMenuRadioItem>
))}
</DropdownMenuRadioGroup>
</DropdownMenuSubContent>
</DropdownMenuSub>
<DropdownMenuSeparator />
<DropdownMenuItem>
Delete
<DropdownMenuShortcut></DropdownMenuShortcut>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
}

View File

@@ -0,0 +1,61 @@
"use client"
import { Cross2Icon } from "@radix-ui/react-icons"
import { Table } from "@tanstack/react-table"
import { Button } from "@/registry/new-york/ui/button"
import { Input } from "@/registry/new-york/ui/input"
import { DataTableViewOptions } from "@/app/examples/tasks/components/data-table-view-options"
import { priorities, statuses } from "../data/data"
import { DataTableFacetedFilter } from "./data-table-faceted-filter"
interface DataTableToolbarProps<TData> {
table: Table<TData>
}
export function DataTableToolbar<TData>({
table,
}: DataTableToolbarProps<TData>) {
const isFiltered = table.getState().columnFilters.length > 0
return (
<div className="flex items-center justify-between">
<div className="flex flex-1 items-center space-x-2">
<Input
placeholder="Filter tasks..."
value={(table.getColumn("title")?.getFilterValue() as string) ?? ""}
onChange={(event) =>
table.getColumn("title")?.setFilterValue(event.target.value)
}
className="h-8 w-[150px] lg:w-[250px]"
/>
{table.getColumn("status") && (
<DataTableFacetedFilter
column={table.getColumn("status")}
title="Status"
options={statuses}
/>
)}
{table.getColumn("priority") && (
<DataTableFacetedFilter
column={table.getColumn("priority")}
title="Priority"
options={priorities}
/>
)}
{isFiltered && (
<Button
variant="ghost"
onClick={() => table.resetColumnFilters()}
className="h-8 px-2 lg:px-3"
>
Reset
<Cross2Icon className="ml-2 h-4 w-4" />
</Button>
)}
</div>
<DataTableViewOptions table={table} />
</div>
)
}

View File

@@ -0,0 +1,59 @@
"use client"
import { DropdownMenuTrigger } from "@radix-ui/react-dropdown-menu"
import { MixerHorizontalIcon } from "@radix-ui/react-icons"
import { Table } from "@tanstack/react-table"
import { Button } from "@/registry/new-york/ui/button"
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuLabel,
DropdownMenuSeparator,
} from "@/registry/new-york/ui/dropdown-menu"
interface DataTableViewOptionsProps<TData> {
table: Table<TData>
}
export function DataTableViewOptions<TData>({
table,
}: DataTableViewOptionsProps<TData>) {
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="outline"
size="sm"
className="ml-auto hidden h-8 lg:flex"
>
<MixerHorizontalIcon className="mr-2 h-4 w-4" />
View
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-[150px]">
<DropdownMenuLabel>Toggle columns</DropdownMenuLabel>
<DropdownMenuSeparator />
{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>
)
}

View File

@@ -0,0 +1,126 @@
"use client"
import * as React from "react"
import {
ColumnDef,
ColumnFiltersState,
SortingState,
VisibilityState,
flexRender,
getCoreRowModel,
getFacetedRowModel,
getFacetedUniqueValues,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
} from "@tanstack/react-table"
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/registry/new-york/ui/table"
import { DataTablePagination } from "../components/data-table-pagination"
import { DataTableToolbar } from "../components/data-table-toolbar"
interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[]
data: TData[]
}
export function DataTable<TData, TValue>({
columns,
data,
}: DataTableProps<TData, TValue>) {
const [rowSelection, setRowSelection] = React.useState({})
const [columnVisibility, setColumnVisibility] =
React.useState<VisibilityState>({})
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
[]
)
const [sorting, setSorting] = React.useState<SortingState>([])
const table = useReactTable({
data,
columns,
state: {
sorting,
columnVisibility,
rowSelection,
columnFilters,
},
enableRowSelection: true,
onRowSelectionChange: setRowSelection,
onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters,
onColumnVisibilityChange: setColumnVisibility,
getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
getFacetedRowModel: getFacetedRowModel(),
getFacetedUniqueValues: getFacetedUniqueValues(),
})
return (
<div className="space-y-4">
<DataTableToolbar table={table} />
<div className="rounded-md border">
<Table>
<TableHeader>
{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>
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<TableRow
key={row.id}
data-state={row.getIsSelected() && "selected"}
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell
colSpan={columns.length}
className="h-24 text-center"
>
No results.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
<DataTablePagination table={table} />
</div>
)
}

View File

@@ -0,0 +1,62 @@
import {
Avatar,
AvatarFallback,
AvatarImage,
} from "@/registry/new-york/ui/avatar"
import { Button } from "@/registry/new-york/ui/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuTrigger,
} from "@/registry/new-york/ui/dropdown-menu"
export function UserNav() {
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="relative h-8 w-8 rounded-full">
<Avatar className="h-9 w-9">
<AvatarImage src="/avatars/03.png" alt="@shadcn" />
<AvatarFallback>SC</AvatarFallback>
</Avatar>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56" align="end" forceMount>
<DropdownMenuLabel className="font-normal">
<div className="flex flex-col space-y-1">
<p className="text-sm font-medium leading-none">shadcn</p>
<p className="text-xs leading-none text-muted-foreground">
m@example.com
</p>
</div>
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
Profile
<DropdownMenuShortcut>P</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem>
Billing
<DropdownMenuShortcut>B</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem>
Settings
<DropdownMenuShortcut>S</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem>New Team</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuItem>
Log out
<DropdownMenuShortcut>Q</DropdownMenuShortcut>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
}

View File

@@ -0,0 +1,71 @@
import {
ArrowDownIcon,
ArrowRightIcon,
ArrowUpIcon,
CheckCircledIcon,
CircleIcon,
CrossCircledIcon,
QuestionMarkCircledIcon,
StopwatchIcon,
} from "@radix-ui/react-icons"
export const labels = [
{
value: "bug",
label: "Bug",
},
{
value: "feature",
label: "Feature",
},
{
value: "documentation",
label: "Documentation",
},
]
export const statuses = [
{
value: "backlog",
label: "Backlog",
icon: QuestionMarkCircledIcon,
},
{
value: "todo",
label: "Todo",
icon: CircleIcon,
},
{
value: "in progress",
label: "In Progress",
icon: StopwatchIcon,
},
{
value: "done",
label: "Done",
icon: CheckCircledIcon,
},
{
value: "canceled",
label: "Canceled",
icon: CrossCircledIcon,
},
]
export const priorities = [
{
label: "Low",
value: "low",
icon: ArrowDownIcon,
},
{
label: "Medium",
value: "medium",
icon: ArrowRightIcon,
},
{
label: "High",
value: "high",
icon: ArrowUpIcon,
},
]

View File

@@ -0,0 +1,13 @@
import { z } from "zod"
// We're keeping a simple non-relational schema here.
// IRL, you will have a schema for your data models.
export const taskSchema = z.object({
id: z.string(),
title: z.string(),
status: z.string(),
label: z.string(),
priority: z.string(),
})
export type Task = z.infer<typeof taskSchema>

View File

@@ -0,0 +1,20 @@
import fs from "fs"
import path from "path"
import { faker } from "@faker-js/faker"
import { labels, priorities, statuses } from "./data"
const tasks = Array.from({ length: 100 }, () => ({
id: `TASK-${faker.number.int({ min: 1000, max: 9999 })}`,
title: faker.hacker.phrase().replace(/^./, (letter) => letter.toUpperCase()),
status: faker.helpers.arrayElement(statuses).value,
label: faker.helpers.arrayElement(labels).value,
priority: faker.helpers.arrayElement(priorities).value,
}))
fs.writeFileSync(
path.join(__dirname, "tasks.json"),
JSON.stringify(tasks, null, 2)
)
console.log("✅ Tasks data generated.")

View File

@@ -0,0 +1,702 @@
[
{
"id": "TASK-8782",
"title": "You can't compress the program without quantifying the open-source SSD pixel!",
"status": "in progress",
"label": "documentation",
"priority": "medium"
},
{
"id": "TASK-7878",
"title": "Try to calculate the EXE feed, maybe it will index the multi-byte pixel!",
"status": "backlog",
"label": "documentation",
"priority": "medium"
},
{
"id": "TASK-7839",
"title": "We need to bypass the neural TCP card!",
"status": "todo",
"label": "bug",
"priority": "high"
},
{
"id": "TASK-5562",
"title": "The SAS interface is down, bypass the open-source pixel so we can back up the PNG bandwidth!",
"status": "backlog",
"label": "feature",
"priority": "medium"
},
{
"id": "TASK-8686",
"title": "I'll parse the wireless SSL protocol, that should driver the API panel!",
"status": "canceled",
"label": "feature",
"priority": "medium"
},
{
"id": "TASK-1280",
"title": "Use the digital TLS panel, then you can transmit the haptic system!",
"status": "done",
"label": "bug",
"priority": "high"
},
{
"id": "TASK-7262",
"title": "The UTF8 application is down, parse the neural bandwidth so we can back up the PNG firewall!",
"status": "done",
"label": "feature",
"priority": "high"
},
{
"id": "TASK-1138",
"title": "Generating the driver won't do anything, we need to quantify the 1080p SMTP bandwidth!",
"status": "in progress",
"label": "feature",
"priority": "medium"
},
{
"id": "TASK-7184",
"title": "We need to program the back-end THX pixel!",
"status": "todo",
"label": "feature",
"priority": "low"
},
{
"id": "TASK-5160",
"title": "Calculating the bus won't do anything, we need to navigate the back-end JSON protocol!",
"status": "in progress",
"label": "documentation",
"priority": "high"
},
{
"id": "TASK-5618",
"title": "Generating the driver won't do anything, we need to index the online SSL application!",
"status": "done",
"label": "documentation",
"priority": "medium"
},
{
"id": "TASK-6699",
"title": "I'll transmit the wireless JBOD capacitor, that should hard drive the SSD feed!",
"status": "backlog",
"label": "documentation",
"priority": "medium"
},
{
"id": "TASK-2858",
"title": "We need to override the online UDP bus!",
"status": "backlog",
"label": "bug",
"priority": "medium"
},
{
"id": "TASK-9864",
"title": "I'll reboot the 1080p FTP panel, that should matrix the HEX hard drive!",
"status": "done",
"label": "bug",
"priority": "high"
},
{
"id": "TASK-8404",
"title": "We need to generate the virtual HEX alarm!",
"status": "in progress",
"label": "bug",
"priority": "low"
},
{
"id": "TASK-5365",
"title": "Backing up the pixel won't do anything, we need to transmit the primary IB array!",
"status": "in progress",
"label": "documentation",
"priority": "low"
},
{
"id": "TASK-1780",
"title": "The CSS feed is down, index the bluetooth transmitter so we can compress the CLI protocol!",
"status": "todo",
"label": "documentation",
"priority": "high"
},
{
"id": "TASK-6938",
"title": "Use the redundant SCSI application, then you can hack the optical alarm!",
"status": "todo",
"label": "documentation",
"priority": "high"
},
{
"id": "TASK-9885",
"title": "We need to compress the auxiliary VGA driver!",
"status": "backlog",
"label": "bug",
"priority": "high"
},
{
"id": "TASK-3216",
"title": "Transmitting the transmitter won't do anything, we need to compress the virtual HDD sensor!",
"status": "backlog",
"label": "documentation",
"priority": "medium"
},
{
"id": "TASK-9285",
"title": "The IP monitor is down, copy the haptic alarm so we can generate the HTTP transmitter!",
"status": "todo",
"label": "bug",
"priority": "high"
},
{
"id": "TASK-1024",
"title": "Overriding the microchip won't do anything, we need to transmit the digital OCR transmitter!",
"status": "in progress",
"label": "documentation",
"priority": "low"
},
{
"id": "TASK-7068",
"title": "You can't generate the capacitor without indexing the wireless HEX pixel!",
"status": "canceled",
"label": "bug",
"priority": "low"
},
{
"id": "TASK-6502",
"title": "Navigating the microchip won't do anything, we need to bypass the back-end SQL bus!",
"status": "todo",
"label": "bug",
"priority": "high"
},
{
"id": "TASK-5326",
"title": "We need to hack the redundant UTF8 transmitter!",
"status": "todo",
"label": "bug",
"priority": "low"
},
{
"id": "TASK-6274",
"title": "Use the virtual PCI circuit, then you can parse the bluetooth alarm!",
"status": "canceled",
"label": "documentation",
"priority": "low"
},
{
"id": "TASK-1571",
"title": "I'll input the neural DRAM circuit, that should protocol the SMTP interface!",
"status": "in progress",
"label": "feature",
"priority": "medium"
},
{
"id": "TASK-9518",
"title": "Compressing the interface won't do anything, we need to compress the online SDD matrix!",
"status": "canceled",
"label": "documentation",
"priority": "medium"
},
{
"id": "TASK-5581",
"title": "I'll synthesize the digital COM pixel, that should transmitter the UTF8 protocol!",
"status": "backlog",
"label": "documentation",
"priority": "high"
},
{
"id": "TASK-2197",
"title": "Parsing the feed won't do anything, we need to copy the bluetooth DRAM bus!",
"status": "todo",
"label": "documentation",
"priority": "low"
},
{
"id": "TASK-8484",
"title": "We need to parse the solid state UDP firewall!",
"status": "in progress",
"label": "bug",
"priority": "low"
},
{
"id": "TASK-9892",
"title": "If we back up the application, we can get to the UDP application through the multi-byte THX capacitor!",
"status": "done",
"label": "documentation",
"priority": "high"
},
{
"id": "TASK-9616",
"title": "We need to synthesize the cross-platform ASCII pixel!",
"status": "in progress",
"label": "feature",
"priority": "medium"
},
{
"id": "TASK-9744",
"title": "Use the back-end IP card, then you can input the solid state hard drive!",
"status": "done",
"label": "documentation",
"priority": "low"
},
{
"id": "TASK-1376",
"title": "Generating the alarm won't do anything, we need to generate the mobile IP capacitor!",
"status": "backlog",
"label": "documentation",
"priority": "low"
},
{
"id": "TASK-7382",
"title": "If we back up the firewall, we can get to the RAM alarm through the primary UTF8 pixel!",
"status": "todo",
"label": "feature",
"priority": "low"
},
{
"id": "TASK-2290",
"title": "I'll compress the virtual JSON panel, that should application the UTF8 bus!",
"status": "canceled",
"label": "documentation",
"priority": "high"
},
{
"id": "TASK-1533",
"title": "You can't input the firewall without overriding the wireless TCP firewall!",
"status": "done",
"label": "bug",
"priority": "high"
},
{
"id": "TASK-4920",
"title": "Bypassing the hard drive won't do anything, we need to input the bluetooth JSON program!",
"status": "in progress",
"label": "bug",
"priority": "high"
},
{
"id": "TASK-5168",
"title": "If we synthesize the bus, we can get to the IP panel through the virtual TLS array!",
"status": "in progress",
"label": "feature",
"priority": "low"
},
{
"id": "TASK-7103",
"title": "We need to parse the multi-byte EXE bandwidth!",
"status": "canceled",
"label": "feature",
"priority": "low"
},
{
"id": "TASK-4314",
"title": "If we compress the program, we can get to the XML alarm through the multi-byte COM matrix!",
"status": "in progress",
"label": "bug",
"priority": "high"
},
{
"id": "TASK-3415",
"title": "Use the cross-platform XML application, then you can quantify the solid state feed!",
"status": "todo",
"label": "feature",
"priority": "high"
},
{
"id": "TASK-8339",
"title": "Try to calculate the DNS interface, maybe it will input the bluetooth capacitor!",
"status": "in progress",
"label": "feature",
"priority": "low"
},
{
"id": "TASK-6995",
"title": "Try to hack the XSS bandwidth, maybe it will override the bluetooth matrix!",
"status": "todo",
"label": "feature",
"priority": "high"
},
{
"id": "TASK-8053",
"title": "If we connect the program, we can get to the UTF8 matrix through the digital UDP protocol!",
"status": "todo",
"label": "feature",
"priority": "medium"
},
{
"id": "TASK-4336",
"title": "If we synthesize the microchip, we can get to the SAS sensor through the optical UDP program!",
"status": "todo",
"label": "documentation",
"priority": "low"
},
{
"id": "TASK-8790",
"title": "I'll back up the optical COM alarm, that should alarm the RSS capacitor!",
"status": "done",
"label": "bug",
"priority": "medium"
},
{
"id": "TASK-8980",
"title": "Try to navigate the SQL transmitter, maybe it will back up the virtual firewall!",
"status": "canceled",
"label": "bug",
"priority": "low"
},
{
"id": "TASK-7342",
"title": "Use the neural CLI card, then you can parse the online port!",
"status": "backlog",
"label": "documentation",
"priority": "low"
},
{
"id": "TASK-5608",
"title": "I'll hack the haptic SSL program, that should bus the UDP transmitter!",
"status": "canceled",
"label": "documentation",
"priority": "low"
},
{
"id": "TASK-1606",
"title": "I'll generate the bluetooth PNG firewall, that should pixel the SSL driver!",
"status": "done",
"label": "feature",
"priority": "medium"
},
{
"id": "TASK-7872",
"title": "Transmitting the circuit won't do anything, we need to reboot the 1080p RSS monitor!",
"status": "canceled",
"label": "feature",
"priority": "medium"
},
{
"id": "TASK-4167",
"title": "Use the cross-platform SMS circuit, then you can synthesize the optical feed!",
"status": "canceled",
"label": "bug",
"priority": "medium"
},
{
"id": "TASK-9581",
"title": "You can't index the port without hacking the cross-platform XSS monitor!",
"status": "backlog",
"label": "documentation",
"priority": "low"
},
{
"id": "TASK-8806",
"title": "We need to bypass the back-end SSL panel!",
"status": "done",
"label": "bug",
"priority": "medium"
},
{
"id": "TASK-6542",
"title": "Try to quantify the RSS firewall, maybe it will quantify the open-source system!",
"status": "done",
"label": "feature",
"priority": "low"
},
{
"id": "TASK-6806",
"title": "The VGA protocol is down, reboot the back-end matrix so we can parse the CSS panel!",
"status": "canceled",
"label": "documentation",
"priority": "low"
},
{
"id": "TASK-9549",
"title": "You can't bypass the bus without connecting the neural JBOD bus!",
"status": "todo",
"label": "feature",
"priority": "high"
},
{
"id": "TASK-1075",
"title": "Backing up the driver won't do anything, we need to parse the redundant RAM pixel!",
"status": "done",
"label": "feature",
"priority": "medium"
},
{
"id": "TASK-1427",
"title": "Use the auxiliary PCI circuit, then you can calculate the cross-platform interface!",
"status": "done",
"label": "documentation",
"priority": "high"
},
{
"id": "TASK-1907",
"title": "Hacking the circuit won't do anything, we need to back up the online DRAM system!",
"status": "todo",
"label": "documentation",
"priority": "high"
},
{
"id": "TASK-4309",
"title": "If we generate the system, we can get to the TCP sensor through the optical GB pixel!",
"status": "backlog",
"label": "bug",
"priority": "medium"
},
{
"id": "TASK-3973",
"title": "I'll parse the back-end ADP array, that should bandwidth the RSS bandwidth!",
"status": "todo",
"label": "feature",
"priority": "medium"
},
{
"id": "TASK-7962",
"title": "Use the wireless RAM program, then you can hack the cross-platform feed!",
"status": "canceled",
"label": "bug",
"priority": "low"
},
{
"id": "TASK-3360",
"title": "You can't quantify the program without synthesizing the neural OCR interface!",
"status": "done",
"label": "feature",
"priority": "medium"
},
{
"id": "TASK-9887",
"title": "Use the auxiliary ASCII sensor, then you can connect the solid state port!",
"status": "backlog",
"label": "bug",
"priority": "medium"
},
{
"id": "TASK-3649",
"title": "I'll input the virtual USB system, that should circuit the DNS monitor!",
"status": "in progress",
"label": "feature",
"priority": "medium"
},
{
"id": "TASK-3586",
"title": "If we quantify the circuit, we can get to the CLI feed through the mobile SMS hard drive!",
"status": "in progress",
"label": "bug",
"priority": "low"
},
{
"id": "TASK-5150",
"title": "I'll hack the wireless XSS port, that should transmitter the IP interface!",
"status": "canceled",
"label": "feature",
"priority": "medium"
},
{
"id": "TASK-3652",
"title": "The SQL interface is down, override the optical bus so we can program the ASCII interface!",
"status": "backlog",
"label": "feature",
"priority": "low"
},
{
"id": "TASK-6884",
"title": "Use the digital PCI circuit, then you can synthesize the multi-byte microchip!",
"status": "canceled",
"label": "feature",
"priority": "high"
},
{
"id": "TASK-1591",
"title": "We need to connect the mobile XSS driver!",
"status": "in progress",
"label": "feature",
"priority": "high"
},
{
"id": "TASK-3802",
"title": "Try to override the ASCII protocol, maybe it will parse the virtual matrix!",
"status": "in progress",
"label": "feature",
"priority": "low"
},
{
"id": "TASK-7253",
"title": "Programming the capacitor won't do anything, we need to bypass the neural IB hard drive!",
"status": "backlog",
"label": "bug",
"priority": "high"
},
{
"id": "TASK-9739",
"title": "We need to hack the multi-byte HDD bus!",
"status": "done",
"label": "documentation",
"priority": "medium"
},
{
"id": "TASK-4424",
"title": "Try to hack the HEX alarm, maybe it will connect the optical pixel!",
"status": "in progress",
"label": "documentation",
"priority": "medium"
},
{
"id": "TASK-3922",
"title": "You can't back up the capacitor without generating the wireless PCI program!",
"status": "backlog",
"label": "bug",
"priority": "low"
},
{
"id": "TASK-4921",
"title": "I'll index the open-source IP feed, that should system the GB application!",
"status": "canceled",
"label": "bug",
"priority": "low"
},
{
"id": "TASK-5814",
"title": "We need to calculate the 1080p AGP feed!",
"status": "backlog",
"label": "bug",
"priority": "high"
},
{
"id": "TASK-2645",
"title": "Synthesizing the system won't do anything, we need to navigate the multi-byte HDD firewall!",
"status": "todo",
"label": "documentation",
"priority": "medium"
},
{
"id": "TASK-4535",
"title": "Try to copy the JSON circuit, maybe it will connect the wireless feed!",
"status": "in progress",
"label": "feature",
"priority": "low"
},
{
"id": "TASK-4463",
"title": "We need to copy the solid state AGP monitor!",
"status": "done",
"label": "documentation",
"priority": "low"
},
{
"id": "TASK-9745",
"title": "If we connect the protocol, we can get to the GB system through the bluetooth PCI microchip!",
"status": "canceled",
"label": "feature",
"priority": "high"
},
{
"id": "TASK-2080",
"title": "If we input the bus, we can get to the RAM matrix through the auxiliary RAM card!",
"status": "todo",
"label": "bug",
"priority": "medium"
},
{
"id": "TASK-3838",
"title": "I'll bypass the online TCP application, that should panel the AGP system!",
"status": "backlog",
"label": "bug",
"priority": "high"
},
{
"id": "TASK-1340",
"title": "We need to navigate the virtual PNG circuit!",
"status": "todo",
"label": "bug",
"priority": "medium"
},
{
"id": "TASK-6665",
"title": "If we parse the monitor, we can get to the SSD hard drive through the cross-platform AGP alarm!",
"status": "canceled",
"label": "feature",
"priority": "low"
},
{
"id": "TASK-7585",
"title": "If we calculate the hard drive, we can get to the SSL program through the multi-byte CSS microchip!",
"status": "backlog",
"label": "feature",
"priority": "low"
},
{
"id": "TASK-6319",
"title": "We need to copy the multi-byte SCSI program!",
"status": "backlog",
"label": "bug",
"priority": "high"
},
{
"id": "TASK-4369",
"title": "Try to input the SCSI bus, maybe it will generate the 1080p pixel!",
"status": "backlog",
"label": "bug",
"priority": "high"
},
{
"id": "TASK-9035",
"title": "We need to override the solid state PNG array!",
"status": "canceled",
"label": "documentation",
"priority": "low"
},
{
"id": "TASK-3970",
"title": "You can't index the transmitter without quantifying the haptic ASCII card!",
"status": "todo",
"label": "documentation",
"priority": "medium"
},
{
"id": "TASK-4473",
"title": "You can't bypass the protocol without overriding the neural RSS program!",
"status": "todo",
"label": "documentation",
"priority": "low"
},
{
"id": "TASK-4136",
"title": "You can't hack the hard drive without hacking the primary JSON program!",
"status": "canceled",
"label": "bug",
"priority": "medium"
},
{
"id": "TASK-3939",
"title": "Use the back-end SQL firewall, then you can connect the neural hard drive!",
"status": "done",
"label": "feature",
"priority": "low"
},
{
"id": "TASK-2007",
"title": "I'll input the back-end USB protocol, that should bandwidth the PCI system!",
"status": "backlog",
"label": "bug",
"priority": "high"
},
{
"id": "TASK-7516",
"title": "Use the primary SQL program, then you can generate the auxiliary transmitter!",
"status": "done",
"label": "documentation",
"priority": "medium"
},
{
"id": "TASK-6906",
"title": "Try to back up the DRAM system, maybe it will reboot the online transmitter!",
"status": "done",
"label": "feature",
"priority": "high"
},
{
"id": "TASK-5207",
"title": "The SMS interface is down, copy the bluetooth bus so we can quantify the VGA card!",
"status": "in progress",
"label": "bug",
"priority": "low"
}
]

View File

@@ -0,0 +1,65 @@
import { promises as fs } from "fs"
import path from "path"
import { Metadata } from "next"
import Image from "next/image"
import { z } from "zod"
import { columns } from "./components/columns"
import { DataTable } from "./components/data-table"
import { UserNav } from "./components/user-nav"
import { taskSchema } from "./data/schema"
export const metadata: Metadata = {
title: "Tasks",
description: "A task and issue tracker build using Tanstack Table.",
}
// Simulate a database read for tasks.
async function getTasks() {
const data = await fs.readFile(
path.join(process.cwd(), "app/examples/tasks/data/tasks.json")
)
const tasks = JSON.parse(data.toString())
return z.array(taskSchema).parse(tasks)
}
export default async function TaskPage() {
const tasks = await getTasks()
return (
<>
<div className="md:hidden">
<Image
src="/examples/tasks-light.png"
width={1280}
height={998}
alt="Playground"
className="block dark:hidden"
/>
<Image
src="/examples/tasks-dark.png"
width={1280}
height={998}
alt="Playground"
className="hidden dark:block"
/>
</div>
<div className="hidden h-full flex-1 flex-col space-y-8 p-8 md:flex">
<div className="flex items-center justify-between space-y-2">
<div>
<h2 className="text-2xl font-bold tracking-tight">Welcome back!</h2>
<p className="text-muted-foreground">
Here&apos;s a list of your tasks for this month!
</p>
</div>
<div className="flex items-center space-x-2">
<UserNav />
</div>
</div>
<DataTable data={tasks} columns={columns} />
</div>
</>
)
}

View File

@@ -1,61 +0,0 @@
import Link from "next/link"
import { Heart } from "lucide-react"
import { cn } from "@/lib/utils"
import { AspectRatio } from "@/components/ui/aspect-ratio"
import { buttonVariants } from "@/components/ui/button"
export default function FigmaPage() {
return (
<>
<section className="grid items-center gap-6 pt-6 pb-8 md:py-10">
<div className="flex max-w-[980px] flex-col items-start gap-2">
<h1 className="text-3xl font-extrabold leading-tight tracking-tighter md:text-5xl lg:text-6xl lg:leading-[1.1]">
Figma UI Kit. Crafted to perfectly match the Radix UI components.
</h1>
<p className="max-w-[750px] text-lg text-slate-700 dark:text-slate-400 sm:text-xl">
Every component recreated in Figma. With customizable props,
typography and icons. Open sourced by{" "}
<Link
href="https://twitter.com/skirano"
target="_blank"
rel="noreferrer"
className="font-medium underline underline-offset-4"
>
Pietro Schirano
</Link>
.
</p>
</div>
<div className="flex flex-col space-y-4 sm:flex-row sm:space-y-0 sm:space-x-4 md:flex-row">
<Link
href="https://www.figma.com/community/file/1203061493325953101"
className={buttonVariants({ size: "lg" })}
target="_blank"
rel="noreferrer"
>
Get a copy
</Link>
<Link
href="https://twitter.com/skirano"
className={cn(
buttonVariants({ size: "lg", variant: "outline" }),
"px-5"
)}
target="_blank"
rel="noreferrer"
>
<Heart className="mr-2 h-4 w-4 fill-current" />
Follow Pietro
</Link>
</div>
</section>
<AspectRatio ratio={16 / 9} className="w-full">
<iframe
src="https://embed.figma.com/file/1203061493325953101/hf_embed?community_viewer=true&embed_host=shadcn&hub_file_id=1203061493325953101&kind=&viewer=1"
className="h-full w-full overflow-hidden rounded-lg border border-slate-200 bg-slate-100 dark:border-slate-700 dark:bg-slate-800"
/>
</AspectRatio>
</>
)
}

View File

@@ -1,43 +0,0 @@
import { siteConfig } from "@/config/site"
export default function Head() {
const url = process.env.NEXT_PUBLIC_APP_URL
const ogUrl = new URL(`${url}/og.jpg`)
return (
<>
<title>{`${siteConfig.name} - ${siteConfig.description}`}</title>
<meta charSet="utf-8" />
<meta name="description" content={siteConfig.description} />
<link
rel="apple-touch-icon"
sizes="180x180"
href="/apple-touch-icon.png"
/>
<link
rel="icon"
type="image/png"
sizes="32x32"
href="/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="/favicon-16x16.png"
/>
<link rel="manifest" href="/site.webmanifest" />
<meta content="width=device-width, initial-scale=1" name="viewport" />
<meta property="og:type" content="website" />
<meta property="og:title" content={siteConfig.name} />
<meta property="og:description" content={siteConfig.description} />
<meta property="og:url" content={url?.toString()} />
<meta property="og:image" content={ogUrl.toString()} />
<meta name="twitter:title" content={siteConfig.name} />
<meta name="twitter:description" content={siteConfig.description} />
<meta name="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content={url?.toString()} />
<meta name="twitter:image" content={ogUrl.toString()} />
</>
)
}

View File

@@ -1,19 +1,77 @@
import { Inter as FontSans } from "next/font/google"
import "@/styles/globals.css"
import { Metadata, Viewport } from "next"
import { siteConfig } from "@/config/site"
import { fontSans } from "@/lib/fonts"
import { cn } from "@/lib/utils"
import { Analytics } from "@/components/analytics"
import { ThemeProvider } from "@/components/providers"
import { SiteFooter } from "@/components/site-footer"
import { SiteHeader } from "@/components/site-header"
import { TailwindIndicator } from "@/components/tailwind-indicator"
import { ThemeProvider } from "@/components/theme-provider"
import { Toaster } from "@/components/ui/toaster"
import { ThemeSwitcher } from "@/components/theme-switcher"
import { Toaster as DefaultToaster } from "@/registry/default/ui/toaster"
import { Toaster as NewYorkSonner } from "@/registry/new-york/ui/sonner"
import { Toaster as NewYorkToaster } from "@/registry/new-york/ui/toaster"
const fontSans = FontSans({
subsets: ["latin"],
variable: "--font-sans",
display: "swap",
})
export const metadata: Metadata = {
title: {
default: siteConfig.name,
template: `%s - ${siteConfig.name}`,
},
metadataBase: new URL(siteConfig.url),
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: siteConfig.url,
title: siteConfig.name,
description: siteConfig.description,
siteName: siteConfig.name,
images: [
{
url: siteConfig.ogImage,
width: 1200,
height: 630,
alt: siteConfig.name,
},
],
},
twitter: {
card: "summary_large_image",
title: siteConfig.name,
description: siteConfig.description,
images: [siteConfig.ogImage],
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: [
{ media: "(prefers-color-scheme: light)", color: "white" },
{ media: "(prefers-color-scheme: dark)", color: "black" },
],
}
interface RootLayoutProps {
children: React.ReactNode
@@ -26,20 +84,30 @@ export default function RootLayout({ children }: RootLayoutProps) {
<head />
<body
className={cn(
"min-h-screen bg-white font-sans text-slate-900 antialiased dark:bg-slate-900 dark:text-slate-50",
fontSans.variable
"min-h-screen bg-background font-sans antialiased",
fontSans.className
)}
>
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
<div className="flex min-h-screen flex-col">
<SiteHeader />
<div className="container flex-1">{children}</div>
<SiteFooter />
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
<div vaul-drawer-wrapper="">
<div className="relative flex min-h-screen flex-col bg-background">
<SiteHeader />
<main className="flex-1">{children}</main>
<SiteFooter />
</div>
</div>
<TailwindIndicator />
<ThemeSwitcher />
<Analytics />
<NewYorkToaster />
<DefaultToaster />
<NewYorkSonner />
</ThemeProvider>
<Analytics />
<Toaster />
</body>
</html>
</>

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