mirror of
https://github.com/shadcn-ui/ui.git
synced 2026-06-12 18:31:46 +00:00
Compare commits
230 Commits
shadcn@2.0
...
shadcn/min
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e10567393d | ||
|
|
6382edc9bf | ||
|
|
be3c1a9a98 | ||
|
|
1befb7a653 | ||
|
|
e8f245812f | ||
|
|
c90bcef401 | ||
|
|
7d97a9eba3 | ||
|
|
2b23bdcbec | ||
|
|
418b2d9e14 | ||
|
|
7eb77fb3b9 | ||
|
|
f759a25354 | ||
|
|
bd09f57cb7 | ||
|
|
0b288883d2 | ||
|
|
cf4ccb34e2 | ||
|
|
dc80a326c2 | ||
|
|
92e5c7ab2c | ||
|
|
d44971b6c2 | ||
|
|
8539dd6eec | ||
|
|
bc7df68620 | ||
|
|
1832f258bd | ||
|
|
e2730f7276 | ||
|
|
22ba26152a | ||
|
|
76d6a59f9f | ||
|
|
3a2a87386f | ||
|
|
1822d95883 | ||
|
|
f90f5148eb | ||
|
|
99b0a5ac90 | ||
|
|
984cb2a1ea | ||
|
|
fed2bac1d9 | ||
|
|
1ebfcd7cd9 | ||
|
|
535a7d9220 | ||
|
|
e7f8cd4566 | ||
|
|
8506977f83 | ||
|
|
575c0214da | ||
|
|
a1ab28bf58 | ||
|
|
190ae2dcd8 | ||
|
|
d5920cc3c1 | ||
|
|
89652889db | ||
|
|
57d15bb2d5 | ||
|
|
779517a1d4 | ||
|
|
b567f7a6c1 | ||
|
|
839afa714f | ||
|
|
32f0bc0de9 | ||
|
|
a3fee32c96 | ||
|
|
7d2499c803 | ||
|
|
84b88440c3 | ||
|
|
a5122f9029 | ||
|
|
3b90317e3c | ||
|
|
9d7c7b8978 | ||
|
|
957df8997c | ||
|
|
460240a86c | ||
|
|
bfffb08f42 | ||
|
|
5282332e52 | ||
|
|
3db8a07b3f | ||
|
|
187959435e | ||
|
|
5953229417 | ||
|
|
d6159023ed | ||
|
|
b0774b0578 | ||
|
|
7cefabbe98 | ||
|
|
3740373f99 | ||
|
|
9c4419f249 | ||
|
|
ea9b81594d | ||
|
|
4810f744e3 | ||
|
|
38c5fb4ace | ||
|
|
f37425bdb0 | ||
|
|
592ef33658 | ||
|
|
1a6da427ff | ||
|
|
bd8533bd26 | ||
|
|
202131cd7b | ||
|
|
7977975c9d | ||
|
|
a23fec1c31 | ||
|
|
2af2979cf4 | ||
|
|
aee53d90e2 | ||
|
|
1a68715048 | ||
|
|
34c99c1728 | ||
|
|
86642840e2 | ||
|
|
fac0daac7b | ||
|
|
13fe24e1f8 | ||
|
|
1eb2d74d7c | ||
|
|
16d4d38f56 | ||
|
|
5234c46722 | ||
|
|
f9037239af | ||
|
|
14e4726400 | ||
|
|
9f4d65fc8f | ||
|
|
1e357cb20d | ||
|
|
6339aaa315 | ||
|
|
e2caa3cd35 | ||
|
|
c74a094f14 | ||
|
|
bfb5b6357e | ||
|
|
ac3d965c6e | ||
|
|
6e7cd11d68 | ||
|
|
a75b4ca80e | ||
|
|
475abbbb20 | ||
|
|
762cd73554 | ||
|
|
8c06932800 | ||
|
|
e516481394 | ||
|
|
d1eb24e23a | ||
|
|
9a14c1d092 | ||
|
|
5ef2bc5f45 | ||
|
|
8f6a64f176 | ||
|
|
e85920c191 | ||
|
|
c8c4027b6b | ||
|
|
699195ba77 | ||
|
|
9643db42cf | ||
|
|
dd71498762 | ||
|
|
ddf761e802 | ||
|
|
5f7957ab51 | ||
|
|
f07c7ad5d0 | ||
|
|
d5aa527f0b | ||
|
|
5ec990a474 | ||
|
|
cb742e9825 | ||
|
|
254198b4bf | ||
|
|
1081536246 | ||
|
|
811bb59a8f | ||
|
|
ea677cc74e | ||
|
|
9253b43f29 | ||
|
|
c8fda09a63 | ||
|
|
1ff01b1bb5 | ||
|
|
f10d59fee9 | ||
|
|
2f869a2590 | ||
|
|
102b0b0c62 | ||
|
|
387756c4ab | ||
|
|
05145e66d3 | ||
|
|
704991247c | ||
|
|
729b9ec8ca | ||
|
|
a1bed464f3 | ||
|
|
805ed4120a | ||
|
|
600a593c87 | ||
|
|
500dbe2664 | ||
|
|
c577ee0666 | ||
|
|
d5bf0018fd | ||
|
|
fb36ca4159 | ||
|
|
824a35ada1 | ||
|
|
8d520c8d49 | ||
|
|
0873835339 | ||
|
|
4a0d4cfdb9 | ||
|
|
c4c5d8d419 | ||
|
|
9253682b87 | ||
|
|
c7cd16a637 | ||
|
|
eff1918d41 | ||
|
|
366d6b656b | ||
|
|
e489c5e08e | ||
|
|
d87003e0a4 | ||
|
|
a8633075f7 | ||
|
|
432d5e6e28 | ||
|
|
8f0c26f22a | ||
|
|
149b321c1b | ||
|
|
c1ae5a57cc | ||
|
|
b8ed303d8c | ||
|
|
13c97acf9f | ||
|
|
bed277c54d | ||
|
|
f7c42169a6 | ||
|
|
2c2fe97eb9 | ||
|
|
d64374d009 | ||
|
|
e24e51a2fa | ||
|
|
cdfecd1d97 | ||
|
|
2c043e709f | ||
|
|
aed19aa911 | ||
|
|
9e35d229ae | ||
|
|
db1975ef4d | ||
|
|
961e0b62d7 | ||
|
|
70c684c224 | ||
|
|
4ff64ba818 | ||
|
|
500a353816 | ||
|
|
c830780d62 | ||
|
|
debd51a854 | ||
|
|
78426dd862 | ||
|
|
6e47a94a8f | ||
|
|
ab6a856930 | ||
|
|
b33d3868e9 | ||
|
|
9e0a86122a | ||
|
|
2b276de95a | ||
|
|
64739f8399 | ||
|
|
f0cff7e0eb | ||
|
|
e242adaa9c | ||
|
|
986c00ee0e | ||
|
|
d0eece06d4 | ||
|
|
0a0a566a4e | ||
|
|
bf5a79c4d4 | ||
|
|
f02b412478 | ||
|
|
0d31293c7b | ||
|
|
3d1d19fc1b | ||
|
|
e5b56c84a9 | ||
|
|
630afe836e | ||
|
|
52c12bc27a | ||
|
|
182f2083cd | ||
|
|
3febcdc523 | ||
|
|
ced2513137 | ||
|
|
93ae8bd67f | ||
|
|
3259fb7ca1 | ||
|
|
d8397d80a8 | ||
|
|
06e74fce78 | ||
|
|
bc9e5eaaab | ||
|
|
c9b69d0836 | ||
|
|
539212c49e | ||
|
|
123887c36c | ||
|
|
35c1ba57c2 | ||
|
|
b34516f471 | ||
|
|
66b95402c1 | ||
|
|
5460177a7a | ||
|
|
b0049c2266 | ||
|
|
4e6e21f094 | ||
|
|
3b808c83be | ||
|
|
0e6b37e99a | ||
|
|
9ec433838f | ||
|
|
444ff70590 | ||
|
|
27bc5deff1 | ||
|
|
cacd7c8798 | ||
|
|
f227f93742 | ||
|
|
87e099a3d7 | ||
|
|
303d65718c | ||
|
|
909219df14 | ||
|
|
e8ada4e3c7 | ||
|
|
8fc80836ff | ||
|
|
d0a308cc64 | ||
|
|
e461c02389 | ||
|
|
50c2f6045a | ||
|
|
14aca65eee | ||
|
|
14c952b594 | ||
|
|
1e9434e6f9 | ||
|
|
f3d14c48cb | ||
|
|
c668c35bb9 | ||
|
|
1297abc882 | ||
|
|
36ebbf26dc | ||
|
|
a2abc4ad95 | ||
|
|
4b546bfb13 | ||
|
|
5fc9ade413 | ||
|
|
96880e7c9a | ||
|
|
7dfdb029e7 | ||
|
|
28f34ed3c3 |
5
.changeset/brave-cheetahs-smile.md
Normal file
5
.changeset/brave-cheetahs-smile.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"shadcn": minor
|
||||
---
|
||||
|
||||
add support for TanStack Start
|
||||
5
.changeset/cold-impalas-call.md
Normal file
5
.changeset/cold-impalas-call.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"shadcn": patch
|
||||
---
|
||||
|
||||
support for version detection in monorepo
|
||||
@@ -7,5 +7,5 @@
|
||||
"access": "public",
|
||||
"baseBranch": "main",
|
||||
"updateInternalDependencies": "patch",
|
||||
"ignore": ["www", "**-template"]
|
||||
"ignore": ["www", "v4"]
|
||||
}
|
||||
|
||||
5
.changeset/cool-mails-bake.md
Normal file
5
.changeset/cool-mails-bake.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"shadcn": patch
|
||||
---
|
||||
|
||||
upgrade @antfu/ni
|
||||
5
.changeset/curvy-taxis-help.md
Normal file
5
.changeset/curvy-taxis-help.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"shadcn": patch
|
||||
---
|
||||
|
||||
do not add ring for v3
|
||||
5
.changeset/few-houses-impress.md
Normal file
5
.changeset/few-houses-impress.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"shadcn": minor
|
||||
---
|
||||
|
||||
add theme vars support
|
||||
5
.changeset/five-hounds-tell.md
Normal file
5
.changeset/five-hounds-tell.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"shadcn": minor
|
||||
---
|
||||
|
||||
add tailwind version detection
|
||||
5
.changeset/fresh-cherries-brush.md
Normal file
5
.changeset/fresh-cherries-brush.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"shadcn": minor
|
||||
---
|
||||
|
||||
add support for tailwind v4
|
||||
5
.changeset/great-olives-flow.md
Normal file
5
.changeset/great-olives-flow.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"shadcn": minor
|
||||
---
|
||||
|
||||
default to css vars. add --no-css-variables
|
||||
5
.changeset/green-eels-shout.md
Normal file
5
.changeset/green-eels-shout.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"shadcn": patch
|
||||
---
|
||||
|
||||
cache registry calls
|
||||
5
.changeset/new-cheetahs-dance.md
Normal file
5
.changeset/new-cheetahs-dance.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"shadcn": minor
|
||||
---
|
||||
|
||||
default for new-york for v4
|
||||
5
.changeset/nine-llamas-sell.md
Normal file
5
.changeset/nine-llamas-sell.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"shadcn": minor
|
||||
---
|
||||
|
||||
fix handling of sidebar colors
|
||||
5
.changeset/ninety-needles-brake.md
Normal file
5
.changeset/ninety-needles-brake.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"shadcn": patch
|
||||
---
|
||||
|
||||
do not overwrite user defined vars
|
||||
5
.changeset/orange-papayas-relax.md
Normal file
5
.changeset/orange-papayas-relax.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"shadcn": patch
|
||||
---
|
||||
|
||||
fix cn import bug in monorepo
|
||||
5
.changeset/proud-snails-switch.md
Normal file
5
.changeset/proud-snails-switch.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"shadcn": patch
|
||||
---
|
||||
|
||||
filter out deprecated from --all
|
||||
5
.changeset/quiet-grapes-poke.md
Normal file
5
.changeset/quiet-grapes-poke.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"shadcn": minor
|
||||
---
|
||||
|
||||
add oklch base color
|
||||
5
.changeset/serious-geese-reply.md
Normal file
5
.changeset/serious-geese-reply.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"shadcn": minor
|
||||
---
|
||||
|
||||
hotswap style for v4
|
||||
5
.changeset/shaggy-months-tease.md
Normal file
5
.changeset/shaggy-months-tease.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"shadcn": patch
|
||||
---
|
||||
|
||||
check for empty css vars
|
||||
5
.changeset/slow-tools-relax.md
Normal file
5
.changeset/slow-tools-relax.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"shadcn": minor
|
||||
---
|
||||
|
||||
add warning for deprecated components
|
||||
5
.changeset/spotty-plants-juggle.md
Normal file
5
.changeset/spotty-plants-juggle.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"shadcn": patch
|
||||
---
|
||||
|
||||
fix tanstack check
|
||||
5
.changeset/tasty-walls-drum.md
Normal file
5
.changeset/tasty-walls-drum.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"shadcn": minor
|
||||
---
|
||||
|
||||
add support for route install for react-router and laravel
|
||||
8
.eslintignore
Normal file
8
.eslintignore
Normal file
@@ -0,0 +1,8 @@
|
||||
node_modules/
|
||||
target/
|
||||
.next/
|
||||
build/
|
||||
dist/
|
||||
|
||||
/templates/
|
||||
/fixtures/
|
||||
9
.github/workflows/code-check.yml
vendored
9
.github/workflows/code-check.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 20
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
@@ -52,7 +52,7 @@ jobs:
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 20
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
@@ -90,7 +90,7 @@ jobs:
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 20
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
@@ -113,4 +113,7 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Build packages
|
||||
run: pnpm --filter=shadcn build
|
||||
|
||||
- run: pnpm typecheck
|
||||
|
||||
38
.github/workflows/issue-stale.yml
vendored
38
.github/workflows/issue-stale.yml
vendored
@@ -1,5 +1,5 @@
|
||||
# Adapted from vercel/next.js
|
||||
name: Issue Stale
|
||||
name: "Stale issue handler"
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
@@ -11,17 +11,35 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository_owner == 'shadcn-ui'
|
||||
steps:
|
||||
- uses: actions/stale@v4
|
||||
id: stale-no-repro
|
||||
name: "Close stale issues with no reproduction"
|
||||
- uses: actions/stale@v9
|
||||
id: issue-stale
|
||||
name: "Mark stale issues, close stale issues"
|
||||
with:
|
||||
repo-token: ${{ secrets.STALE_TOKEN }}
|
||||
close-issue-message: "This issue has been automatically closed because it received no activity for a while. If you think it was closed by accident, please reopen or leave a comment. Thank you.\n(This is an automated message.)"
|
||||
ascending: true
|
||||
days-before-issue-close: 7
|
||||
days-before-issue-stale: 30
|
||||
stale-pr-label: "stale?"
|
||||
days-before-pr-close: 7
|
||||
days-before-pr-stale: 15
|
||||
only-pr-labels: "postpone: more info or changes requested,please add a reproduction"
|
||||
days-before-issue-stale: 365 # ~2 years
|
||||
days-before-pr-stale: -1
|
||||
days-before-pr-close: -1
|
||||
remove-issue-stale-when-updated: true
|
||||
stale-issue-label: "stale?"
|
||||
exempt-issue-labels: "roadmap,next,bug"
|
||||
stale-issue-message: "This issue has been automatically marked as stale due to one year of inactivity. It will be closed in 7 days unless there’s further input. If you believe this issue is still relevant, please leave a comment or provide updated details. Thank you."
|
||||
close-issue-message: "This issue has been automatically closed due to one year of inactivity. If you’re still experiencing a similar problem or have additional details to share, please open a new issue following our current issue template. Your updated report helps us investigate and address concerns more efficiently. Thank you for your understanding!"
|
||||
operations-per-run: 300 # 1 operation per 100 issues, the rest is to label/comment/close
|
||||
- uses: actions/stale@v9
|
||||
id: pr-state
|
||||
name: "Mark stale PRs, close stale PRs"
|
||||
with:
|
||||
repo-token: ${{ secrets.STALE_TOKEN }}
|
||||
ascending: true
|
||||
days-before-issue-close: -1
|
||||
days-before-issue-stale: -1
|
||||
days-before-pr-close: 7
|
||||
days-before-pr-stale: 365 # PRs with no activity in over 90 days will be marked as stale
|
||||
remove-pr-stale-when-updated: true
|
||||
exempt-pr-labels: "roadmap,nex,awaiting-approval,work-in-progress"
|
||||
stale-pr-label: "stale?"
|
||||
stale-pr-message: "This PR has been automatically marked as stale due to one year of inactivity. It will be closed in 7 days unless there’s further input. If you believe this PR is still relevant, please leave a comment or provide updated details. Thank you."
|
||||
close-pr-message: "This PR has been automatically closed due to one year of inactivity. Thank you for your understanding!"
|
||||
operations-per-run: 300 # 1 operation per 100 issues, the rest is to label/comment/close
|
||||
|
||||
2
.github/workflows/prerelease-comment.yml
vendored
2
.github/workflows/prerelease-comment.yml
vendored
@@ -49,7 +49,7 @@ jobs:
|
||||
A new prerelease is available for testing:
|
||||
|
||||
```sh
|
||||
npx shadcn@${{ env.BETA_PACKAGE_VERSION }}
|
||||
pnpm dlx shadcn@${{ env.BETA_PACKAGE_VERSION }}
|
||||
```
|
||||
|
||||
- name: "Remove the autorelease label once published"
|
||||
|
||||
2
.github/workflows/prerelease.yml
vendored
2
.github/workflows/prerelease.yml
vendored
@@ -54,7 +54,7 @@ jobs:
|
||||
path: packages/shadcn
|
||||
|
||||
- name: Upload packaged artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: npm-package-shadcn@${{ steps.package-version.outputs.current-version }}-pr-${{ github.event.number }} # encode the PR number into the artifact name
|
||||
path: packages/shadcn/dist/index.js
|
||||
|
||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -33,4 +33,9 @@ yarn-error.log*
|
||||
.turbo
|
||||
|
||||
.contentlayer
|
||||
tsconfig.tsbuildinfo
|
||||
tsconfig.tsbuildinfo
|
||||
|
||||
# ide
|
||||
.idea
|
||||
.fleet
|
||||
.vscode
|
||||
|
||||
6
.vscode/settings.json
vendored
6
.vscode/settings.json
vendored
@@ -4,8 +4,10 @@
|
||||
{ "pattern": "packages/*/" }
|
||||
],
|
||||
"tailwindCSS.experimental.classRegex": [
|
||||
["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"],
|
||||
["cn\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"]
|
||||
["cva\\(((?:[^()]|\\([^()]*\\))*)\\)", "[\"'`]?([^\"'`]+)[\"'`]?"],
|
||||
["cn\\(((?:[^()]|\\([^()]*\\))*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"]
|
||||
// "cva\\(([^)]*)\\)",
|
||||
// "[\"'`]([^\"'`]*).*?[\"'`]"
|
||||
],
|
||||
"vitest.debugExclude": [
|
||||
"<node_internals>/**",
|
||||
|
||||
@@ -91,6 +91,42 @@ pnpm --filter=www dev
|
||||
pnpm --filter=shadcn-ui dev
|
||||
```
|
||||
|
||||
## Running the CLI Locally
|
||||
|
||||
To run the CLI locally, you can follow the workflow:
|
||||
|
||||
1. Start by running the registry (main site) to make sure the components are up to date:
|
||||
|
||||
```bash
|
||||
pnpm www:dev
|
||||
```
|
||||
|
||||
2. Run the development script for the CLI:
|
||||
|
||||
```bash
|
||||
pnpm shadcn:dev
|
||||
```
|
||||
|
||||
3. In another terminal tab, test the CLI by running:
|
||||
|
||||
```bash
|
||||
pnpm shadcn
|
||||
```
|
||||
|
||||
To test the CLI in a specific app, use a command like:
|
||||
|
||||
```bash
|
||||
pnpm shadcn <init | add | ...> -c ~/Desktop/my-app
|
||||
```
|
||||
|
||||
4. To run the tests for the CLI:
|
||||
|
||||
```bash
|
||||
pnpm --filter=shadcn test
|
||||
```
|
||||
|
||||
This workflow ensures that you are running the most recent version of the registry and testing the CLI properly in your local environment.
|
||||
|
||||
## Documentation
|
||||
|
||||
The documentation for this project is located in the `www` workspace. You can run the documentation locally by running the following command:
|
||||
|
||||
@@ -3,7 +3,12 @@
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/versions
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
@@ -23,9 +28,10 @@
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
# env files (can opt-in for committing if needed)
|
||||
.env*
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
6
apps/v4/.prettierignore
Normal file
6
apps/v4/.prettierignore
Normal file
@@ -0,0 +1,6 @@
|
||||
dist
|
||||
node_modules
|
||||
.next
|
||||
build
|
||||
.contentlayer
|
||||
registry/__index__.tsx
|
||||
1
apps/v4/README.md
Normal file
1
apps/v4/README.md
Normal file
@@ -0,0 +1 @@
|
||||
This is a wip registry for the `shadcn` canary version. It has React 19 and Tailwind v4 components.
|
||||
1
apps/v4/__registry__/.autogenerated
Normal file
1
apps/v4/__registry__/.autogenerated
Normal file
@@ -0,0 +1 @@
|
||||
// The content of this directory is autogenerated by the registry server.
|
||||
0
apps/v4/__registry__/.gitkeep
Normal file
0
apps/v4/__registry__/.gitkeep
Normal file
1
apps/v4/__registry__/README.md
Normal file
1
apps/v4/__registry__/README.md
Normal file
@@ -0,0 +1 @@
|
||||
> Files inside this directory is autogenerated by `./scripts/build-registry.ts`. **Do not edit them manually.** - shadcn
|
||||
3829
apps/v4/__registry__/index.tsx
Normal file
3829
apps/v4/__registry__/index.tsx
Normal file
File diff suppressed because it is too large
Load Diff
76
apps/v4/app/(app)/charts/charts.tsx
Normal file
76
apps/v4/app/(app)/charts/charts.tsx
Normal file
@@ -0,0 +1,76 @@
|
||||
export { ChartAreaDefault } from "@/registry/new-york-v4/charts/chart-area-default"
|
||||
export { ChartAreaLinear } from "@/registry/new-york-v4/charts/chart-area-linear"
|
||||
export { ChartAreaStep } from "@/registry/new-york-v4/charts/chart-area-step"
|
||||
export { ChartAreaLegend } from "@/registry/new-york-v4/charts/chart-area-legend"
|
||||
export { ChartAreaStacked } from "@/registry/new-york-v4/charts/chart-area-stacked"
|
||||
export { ChartAreaStackedExpand } from "@/registry/new-york-v4/charts/chart-area-stacked-expand"
|
||||
export { ChartAreaIcons } from "@/registry/new-york-v4/charts/chart-area-icons"
|
||||
export { ChartAreaGradient } from "@/registry/new-york-v4/charts/chart-area-gradient"
|
||||
export { ChartAreaAxes } from "@/registry/new-york-v4/charts/chart-area-axes"
|
||||
export { ChartAreaInteractive } from "@/registry/new-york-v4/charts/chart-area-interactive"
|
||||
|
||||
export { ChartBarDefault } from "@/registry/new-york-v4/charts/chart-bar-default"
|
||||
export { ChartBarHorizontal } from "@/registry/new-york-v4/charts/chart-bar-horizontal"
|
||||
export { ChartBarMultiple } from "@/registry/new-york-v4/charts/chart-bar-multiple"
|
||||
export { ChartBarStacked } from "@/registry/new-york-v4/charts/chart-bar-stacked"
|
||||
export { ChartBarLabel } from "@/registry/new-york-v4/charts/chart-bar-label"
|
||||
export { ChartBarLabelCustom } from "@/registry/new-york-v4/charts/chart-bar-label-custom"
|
||||
export { ChartBarMixed } from "@/registry/new-york-v4/charts/chart-bar-mixed"
|
||||
export { ChartBarActive } from "@/registry/new-york-v4/charts/chart-bar-active"
|
||||
export { ChartBarNegative } from "@/registry/new-york-v4/charts/chart-bar-negative"
|
||||
export { ChartBarInteractive } from "@/registry/new-york-v4/charts/chart-bar-interactive"
|
||||
|
||||
export { ChartLineDefault } from "@/registry/new-york-v4/charts/chart-line-default"
|
||||
export { ChartLineLinear } from "@/registry/new-york-v4/charts/chart-line-linear"
|
||||
export { ChartLineStep } from "@/registry/new-york-v4/charts/chart-line-step"
|
||||
export { ChartLineMultiple } from "@/registry/new-york-v4/charts/chart-line-multiple"
|
||||
export { ChartLineDots } from "@/registry/new-york-v4/charts/chart-line-dots"
|
||||
export { ChartLineDotsCustom } from "@/registry/new-york-v4/charts/chart-line-dots-custom"
|
||||
export { ChartLineDotsColors } from "@/registry/new-york-v4/charts/chart-line-dots-colors"
|
||||
export { ChartLineLabel } from "@/registry/new-york-v4/charts/chart-line-label"
|
||||
export { ChartLineLabelCustom } from "@/registry/new-york-v4/charts/chart-line-label-custom"
|
||||
export { ChartLineInteractive } from "@/registry/new-york-v4/charts/chart-line-interactive"
|
||||
|
||||
export { ChartPieSimple } from "@/registry/new-york-v4/charts/chart-pie-simple"
|
||||
export { ChartPieSeparatorNone } from "@/registry/new-york-v4/charts/chart-pie-separator-none"
|
||||
export { ChartPieLabel } from "@/registry/new-york-v4/charts/chart-pie-label"
|
||||
export { ChartPieLabelCustom } from "@/registry/new-york-v4/charts/chart-pie-label-custom"
|
||||
export { ChartPieLabelList } from "@/registry/new-york-v4/charts/chart-pie-label-list"
|
||||
export { ChartPieLegend } from "@/registry/new-york-v4/charts/chart-pie-legend"
|
||||
export { ChartPieDonut } from "@/registry/new-york-v4/charts/chart-pie-donut"
|
||||
export { ChartPieDonutActive } from "@/registry/new-york-v4/charts/chart-pie-donut-active"
|
||||
export { ChartPieDonutText } from "@/registry/new-york-v4/charts/chart-pie-donut-text"
|
||||
export { ChartPieStacked } from "@/registry/new-york-v4/charts/chart-pie-stacked"
|
||||
export { ChartPieInteractive } from "@/registry/new-york-v4/charts/chart-pie-interactive"
|
||||
|
||||
export { ChartRadarDefault } from "@/registry/new-york-v4/charts/chart-radar-default"
|
||||
export { ChartRadarDots } from "@/registry/new-york-v4/charts/chart-radar-dots"
|
||||
export { ChartRadarLinesOnly } from "@/registry/new-york-v4/charts/chart-radar-lines-only"
|
||||
export { ChartRadarLabelCustom } from "@/registry/new-york-v4/charts/chart-radar-label-custom"
|
||||
export { ChartRadarGridCustom } from "@/registry/new-york-v4/charts/chart-radar-grid-custom"
|
||||
export { ChartRadarGridNone } from "@/registry/new-york-v4/charts/chart-radar-grid-none"
|
||||
export { ChartRadarGridCircle } from "@/registry/new-york-v4/charts/chart-radar-grid-circle"
|
||||
export { ChartRadarGridCircleNoLines } from "@/registry/new-york-v4/charts/chart-radar-grid-circle-no-lines"
|
||||
export { ChartRadarGridCircleFill } from "@/registry/new-york-v4/charts/chart-radar-grid-circle-fill"
|
||||
export { ChartRadarGridFill } from "@/registry/new-york-v4/charts/chart-radar-grid-fill"
|
||||
export { ChartRadarMultiple } from "@/registry/new-york-v4/charts/chart-radar-multiple"
|
||||
export { ChartRadarLegend } from "@/registry/new-york-v4/charts/chart-radar-legend"
|
||||
export { ChartRadarIcons } from "@/registry/new-york-v4/charts/chart-radar-icons"
|
||||
export { ChartRadarRadius } from "@/registry/new-york-v4/charts/chart-radar-radius"
|
||||
|
||||
export { ChartRadialSimple } from "@/registry/new-york-v4/charts/chart-radial-simple"
|
||||
export { ChartRadialLabel } from "@/registry/new-york-v4/charts/chart-radial-label"
|
||||
export { ChartRadialGrid } from "@/registry/new-york-v4/charts/chart-radial-grid"
|
||||
export { ChartRadialText } from "@/registry/new-york-v4/charts/chart-radial-text"
|
||||
export { ChartRadialShape } from "@/registry/new-york-v4/charts/chart-radial-shape"
|
||||
export { ChartRadialStacked } from "@/registry/new-york-v4/charts/chart-radial-stacked"
|
||||
|
||||
export { ChartTooltipDefault } from "@/registry/new-york-v4/charts/chart-tooltip-default"
|
||||
export { ChartTooltipIndicatorLine } from "@/registry/new-york-v4/charts/chart-tooltip-indicator-line"
|
||||
export { ChartTooltipIndicatorNone } from "@/registry/new-york-v4/charts/chart-tooltip-indicator-none"
|
||||
export { ChartTooltipLabelCustom } from "@/registry/new-york-v4/charts/chart-tooltip-label-custom"
|
||||
export { ChartTooltipLabelFormatter } from "@/registry/new-york-v4/charts/chart-tooltip-label-formatter"
|
||||
export { ChartTooltipLabelNone } from "@/registry/new-york-v4/charts/chart-tooltip-label-none"
|
||||
export { ChartTooltipFormatter } from "@/registry/new-york-v4/charts/chart-tooltip-formatter"
|
||||
export { ChartTooltipIcons } from "@/registry/new-york-v4/charts/chart-tooltip-icons"
|
||||
export { ChartTooltipAdvanced } from "@/registry/new-york-v4/charts/chart-tooltip-advanced"
|
||||
20
apps/v4/app/(app)/charts/page.tsx
Normal file
20
apps/v4/app/(app)/charts/page.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import { ComponentWrapper } from "@/components/component-wrapper"
|
||||
import * as Charts from "@/app/(app)/charts/charts"
|
||||
|
||||
export default function ChartsPage() {
|
||||
return (
|
||||
<div className="grid flex-1 grid-cols-3 items-start gap-4 p-4 2xl:grid-cols-4">
|
||||
{Object.entries(Charts)
|
||||
.sort()
|
||||
.map(([key, Component]) => (
|
||||
<ComponentWrapper
|
||||
key={key}
|
||||
name={key}
|
||||
className="w-auto data-[name=chartareainteractive]:col-span-3 data-[name=chartbarinteractive]:col-span-3 data-[name=chartlineinteractive]:col-span-3 **:data-[slot=card]:w-full"
|
||||
>
|
||||
<Component />
|
||||
</ComponentWrapper>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
9
apps/v4/app/(app)/forms/page.tsx
Normal file
9
apps/v4/app/(app)/forms/page.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import { FormsDemo } from "@/components/forms-demo"
|
||||
|
||||
export default function FormsPage() {
|
||||
return (
|
||||
<div className="flex flex-1 items-center justify-center p-4">
|
||||
<FormsDemo />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
42
apps/v4/app/(app)/layout.tsx
Normal file
42
apps/v4/app/(app)/layout.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import { cookies } from "next/headers"
|
||||
|
||||
import { AppSidebar } from "@/components/app-sidebar"
|
||||
import { ModeSwitcher } from "@/components/mode-switcher"
|
||||
import { NavHeader } from "@/components/nav-header"
|
||||
import { Separator } from "@/registry/new-york-v4/ui/separator"
|
||||
import {
|
||||
SidebarInset,
|
||||
SidebarProvider,
|
||||
SidebarTrigger,
|
||||
} from "@/registry/new-york-v4/ui/sidebar"
|
||||
|
||||
export default async function AppLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode
|
||||
}>) {
|
||||
const cookieStore = await cookies()
|
||||
const defaultOpen = cookieStore.get("sidebar_state")?.value === "true"
|
||||
|
||||
return (
|
||||
<SidebarProvider defaultOpen={defaultOpen}>
|
||||
<AppSidebar />
|
||||
<SidebarInset>
|
||||
<header className="bg-background sticky inset-x-0 top-0 isolate z-10 flex shrink-0 items-center gap-2 border-b">
|
||||
<div className="flex h-14 w-full items-center gap-2 px-4">
|
||||
<SidebarTrigger className="-ml-1.5" />
|
||||
<Separator
|
||||
orientation="vertical"
|
||||
className="mr-2 data-[orientation=vertical]:h-4"
|
||||
/>
|
||||
<NavHeader />
|
||||
<div className="ml-auto flex items-center gap-2">
|
||||
<ModeSwitcher />
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
{children}
|
||||
</SidebarInset>
|
||||
</SidebarProvider>
|
||||
)
|
||||
}
|
||||
40
apps/v4/app/(app)/login/page.tsx
Normal file
40
apps/v4/app/(app)/login/page.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import {
|
||||
Manrope as FontManrope,
|
||||
Lexend as FontSans,
|
||||
Newsreader as FontSerif,
|
||||
} from "next/font/google"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { LoginForm } from "@/components/login-form"
|
||||
|
||||
const fontSans = FontSans({ subsets: ["latin"], variable: "--font-sans" })
|
||||
const fontSerif = FontSerif({ subsets: ["latin"], variable: "--font-serif" })
|
||||
const fontManrope = FontManrope({
|
||||
subsets: ["latin"],
|
||||
variable: "--font-manrope",
|
||||
})
|
||||
export default function LoginPage() {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"bg-muted flex flex-1 flex-col items-center justify-center gap-16 p-6 md:p-10",
|
||||
fontSans.variable,
|
||||
fontSerif.variable,
|
||||
fontManrope.variable
|
||||
)}
|
||||
>
|
||||
<div className="w-full max-w-sm md:max-w-3xl">
|
||||
<LoginForm />
|
||||
</div>
|
||||
<div className="theme-login-one w-full max-w-sm md:max-w-3xl">
|
||||
<LoginForm imageUrl="https://images.unsplash.com/photo-1482872376051-5ce74ebf0908?q=80&w=3050&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" />
|
||||
</div>
|
||||
<div className="theme-login-two w-full max-w-sm md:max-w-3xl">
|
||||
<LoginForm imageUrl="https://images.unsplash.com/photo-1498758536662-35b82cd15e29?q=80&w=3088&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" />
|
||||
</div>
|
||||
<div className="theme-login-three w-full max-w-sm md:max-w-3xl">
|
||||
<LoginForm imageUrl="https://images.unsplash.com/photo-1536147116438-62679a5e01f2?q=80&w=2688&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
196
apps/v4/app/(app)/page.tsx
Normal file
196
apps/v4/app/(app)/page.tsx
Normal file
@@ -0,0 +1,196 @@
|
||||
import { AccordionDemo } from "@/components/accordion-demo"
|
||||
import { AlertDemo } from "@/components/alert-demo"
|
||||
import { AlertDialogDemo } from "@/components/alert-dialog-demo"
|
||||
import { AspectRatioDemo } from "@/components/aspect-ratio-demo"
|
||||
import { AvatarDemo } from "@/components/avatar-demo"
|
||||
import { BadgeDemo } from "@/components/badge-demo"
|
||||
import { BreadcrumbDemo } from "@/components/breadcrumb-demo"
|
||||
import { ButtonDemo } from "@/components/button-demo"
|
||||
import { CalendarDemo } from "@/components/calendar-demo"
|
||||
import { CardDemo } from "@/components/card-demo"
|
||||
import { CarouselDemo } from "@/components/carousel-demo"
|
||||
import { ChartDemo } from "@/components/chart-demo"
|
||||
import { CheckboxDemo } from "@/components/checkbox-demo"
|
||||
import { CollapsibleDemo } from "@/components/collapsible-demo"
|
||||
import { ComboboxDemo } from "@/components/combobox-demo"
|
||||
import { CommandDemo } from "@/components/command-demo"
|
||||
import { ComponentWrapper } from "@/components/component-wrapper"
|
||||
import { ContextMenuDemo } from "@/components/context-menu-demo"
|
||||
import { DatePickerDemo } from "@/components/date-picker-demo"
|
||||
import { DialogDemo } from "@/components/dialog-demo"
|
||||
import { DrawerDemo } from "@/components/drawer-demo"
|
||||
import { DropdownMenuDemo } from "@/components/dropdown-menu-demo"
|
||||
import { FormDemo } from "@/components/form-demo"
|
||||
import { HoverCardDemo } from "@/components/hover-card-demo"
|
||||
import { InputDemo } from "@/components/input-demo"
|
||||
import { InputOTPDemo } from "@/components/input-otp-demo"
|
||||
import { LabelDemo } from "@/components/label-demo"
|
||||
import { MenubarDemo } from "@/components/menubar-demo"
|
||||
import { NavigationMenuDemo } from "@/components/navigation-menu-demo"
|
||||
import { PaginationDemo } from "@/components/pagination-demo"
|
||||
import { PopoverDemo } from "@/components/popover-demo"
|
||||
import { ProgressDemo } from "@/components/progress-demo"
|
||||
import { RadioGroupDemo } from "@/components/radio-group-demo"
|
||||
import { ResizableDemo } from "@/components/resizable-demo"
|
||||
import { ScrollAreaDemo } from "@/components/scroll-area-demo"
|
||||
import { SelectDemo } from "@/components/select-demo"
|
||||
import { SeparatorDemo } from "@/components/separator-demo"
|
||||
import { SheetDemo } from "@/components/sheet-demo"
|
||||
import { SkeletonDemo } from "@/components/skeleton-demo"
|
||||
import { SliderDemo } from "@/components/slider-demo"
|
||||
import { SonnerDemo } from "@/components/sonner-demo"
|
||||
import { SwitchDemo } from "@/components/switch-demo"
|
||||
import { TableDemo } from "@/components/table-demo"
|
||||
import { TabsDemo } from "@/components/tabs-demo"
|
||||
import { TextareaDemo } from "@/components/textarea-demo"
|
||||
import { ToggleDemo } from "@/components/toggle-demo"
|
||||
import { ToggleGroupDemo } from "@/components/toggle-group-demo"
|
||||
import { TooltipDemo } from "@/components/tooltip-demo"
|
||||
|
||||
export default function SinkPage() {
|
||||
return (
|
||||
<div className="@container grid flex-1 gap-4 p-4">
|
||||
<ComponentWrapper name="chart" className="w-full">
|
||||
<ChartDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="accordion">
|
||||
<AccordionDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="alert">
|
||||
<AlertDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="alert-dialog">
|
||||
<AlertDialogDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="aspect-ratio">
|
||||
<AspectRatioDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="avatar">
|
||||
<AvatarDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="badge">
|
||||
<BadgeDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="breadcrumb">
|
||||
<BreadcrumbDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="button">
|
||||
<ButtonDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="calendar">
|
||||
<CalendarDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="card">
|
||||
<CardDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="carousel">
|
||||
<CarouselDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="checkbox">
|
||||
<CheckboxDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="collapsible">
|
||||
<CollapsibleDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="combobox">
|
||||
<ComboboxDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="command">
|
||||
<CommandDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="context-menu">
|
||||
<ContextMenuDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="date-picker">
|
||||
<DatePickerDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="dialog">
|
||||
<DialogDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="drawer">
|
||||
<DrawerDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="dropdown-menu">
|
||||
<DropdownMenuDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="form">
|
||||
<FormDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="hover-card">
|
||||
<HoverCardDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="input">
|
||||
<InputDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="input-otp">
|
||||
<InputOTPDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="label">
|
||||
<LabelDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="menubar">
|
||||
<MenubarDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="navigation-menu">
|
||||
<NavigationMenuDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="pagination">
|
||||
<PaginationDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="popover">
|
||||
<PopoverDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="progress">
|
||||
<ProgressDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="radio-group">
|
||||
<RadioGroupDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="resizable">
|
||||
<ResizableDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="scroll-area">
|
||||
<ScrollAreaDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="select">
|
||||
<SelectDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="separator">
|
||||
<SeparatorDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="sheet">
|
||||
<SheetDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="skeleton">
|
||||
<SkeletonDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="slider">
|
||||
<SliderDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="sonner">
|
||||
<SonnerDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="switch">
|
||||
<SwitchDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="table">
|
||||
<TableDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="tabs">
|
||||
<TabsDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="textarea">
|
||||
<TextareaDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="toggle">
|
||||
<ToggleDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="toggle-group">
|
||||
<ToggleGroupDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="tooltip">
|
||||
<TooltipDemo />
|
||||
</ComponentWrapper>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
96
apps/v4/app/(view)/view/[name]/page.tsx
Normal file
96
apps/v4/app/(view)/view/[name]/page.tsx
Normal file
@@ -0,0 +1,96 @@
|
||||
import * as React from "react"
|
||||
import { Metadata } from "next"
|
||||
import { notFound } from "next/navigation"
|
||||
import { registryItemSchema } from "shadcn/registry"
|
||||
import { z } from "zod"
|
||||
|
||||
import { getRegistryComponent, getRegistryItem } from "@/lib/registry"
|
||||
import { absoluteUrl, cn } from "@/lib/utils"
|
||||
import { siteConfig } from "@/www/config/site"
|
||||
|
||||
const getCachedRegistryItem = React.cache(async (name: string) => {
|
||||
return await getRegistryItem(name)
|
||||
})
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{
|
||||
name: string
|
||||
}>
|
||||
}): Promise<Metadata> {
|
||||
const { name } = await params
|
||||
const item = await getCachedRegistryItem(name)
|
||||
|
||||
if (!item) {
|
||||
return {}
|
||||
}
|
||||
|
||||
const title = item.name
|
||||
const description = item.description
|
||||
|
||||
return {
|
||||
title: `${item.name}${item.description ? ` - ${item.description}` : ""}`,
|
||||
description,
|
||||
openGraph: {
|
||||
title,
|
||||
description,
|
||||
type: "article",
|
||||
url: absoluteUrl(`/blocks/${item.name}`),
|
||||
images: [
|
||||
{
|
||||
url: siteConfig.ogImage,
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: siteConfig.name,
|
||||
},
|
||||
],
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title,
|
||||
description,
|
||||
images: [siteConfig.ogImage],
|
||||
creator: "@shadcn",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export const dynamicParams = false
|
||||
|
||||
export async function generateStaticParams() {
|
||||
const { Index } = await import("@/__registry__")
|
||||
const index = z.record(registryItemSchema).parse(Index)
|
||||
|
||||
return Object.values(index)
|
||||
.filter((block) =>
|
||||
["registry:block", "registry:component"].includes(block.type)
|
||||
)
|
||||
.map((block) => ({
|
||||
name: block.name,
|
||||
}))
|
||||
}
|
||||
|
||||
export default async function BlockPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{
|
||||
name: string
|
||||
}>
|
||||
}) {
|
||||
const { name } = await params
|
||||
const item = await getCachedRegistryItem(name)
|
||||
const Component = getRegistryComponent(name)
|
||||
|
||||
if (!item || !Component) {
|
||||
return notFound()
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={cn("themes-wrapper bg-background", item.meta?.container)}>
|
||||
<Component />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
199
apps/v4/app/globals.css
Normal file
199
apps/v4/app/globals.css
Normal file
@@ -0,0 +1,199 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
@plugin "tailwindcss-animate";
|
||||
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
:root {
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.145 0 0);
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.145 0 0);
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.145 0 0);
|
||||
--primary: oklch(0.205 0 0);
|
||||
--primary-foreground: oklch(0.985 0 0);
|
||||
--secondary: oklch(0.97 0 0);
|
||||
--secondary-foreground: oklch(0.205 0 0);
|
||||
--muted: oklch(0.97 0 0);
|
||||
--muted-foreground: oklch(0.556 0 0);
|
||||
--accent: oklch(0.97 0 0);
|
||||
--accent-foreground: oklch(0.205 0 0);
|
||||
--destructive: oklch(0.577 0.245 27.325);
|
||||
--destructive-foreground: oklch(0.577 0.245 27.325);
|
||||
--border: oklch(0.922 0 0);
|
||||
--input: oklch(0.922 0 0);
|
||||
--ring: oklch(0.708 0 0);
|
||||
--chart-1: oklch(0.646 0.222 41.116);
|
||||
--chart-2: oklch(0.6 0.118 184.704);
|
||||
--chart-3: oklch(0.398 0.07 227.392);
|
||||
--chart-4: oklch(0.828 0.189 84.429);
|
||||
--chart-5: oklch(0.769 0.188 70.08);
|
||||
--radius: 0.625rem;
|
||||
--sidebar: oklch(0.985 0 0);
|
||||
--sidebar-foreground: oklch(0.145 0 0);
|
||||
--sidebar-primary: oklch(0.205 0 0);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.97 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.205 0 0);
|
||||
--sidebar-border: oklch(0.922 0 0);
|
||||
--sidebar-ring: oklch(0.708 0 0);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: oklch(0.145 0 0);
|
||||
--foreground: oklch(0.985 0 0);
|
||||
--card: oklch(0.145 0 0);
|
||||
--card-foreground: oklch(0.985 0 0);
|
||||
--popover: oklch(0.145 0 0);
|
||||
--popover-foreground: oklch(0.985 0 0);
|
||||
--primary: oklch(0.985 0 0);
|
||||
--primary-foreground: oklch(0.205 0 0);
|
||||
--secondary: oklch(0.269 0 0);
|
||||
--secondary-foreground: oklch(0.985 0 0);
|
||||
--muted: oklch(0.269 0 0);
|
||||
--muted-foreground: oklch(0.708 0 0);
|
||||
--accent: oklch(0.269 0 0);
|
||||
--accent-foreground: oklch(0.985 0 0);
|
||||
--destructive: oklch(0.396 0.141 25.723);
|
||||
--destructive-foreground: oklch(0.637 0.237 25.331);
|
||||
--border: oklch(0.269 0 0);
|
||||
--input: oklch(0.269 0 0);
|
||||
--ring: oklch(0.556 0 0);
|
||||
--chart-1: oklch(0.488 0.243 264.376);
|
||||
--chart-2: oklch(0.696 0.17 162.48);
|
||||
--chart-3: oklch(0.769 0.188 70.08);
|
||||
--chart-4: oklch(0.627 0.265 303.9);
|
||||
--chart-5: oklch(0.645 0.246 16.439);
|
||||
--sidebar: oklch(0.205 0 0);
|
||||
--sidebar-foreground: oklch(0.985 0 0);
|
||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.269 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||
--sidebar-border: oklch(0.269 0 0);
|
||||
--sidebar-ring: oklch(0.439 0 0);
|
||||
}
|
||||
|
||||
.theme-login-one {
|
||||
--primary: #ce2a2d;
|
||||
--primary-foreground: #fff;
|
||||
--ring: #ce2a2d9c;
|
||||
--radius: 0rem;
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
|
||||
font-family: var(--font-sans);
|
||||
|
||||
a {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
[data-slot="card"] {
|
||||
border-radius: 0rem;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
.theme-login-two {
|
||||
--primary: #035fa8;
|
||||
--primary-foreground: #fff;
|
||||
--ring: #035fa89c;
|
||||
font-family: var(--font-serif);
|
||||
|
||||
a {
|
||||
color: var(--primary);
|
||||
}
|
||||
}
|
||||
|
||||
.theme-login-three {
|
||||
--primary: #22c55e;
|
||||
--primary-foreground: #000;
|
||||
--ring: #22c55e;
|
||||
--radius: 1.5rem;
|
||||
|
||||
font-family: var(--font-manrope);
|
||||
|
||||
a {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
[data-slot="card"] {
|
||||
@apply shadow-xl;
|
||||
}
|
||||
|
||||
[data-slot="input"] {
|
||||
@apply dark:bg-input;
|
||||
}
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
--font-sans: var(--font-sans);
|
||||
--font-mono: var(--font-mono);
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-card: var(--card);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
--color-popover: var(--popover);
|
||||
--color-popover-foreground: var(--popover-foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-accent: var(--accent);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-destructive-foreground: var(--destructive-foreground);
|
||||
--color-border: var(--border);
|
||||
--color-input: var(--input);
|
||||
--color-ring: var(--ring);
|
||||
--color-chart-1: var(--chart-1);
|
||||
--color-chart-2: var(--chart-2);
|
||||
--color-chart-3: var(--chart-3);
|
||||
--color-chart-4: var(--chart-4);
|
||||
--color-chart-5: var(--chart-5);
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
--color-sidebar: var(--sidebar);
|
||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||
--color-sidebar-primary: var(--sidebar-primary);
|
||||
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||
--color-sidebar-accent: var(--sidebar-accent);
|
||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||
--color-sidebar-border: var(--sidebar-border);
|
||||
--color-sidebar-ring: var(--sidebar-ring);
|
||||
--animate-accordion-down: accordion-down 0.2s ease-out;
|
||||
--animate-accordion-up: accordion-up 0.2s ease-out;
|
||||
|
||||
@keyframes accordion-down {
|
||||
from {
|
||||
height: 0;
|
||||
}
|
||||
to {
|
||||
height: var(--radix-accordion-content-height);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes accordion-up {
|
||||
from {
|
||||
height: var(--radix-accordion-content-height);
|
||||
}
|
||||
to {
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border outline-ring/50;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
123
apps/v4/app/layout.tsx
Normal file
123
apps/v4/app/layout.tsx
Normal file
@@ -0,0 +1,123 @@
|
||||
import type { Metadata, Viewport } from "next"
|
||||
import { Geist_Mono as FontMono, Inter as FontSans } from "next/font/google"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Analytics } from "@/components/analytics"
|
||||
import { ThemeProvider } from "@/components/theme-provider"
|
||||
import { Toaster } from "@/registry/new-york-v4/ui/sonner"
|
||||
import { siteConfig } from "@/www/config/site"
|
||||
|
||||
import "./globals.css"
|
||||
|
||||
const fontSans = FontSans({
|
||||
subsets: ["latin"],
|
||||
variable: "--font-sans",
|
||||
})
|
||||
|
||||
const fontMono = FontMono({
|
||||
subsets: ["latin"],
|
||||
variable: "--font-mono",
|
||||
})
|
||||
|
||||
const META_THEME_COLORS = {
|
||||
light: "#ffffff",
|
||||
dark: "#09090b",
|
||||
}
|
||||
|
||||
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: META_THEME_COLORS.light,
|
||||
}
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<head>
|
||||
<script
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
try {
|
||||
if (localStorage.theme === 'dark' || ((!('theme' in localStorage) || localStorage.theme === 'system') && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
||||
document.querySelector('meta[name="theme-color"]').setAttribute('content', '${META_THEME_COLORS.dark}')
|
||||
}
|
||||
} catch (_) {}
|
||||
`,
|
||||
}}
|
||||
/>
|
||||
</head>
|
||||
<body
|
||||
className={cn(
|
||||
"bg-background overscroll-none font-sans antialiased",
|
||||
fontSans.variable,
|
||||
fontMono.variable
|
||||
)}
|
||||
>
|
||||
<ThemeProvider
|
||||
attribute="class"
|
||||
defaultTheme="system"
|
||||
enableSystem
|
||||
disableTransitionOnChange
|
||||
>
|
||||
{children}
|
||||
<Toaster />
|
||||
<Analytics />
|
||||
</ThemeProvider>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
21
apps/v4/components.json
Normal file
21
apps/v4/components.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "new-york",
|
||||
"rsc": true,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "",
|
||||
"css": "app/globals.css",
|
||||
"baseColor": "neutral",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils",
|
||||
"ui": "@/registry/new-york-v4/ui",
|
||||
"lib": "@/lib",
|
||||
"hooks": "@/hooks"
|
||||
},
|
||||
"iconLibrary": "lucide"
|
||||
}
|
||||
72
apps/v4/components/accordion-demo.tsx
Normal file
72
apps/v4/components/accordion-demo.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from "@/registry/new-york-v4/ui/accordion"
|
||||
|
||||
export function AccordionDemo() {
|
||||
return (
|
||||
<div className="grid w-full max-w-xl gap-4">
|
||||
<Accordion type="single" collapsible className="w-full">
|
||||
<AccordionItem value="item-1">
|
||||
<AccordionTrigger>Is it accessible?</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Yes. It adheres to the WAI-ARIA design pattern.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="item-2">
|
||||
<AccordionTrigger>Is it styled?</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Yes. It comes with default styles that matches the other
|
||||
components' aesthetic.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="item-3">
|
||||
<AccordionTrigger>Is it animated?</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Yes. It's animated by default, but you can disable it if you
|
||||
prefer.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
<Accordion type="single" collapsible className="w-full">
|
||||
<AccordionItem value="item-1">
|
||||
<AccordionTrigger>
|
||||
What are the key considerations when implementing a comprehensive
|
||||
enterprise-level authentication system?
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Implementing a robust enterprise authentication system requires
|
||||
careful consideration of multiple factors. This includes secure
|
||||
password hashing and storage, multi-factor authentication (MFA)
|
||||
implementation, session management, OAuth2 and SSO integration,
|
||||
regular security audits, rate limiting to prevent brute force
|
||||
attacks, and maintaining detailed audit logs. Additionally,
|
||||
you'll need to consider scalability, performance impact, and
|
||||
compliance with relevant data protection regulations such as GDPR or
|
||||
HIPAA.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="item-2">
|
||||
<AccordionTrigger>
|
||||
How does modern distributed system architecture handle eventual
|
||||
consistency and data synchronization across multiple regions?
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Modern distributed systems employ various strategies to maintain
|
||||
data consistency across regions. This often involves using
|
||||
techniques like CRDT (Conflict-Free Replicated Data Types), vector
|
||||
clocks, and gossip protocols. Systems might implement event sourcing
|
||||
patterns, utilize message queues for asynchronous updates, and
|
||||
employ sophisticated conflict resolution strategies. Popular
|
||||
solutions like Amazon's DynamoDB and Google's Spanner
|
||||
demonstrate different approaches to solving these challenges,
|
||||
balancing between consistency, availability, and partition tolerance
|
||||
as described in the CAP theorem.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
115
apps/v4/components/alert-demo.tsx
Normal file
115
apps/v4/components/alert-demo.tsx
Normal file
@@ -0,0 +1,115 @@
|
||||
import {
|
||||
AlertCircleIcon,
|
||||
BookmarkCheckIcon,
|
||||
CheckCircle2Icon,
|
||||
GiftIcon,
|
||||
PopcornIcon,
|
||||
ShieldAlertIcon,
|
||||
} from "lucide-react"
|
||||
|
||||
import {
|
||||
Alert,
|
||||
AlertDescription,
|
||||
AlertTitle,
|
||||
} from "@/registry/new-york-v4/ui/alert"
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
|
||||
export function AlertDemo() {
|
||||
return (
|
||||
<div className="grid max-w-xl items-start gap-4">
|
||||
<Alert>
|
||||
<CheckCircle2Icon />
|
||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
||||
<AlertDescription>
|
||||
This is an alert with icon, title and description.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
<Alert>
|
||||
<BookmarkCheckIcon>Heads up!</BookmarkCheckIcon>
|
||||
<AlertDescription>
|
||||
This one has an icon and a description only. No title.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
<Alert>
|
||||
<AlertDescription>
|
||||
This one has a description only. No title. No icon.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
<Alert>
|
||||
<PopcornIcon />
|
||||
<AlertTitle>Let's try one with icon and title.</AlertTitle>
|
||||
</Alert>
|
||||
<Alert>
|
||||
<ShieldAlertIcon />
|
||||
<AlertTitle>
|
||||
This is a very long alert title that demonstrates how the component
|
||||
handles extended text content and potentially wraps across multiple
|
||||
lines
|
||||
</AlertTitle>
|
||||
</Alert>
|
||||
<Alert>
|
||||
<GiftIcon />
|
||||
<AlertDescription>
|
||||
This is a very long alert description that demonstrates how the
|
||||
component handles extended text content and potentially wraps across
|
||||
multiple lines
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
<Alert>
|
||||
<AlertCircleIcon />
|
||||
<AlertTitle>
|
||||
This is an extremely long alert title that spans multiple lines to
|
||||
demonstrate how the component handles very lengthy headings while
|
||||
maintaining readability and proper text wrapping behavior
|
||||
</AlertTitle>
|
||||
<AlertDescription>
|
||||
This is an equally long description that contains detailed information
|
||||
about the alert. It shows how the component can accommodate extensive
|
||||
content while preserving proper spacing, alignment, and readability
|
||||
across different screen sizes and viewport widths. This helps ensure
|
||||
the user experience remains consistent regardless of the content
|
||||
length.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
<Alert variant="destructive">
|
||||
<AlertCircleIcon />
|
||||
<AlertTitle>Something went wrong!</AlertTitle>
|
||||
<AlertDescription>
|
||||
Your session has expired. Please log in again.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
<Alert variant="destructive">
|
||||
<AlertCircleIcon />
|
||||
<AlertTitle>Unable to process your payment.</AlertTitle>
|
||||
<AlertDescription>
|
||||
<p>Please verify your billing information and try again.</p>
|
||||
<ul className="list-inside list-disc text-sm">
|
||||
<li>Check your card details</li>
|
||||
<li>Ensure sufficient funds</li>
|
||||
<li>Verify billing address</li>
|
||||
</ul>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
<Alert>
|
||||
<CheckCircle2Icon />
|
||||
<AlertTitle className="max-w-[calc(100%-4rem)] overflow-ellipsis">
|
||||
The selected emails have been marked as spam.
|
||||
</AlertTitle>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="absolute top-2.5 right-3 h-6 shadow-none"
|
||||
>
|
||||
Undo
|
||||
</Button>
|
||||
</Alert>
|
||||
<Alert className="border-amber-50 bg-amber-50 text-amber-900 dark:border-amber-950 dark:bg-amber-950 dark:text-amber-100">
|
||||
<CheckCircle2Icon />
|
||||
<AlertTitle>Plot Twist: This Alert is Actually Amber!</AlertTitle>
|
||||
<AlertDescription>
|
||||
This one has custom colors for light and dark mode.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
35
apps/v4/components/alert-dialog-demo.tsx
Normal file
35
apps/v4/components/alert-dialog-demo.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
AlertDialogTrigger,
|
||||
} from "@/registry/new-york-v4/ui/alert-dialog"
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
|
||||
export function AlertDialogDemo() {
|
||||
return (
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button variant="outline">Show Dialog</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
This action cannot be undone. This will permanently delete your
|
||||
account and remove your data from our servers.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction>Continue</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
)
|
||||
}
|
||||
7
apps/v4/components/analytics.tsx
Normal file
7
apps/v4/components/analytics.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
"use client"
|
||||
|
||||
import { Analytics as VercelAnalytics } from "@vercel/analytics/react"
|
||||
|
||||
export function Analytics() {
|
||||
return <VercelAnalytics />
|
||||
}
|
||||
243
apps/v4/components/app-sidebar.tsx
Normal file
243
apps/v4/components/app-sidebar.tsx
Normal file
@@ -0,0 +1,243 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { Index } from "@/__registry__"
|
||||
import {
|
||||
AudioWaveform,
|
||||
BookOpen,
|
||||
Bot,
|
||||
ChevronRightIcon,
|
||||
Command,
|
||||
GalleryVerticalEnd,
|
||||
Search,
|
||||
Settings2,
|
||||
SquareTerminal,
|
||||
} from "lucide-react"
|
||||
|
||||
import { NavUser } from "@/registry/new-york-v4/blocks/sidebar-07/components/nav-user"
|
||||
import { TeamSwitcher } from "@/registry/new-york-v4/blocks/sidebar-07/components/team-switcher"
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
CollapsibleTrigger,
|
||||
} from "@/registry/new-york-v4/ui/collapsible"
|
||||
import { Label } from "@/registry/new-york-v4/ui/label"
|
||||
import {
|
||||
Sidebar,
|
||||
SidebarContent,
|
||||
SidebarFooter,
|
||||
SidebarGroup,
|
||||
SidebarGroupContent,
|
||||
SidebarGroupLabel,
|
||||
SidebarHeader,
|
||||
SidebarInput,
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
SidebarMenuSub,
|
||||
SidebarMenuSubButton,
|
||||
SidebarMenuSubItem,
|
||||
SidebarRail,
|
||||
} from "@/registry/new-york-v4/ui/sidebar"
|
||||
|
||||
// This is sample data.
|
||||
const data = {
|
||||
user: {
|
||||
name: "shadcn",
|
||||
email: "m@example.com",
|
||||
avatar: "/avatars/shadcn.jpg",
|
||||
},
|
||||
teams: [
|
||||
{
|
||||
name: "Acme Inc",
|
||||
logo: GalleryVerticalEnd,
|
||||
plan: "Enterprise",
|
||||
},
|
||||
{
|
||||
name: "Acme Corp.",
|
||||
logo: AudioWaveform,
|
||||
plan: "Startup",
|
||||
},
|
||||
{
|
||||
name: "Evil Corp.",
|
||||
logo: Command,
|
||||
plan: "Free",
|
||||
},
|
||||
],
|
||||
navMain: [
|
||||
{
|
||||
title: "Playground",
|
||||
url: "#",
|
||||
icon: SquareTerminal,
|
||||
isActive: true,
|
||||
items: [
|
||||
{
|
||||
title: "History",
|
||||
url: "#",
|
||||
},
|
||||
{
|
||||
title: "Starred",
|
||||
url: "#",
|
||||
},
|
||||
{
|
||||
title: "Settings",
|
||||
url: "#",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Models",
|
||||
url: "#",
|
||||
icon: Bot,
|
||||
items: [
|
||||
{
|
||||
title: "Genesis",
|
||||
url: "#",
|
||||
},
|
||||
{
|
||||
title: "Explorer",
|
||||
url: "#",
|
||||
},
|
||||
{
|
||||
title: "Quantum",
|
||||
url: "#",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Documentation",
|
||||
url: "#",
|
||||
icon: BookOpen,
|
||||
items: [
|
||||
{
|
||||
title: "Introduction",
|
||||
url: "#",
|
||||
},
|
||||
{
|
||||
title: "Get Started",
|
||||
url: "#",
|
||||
},
|
||||
{
|
||||
title: "Tutorials",
|
||||
url: "#",
|
||||
},
|
||||
{
|
||||
title: "Changelog",
|
||||
url: "#",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Settings",
|
||||
url: "#",
|
||||
icon: Settings2,
|
||||
items: [
|
||||
{
|
||||
title: "General",
|
||||
url: "#",
|
||||
},
|
||||
{
|
||||
title: "Team",
|
||||
url: "#",
|
||||
},
|
||||
{
|
||||
title: "Billing",
|
||||
url: "#",
|
||||
},
|
||||
{
|
||||
title: "Limits",
|
||||
url: "#",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
components: Object.values(Index).filter(
|
||||
(item) => item.type === "registry:ui"
|
||||
),
|
||||
}
|
||||
|
||||
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
||||
return (
|
||||
<Sidebar collapsible="icon" {...props}>
|
||||
<SidebarHeader>
|
||||
<TeamSwitcher teams={data.teams} />
|
||||
<SidebarGroup className="py-0 group-data-[collapsible=icon]:hidden">
|
||||
<SidebarGroupContent>
|
||||
<form className="relative">
|
||||
<Label htmlFor="search" className="sr-only">
|
||||
Search
|
||||
</Label>
|
||||
<SidebarInput
|
||||
id="search"
|
||||
placeholder="Search the docs..."
|
||||
className="pl-8"
|
||||
/>
|
||||
<Search className="pointer-events-none absolute top-1/2 left-2 size-4 -translate-y-1/2 opacity-50 select-none" />
|
||||
</form>
|
||||
</SidebarGroupContent>
|
||||
</SidebarGroup>
|
||||
</SidebarHeader>
|
||||
<SidebarContent>
|
||||
<SidebarGroup>
|
||||
<SidebarGroupLabel>Platform</SidebarGroupLabel>
|
||||
<SidebarMenu>
|
||||
{data.navMain.map((item) => (
|
||||
<Collapsible
|
||||
key={item.title}
|
||||
asChild
|
||||
defaultOpen={item.isActive}
|
||||
className="group/collapsible"
|
||||
>
|
||||
<SidebarMenuItem>
|
||||
<CollapsibleTrigger asChild>
|
||||
<SidebarMenuButton tooltip={item.title}>
|
||||
{item.icon && <item.icon />}
|
||||
<span>{item.title}</span>
|
||||
<ChevronRightIcon className="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" />
|
||||
</SidebarMenuButton>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<SidebarMenuSub>
|
||||
{item.items?.map((subItem) => (
|
||||
<SidebarMenuSubItem key={subItem.title}>
|
||||
<SidebarMenuSubButton asChild>
|
||||
<a href={subItem.url}>
|
||||
<span>{subItem.title}</span>
|
||||
</a>
|
||||
</SidebarMenuSubButton>
|
||||
</SidebarMenuSubItem>
|
||||
))}
|
||||
</SidebarMenuSub>
|
||||
</CollapsibleContent>
|
||||
</SidebarMenuItem>
|
||||
</Collapsible>
|
||||
))}
|
||||
</SidebarMenu>
|
||||
</SidebarGroup>
|
||||
<SidebarGroup className="group-data-[collapsible=icon]:hidden">
|
||||
<SidebarGroupLabel>Components</SidebarGroupLabel>
|
||||
<SidebarMenu>
|
||||
{data.components.map((item) => (
|
||||
<SidebarMenuItem key={item.name}>
|
||||
<SidebarMenuButton asChild>
|
||||
<a href={`/#${item.name}`}>
|
||||
<span>{getComponentName(item.name)}</span>
|
||||
</a>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
))}
|
||||
</SidebarMenu>
|
||||
</SidebarGroup>
|
||||
</SidebarContent>
|
||||
<SidebarFooter>
|
||||
<NavUser user={data.user} />
|
||||
</SidebarFooter>
|
||||
<SidebarRail />
|
||||
</Sidebar>
|
||||
)
|
||||
}
|
||||
|
||||
function getComponentName(name: string) {
|
||||
// convert kebab-case to title case
|
||||
return name.replace(/-/g, " ").replace(/\b\w/g, (char) => char.toUpperCase())
|
||||
}
|
||||
26
apps/v4/components/aspect-ratio-demo.tsx
Normal file
26
apps/v4/components/aspect-ratio-demo.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import Image from "next/image"
|
||||
|
||||
import { AspectRatio } from "@/registry/new-york-v4/ui/aspect-ratio"
|
||||
|
||||
export function AspectRatioDemo() {
|
||||
return (
|
||||
<div className="grid w-full max-w-sm items-start gap-4">
|
||||
<AspectRatio ratio={16 / 9} className="bg-muted rounded-lg">
|
||||
<Image
|
||||
src="https://images.unsplash.com/photo-1588345921523-c2dcdb7f1dcd?w=800&dpr=2&q=80"
|
||||
alt="Photo by Drew Beamer"
|
||||
fill
|
||||
className="h-full w-full rounded-lg object-cover dark:brightness-[0.2] dark:grayscale"
|
||||
/>
|
||||
</AspectRatio>
|
||||
<AspectRatio ratio={1 / 1} className="bg-muted rounded-lg">
|
||||
<Image
|
||||
src="https://images.unsplash.com/photo-1588345921523-c2dcdb7f1dcd?w=800&dpr=2&q=80"
|
||||
alt="Photo by Drew Beamer"
|
||||
fill
|
||||
className="h-full w-full rounded-lg object-cover dark:brightness-[0.2] dark:grayscale"
|
||||
/>
|
||||
</AspectRatio>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
81
apps/v4/components/avatar-demo.tsx
Normal file
81
apps/v4/components/avatar-demo.tsx
Normal file
@@ -0,0 +1,81 @@
|
||||
import {
|
||||
Avatar,
|
||||
AvatarFallback,
|
||||
AvatarImage,
|
||||
} from "@/registry/new-york-v4/ui/avatar"
|
||||
|
||||
export function AvatarDemo() {
|
||||
return (
|
||||
<div className="flex flex-row flex-wrap items-center gap-4">
|
||||
<Avatar>
|
||||
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||
<AvatarFallback>CN</AvatarFallback>
|
||||
</Avatar>
|
||||
<Avatar>
|
||||
<AvatarFallback>CN</AvatarFallback>
|
||||
</Avatar>
|
||||
<Avatar className="size-12">
|
||||
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||
<AvatarFallback>CN</AvatarFallback>
|
||||
</Avatar>
|
||||
<Avatar className="rounded-lg">
|
||||
<AvatarImage
|
||||
src="https://github.com/evilrabbit.png"
|
||||
alt="@evilrabbit"
|
||||
/>
|
||||
<AvatarFallback>ER</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="*:data-[slot=avatar]:ring-background flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:grayscale">
|
||||
<Avatar>
|
||||
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||
<AvatarFallback>CN</AvatarFallback>
|
||||
</Avatar>
|
||||
<Avatar>
|
||||
<AvatarImage src="https://github.com/leerob.png" alt="@leerob" />
|
||||
<AvatarFallback>LR</AvatarFallback>
|
||||
</Avatar>
|
||||
<Avatar>
|
||||
<AvatarImage
|
||||
src="https://github.com/evilrabbit.png"
|
||||
alt="@evilrabbit"
|
||||
/>
|
||||
<AvatarFallback>ER</AvatarFallback>
|
||||
</Avatar>
|
||||
</div>
|
||||
<div className="*:data-[slot=avatar]:ring-background flex -space-x-2 *:data-[slot=avatar]:size-12 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:grayscale">
|
||||
<Avatar>
|
||||
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||
<AvatarFallback>CN</AvatarFallback>
|
||||
</Avatar>
|
||||
<Avatar>
|
||||
<AvatarImage src="https://github.com/leerob.png" alt="@leerob" />
|
||||
<AvatarFallback>LR</AvatarFallback>
|
||||
</Avatar>
|
||||
<Avatar>
|
||||
<AvatarImage
|
||||
src="https://github.com/evilrabbit.png"
|
||||
alt="@evilrabbit"
|
||||
/>
|
||||
<AvatarFallback>ER</AvatarFallback>
|
||||
</Avatar>
|
||||
</div>
|
||||
<div className="*:data-[slot=avatar]:ring-background flex -space-x-2 hover:space-x-1 *:data-[slot=avatar]:size-12 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:grayscale *:data-[slot=avatar]:transition-all *:data-[slot=avatar]:duration-300 *:data-[slot=avatar]:ease-in-out">
|
||||
<Avatar>
|
||||
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||
<AvatarFallback>CN</AvatarFallback>
|
||||
</Avatar>
|
||||
<Avatar>
|
||||
<AvatarImage src="https://github.com/leerob.png" alt="@leerob" />
|
||||
<AvatarFallback>LR</AvatarFallback>
|
||||
</Avatar>
|
||||
<Avatar>
|
||||
<AvatarImage
|
||||
src="https://github.com/evilrabbit.png"
|
||||
alt="@evilrabbit"
|
||||
/>
|
||||
<AvatarFallback>ER</AvatarFallback>
|
||||
</Avatar>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
61
apps/v4/components/badge-demo.tsx
Normal file
61
apps/v4/components/badge-demo.tsx
Normal file
@@ -0,0 +1,61 @@
|
||||
import { AlertCircleIcon, ArrowRightIcon, CheckIcon } from "lucide-react"
|
||||
|
||||
import { Badge } from "@/registry/new-york-v4/ui/badge"
|
||||
|
||||
export function BadgeDemo() {
|
||||
return (
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<div className="flex w-full flex-wrap gap-2">
|
||||
<Badge>Badge</Badge>
|
||||
<Badge variant="secondary">Secondary</Badge>
|
||||
<Badge variant="destructive">Destructive</Badge>
|
||||
<Badge variant="outline">Outline</Badge>
|
||||
<Badge variant="outline">
|
||||
<CheckIcon />
|
||||
Badge
|
||||
</Badge>
|
||||
<Badge variant="destructive">
|
||||
<AlertCircleIcon />
|
||||
Alert
|
||||
</Badge>
|
||||
<Badge className="h-5 min-w-5 rounded-full px-1 font-mono tabular-nums">
|
||||
8
|
||||
</Badge>
|
||||
<Badge
|
||||
className="h-5 min-w-5 rounded-full px-1 font-mono tabular-nums"
|
||||
variant="destructive"
|
||||
>
|
||||
99
|
||||
</Badge>
|
||||
<Badge
|
||||
className="h-5 min-w-5 rounded-full px-1 font-mono tabular-nums"
|
||||
variant="outline"
|
||||
>
|
||||
20+
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="flex w-full flex-wrap gap-2">
|
||||
<Badge asChild>
|
||||
<a href="#">
|
||||
Link <ArrowRightIcon />
|
||||
</a>
|
||||
</Badge>
|
||||
<Badge asChild variant="secondary">
|
||||
<a href="#">
|
||||
Link <ArrowRightIcon />
|
||||
</a>
|
||||
</Badge>
|
||||
<Badge asChild variant="destructive">
|
||||
<a href="#">
|
||||
Link <ArrowRightIcon />
|
||||
</a>
|
||||
</Badge>
|
||||
<Badge asChild variant="outline">
|
||||
<a href="#">
|
||||
Link <ArrowRightIcon />
|
||||
</a>
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
49
apps/v4/components/breadcrumb-demo.tsx
Normal file
49
apps/v4/components/breadcrumb-demo.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbEllipsis,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
BreadcrumbList,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
} from "@/registry/new-york-v4/ui/breadcrumb"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/registry/new-york-v4/ui/dropdown-menu"
|
||||
|
||||
export function BreadcrumbDemo() {
|
||||
return (
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink href="/">Home</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger className="flex items-center gap-1">
|
||||
<BreadcrumbEllipsis className="h-4 w-4" />
|
||||
<span className="sr-only">Toggle menu</span>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start">
|
||||
<DropdownMenuItem>Documentation</DropdownMenuItem>
|
||||
<DropdownMenuItem>Themes</DropdownMenuItem>
|
||||
<DropdownMenuItem>GitHub</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink href="/docs/components">Components</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage>Breadcrumb</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
)
|
||||
}
|
||||
84
apps/v4/components/button-demo.tsx
Normal file
84
apps/v4/components/button-demo.tsx
Normal file
@@ -0,0 +1,84 @@
|
||||
import { ArrowRightIcon, Loader2Icon, SendIcon } from "lucide-react"
|
||||
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
|
||||
export function ButtonDemo() {
|
||||
return (
|
||||
<div className="flex flex-col gap-6">
|
||||
<div className="flex flex-wrap items-center gap-2 md:flex-row">
|
||||
<Button>Button</Button>
|
||||
<Button variant="outline">Outline</Button>
|
||||
<Button variant="ghost">Ghost</Button>
|
||||
<Button variant="destructive">Destructive</Button>
|
||||
<Button variant="secondary">Secondary</Button>
|
||||
<Button variant="link">Link</Button>
|
||||
<Button variant="outline">
|
||||
<SendIcon /> Send
|
||||
</Button>
|
||||
<Button variant="outline">
|
||||
Learn More <ArrowRightIcon />
|
||||
</Button>
|
||||
<Button disabled variant="outline">
|
||||
<Loader2Icon className="animate-spin" />
|
||||
Please wait
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-2 md:flex-row">
|
||||
<Button size="sm">Small</Button>
|
||||
<Button variant="outline" size="sm">
|
||||
Outline
|
||||
</Button>
|
||||
<Button variant="ghost" size="sm">
|
||||
Ghost
|
||||
</Button>
|
||||
<Button variant="destructive" size="sm">
|
||||
Destructive
|
||||
</Button>
|
||||
<Button variant="secondary" size="sm">
|
||||
Secondary
|
||||
</Button>
|
||||
<Button variant="link" size="sm">
|
||||
Link
|
||||
</Button>
|
||||
<Button variant="outline" size="sm">
|
||||
<SendIcon /> Send
|
||||
</Button>
|
||||
<Button variant="outline" size="sm">
|
||||
Learn More <ArrowRightIcon />
|
||||
</Button>
|
||||
<Button disabled size="sm" variant="outline">
|
||||
<Loader2Icon className="animate-spin" />
|
||||
Please wait
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-2 md:flex-row">
|
||||
<Button size="lg">Large</Button>
|
||||
<Button variant="outline" size="lg">
|
||||
Outline
|
||||
</Button>
|
||||
<Button variant="ghost" size="lg">
|
||||
Ghost
|
||||
</Button>
|
||||
<Button variant="destructive" size="lg">
|
||||
Destructive
|
||||
</Button>
|
||||
<Button variant="secondary" size="lg">
|
||||
Secondary
|
||||
</Button>
|
||||
<Button variant="link" size="lg">
|
||||
Link
|
||||
</Button>
|
||||
<Button variant="outline" size="lg">
|
||||
<SendIcon /> Send
|
||||
</Button>
|
||||
<Button variant="outline" size="lg">
|
||||
Learn More <ArrowRightIcon />
|
||||
</Button>
|
||||
<Button disabled size="lg" variant="outline">
|
||||
<Loader2Icon className="animate-spin" />
|
||||
Please wait
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
47
apps/v4/components/calendar-demo.tsx
Normal file
47
apps/v4/components/calendar-demo.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { addDays } from "date-fns"
|
||||
import { type DateRange } from "react-day-picker"
|
||||
|
||||
import { Calendar } from "@/registry/new-york-v4/ui/calendar"
|
||||
|
||||
export function CalendarDemo() {
|
||||
const [date, setDate] = React.useState<Date | undefined>(new Date())
|
||||
const [dateRange, setDateRange] = React.useState<DateRange | undefined>({
|
||||
from: new Date(new Date().getFullYear(), 0, 12),
|
||||
to: addDays(new Date(new Date().getFullYear(), 0, 12), 30),
|
||||
})
|
||||
const [range, setRange] = React.useState<DateRange | undefined>({
|
||||
from: new Date(new Date().getFullYear(), 0, 12),
|
||||
to: addDays(new Date(new Date().getFullYear(), 0, 12), 50),
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="flex flex-col flex-wrap items-start gap-2 @md:flex-row">
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={date}
|
||||
onSelect={setDate}
|
||||
className="rounded-md border shadow-sm"
|
||||
/>
|
||||
<Calendar
|
||||
mode="range"
|
||||
defaultMonth={dateRange?.from}
|
||||
selected={dateRange}
|
||||
onSelect={setDateRange}
|
||||
numberOfMonths={2}
|
||||
disabled={(date) => date > new Date() || date < new Date("1900-01-01")}
|
||||
className="rounded-md border shadow-sm"
|
||||
/>
|
||||
<Calendar
|
||||
mode="range"
|
||||
defaultMonth={range?.from}
|
||||
selected={range}
|
||||
onSelect={setRange}
|
||||
numberOfMonths={3}
|
||||
className="hidden rounded-md border shadow-sm @4xl:flex [&>div]:gap-5"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
191
apps/v4/components/card-demo.tsx
Normal file
191
apps/v4/components/card-demo.tsx
Normal file
@@ -0,0 +1,191 @@
|
||||
import Image from "next/image"
|
||||
import { BathIcon, BedIcon, LandPlotIcon } from "lucide-react"
|
||||
|
||||
import {
|
||||
Avatar,
|
||||
AvatarFallback,
|
||||
AvatarImage,
|
||||
} from "@/registry/new-york-v4/ui/avatar"
|
||||
import { Badge } from "@/registry/new-york-v4/ui/badge"
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/registry/new-york-v4/ui/card"
|
||||
import { Input } from "@/registry/new-york-v4/ui/input"
|
||||
import { Label } from "@/registry/new-york-v4/ui/label"
|
||||
|
||||
export function CardDemo() {
|
||||
return (
|
||||
<div className="flex flex-col items-start gap-4">
|
||||
<form>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Login to your account</CardTitle>
|
||||
<CardDescription>
|
||||
Enter your email below to login to your account
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex flex-col gap-6">
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="email">Email</Label>
|
||||
<Input
|
||||
id="email"
|
||||
type="email"
|
||||
placeholder="m@example.com"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<div className="flex items-center">
|
||||
<Label htmlFor="password">Password</Label>
|
||||
<a
|
||||
href="#"
|
||||
className="ml-auto inline-block text-sm underline-offset-4 hover:underline"
|
||||
>
|
||||
Forgot your password?
|
||||
</a>
|
||||
</div>
|
||||
<Input id="password" type="password" required />
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col gap-2">
|
||||
<Button type="submit" className="w-full">
|
||||
Login
|
||||
</Button>
|
||||
<Button variant="outline" className="w-full">
|
||||
Login with Google
|
||||
</Button>
|
||||
<div className="mt-4 text-center text-sm">
|
||||
Don't have an account?{" "}
|
||||
<a href="#" className="underline underline-offset-4">
|
||||
Sign up
|
||||
</a>
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</form>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Meeting Notes</CardTitle>
|
||||
<CardDescription>
|
||||
Transcript from the meeting with the client.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="text-sm">
|
||||
<p>
|
||||
Client requested dashboard redesign with focus on mobile
|
||||
responsiveness.
|
||||
</p>
|
||||
<ol className="mt-4 flex list-decimal flex-col gap-2 pl-6">
|
||||
<li>New analytics widgets for daily/weekly metrics</li>
|
||||
<li>Simplified navigation menu</li>
|
||||
<li>Dark mode support</li>
|
||||
<li>Timeline: 6 weeks</li>
|
||||
<li>Follow-up meeting scheduled for next Tuesday</li>
|
||||
</ol>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<div className="*:data-[slot=avatar]:ring-background flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:grayscale">
|
||||
<Avatar>
|
||||
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||
<AvatarFallback>CN</AvatarFallback>
|
||||
</Avatar>
|
||||
<Avatar>
|
||||
<AvatarImage src="https://github.com/leerob.png" alt="@leerob" />
|
||||
<AvatarFallback>LR</AvatarFallback>
|
||||
</Avatar>
|
||||
<Avatar>
|
||||
<AvatarImage
|
||||
src="https://github.com/evilrabbit.png"
|
||||
alt="@evilrabbit"
|
||||
/>
|
||||
<AvatarFallback>ER</AvatarFallback>
|
||||
</Avatar>
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Is this an image?</CardTitle>
|
||||
<CardDescription>This is a card with an image.</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="px-0">
|
||||
<Image
|
||||
src="https://images.unsplash.com/photo-1588345921523-c2dcdb7f1dcd?w=800&dpr=2&q=80"
|
||||
alt="Photo by Drew Beamer"
|
||||
className="aspect-video object-cover"
|
||||
width={500}
|
||||
height={500}
|
||||
/>
|
||||
</CardContent>
|
||||
<CardFooter className="flex items-center gap-2">
|
||||
<Badge variant="outline">
|
||||
<BedIcon /> 4
|
||||
</Badge>
|
||||
<Badge variant="outline">
|
||||
<BathIcon /> 2
|
||||
</Badge>
|
||||
<Badge variant="outline">
|
||||
<LandPlotIcon /> 350m²
|
||||
</Badge>
|
||||
<div className="ml-auto font-medium tabular-nums">$135,000</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
<div className="flex w-full flex-wrap items-start gap-8 md:*:data-[slot=card]:basis-1/4">
|
||||
<Card>
|
||||
<CardContent className="text-sm">Content Only</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Header Only</CardTitle>
|
||||
<CardDescription>
|
||||
This is a card with a header and a description.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Header and Content</CardTitle>
|
||||
<CardDescription>
|
||||
This is a card with a header and a content.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="text-sm">Content</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardFooter className="text-sm">Footer Only</CardFooter>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Header + Footer</CardTitle>
|
||||
<CardDescription>
|
||||
This is a card with a header and a footer.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardFooter className="text-sm">Footer</CardFooter>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardContent className="text-sm">Content</CardContent>
|
||||
<CardFooter className="text-sm">Footer</CardFooter>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Header + Footer</CardTitle>
|
||||
<CardDescription>
|
||||
This is a card with a header and a footer.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="text-sm">Content</CardContent>
|
||||
<CardFooter className="text-sm">Footer</CardFooter>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
73
apps/v4/components/carousel-demo.tsx
Normal file
73
apps/v4/components/carousel-demo.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
import * as React from "react"
|
||||
|
||||
import { Card, CardContent } from "@/registry/new-york-v4/ui/card"
|
||||
import {
|
||||
Carousel,
|
||||
CarouselContent,
|
||||
CarouselItem,
|
||||
CarouselNext,
|
||||
CarouselPrevious,
|
||||
} from "@/registry/new-york-v4/ui/carousel"
|
||||
|
||||
export function CarouselDemo() {
|
||||
return (
|
||||
<div className="hidden w-full flex-col items-center gap-4 @4xl:flex">
|
||||
<Carousel className="max-w-sm *:data-[slot=carousel-next]:hidden *:data-[slot=carousel-previous]:hidden *:data-[slot=carousel-next]:md:inline-flex *:data-[slot=carousel-previous]:md:inline-flex">
|
||||
<CarouselContent>
|
||||
{Array.from({ length: 5 }).map((_, index) => (
|
||||
<CarouselItem key={index}>
|
||||
<div className="p-1">
|
||||
<Card>
|
||||
<CardContent className="flex aspect-square items-center justify-center p-6">
|
||||
<span className="text-4xl font-semibold">{index + 1}</span>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</CarouselItem>
|
||||
))}
|
||||
</CarouselContent>
|
||||
<CarouselPrevious />
|
||||
<CarouselNext />
|
||||
</Carousel>
|
||||
<Carousel
|
||||
className="max-w-sm *:data-[slot=carousel-next]:hidden *:data-[slot=carousel-previous]:hidden *:data-[slot=carousel-next]:md:inline-flex *:data-[slot=carousel-previous]:md:inline-flex"
|
||||
opts={{
|
||||
align: "start",
|
||||
}}
|
||||
>
|
||||
<CarouselContent>
|
||||
{Array.from({ length: 5 }).map((_, index) => (
|
||||
<CarouselItem key={index} className="md:basis-1/2 lg:basis-1/3">
|
||||
<div className="p-1">
|
||||
<Card>
|
||||
<CardContent className="flex aspect-square items-center justify-center p-6">
|
||||
<span className="text-3xl font-semibold">{index + 1}</span>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</CarouselItem>
|
||||
))}
|
||||
</CarouselContent>
|
||||
<CarouselPrevious />
|
||||
<CarouselNext />
|
||||
</Carousel>
|
||||
<Carousel className="max-w-sm *:data-[slot=carousel-next]:hidden *:data-[slot=carousel-previous]:hidden *:data-[slot=carousel-next]:md:inline-flex *:data-[slot=carousel-previous]:md:inline-flex">
|
||||
<CarouselContent className="-ml-1">
|
||||
{Array.from({ length: 5 }).map((_, index) => (
|
||||
<CarouselItem key={index} className="pl-1 md:basis-1/2">
|
||||
<div className="p-1">
|
||||
<Card>
|
||||
<CardContent className="flex aspect-square items-center justify-center p-6">
|
||||
<span className="text-2xl font-semibold">{index + 1}</span>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</CarouselItem>
|
||||
))}
|
||||
</CarouselContent>
|
||||
<CarouselPrevious />
|
||||
<CarouselNext />
|
||||
</Carousel>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
94
apps/v4/components/chart-area-demo.tsx
Normal file
94
apps/v4/components/chart-area-demo.tsx
Normal file
@@ -0,0 +1,94 @@
|
||||
"use client"
|
||||
|
||||
import { TrendingUp } from "lucide-react"
|
||||
import { Area, AreaChart, CartesianGrid, XAxis } from "recharts"
|
||||
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/registry/new-york-v4/ui/card"
|
||||
import {
|
||||
ChartConfig,
|
||||
ChartContainer,
|
||||
ChartTooltip,
|
||||
ChartTooltipContent,
|
||||
} from "@/registry/new-york-v4/ui/chart"
|
||||
|
||||
export const description = "A simple area chart"
|
||||
|
||||
const chartData = [
|
||||
{ month: "January", desktop: 186 },
|
||||
{ month: "February", desktop: 305 },
|
||||
{ month: "March", desktop: 237 },
|
||||
{ month: "April", desktop: 73 },
|
||||
{ month: "May", desktop: 209 },
|
||||
{ month: "June", desktop: 214 },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
desktop: {
|
||||
label: "Desktop",
|
||||
color: "var(--chart-1)",
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export function ChartAreaDemo() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Area Chart</CardTitle>
|
||||
<CardDescription>
|
||||
Showing total visitors for the last 6 months
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ChartContainer config={chartConfig}>
|
||||
<AreaChart
|
||||
accessibilityLayer
|
||||
data={chartData}
|
||||
margin={{
|
||||
left: 12,
|
||||
right: 12,
|
||||
}}
|
||||
>
|
||||
<CartesianGrid vertical={false} />
|
||||
<XAxis
|
||||
dataKey="month"
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
tickMargin={8}
|
||||
tickFormatter={(value) => value.slice(0, 3)}
|
||||
/>
|
||||
<ChartTooltip
|
||||
cursor={false}
|
||||
content={<ChartTooltipContent indicator="line" />}
|
||||
/>
|
||||
<Area
|
||||
dataKey="desktop"
|
||||
type="natural"
|
||||
fill="var(--color-desktop)"
|
||||
fillOpacity={0.4}
|
||||
stroke="var(--color-desktop)"
|
||||
/>
|
||||
</AreaChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<div className="flex w-full items-start gap-2 text-sm">
|
||||
<div className="grid gap-2">
|
||||
<div className="flex items-center gap-2 leading-none font-medium">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="text-muted-foreground flex items-center gap-2 leading-none">
|
||||
January - June 2024
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
80
apps/v4/components/chart-bar-demo.tsx
Normal file
80
apps/v4/components/chart-bar-demo.tsx
Normal file
@@ -0,0 +1,80 @@
|
||||
"use client"
|
||||
|
||||
import { TrendingUp } from "lucide-react"
|
||||
import { Bar, BarChart, CartesianGrid, XAxis } from "recharts"
|
||||
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/registry/new-york-v4/ui/card"
|
||||
import {
|
||||
ChartConfig,
|
||||
ChartContainer,
|
||||
ChartTooltip,
|
||||
ChartTooltipContent,
|
||||
} from "@/registry/new-york-v4/ui/chart"
|
||||
|
||||
export const description = "A multiple bar chart"
|
||||
|
||||
const chartData = [
|
||||
{ month: "January", desktop: 186, mobile: 80 },
|
||||
{ month: "February", desktop: 305, mobile: 200 },
|
||||
{ month: "March", desktop: 237, mobile: 120 },
|
||||
{ month: "April", desktop: 73, mobile: 190 },
|
||||
{ month: "May", desktop: 209, mobile: 130 },
|
||||
{ month: "June", desktop: 214, mobile: 140 },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
desktop: {
|
||||
label: "Desktop",
|
||||
color: "var(--chart-1)",
|
||||
},
|
||||
mobile: {
|
||||
label: "Mobile",
|
||||
color: "var(--chart-2)",
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export function ChartBarDemo() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Bar Chart - Multiple</CardTitle>
|
||||
<CardDescription>January - June 2024</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ChartContainer config={chartConfig}>
|
||||
<BarChart accessibilityLayer data={chartData}>
|
||||
<CartesianGrid vertical={false} />
|
||||
<XAxis
|
||||
dataKey="month"
|
||||
tickLine={false}
|
||||
tickMargin={10}
|
||||
axisLine={false}
|
||||
tickFormatter={(value) => value.slice(0, 3)}
|
||||
/>
|
||||
<ChartTooltip
|
||||
cursor={false}
|
||||
content={<ChartTooltipContent indicator="dashed" />}
|
||||
/>
|
||||
<Bar dataKey="desktop" fill="var(--color-desktop)" radius={4} />
|
||||
<Bar dataKey="mobile" fill="var(--color-mobile)" radius={4} />
|
||||
</BarChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col items-start gap-2 text-sm">
|
||||
<div className="flex gap-2 leading-none font-medium">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="text-muted-foreground leading-none">
|
||||
Showing total visitors for the last 6 months
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
103
apps/v4/components/chart-bar-mixed.tsx
Normal file
103
apps/v4/components/chart-bar-mixed.tsx
Normal file
@@ -0,0 +1,103 @@
|
||||
"use client"
|
||||
|
||||
import { TrendingUp } from "lucide-react"
|
||||
import { Bar, BarChart, XAxis, YAxis } from "recharts"
|
||||
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/registry/new-york-v4/ui/card"
|
||||
import {
|
||||
ChartConfig,
|
||||
ChartContainer,
|
||||
ChartTooltip,
|
||||
ChartTooltipContent,
|
||||
} from "@/registry/new-york-v4/ui/chart"
|
||||
|
||||
export const description = "A mixed bar chart"
|
||||
|
||||
const chartData = [
|
||||
{ browser: "chrome", visitors: 275, fill: "var(--color-chrome)" },
|
||||
{ browser: "safari", visitors: 200, fill: "var(--color-safari)" },
|
||||
{ browser: "firefox", visitors: 187, fill: "var(--color-firefox)" },
|
||||
{ browser: "edge", visitors: 173, fill: "var(--color-edge)" },
|
||||
{ browser: "other", visitors: 90, fill: "var(--color-other)" },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
visitors: {
|
||||
label: "Visitors",
|
||||
},
|
||||
chrome: {
|
||||
label: "Chrome",
|
||||
color: "var(--chart-1)",
|
||||
},
|
||||
safari: {
|
||||
label: "Safari",
|
||||
color: "var(--chart-2)",
|
||||
},
|
||||
firefox: {
|
||||
label: "Firefox",
|
||||
color: "var(--chart-3)",
|
||||
},
|
||||
edge: {
|
||||
label: "Edge",
|
||||
color: "var(--chart-4)",
|
||||
},
|
||||
other: {
|
||||
label: "Other",
|
||||
color: "var(--chart-5)",
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export function ChartBarMixed() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Bar Chart - Mixed</CardTitle>
|
||||
<CardDescription>January - June 2024</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ChartContainer config={chartConfig}>
|
||||
<BarChart
|
||||
accessibilityLayer
|
||||
data={chartData}
|
||||
layout="vertical"
|
||||
margin={{
|
||||
left: 0,
|
||||
}}
|
||||
>
|
||||
<YAxis
|
||||
dataKey="browser"
|
||||
type="category"
|
||||
tickLine={false}
|
||||
tickMargin={10}
|
||||
axisLine={false}
|
||||
tickFormatter={(value) =>
|
||||
chartConfig[value as keyof typeof chartConfig]?.label
|
||||
}
|
||||
/>
|
||||
<XAxis dataKey="visitors" type="number" hide />
|
||||
<ChartTooltip
|
||||
cursor={false}
|
||||
content={<ChartTooltipContent hideLabel />}
|
||||
/>
|
||||
<Bar dataKey="visitors" layout="vertical" radius={5} />
|
||||
</BarChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col items-start gap-2 text-sm">
|
||||
<div className="flex gap-2 leading-none font-medium">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="text-muted-foreground leading-none">
|
||||
Showing total visitors for the last 6 months
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
17
apps/v4/components/chart-demo.tsx
Normal file
17
apps/v4/components/chart-demo.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import { ChartAreaDemo } from "@/components/chart-area-demo"
|
||||
import { ChartBarDemo } from "@/components/chart-bar-demo"
|
||||
import { ChartLineDemo } from "@/components/chart-line-demo"
|
||||
import { ChartBarMixed } from "@/registry/new-york-v4/charts/chart-bar-mixed"
|
||||
|
||||
export function ChartDemo() {
|
||||
return (
|
||||
<div className="grid w-full max-w-screen-2xl gap-4 *:data-[slot=card]:flex-1 @2xl:grid-cols-2 @6xl:grid-cols-3">
|
||||
<ChartAreaDemo />
|
||||
<ChartBarDemo />
|
||||
<ChartBarMixed />
|
||||
<div className="@6xl:hidden">
|
||||
<ChartLineDemo />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
100
apps/v4/components/chart-line-demo.tsx
Normal file
100
apps/v4/components/chart-line-demo.tsx
Normal file
@@ -0,0 +1,100 @@
|
||||
"use client"
|
||||
|
||||
import { TrendingUp } from "lucide-react"
|
||||
import { CartesianGrid, Line, LineChart, XAxis } from "recharts"
|
||||
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/registry/new-york-v4/ui/card"
|
||||
import {
|
||||
ChartConfig,
|
||||
ChartContainer,
|
||||
ChartTooltip,
|
||||
ChartTooltipContent,
|
||||
} from "@/registry/new-york-v4/ui/chart"
|
||||
|
||||
export const description = "A multiple line chart"
|
||||
|
||||
const chartData = [
|
||||
{ month: "January", desktop: 186, mobile: 80 },
|
||||
{ month: "February", desktop: 305, mobile: 200 },
|
||||
{ month: "March", desktop: 237, mobile: 120 },
|
||||
{ month: "April", desktop: 73, mobile: 190 },
|
||||
{ month: "May", desktop: 209, mobile: 130 },
|
||||
{ month: "June", desktop: 214, mobile: 140 },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
desktop: {
|
||||
label: "Desktop",
|
||||
color: "var(--chart-1)",
|
||||
},
|
||||
mobile: {
|
||||
label: "Mobile",
|
||||
color: "var(--chart-2)",
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export function ChartLineDemo() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Line Chart - Multiple</CardTitle>
|
||||
<CardDescription>January - June 2024</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ChartContainer config={chartConfig}>
|
||||
<LineChart
|
||||
accessibilityLayer
|
||||
data={chartData}
|
||||
margin={{
|
||||
left: 12,
|
||||
right: 12,
|
||||
}}
|
||||
>
|
||||
<CartesianGrid vertical={false} />
|
||||
<XAxis
|
||||
dataKey="month"
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
tickMargin={8}
|
||||
tickFormatter={(value) => value.slice(0, 3)}
|
||||
/>
|
||||
<ChartTooltip cursor={false} content={<ChartTooltipContent />} />
|
||||
<Line
|
||||
dataKey="desktop"
|
||||
type="monotone"
|
||||
stroke="var(--color-desktop)"
|
||||
strokeWidth={2}
|
||||
dot={false}
|
||||
/>
|
||||
<Line
|
||||
dataKey="mobile"
|
||||
type="monotone"
|
||||
stroke="var(--color-mobile)"
|
||||
strokeWidth={2}
|
||||
dot={false}
|
||||
/>
|
||||
</LineChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<div className="flex w-full items-start gap-2 text-sm">
|
||||
<div className="grid gap-2">
|
||||
<div className="flex items-center gap-2 leading-none font-medium">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="text-muted-foreground flex items-center gap-2 leading-none">
|
||||
Showing total visitors for the last 6 months
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
43
apps/v4/components/checkbox-demo.tsx
Normal file
43
apps/v4/components/checkbox-demo.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
"use client"
|
||||
|
||||
import { Checkbox } from "@/registry/new-york-v4/ui/checkbox"
|
||||
import { Label } from "@/registry/new-york-v4/ui/label"
|
||||
|
||||
export function CheckboxDemo() {
|
||||
return (
|
||||
<div className="flex flex-col gap-6">
|
||||
<div className="flex items-center gap-3">
|
||||
<Checkbox id="terms" />
|
||||
<Label htmlFor="terms">Accept terms and conditions</Label>
|
||||
</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<Checkbox id="terms-2" defaultChecked />
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="terms-2">Accept terms and conditions</Label>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
By clicking this checkbox, you agree to the terms and conditions.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<Checkbox id="toggle" disabled />
|
||||
<Label htmlFor="toggle">Enable notifications</Label>
|
||||
</div>
|
||||
<Label className="hover:bg-accent/50 flex items-start gap-3 rounded-lg border p-3 has-[[aria-checked=true]]:border-blue-600 has-[[aria-checked=true]]:bg-blue-50 dark:has-[[aria-checked=true]]:border-blue-900 dark:has-[[aria-checked=true]]:bg-blue-950">
|
||||
<Checkbox
|
||||
id="toggle-2"
|
||||
defaultChecked
|
||||
className="data-[state=checked]:border-blue-600 data-[state=checked]:bg-blue-600 data-[state=checked]:text-white dark:data-[state=checked]:border-blue-700 dark:data-[state=checked]:bg-blue-700"
|
||||
/>
|
||||
<div className="grid gap-1.5 font-normal">
|
||||
<p className="text-sm leading-none font-medium">
|
||||
Enable notifications
|
||||
</p>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
You can enable or disable notifications at any time.
|
||||
</p>
|
||||
</div>
|
||||
</Label>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
46
apps/v4/components/collapsible-demo.tsx
Normal file
46
apps/v4/components/collapsible-demo.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { ChevronsUpDown } from "lucide-react"
|
||||
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
CollapsibleTrigger,
|
||||
} from "@/registry/new-york-v4/ui/collapsible"
|
||||
|
||||
export function CollapsibleDemo() {
|
||||
const [isOpen, setIsOpen] = React.useState(false)
|
||||
|
||||
return (
|
||||
<Collapsible
|
||||
open={isOpen}
|
||||
onOpenChange={setIsOpen}
|
||||
className="flex w-full flex-col gap-2 md:w-[350px]"
|
||||
>
|
||||
<div className="flex items-center justify-between gap-4 px-4">
|
||||
<h4 className="line-clamp-1 text-sm font-semibold">
|
||||
@peduarte starred 3 repositories
|
||||
</h4>
|
||||
<CollapsibleTrigger asChild>
|
||||
<Button variant="ghost" size="sm">
|
||||
<ChevronsUpDown className="h-4 w-4" />
|
||||
<span className="sr-only">Toggle</span>
|
||||
</Button>
|
||||
</CollapsibleTrigger>
|
||||
</div>
|
||||
<div className="rounded-md border px-4 py-2 font-mono text-sm shadow-xs">
|
||||
@radix-ui/primitives
|
||||
</div>
|
||||
<CollapsibleContent className="flex flex-col gap-2">
|
||||
<div className="rounded-md border px-4 py-2 font-mono text-sm shadow-xs">
|
||||
@radix-ui/colors
|
||||
</div>
|
||||
<div className="rounded-md border px-4 py-2 font-mono text-sm shadow-xs">
|
||||
@stitches/react
|
||||
</div>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
)
|
||||
}
|
||||
344
apps/v4/components/combobox-demo.tsx
Normal file
344
apps/v4/components/combobox-demo.tsx
Normal file
@@ -0,0 +1,344 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import {
|
||||
CheckIcon,
|
||||
ChevronDownIcon,
|
||||
ChevronsUpDown,
|
||||
PlusCircleIcon,
|
||||
} from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import {
|
||||
Avatar,
|
||||
AvatarFallback,
|
||||
AvatarImage,
|
||||
} from "@/registry/new-york-v4/ui/avatar"
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
import {
|
||||
Command,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
CommandList,
|
||||
CommandSeparator,
|
||||
} from "@/registry/new-york-v4/ui/command"
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/registry/new-york-v4/ui/popover"
|
||||
|
||||
const frameworks = [
|
||||
{
|
||||
value: "next.js",
|
||||
label: "Next.js",
|
||||
},
|
||||
{
|
||||
value: "sveltekit",
|
||||
label: "SvelteKit",
|
||||
},
|
||||
{
|
||||
value: "nuxt.js",
|
||||
label: "Nuxt.js",
|
||||
},
|
||||
{
|
||||
value: "remix",
|
||||
label: "Remix",
|
||||
},
|
||||
{
|
||||
value: "astro",
|
||||
label: "Astro",
|
||||
},
|
||||
]
|
||||
|
||||
type Framework = (typeof frameworks)[number]
|
||||
|
||||
const users = [
|
||||
{
|
||||
id: "1",
|
||||
username: "shadcn",
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
username: "leerob",
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
username: "evilrabbit",
|
||||
},
|
||||
] as const
|
||||
|
||||
type User = (typeof users)[number]
|
||||
|
||||
const timezones = [
|
||||
{
|
||||
label: "Americas",
|
||||
timezones: [
|
||||
{ value: "America/New_York", label: "(GMT-5) New York" },
|
||||
{ value: "America/Los_Angeles", label: "(GMT-8) Los Angeles" },
|
||||
{ value: "America/Chicago", label: "(GMT-6) Chicago" },
|
||||
{ value: "America/Toronto", label: "(GMT-5) Toronto" },
|
||||
{ value: "America/Vancouver", label: "(GMT-8) Vancouver" },
|
||||
{ value: "America/Sao_Paulo", label: "(GMT-3) São Paulo" },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Europe",
|
||||
timezones: [
|
||||
{ value: "Europe/London", label: "(GMT+0) London" },
|
||||
{ value: "Europe/Paris", label: "(GMT+1) Paris" },
|
||||
{ value: "Europe/Berlin", label: "(GMT+1) Berlin" },
|
||||
{ value: "Europe/Rome", label: "(GMT+1) Rome" },
|
||||
{ value: "Europe/Madrid", label: "(GMT+1) Madrid" },
|
||||
{ value: "Europe/Amsterdam", label: "(GMT+1) Amsterdam" },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Asia/Pacific",
|
||||
timezones: [
|
||||
{ value: "Asia/Tokyo", label: "(GMT+9) Tokyo" },
|
||||
{ value: "Asia/Shanghai", label: "(GMT+8) Shanghai" },
|
||||
{ value: "Asia/Singapore", label: "(GMT+8) Singapore" },
|
||||
{ value: "Asia/Dubai", label: "(GMT+4) Dubai" },
|
||||
{ value: "Australia/Sydney", label: "(GMT+11) Sydney" },
|
||||
{ value: "Asia/Seoul", label: "(GMT+9) Seoul" },
|
||||
],
|
||||
},
|
||||
] as const
|
||||
|
||||
type Timezone = (typeof timezones)[number]
|
||||
|
||||
export function ComboboxDemo() {
|
||||
return (
|
||||
<div className="flex w-full flex-wrap items-start gap-4">
|
||||
<FrameworkCombobox frameworks={[...frameworks]} />
|
||||
<UserCombobox users={[...users]} selectedUserId={users[0].id} />
|
||||
<TimezoneCombobox
|
||||
timezones={[...timezones]}
|
||||
selectedTimezone={timezones[0].timezones[0]}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function FrameworkCombobox({ frameworks }: { frameworks: Framework[] }) {
|
||||
const [open, setOpen] = React.useState(false)
|
||||
const [value, setValue] = React.useState("")
|
||||
|
||||
return (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
aria-expanded={open}
|
||||
className="w-full justify-between md:max-w-[200px]"
|
||||
>
|
||||
{value
|
||||
? frameworks.find((framework) => framework.value === value)?.label
|
||||
: "Select framework..."}
|
||||
<ChevronsUpDown className="text-muted-foreground" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-(--radix-popover-trigger-width) p-0">
|
||||
<Command>
|
||||
<CommandInput placeholder="Search framework..." />
|
||||
<CommandList>
|
||||
<CommandEmpty>No framework found.</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{frameworks.map((framework) => (
|
||||
<CommandItem
|
||||
key={framework.value}
|
||||
value={framework.value}
|
||||
onSelect={(currentValue) => {
|
||||
setValue(currentValue === value ? "" : currentValue)
|
||||
setOpen(false)
|
||||
}}
|
||||
>
|
||||
{framework.label}
|
||||
<CheckIcon
|
||||
className={cn(
|
||||
"ml-auto",
|
||||
value === framework.value ? "opacity-100" : "opacity-0"
|
||||
)}
|
||||
/>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
|
||||
function UserCombobox({
|
||||
users,
|
||||
selectedUserId,
|
||||
}: {
|
||||
users: User[]
|
||||
selectedUserId: string
|
||||
}) {
|
||||
const [open, setOpen] = React.useState(false)
|
||||
const [value, setValue] = React.useState(selectedUserId)
|
||||
|
||||
const selectedUser = React.useMemo(
|
||||
() => users.find((user) => user.id === value),
|
||||
[value, users]
|
||||
)
|
||||
|
||||
return (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
aria-expanded={open}
|
||||
className="w-full justify-between px-2 md:max-w-[200px]"
|
||||
>
|
||||
{selectedUser ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<Avatar className="size-5">
|
||||
<AvatarImage
|
||||
src={`https://github.com/${selectedUser.username}.png`}
|
||||
/>
|
||||
<AvatarFallback>{selectedUser.username[0]}</AvatarFallback>
|
||||
</Avatar>
|
||||
{selectedUser.username}
|
||||
</div>
|
||||
) : (
|
||||
"Select user..."
|
||||
)}
|
||||
<ChevronsUpDown className="text-muted-foreground" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-(--radix-popover-trigger-width) p-0">
|
||||
<Command>
|
||||
<CommandInput placeholder="Search user..." />
|
||||
<CommandList>
|
||||
<CommandEmpty>No user found.</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{users.map((user) => (
|
||||
<CommandItem
|
||||
key={user.id}
|
||||
value={user.id}
|
||||
onSelect={(currentValue) => {
|
||||
setValue(currentValue === value ? "" : currentValue)
|
||||
setOpen(false)
|
||||
}}
|
||||
>
|
||||
<Avatar className="size-5">
|
||||
<AvatarImage
|
||||
src={`https://github.com/${user.username}.png`}
|
||||
/>
|
||||
<AvatarFallback>{user.username[0]}</AvatarFallback>
|
||||
</Avatar>
|
||||
{user.username}
|
||||
<CheckIcon
|
||||
className={cn(
|
||||
"ml-auto",
|
||||
value === user.id ? "opacity-100" : "opacity-0"
|
||||
)}
|
||||
/>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
<CommandSeparator />
|
||||
<CommandGroup>
|
||||
<CommandItem>
|
||||
<PlusCircleIcon />
|
||||
Create user
|
||||
</CommandItem>
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
|
||||
function TimezoneCombobox({
|
||||
timezones,
|
||||
selectedTimezone,
|
||||
}: {
|
||||
timezones: Timezone[]
|
||||
selectedTimezone: Timezone["timezones"][number]
|
||||
}) {
|
||||
const [open, setOpen] = React.useState(false)
|
||||
const [value, setValue] = React.useState(selectedTimezone.value)
|
||||
|
||||
const selectedGroup = React.useMemo(
|
||||
() =>
|
||||
timezones.find((group) =>
|
||||
group.timezones.find((tz) => tz.value === value)
|
||||
),
|
||||
[value, timezones]
|
||||
)
|
||||
|
||||
const selectedTimezoneLabel = React.useMemo(
|
||||
() => selectedGroup?.timezones.find((tz) => tz.value === value)?.label,
|
||||
[value, selectedGroup]
|
||||
)
|
||||
|
||||
return (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="h-12 w-full justify-between px-2.5 md:max-w-[200px]"
|
||||
>
|
||||
{selectedTimezone ? (
|
||||
<div className="flex flex-col items-start gap-0.5">
|
||||
<span className="text-muted-foreground text-xs font-normal">
|
||||
{selectedGroup?.label}
|
||||
</span>
|
||||
<span>{selectedTimezoneLabel}</span>
|
||||
</div>
|
||||
) : (
|
||||
"Select timezone"
|
||||
)}
|
||||
<ChevronDownIcon className="text-muted-foreground" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="p-0" align="start">
|
||||
<Command>
|
||||
<CommandInput placeholder="Search timezone..." />
|
||||
<CommandList className="scroll-pb-12">
|
||||
<CommandEmpty>No timezone found.</CommandEmpty>
|
||||
{timezones.map((region) => (
|
||||
<CommandGroup key={region.label} heading={region.label}>
|
||||
{region.timezones.map((timezone) => (
|
||||
<CommandItem
|
||||
key={timezone.value}
|
||||
value={timezone.value}
|
||||
onSelect={(currentValue) => {
|
||||
setValue(
|
||||
currentValue as Timezone["timezones"][number]["value"]
|
||||
)
|
||||
setOpen(false)
|
||||
}}
|
||||
>
|
||||
{timezone.label}
|
||||
<CheckIcon
|
||||
className="ml-auto opacity-0 data-[selected=true]:opacity-100"
|
||||
data-selected={value === timezone.value}
|
||||
/>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
))}
|
||||
<CommandSeparator className="sticky bottom-10" />
|
||||
<CommandGroup className="bg-popover sticky bottom-0">
|
||||
<CommandItem>
|
||||
<PlusCircleIcon />
|
||||
Create timezone
|
||||
</CommandItem>
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
87
apps/v4/components/command-demo.tsx
Normal file
87
apps/v4/components/command-demo.tsx
Normal file
@@ -0,0 +1,87 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import {
|
||||
Calculator,
|
||||
Calendar,
|
||||
CreditCard,
|
||||
Settings,
|
||||
Smile,
|
||||
User,
|
||||
} from "lucide-react"
|
||||
|
||||
import {
|
||||
CommandDialog,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
CommandList,
|
||||
CommandSeparator,
|
||||
CommandShortcut,
|
||||
} from "@/registry/new-york-v4/ui/command"
|
||||
|
||||
export function CommandDemo() {
|
||||
const [open, setOpen] = React.useState(false)
|
||||
|
||||
React.useEffect(() => {
|
||||
const down = (e: KeyboardEvent) => {
|
||||
if (e.key === "j" && (e.metaKey || e.ctrlKey)) {
|
||||
e.preventDefault()
|
||||
setOpen((open) => !open)
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("keydown", down)
|
||||
return () => document.removeEventListener("keydown", down)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
Press{" "}
|
||||
<kbd className="bg-muted text-muted-foreground pointer-events-none inline-flex h-5 items-center gap-1 rounded border px-1.5 font-mono text-[10px] font-medium opacity-100 select-none">
|
||||
<span className="text-xs">⌘</span>J
|
||||
</kbd>
|
||||
</p>
|
||||
<CommandDialog open={open} onOpenChange={setOpen}>
|
||||
<CommandInput placeholder="Type a command or search..." />
|
||||
<CommandList>
|
||||
<CommandEmpty>No results found.</CommandEmpty>
|
||||
<CommandGroup heading="Suggestions">
|
||||
<CommandItem>
|
||||
<Calendar />
|
||||
<span>Calendar</span>
|
||||
</CommandItem>
|
||||
<CommandItem>
|
||||
<Smile />
|
||||
<span>Search Emoji</span>
|
||||
</CommandItem>
|
||||
<CommandItem>
|
||||
<Calculator />
|
||||
<span>Calculator</span>
|
||||
</CommandItem>
|
||||
</CommandGroup>
|
||||
<CommandSeparator />
|
||||
<CommandGroup heading="Settings">
|
||||
<CommandItem>
|
||||
<User />
|
||||
<span>Profile</span>
|
||||
<CommandShortcut>⌘P</CommandShortcut>
|
||||
</CommandItem>
|
||||
<CommandItem>
|
||||
<CreditCard />
|
||||
<span>Billing</span>
|
||||
<CommandShortcut>⌘B</CommandShortcut>
|
||||
</CommandItem>
|
||||
<CommandItem>
|
||||
<Settings />
|
||||
<span>Settings</span>
|
||||
<CommandShortcut>⌘S</CommandShortcut>
|
||||
</CommandItem>
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</CommandDialog>
|
||||
</>
|
||||
)
|
||||
}
|
||||
66
apps/v4/components/component-wrapper.tsx
Normal file
66
apps/v4/components/component-wrapper.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
|
||||
import { cn } from "@/registry/new-york-v4/lib/utils"
|
||||
|
||||
export function ComponentWrapper({
|
||||
className,
|
||||
name,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentPropsWithoutRef<"div"> & { name: string }) {
|
||||
return (
|
||||
<ComponentErrorBoundary name={name}>
|
||||
<div
|
||||
id={name}
|
||||
data-name={name.toLowerCase()}
|
||||
className={cn(
|
||||
"flex w-full scroll-mt-16 flex-col rounded-lg border",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div className="border-b px-4 py-3">
|
||||
<div className="text-sm font-medium">{getComponentName(name)}</div>
|
||||
</div>
|
||||
<div className="flex flex-1 items-center gap-2 p-4">{children}</div>
|
||||
</div>
|
||||
</ComponentErrorBoundary>
|
||||
)
|
||||
}
|
||||
|
||||
class ComponentErrorBoundary extends React.Component<
|
||||
{ children: React.ReactNode; name: string },
|
||||
{ hasError: boolean }
|
||||
> {
|
||||
constructor(props: { children: React.ReactNode; name: string }) {
|
||||
super(props)
|
||||
this.state = { hasError: false }
|
||||
}
|
||||
|
||||
static getDerivedStateFromError() {
|
||||
return { hasError: true }
|
||||
}
|
||||
|
||||
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
||||
console.error(`Error in component ${this.props.name}:`, error, errorInfo)
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
return (
|
||||
<div className="p-4 text-red-500">
|
||||
Something went wrong in component: {this.props.name}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return this.props.children
|
||||
}
|
||||
}
|
||||
|
||||
function getComponentName(name: string) {
|
||||
// convert kebab-case to title case
|
||||
return name.replace(/-/g, " ").replace(/\b\w/g, (char) => char.toUpperCase())
|
||||
}
|
||||
79
apps/v4/components/context-menu-demo.tsx
Normal file
79
apps/v4/components/context-menu-demo.tsx
Normal file
@@ -0,0 +1,79 @@
|
||||
import { Code2Icon, PlusIcon, TrashIcon } from "lucide-react"
|
||||
|
||||
import {
|
||||
ContextMenu,
|
||||
ContextMenuCheckboxItem,
|
||||
ContextMenuContent,
|
||||
ContextMenuItem,
|
||||
ContextMenuLabel,
|
||||
ContextMenuRadioGroup,
|
||||
ContextMenuRadioItem,
|
||||
ContextMenuSeparator,
|
||||
ContextMenuShortcut,
|
||||
ContextMenuSub,
|
||||
ContextMenuSubContent,
|
||||
ContextMenuSubTrigger,
|
||||
ContextMenuTrigger,
|
||||
} from "@/registry/new-york-v4/ui/context-menu"
|
||||
|
||||
export function ContextMenuDemo() {
|
||||
return (
|
||||
<ContextMenu>
|
||||
<ContextMenuTrigger className="flex h-[150px] w-[300px] items-center justify-center rounded-md border border-dashed text-sm">
|
||||
Right click here
|
||||
</ContextMenuTrigger>
|
||||
<ContextMenuContent className="w-64">
|
||||
<ContextMenuItem inset>
|
||||
Back
|
||||
<ContextMenuShortcut>⌘[</ContextMenuShortcut>
|
||||
</ContextMenuItem>
|
||||
<ContextMenuItem inset disabled>
|
||||
Forward
|
||||
<ContextMenuShortcut>⌘]</ContextMenuShortcut>
|
||||
</ContextMenuItem>
|
||||
<ContextMenuItem inset>
|
||||
Reload
|
||||
<ContextMenuShortcut>⌘R</ContextMenuShortcut>
|
||||
</ContextMenuItem>
|
||||
<ContextMenuSub>
|
||||
<ContextMenuSubTrigger inset>More Tools</ContextMenuSubTrigger>
|
||||
<ContextMenuSubContent className="w-48">
|
||||
<ContextMenuItem inset>
|
||||
Save Page As...
|
||||
<ContextMenuShortcut>⇧⌘S</ContextMenuShortcut>
|
||||
</ContextMenuItem>
|
||||
<ContextMenuItem>
|
||||
<PlusIcon />
|
||||
Create Shortcut...
|
||||
</ContextMenuItem>
|
||||
<ContextMenuItem inset>Name Window...</ContextMenuItem>
|
||||
<ContextMenuSeparator />
|
||||
<ContextMenuItem>
|
||||
<Code2Icon />
|
||||
Developer Tools
|
||||
</ContextMenuItem>
|
||||
<ContextMenuSeparator />
|
||||
<ContextMenuItem variant="destructive">
|
||||
<TrashIcon />
|
||||
Delete
|
||||
</ContextMenuItem>
|
||||
</ContextMenuSubContent>
|
||||
</ContextMenuSub>
|
||||
<ContextMenuSeparator />
|
||||
<ContextMenuCheckboxItem checked>
|
||||
Show Bookmarks Bar
|
||||
<ContextMenuShortcut>⌘⇧B</ContextMenuShortcut>
|
||||
</ContextMenuCheckboxItem>
|
||||
<ContextMenuCheckboxItem>Show Full URLs</ContextMenuCheckboxItem>
|
||||
<ContextMenuSeparator />
|
||||
<ContextMenuRadioGroup value="pedro">
|
||||
<ContextMenuLabel inset>People</ContextMenuLabel>
|
||||
<ContextMenuRadioItem value="pedro">
|
||||
Pedro Duarte
|
||||
</ContextMenuRadioItem>
|
||||
<ContextMenuRadioItem value="colm">Colm Tuite</ContextMenuRadioItem>
|
||||
</ContextMenuRadioGroup>
|
||||
</ContextMenuContent>
|
||||
</ContextMenu>
|
||||
)
|
||||
}
|
||||
99
apps/v4/components/date-picker-demo.tsx
Normal file
99
apps/v4/components/date-picker-demo.tsx
Normal file
@@ -0,0 +1,99 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { addDays, format } from "date-fns"
|
||||
import { CalendarIcon } from "lucide-react"
|
||||
import { DateRange } from "react-day-picker"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
import { Calendar } from "@/registry/new-york-v4/ui/calendar"
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/registry/new-york-v4/ui/popover"
|
||||
|
||||
export function DatePickerDemo() {
|
||||
return (
|
||||
<div className="flex flex-col items-start gap-4 md:flex-row">
|
||||
<DatePickerSimple />
|
||||
<DatePickerWithRange />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function DatePickerSimple() {
|
||||
const [date, setDate] = React.useState<Date>()
|
||||
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant={"outline"}
|
||||
className={cn(
|
||||
"min-w-[200px] justify-start px-2 font-normal",
|
||||
!date && "text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
<CalendarIcon className="text-muted-foreground" />
|
||||
{date ? format(date, "PPP") : <span>Pick a date</span>}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0" align="start">
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={date}
|
||||
onSelect={setDate}
|
||||
initialFocus
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
|
||||
function DatePickerWithRange() {
|
||||
const [date, setDate] = React.useState<DateRange | undefined>({
|
||||
from: new Date(new Date().getFullYear(), 0, 20),
|
||||
to: addDays(new Date(new Date().getFullYear(), 0, 20), 20),
|
||||
})
|
||||
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
id="date"
|
||||
variant={"outline"}
|
||||
className={cn(
|
||||
"w-fit justify-start px-2 font-normal",
|
||||
!date && "text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
<CalendarIcon className="text-muted-foreground" />
|
||||
{date?.from ? (
|
||||
date.to ? (
|
||||
<>
|
||||
{format(date.from, "LLL dd, y")} -{" "}
|
||||
{format(date.to, "LLL dd, y")}
|
||||
</>
|
||||
) : (
|
||||
format(date.from, "LLL dd, y")
|
||||
)
|
||||
) : (
|
||||
<span>Pick a date</span>
|
||||
)}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0" align="start">
|
||||
<Calendar
|
||||
initialFocus
|
||||
mode="range"
|
||||
defaultMonth={date?.from}
|
||||
selected={date}
|
||||
onSelect={setDate}
|
||||
numberOfMonths={2}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
129
apps/v4/components/dialog-demo.tsx
Normal file
129
apps/v4/components/dialog-demo.tsx
Normal file
@@ -0,0 +1,129 @@
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
import {
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/registry/new-york-v4/ui/dialog"
|
||||
import { Input } from "@/registry/new-york-v4/ui/input"
|
||||
import { Label } from "@/registry/new-york-v4/ui/label"
|
||||
|
||||
export function DialogDemo() {
|
||||
return (
|
||||
<div className="flex flex-col items-start gap-4 md:flex-row">
|
||||
<DialogWithForm />
|
||||
<DialogScrollableContent />
|
||||
<DialogWithStickyFooter />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function DialogWithForm() {
|
||||
return (
|
||||
<Dialog>
|
||||
<form>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="outline">Edit Profile</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-[425px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Edit profile</DialogTitle>
|
||||
<DialogDescription>
|
||||
Make changes to your profile here. Click save when you're
|
||||
done.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="grid gap-4">
|
||||
<div className="grid gap-3">
|
||||
<Label htmlFor="name-1">Name</Label>
|
||||
<Input id="name-1" name="name" defaultValue="Pedro Duarte" />
|
||||
</div>
|
||||
<div className="grid gap-3">
|
||||
<Label htmlFor="username-1">Username</Label>
|
||||
<Input id="username-1" name="username" defaultValue="@peduarte" />
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<DialogClose asChild>
|
||||
<Button variant="outline">Cancel</Button>
|
||||
</DialogClose>
|
||||
<Button type="submit">Save changes</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</form>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
function DialogScrollableContent() {
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="outline">Scrollable Content</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-[425px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Scrollable Content</DialogTitle>
|
||||
<DialogDescription>
|
||||
This is a dialog with scrollable content.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="-mx-6 max-h-[500px] overflow-y-auto px-6 text-sm">
|
||||
<h4 className="mb-4 text-lg leading-none font-medium">Lorem Ipsum</h4>
|
||||
{Array.from({ length: 10 }).map((_, index) => (
|
||||
<p key={index} className="mb-4 leading-normal">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do
|
||||
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
|
||||
enim ad minim veniam, quis nostrud exercitation ullamco laboris
|
||||
nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
|
||||
reprehenderit in voluptate velit esse cillum dolore eu fugiat
|
||||
nulla pariatur. Excepteur sint occaecat cupidatat non proident,
|
||||
sunt in culpa qui officia deserunt mollit anim id est laborum.
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
function DialogWithStickyFooter() {
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="outline">Sticky Footer</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-lg">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Scrollable Content</DialogTitle>
|
||||
<DialogDescription>
|
||||
This is a dialog with scrollable content.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="-mx-6 max-h-[500px] overflow-y-auto px-6 text-sm">
|
||||
<h4 className="mb-4 text-lg leading-none font-medium">Lorem Ipsum</h4>
|
||||
{Array.from({ length: 10 }).map((_, index) => (
|
||||
<p key={index} className="mb-4 leading-normal">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do
|
||||
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
|
||||
enim ad minim veniam, quis nostrud exercitation ullamco laboris
|
||||
nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
|
||||
reprehenderit in voluptate velit esse cillum dolore eu fugiat
|
||||
nulla pariatur. Excepteur sint occaecat cupidatat non proident,
|
||||
sunt in culpa qui officia deserunt mollit anim id est laborum.
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<DialogClose asChild>
|
||||
<Button variant="outline">Close</Button>
|
||||
</DialogClose>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
228
apps/v4/components/drawer-demo.tsx
Normal file
228
apps/v4/components/drawer-demo.tsx
Normal file
@@ -0,0 +1,228 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { Minus, Plus } from "lucide-react"
|
||||
import { Bar, BarChart, ResponsiveContainer } from "recharts"
|
||||
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
import {
|
||||
Drawer,
|
||||
DrawerClose,
|
||||
DrawerContent,
|
||||
DrawerDescription,
|
||||
DrawerFooter,
|
||||
DrawerHeader,
|
||||
DrawerTitle,
|
||||
DrawerTrigger,
|
||||
} from "@/registry/new-york-v4/ui/drawer"
|
||||
|
||||
const data = [
|
||||
{
|
||||
goal: 400,
|
||||
},
|
||||
{
|
||||
goal: 300,
|
||||
},
|
||||
{
|
||||
goal: 200,
|
||||
},
|
||||
{
|
||||
goal: 300,
|
||||
},
|
||||
{
|
||||
goal: 200,
|
||||
},
|
||||
{
|
||||
goal: 278,
|
||||
},
|
||||
{
|
||||
goal: 189,
|
||||
},
|
||||
{
|
||||
goal: 239,
|
||||
},
|
||||
{
|
||||
goal: 300,
|
||||
},
|
||||
{
|
||||
goal: 200,
|
||||
},
|
||||
{
|
||||
goal: 278,
|
||||
},
|
||||
{
|
||||
goal: 189,
|
||||
},
|
||||
{
|
||||
goal: 349,
|
||||
},
|
||||
]
|
||||
|
||||
export function DrawerDemo() {
|
||||
return (
|
||||
<div className="flex flex-wrap items-start gap-4">
|
||||
<DrawerBottom />
|
||||
<DrawerScrollableContent />
|
||||
<DrawerDirections />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function DrawerBottom() {
|
||||
const [goal, setGoal] = React.useState(350)
|
||||
|
||||
const onClick = React.useCallback((adjustment: number) => {
|
||||
setGoal((prevGoal) => Math.max(200, Math.min(400, prevGoal + adjustment)))
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Drawer>
|
||||
<DrawerTrigger asChild>
|
||||
<Button variant="outline">Open Drawer</Button>
|
||||
</DrawerTrigger>
|
||||
<DrawerContent>
|
||||
<div className="mx-auto w-full max-w-sm">
|
||||
<DrawerHeader>
|
||||
<DrawerTitle>Move Goal</DrawerTitle>
|
||||
<DrawerDescription>Set your daily activity goal.</DrawerDescription>
|
||||
</DrawerHeader>
|
||||
<div className="p-4 pb-0">
|
||||
<div className="flex items-center justify-center space-x-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="h-8 w-8 shrink-0 rounded-full"
|
||||
onClick={() => onClick(-10)}
|
||||
disabled={goal <= 200}
|
||||
>
|
||||
<Minus />
|
||||
<span className="sr-only">Decrease</span>
|
||||
</Button>
|
||||
<div className="flex-1 text-center">
|
||||
<div className="text-7xl font-bold tracking-tighter">
|
||||
{goal}
|
||||
</div>
|
||||
<div className="text-muted-foreground text-[0.70rem] uppercase">
|
||||
Calories/day
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="h-8 w-8 shrink-0 rounded-full"
|
||||
onClick={() => onClick(10)}
|
||||
disabled={goal >= 400}
|
||||
>
|
||||
<Plus />
|
||||
<span className="sr-only">Increase</span>
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mt-3 h-[120px]">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<BarChart data={data}>
|
||||
<Bar
|
||||
dataKey="goal"
|
||||
style={
|
||||
{
|
||||
fill: "hsl(var(--foreground))",
|
||||
opacity: 0.9,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
/>
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</div>
|
||||
<DrawerFooter>
|
||||
<Button>Submit</Button>
|
||||
<DrawerClose asChild>
|
||||
<Button variant="outline">Cancel</Button>
|
||||
</DrawerClose>
|
||||
</DrawerFooter>
|
||||
</div>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
)
|
||||
}
|
||||
|
||||
function DrawerScrollableContent() {
|
||||
return (
|
||||
<Drawer direction="right">
|
||||
<DrawerTrigger asChild>
|
||||
<Button variant="outline">Scrollable Content</Button>
|
||||
</DrawerTrigger>
|
||||
<DrawerContent>
|
||||
<DrawerHeader>
|
||||
<DrawerTitle>Move Goal</DrawerTitle>
|
||||
<DrawerDescription>Set your daily activity goal.</DrawerDescription>
|
||||
</DrawerHeader>
|
||||
<div className="overflow-y-auto px-4 text-sm">
|
||||
<h4 className="mb-4 text-lg leading-none font-medium">Lorem Ipsum</h4>
|
||||
{Array.from({ length: 10 }).map((_, index) => (
|
||||
<p key={index} className="mb-4 leading-normal">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do
|
||||
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
|
||||
enim ad minim veniam, quis nostrud exercitation ullamco laboris
|
||||
nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
|
||||
reprehenderit in voluptate velit esse cillum dolore eu fugiat
|
||||
nulla pariatur. Excepteur sint occaecat cupidatat non proident,
|
||||
sunt in culpa qui officia deserunt mollit anim id est laborum.
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
<DrawerFooter>
|
||||
<Button>Submit</Button>
|
||||
<DrawerClose asChild>
|
||||
<Button variant="outline">Cancel</Button>
|
||||
</DrawerClose>
|
||||
</DrawerFooter>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
)
|
||||
}
|
||||
|
||||
const directions = ["top", "right", "bottom", "left"] as const
|
||||
|
||||
function DrawerDirections() {
|
||||
return (
|
||||
<>
|
||||
{directions.map((direction) => (
|
||||
<Drawer key={direction} direction={direction}>
|
||||
<DrawerTrigger asChild>
|
||||
<Button variant="outline" className="capitalize">
|
||||
{direction}
|
||||
</Button>
|
||||
</DrawerTrigger>
|
||||
<DrawerContent>
|
||||
<DrawerHeader>
|
||||
<DrawerTitle>Move Goal</DrawerTitle>
|
||||
<DrawerDescription>
|
||||
Set your daily activity goal.
|
||||
</DrawerDescription>
|
||||
</DrawerHeader>
|
||||
<div className="overflow-y-auto px-4 text-sm">
|
||||
{Array.from({ length: 10 }).map((_, index) => (
|
||||
<p key={index} className="mb-4 leading-normal">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed
|
||||
do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
Ut enim ad minim veniam, quis nostrud exercitation ullamco
|
||||
laboris nisi ut aliquip ex ea commodo consequat. Duis aute
|
||||
irure dolor in reprehenderit in voluptate velit esse cillum
|
||||
dolore eu fugiat nulla pariatur. Excepteur sint occaecat
|
||||
cupidatat non proident, sunt in culpa qui officia deserunt
|
||||
mollit anim id est laborum.
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
<DrawerFooter>
|
||||
<Button>Submit</Button>
|
||||
<DrawerClose asChild>
|
||||
<Button variant="outline">Cancel</Button>
|
||||
</DrawerClose>
|
||||
</DrawerFooter>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
}
|
||||
360
apps/v4/components/dropdown-menu-demo.tsx
Normal file
360
apps/v4/components/dropdown-menu-demo.tsx
Normal file
@@ -0,0 +1,360 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import {
|
||||
BadgeCheckIcon,
|
||||
BellIcon,
|
||||
ChevronsUpDownIcon,
|
||||
CreditCardIcon,
|
||||
LogOut,
|
||||
LogOutIcon,
|
||||
MoreHorizontalIcon,
|
||||
PencilIcon,
|
||||
Settings2Icon,
|
||||
ShareIcon,
|
||||
SparklesIcon,
|
||||
TrashIcon,
|
||||
UserIcon,
|
||||
} from "lucide-react"
|
||||
|
||||
import {
|
||||
Avatar,
|
||||
AvatarFallback,
|
||||
AvatarImage,
|
||||
} from "@/registry/new-york-v4/ui/avatar"
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuPortal,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuShortcut,
|
||||
DropdownMenuSub,
|
||||
DropdownMenuSubContent,
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/registry/new-york-v4/ui/dropdown-menu"
|
||||
|
||||
export function DropdownMenuDemo() {
|
||||
return (
|
||||
<div className="flex flex-wrap items-start gap-4">
|
||||
<DropdownMenuSimple />
|
||||
<DropdownMenuCheckboxes />
|
||||
<DropdownMenuRadioGroupDemo />
|
||||
<DropdownMenuWithAvatar />
|
||||
<DropdownMenuAvatarOnly />
|
||||
<DropdownMenuIconColor />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuSimple() {
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline">Open</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start" className="w-56">
|
||||
<DropdownMenuLabel>My Account</DropdownMenuLabel>
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem>
|
||||
Profile
|
||||
<DropdownMenuShortcut>⇧⌘P</DropdownMenuShortcut>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
Billing
|
||||
<DropdownMenuShortcut>⌘B</DropdownMenuShortcut>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
Settings
|
||||
<DropdownMenuShortcut>⌘S</DropdownMenuShortcut>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
Keyboard shortcuts
|
||||
<DropdownMenuShortcut>⌘K</DropdownMenuShortcut>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem>Team</DropdownMenuItem>
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger>Invite users</DropdownMenuSubTrigger>
|
||||
<DropdownMenuPortal>
|
||||
<DropdownMenuSubContent>
|
||||
<DropdownMenuItem>Email</DropdownMenuItem>
|
||||
<DropdownMenuItem>Message</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>More...</DropdownMenuItem>
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuPortal>
|
||||
</DropdownMenuSub>
|
||||
<DropdownMenuItem>
|
||||
New Team
|
||||
<DropdownMenuShortcut>⌘+T</DropdownMenuShortcut>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>GitHub</DropdownMenuItem>
|
||||
<DropdownMenuItem>Support</DropdownMenuItem>
|
||||
<DropdownMenuItem disabled>API</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>
|
||||
Log out
|
||||
<DropdownMenuShortcut>⇧⌘Q</DropdownMenuShortcut>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuCheckboxes() {
|
||||
const [showStatusBar, setShowStatusBar] = React.useState(true)
|
||||
const [showActivityBar, setShowActivityBar] = React.useState(false)
|
||||
const [showPanel, setShowPanel] = React.useState(false)
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline">Checkboxes</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start" className="w-56">
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuLabel>Account</DropdownMenuLabel>
|
||||
<DropdownMenuItem>
|
||||
<UserIcon /> Profile
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<CreditCardIcon /> Billing
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<Settings2Icon /> Settings
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuLabel>Appearance</DropdownMenuLabel>
|
||||
<DropdownMenuCheckboxItem
|
||||
checked={showStatusBar}
|
||||
onCheckedChange={setShowStatusBar}
|
||||
>
|
||||
Status Bar
|
||||
</DropdownMenuCheckboxItem>
|
||||
<DropdownMenuCheckboxItem
|
||||
checked={showActivityBar}
|
||||
onCheckedChange={setShowActivityBar}
|
||||
disabled
|
||||
>
|
||||
Activity Bar
|
||||
</DropdownMenuCheckboxItem>
|
||||
<DropdownMenuCheckboxItem
|
||||
checked={showPanel}
|
||||
onCheckedChange={setShowPanel}
|
||||
>
|
||||
Panel
|
||||
</DropdownMenuCheckboxItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem>
|
||||
<LogOutIcon /> Sign Out
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuRadioGroupDemo() {
|
||||
const [position, setPosition] = React.useState("bottom")
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline">Radio Group</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start" className="w-56">
|
||||
<DropdownMenuLabel inset>Panel Position</DropdownMenuLabel>
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuRadioGroup value={position} onValueChange={setPosition}>
|
||||
<DropdownMenuRadioItem value="top">Top</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="bottom">Bottom</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="right" disabled>
|
||||
Right
|
||||
</DropdownMenuRadioItem>
|
||||
</DropdownMenuRadioGroup>
|
||||
</DropdownMenuGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuWithAvatar() {
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="h-12 justify-start px-2 md:max-w-[200px]"
|
||||
>
|
||||
<Avatar>
|
||||
<AvatarImage src="https://github.com/shadcn.png" alt="Shadcn" />
|
||||
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||
<span className="truncate font-semibold">shadcn</span>
|
||||
<span className="text-muted-foreground truncate text-xs">
|
||||
shadcn@example.com
|
||||
</span>
|
||||
</div>
|
||||
<ChevronsUpDownIcon className="text-muted-foreground ml-auto" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
className="w-(--radix-dropdown-menu-trigger-width) min-w-56"
|
||||
align="start"
|
||||
>
|
||||
<DropdownMenuLabel className="p-0 font-normal">
|
||||
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
||||
<Avatar>
|
||||
<AvatarImage src="https://github.com/shadcn.png" alt="Shadcn" />
|
||||
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||
<span className="truncate font-semibold">shadcn</span>
|
||||
<span className="text-muted-foreground truncate text-xs">
|
||||
shadcn@example.com
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem>
|
||||
<SparklesIcon />
|
||||
Upgrade to Pro
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem>
|
||||
<BadgeCheckIcon />
|
||||
Account
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<CreditCardIcon />
|
||||
Billing
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<BellIcon />
|
||||
Notifications
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>
|
||||
<LogOut />
|
||||
Sign Out
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuAvatarOnly() {
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="size-8 rounded-full border-none p-0"
|
||||
>
|
||||
<Avatar>
|
||||
<AvatarImage src="https://github.com/leerob.png" alt="leerob" />
|
||||
<AvatarFallback className="rounded-lg">LR</AvatarFallback>
|
||||
</Avatar>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
className="w-(--radix-dropdown-menu-trigger-width) min-w-56"
|
||||
align="start"
|
||||
>
|
||||
<DropdownMenuLabel className="p-0 font-normal">
|
||||
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
||||
<Avatar>
|
||||
<AvatarImage src="https://github.com/leerob.png" alt="leerob" />
|
||||
<AvatarFallback className="rounded-lg">LR</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||
<span className="truncate font-semibold">leerob</span>
|
||||
<span className="text-muted-foreground truncate text-xs">
|
||||
leerob@example.com
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem>
|
||||
<SparklesIcon />
|
||||
Upgrade to Pro
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem>
|
||||
<BadgeCheckIcon />
|
||||
Account
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<CreditCardIcon />
|
||||
Billing
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<BellIcon />
|
||||
Notifications
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>
|
||||
<LogOut />
|
||||
Sign Out
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuIconColor() {
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="icon">
|
||||
<MoreHorizontalIcon />
|
||||
<span className="sr-only">Toggle menu</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start">
|
||||
<DropdownMenuGroup className="*:data-[slot=dropdown-menu-item]:[&>svg]:text-muted-foreground">
|
||||
<DropdownMenuItem>
|
||||
<PencilIcon />
|
||||
Edit
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<ShareIcon />
|
||||
Share
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem variant="destructive">
|
||||
<TrashIcon />
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)
|
||||
}
|
||||
399
apps/v4/components/form-demo.tsx
Normal file
399
apps/v4/components/form-demo.tsx
Normal file
@@ -0,0 +1,399 @@
|
||||
"use client"
|
||||
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { format } from "date-fns"
|
||||
import { CalendarIcon } from "lucide-react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { toast } from "sonner"
|
||||
import { z } from "zod"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
import { Calendar } from "@/registry/new-york-v4/ui/calendar"
|
||||
import { Checkbox } from "@/registry/new-york-v4/ui/checkbox"
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/registry/new-york-v4/ui/form"
|
||||
import { Input } from "@/registry/new-york-v4/ui/input"
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/registry/new-york-v4/ui/popover"
|
||||
import {
|
||||
RadioGroup,
|
||||
RadioGroupItem,
|
||||
} from "@/registry/new-york-v4/ui/radio-group"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/registry/new-york-v4/ui/select"
|
||||
import { Switch } from "@/registry/new-york-v4/ui/switch"
|
||||
import { Textarea } from "@/registry/new-york-v4/ui/textarea"
|
||||
|
||||
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 FormSchema = z.object({
|
||||
username: z.string().min(2, {
|
||||
message: "Username must be at least 2 characters.",
|
||||
}),
|
||||
bio: z
|
||||
.string()
|
||||
.min(10, {
|
||||
message: "Bio must be at least 10 characters.",
|
||||
})
|
||||
.max(160, {
|
||||
message: "Bio must not be longer than 30 characters.",
|
||||
}),
|
||||
email: z
|
||||
.string({
|
||||
required_error: "Please select an email to display.",
|
||||
})
|
||||
.email(),
|
||||
type: z.enum(["all", "mentions", "none"], {
|
||||
required_error: "You need to select a notification type.",
|
||||
}),
|
||||
mobile: z.boolean().default(false).optional(),
|
||||
items: z.array(z.string()).refine((value) => value.some((item) => item), {
|
||||
message: "You have to select at least one item.",
|
||||
}),
|
||||
dob: z.date({
|
||||
required_error: "A date of birth is required.",
|
||||
}),
|
||||
marketing_emails: z.boolean().default(false).optional(),
|
||||
security_emails: z.boolean(),
|
||||
})
|
||||
|
||||
export function FormDemo() {
|
||||
const form = useForm<z.infer<typeof FormSchema>>({
|
||||
resolver: zodResolver(FormSchema),
|
||||
defaultValues: {
|
||||
username: "",
|
||||
items: ["recents", "home"],
|
||||
},
|
||||
})
|
||||
|
||||
function onSubmit(data: z.infer<typeof FormSchema>) {
|
||||
toast("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="grid w-full max-w-sm gap-6"
|
||||
>
|
||||
<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.
|
||||
</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 email addresses in your email settings.
|
||||
</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.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="type"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-col gap-3">
|
||||
<FormLabel>Notify me about...</FormLabel>
|
||||
<FormControl>
|
||||
<RadioGroup
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={field.value}
|
||||
className="flex flex-col gap-3"
|
||||
>
|
||||
<FormItem className="flex items-center gap-2">
|
||||
<FormControl>
|
||||
<RadioGroupItem value="all" />
|
||||
</FormControl>
|
||||
<FormLabel className="font-normal">
|
||||
All new messages
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
<FormItem className="flex items-center gap-2">
|
||||
<FormControl>
|
||||
<RadioGroupItem value="mentions" />
|
||||
</FormControl>
|
||||
<FormLabel className="font-normal">
|
||||
Direct messages and mentions
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
<FormItem className="flex items-center gap-2">
|
||||
<FormControl>
|
||||
<RadioGroupItem value="none" />
|
||||
</FormControl>
|
||||
<FormLabel className="font-normal">Nothing</FormLabel>
|
||||
</FormItem>
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="mobile"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-start gap-3 rounded-md border p-4 shadow-xs">
|
||||
<FormControl>
|
||||
<Checkbox
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
<div className="flex flex-col gap-1">
|
||||
<FormLabel className="leading-snug">
|
||||
Use different settings for my mobile devices
|
||||
</FormLabel>
|
||||
<FormDescription className="leading-snug">
|
||||
You can manage your mobile notifications in the mobile
|
||||
settings page.
|
||||
</FormDescription>
|
||||
</div>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="items"
|
||||
render={() => (
|
||||
<FormItem className="flex flex-col gap-4">
|
||||
<div>
|
||||
<FormLabel className="text-base">Sidebar</FormLabel>
|
||||
<FormDescription>
|
||||
Select the items you want to display in the sidebar.
|
||||
</FormDescription>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
{items.map((item) => (
|
||||
<FormField
|
||||
key={item.id}
|
||||
control={form.control}
|
||||
name="items"
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<FormItem
|
||||
key={item.id}
|
||||
className="flex items-start gap-3"
|
||||
>
|
||||
<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="text-sm leading-tight font-normal">
|
||||
{item.label}
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<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>
|
||||
)}
|
||||
/>
|
||||
<div>
|
||||
<h3 className="mb-4 text-lg font-medium">Email Notifications</h3>
|
||||
<div className="flex flex-col gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="marketing_emails"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-start justify-between rounded-lg border p-4 shadow-xs">
|
||||
<div className="flex flex-col gap-0.5">
|
||||
<FormLabel className="leading-normal">
|
||||
Marketing emails
|
||||
</FormLabel>
|
||||
<FormDescription className="leading-snug">
|
||||
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="security_emails"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-start justify-between rounded-lg border p-4 shadow-xs">
|
||||
<div className="flex flex-col gap-0.5 opacity-60">
|
||||
<FormLabel className="leading-normal">
|
||||
Security emails
|
||||
</FormLabel>
|
||||
<FormDescription className="leading-snug">
|
||||
Receive emails about your account security.
|
||||
</FormDescription>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
disabled
|
||||
aria-readonly
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Button type="submit">Submit</Button>
|
||||
</form>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
231
apps/v4/components/forms-demo.tsx
Normal file
231
apps/v4/components/forms-demo.tsx
Normal file
@@ -0,0 +1,231 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { useTheme } from "next-themes"
|
||||
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/registry/new-york-v4/ui/card"
|
||||
import { Checkbox } from "@/registry/new-york-v4/ui/checkbox"
|
||||
import { Input } from "@/registry/new-york-v4/ui/input"
|
||||
import { Label } from "@/registry/new-york-v4/ui/label"
|
||||
import {
|
||||
RadioGroup,
|
||||
RadioGroupItem,
|
||||
} from "@/registry/new-york-v4/ui/radio-group"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/registry/new-york-v4/ui/select"
|
||||
import { Textarea } from "@/registry/new-york-v4/ui/textarea"
|
||||
|
||||
const plans = [
|
||||
{
|
||||
id: "starter",
|
||||
name: "Starter Plan",
|
||||
description: "Perfect for small businesses.",
|
||||
price: "$10",
|
||||
},
|
||||
{
|
||||
id: "pro",
|
||||
name: "Pro Plan",
|
||||
description: "Advanced features with more storage.",
|
||||
price: "$20",
|
||||
},
|
||||
] as const
|
||||
|
||||
const themes = {
|
||||
stone: {
|
||||
light: {
|
||||
"--primary": "oklch(0.216 0.006 56.043)", // --color-stone-900
|
||||
"--primary-foreground": "oklch(0.985 0.001 106.423)", // --color-stone-50
|
||||
"--accent": "oklch(0.97 0.001 106.424)", // --color-stone-100
|
||||
"--ring": "oklch(0.869 0.005 56.366)", // --color-stone-300
|
||||
},
|
||||
dark: {
|
||||
"--primary": "oklch(0.985 0.001 106.423)", // --color-stone-50
|
||||
"--primary-foreground": "oklch(0.216 0.006 56.043)", // --color-stone-900
|
||||
"--accent": "oklch(0.268 0.007 34.298)", // --color-stone-800
|
||||
"--accent-foreground": "oklch(0.985 0.001 106.423)", // --color-stone-50
|
||||
"--ring": "oklch(0.553 0.013 58.071)", // --color-stone-500
|
||||
},
|
||||
},
|
||||
blue: {
|
||||
light: {
|
||||
"--primary": "oklch(0.546 0.245 262.881)", // --color-blue-600
|
||||
"--primary-foreground": "oklch(0.985 0.001 106.423)", // --color-blue-50
|
||||
"--ring": "oklch(0.707 0.165 254.624)", // --color-blue-400
|
||||
},
|
||||
dark: {
|
||||
"--primary": "oklch(0.546 0.245 262.881)", // --color-blue-600
|
||||
"--primary-foreground": "oklch(0.985 0.001 106.423)", // --color-blue-50
|
||||
"--ring": "oklch(0.379 0.146 265.522)", // --color-blue-400
|
||||
},
|
||||
},
|
||||
amber: {
|
||||
light: {
|
||||
"--primary": "oklch(0.769 0.188 70.08)", // --color-blue-600
|
||||
"--primary-foreground": "oklch(0.985 0.001 106.423)", // --color-blue-50
|
||||
"--ring": "oklch(0.82 0.13 92.25)", // --color-blue-400
|
||||
},
|
||||
dark: {
|
||||
"--primary": "oklch(0.985 0.001 106.423)", // --color-stone-50
|
||||
"--primary-foreground": "oklch(0.216 0.006 56.043)", // --color-stone-900
|
||||
"--ring": "oklch(0.553 0.013 58.071)", // --color-stone-500
|
||||
},
|
||||
},
|
||||
teal: {
|
||||
light: {
|
||||
"--primary": "oklch(0.627 0.194 149.214)", // --color-blue-600
|
||||
"--primary-foreground": "oklch(0.985 0.001 106.423)", // --color-blue-50
|
||||
"--ring": "oklch(0.79 0.19 153.13)", // --color-blue-400
|
||||
},
|
||||
dark: {
|
||||
"--primary": "oklch(0.985 0.001 106.423)", // --color-stone-50
|
||||
"--primary-foreground": "oklch(0.216 0.006 56.043)", // --color-stone-900
|
||||
"--ring": "oklch(0.553 0.013 58.071)", // --color-stone-500
|
||||
},
|
||||
},
|
||||
} as const
|
||||
|
||||
export function FormsDemo() {
|
||||
const { theme: mode = "light" } = useTheme()
|
||||
const [theme, setTheme] = React.useState<keyof typeof themes | undefined>(
|
||||
undefined
|
||||
)
|
||||
|
||||
const themeStyles = React.useMemo(() => {
|
||||
if (!theme) return undefined
|
||||
return themes[theme][mode as keyof (typeof themes)[typeof theme]]
|
||||
}, [theme, mode])
|
||||
|
||||
return (
|
||||
<div className="flex max-w-md flex-col gap-4">
|
||||
<Card style={themeStyles as React.CSSProperties}>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-lg">Upgrade your subscription</CardTitle>
|
||||
<CardDescription>
|
||||
You are currently on the free plan. Upgrade to the pro plan to get
|
||||
access to all features.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex flex-col gap-6">
|
||||
<div className="flex flex-col gap-3 md:flex-row">
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label htmlFor="name">Name</Label>
|
||||
<Input id="name" placeholder="Evil Rabbit" />
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label htmlFor="email">Email</Label>
|
||||
<Input id="email" placeholder="example@acme.com" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label htmlFor="card-number">Card Number</Label>
|
||||
<div className="grid grid-cols-2 gap-3 md:grid-cols-[1fr_80px_60px]">
|
||||
<Input
|
||||
id="card-number"
|
||||
placeholder="1234 1234 1234 1234"
|
||||
className="col-span-2 md:col-span-1"
|
||||
/>
|
||||
<Input id="card-number-expiry" placeholder="MM/YY" />
|
||||
<Input id="card-number-cvc" placeholder="CVC" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label htmlFor="color">Color</Label>
|
||||
<Select
|
||||
onValueChange={(value) =>
|
||||
setTheme(value as keyof typeof themes)
|
||||
}
|
||||
>
|
||||
<SelectTrigger id="color" className="w-full">
|
||||
<SelectValue placeholder="Select a color" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{Object.keys(themes).map((theme) => (
|
||||
<SelectItem key={theme} value={theme}>
|
||||
<div
|
||||
className="size-3.5 rounded-full"
|
||||
style={{
|
||||
backgroundColor:
|
||||
themes[theme as keyof typeof themes]["light"][
|
||||
"--primary"
|
||||
],
|
||||
}}
|
||||
/>
|
||||
{theme}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<fieldset className="flex flex-col gap-3">
|
||||
<legend className="text-sm font-medium">Plan</legend>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
Select the plan that best fits your needs.
|
||||
</p>
|
||||
<RadioGroup
|
||||
defaultValue="starter"
|
||||
className="grid gap-3 md:grid-cols-2"
|
||||
>
|
||||
{plans.map((plan) => (
|
||||
<Label
|
||||
className="has-[[data-state=checked]]:border-ring has-[[data-state=checked]]:bg-ring/10 flex items-start gap-3 rounded-lg border p-3"
|
||||
key={plan.id}
|
||||
>
|
||||
<RadioGroupItem
|
||||
value={plan.id}
|
||||
id={plan.name}
|
||||
className="data-[state=checked]:border-primary"
|
||||
/>
|
||||
<div className="grid gap-1 font-normal">
|
||||
<div className="font-medium">{plan.name}</div>
|
||||
<div className="text-muted-foreground text-xs leading-snug">
|
||||
{plan.description}
|
||||
</div>
|
||||
</div>
|
||||
</Label>
|
||||
))}
|
||||
</RadioGroup>
|
||||
</fieldset>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label htmlFor="notes">Notes</Label>
|
||||
<Textarea id="notes" placeholder="Enter notes" />
|
||||
</div>
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox id="terms" />
|
||||
<Label htmlFor="terms" className="font-normal">
|
||||
I agree to the terms and conditions
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox id="newsletter" defaultChecked />
|
||||
<Label htmlFor="newsletter" className="font-normal">
|
||||
Allow us to send you emails
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
<CardFooter className="flex justify-between">
|
||||
<Button variant="outline" size="sm">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button size="sm">Upgrade Plan</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
43
apps/v4/components/hover-card-demo.tsx
Normal file
43
apps/v4/components/hover-card-demo.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import { CalendarIcon } from "lucide-react"
|
||||
|
||||
import {
|
||||
Avatar,
|
||||
AvatarFallback,
|
||||
AvatarImage,
|
||||
} from "@/registry/new-york-v4/ui/avatar"
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
import {
|
||||
HoverCard,
|
||||
HoverCardContent,
|
||||
HoverCardTrigger,
|
||||
} from "@/registry/new-york-v4/ui/hover-card"
|
||||
|
||||
export function HoverCardDemo() {
|
||||
return (
|
||||
<HoverCard>
|
||||
<HoverCardTrigger asChild>
|
||||
<Button variant="link">@nextjs</Button>
|
||||
</HoverCardTrigger>
|
||||
<HoverCardContent className="w-80" side="right">
|
||||
<div className="flex justify-between gap-4">
|
||||
<Avatar>
|
||||
<AvatarImage src="https://github.com/vercel.png" />
|
||||
<AvatarFallback>VC</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="flex flex-col gap-1">
|
||||
<h4 className="text-sm font-semibold">@nextjs</h4>
|
||||
<p className="text-sm">
|
||||
The React Framework – created and maintained by @vercel.
|
||||
</p>
|
||||
<div className="mt-1 flex items-center gap-2">
|
||||
<CalendarIcon className="text-muted-foreground size-4" />{" "}
|
||||
<span className="text-muted-foreground text-xs">
|
||||
Joined December 2021
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</HoverCardContent>
|
||||
</HoverCard>
|
||||
)
|
||||
}
|
||||
23
apps/v4/components/input-demo.tsx
Normal file
23
apps/v4/components/input-demo.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import { Input } from "@/registry/new-york-v4/ui/input"
|
||||
|
||||
export function InputDemo() {
|
||||
return (
|
||||
<div className="flex flex-col flex-wrap gap-4 md:flex-row">
|
||||
<Input type="email" placeholder="Email" />
|
||||
<Input type="text" placeholder="Error" aria-invalid="true" />
|
||||
<Input type="password" placeholder="Password" />
|
||||
<Input type="number" placeholder="Number" />
|
||||
<Input type="file" placeholder="File" />
|
||||
<Input type="tel" placeholder="Tel" />
|
||||
<Input type="text" placeholder="Text" />
|
||||
<Input type="url" placeholder="URL" />
|
||||
<Input type="search" placeholder="Search" />
|
||||
<Input type="date" placeholder="Date" />
|
||||
<Input type="datetime-local" placeholder="Datetime Local" />
|
||||
<Input type="month" placeholder="Month" />
|
||||
<Input type="time" placeholder="Time" />
|
||||
<Input type="week" placeholder="Week" />
|
||||
<Input disabled placeholder="Disabled" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
109
apps/v4/components/input-otp-demo.tsx
Normal file
109
apps/v4/components/input-otp-demo.tsx
Normal file
@@ -0,0 +1,109 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { REGEXP_ONLY_DIGITS } from "input-otp"
|
||||
|
||||
import {
|
||||
InputOTP,
|
||||
InputOTPGroup,
|
||||
InputOTPSeparator,
|
||||
InputOTPSlot,
|
||||
} from "@/registry/new-york-v4/ui/input-otp"
|
||||
import { Label } from "@/registry/new-york-v4/ui/label"
|
||||
|
||||
export function InputOTPDemo() {
|
||||
return (
|
||||
<div className="flex flex-col flex-wrap gap-6 md:flex-row">
|
||||
<InputOTPSimple />
|
||||
<InputOTPPattern />
|
||||
<InputOTPWithSeparator />
|
||||
<InputOTPWithSpacing />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function InputOTPSimple() {
|
||||
return (
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="simple">Simple</Label>
|
||||
<InputOTP id="simple" maxLength={6}>
|
||||
<InputOTPGroup>
|
||||
<InputOTPSlot index={0} />
|
||||
<InputOTPSlot index={1} />
|
||||
<InputOTPSlot index={2} />
|
||||
</InputOTPGroup>
|
||||
<InputOTPSeparator />
|
||||
<InputOTPGroup>
|
||||
<InputOTPSlot index={3} />
|
||||
<InputOTPSlot index={4} />
|
||||
<InputOTPSlot index={5} />
|
||||
</InputOTPGroup>
|
||||
</InputOTP>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function InputOTPPattern() {
|
||||
return (
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="digits-only">Digits Only</Label>
|
||||
<InputOTP id="digits-only" maxLength={6} pattern={REGEXP_ONLY_DIGITS}>
|
||||
<InputOTPGroup>
|
||||
<InputOTPSlot index={0} />
|
||||
<InputOTPSlot index={1} />
|
||||
<InputOTPSlot index={2} />
|
||||
<InputOTPSlot index={3} />
|
||||
<InputOTPSlot index={4} />
|
||||
<InputOTPSlot index={5} />
|
||||
</InputOTPGroup>
|
||||
</InputOTP>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function InputOTPWithSeparator() {
|
||||
const [value, setValue] = React.useState("123456")
|
||||
|
||||
return (
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="with-separator">With Separator</Label>
|
||||
<InputOTP
|
||||
id="with-separator"
|
||||
maxLength={6}
|
||||
value={value}
|
||||
onChange={setValue}
|
||||
>
|
||||
<InputOTPGroup>
|
||||
<InputOTPSlot index={0} />
|
||||
<InputOTPSlot index={1} />
|
||||
</InputOTPGroup>
|
||||
<InputOTPSeparator />
|
||||
<InputOTPGroup>
|
||||
<InputOTPSlot index={2} />
|
||||
<InputOTPSlot index={3} />
|
||||
</InputOTPGroup>
|
||||
<InputOTPSeparator />
|
||||
<InputOTPGroup>
|
||||
<InputOTPSlot index={4} />
|
||||
<InputOTPSlot index={5} />
|
||||
</InputOTPGroup>
|
||||
</InputOTP>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function InputOTPWithSpacing() {
|
||||
return (
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="with-spacing">With Spacing</Label>
|
||||
<InputOTP id="with-spacing" maxLength={6}>
|
||||
<InputOTPGroup className="gap-2 *:data-[slot=input-otp-slot]:rounded-md *:data-[slot=input-otp-slot]:border">
|
||||
<InputOTPSlot index={0} aria-invalid="true" />
|
||||
<InputOTPSlot index={1} aria-invalid="true" />
|
||||
<InputOTPSlot index={2} aria-invalid="true" />
|
||||
<InputOTPSlot index={3} aria-invalid="true" />
|
||||
</InputOTPGroup>
|
||||
</InputOTP>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
27
apps/v4/components/label-demo.tsx
Normal file
27
apps/v4/components/label-demo.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { Checkbox } from "@/registry/new-york-v4/ui/checkbox"
|
||||
import { Input } from "@/registry/new-york-v4/ui/input"
|
||||
import { Label } from "@/registry/new-york-v4/ui/label"
|
||||
import { Textarea } from "@/registry/new-york-v4/ui/textarea"
|
||||
|
||||
export function LabelDemo() {
|
||||
return (
|
||||
<div className="grid w-full max-w-sm gap-6">
|
||||
<div className="flex items-center gap-3">
|
||||
<Checkbox id="label-demo-terms" />
|
||||
<Label htmlFor="label-demo-terms">Accept terms and conditions</Label>
|
||||
</div>
|
||||
<div className="grid gap-3">
|
||||
<Label htmlFor="label-demo-username">Username</Label>
|
||||
<Input id="label-demo-username" placeholder="Username" />
|
||||
</div>
|
||||
<div className="group grid gap-3" data-disabled={true}>
|
||||
<Label htmlFor="label-demo-disabled">Disabled</Label>
|
||||
<Input id="label-demo-disabled" placeholder="Disabled" disabled />
|
||||
</div>
|
||||
<div className="grid gap-3">
|
||||
<Label htmlFor="label-demo-message">Message</Label>
|
||||
<Textarea id="label-demo-message" placeholder="Message" />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
112
apps/v4/components/login-form.tsx
Normal file
112
apps/v4/components/login-form.tsx
Normal file
@@ -0,0 +1,112 @@
|
||||
import Image from "next/image"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
import { Card, CardContent } from "@/registry/new-york-v4/ui/card"
|
||||
import { Input } from "@/registry/new-york-v4/ui/input"
|
||||
import { Label } from "@/registry/new-york-v4/ui/label"
|
||||
|
||||
export function LoginForm({
|
||||
className,
|
||||
imageUrl,
|
||||
...props
|
||||
}: React.ComponentProps<"div"> & {
|
||||
imageUrl?: string
|
||||
}) {
|
||||
return (
|
||||
<div className={cn("flex flex-col gap-6", className)} {...props}>
|
||||
<Card className="overflow-hidden p-0">
|
||||
<CardContent className="grid p-0 md:grid-cols-2">
|
||||
<form className="p-6 md:p-8">
|
||||
<div className="flex flex-col gap-6">
|
||||
<div className="flex flex-col items-center text-center">
|
||||
<h1 className="text-2xl font-bold">Welcome back</h1>
|
||||
<p className="text-muted-foreground text-balance">
|
||||
Login to your Acme Inc account
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid gap-3">
|
||||
<Label htmlFor="email">Email</Label>
|
||||
<Input
|
||||
id="email"
|
||||
type="email"
|
||||
placeholder="m@example.com"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-3">
|
||||
<div className="flex items-center">
|
||||
<Label htmlFor="password">Password</Label>
|
||||
<a
|
||||
href="#"
|
||||
className="ml-auto text-sm underline-offset-2 hover:underline"
|
||||
>
|
||||
Forgot your password?
|
||||
</a>
|
||||
</div>
|
||||
<Input id="password" type="password" required />
|
||||
</div>
|
||||
<Button type="submit" className="w-full">
|
||||
Login
|
||||
</Button>
|
||||
<div className="after:border-border relative text-center text-sm after:absolute after:inset-0 after:top-1/2 after:z-0 after:flex after:items-center after:border-t">
|
||||
<span className="bg-background text-muted-foreground relative z-10 px-2">
|
||||
Or continue with
|
||||
</span>
|
||||
</div>
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<Button variant="outline" type="button" className="w-full">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path
|
||||
d="M12.152 6.896c-.948 0-2.415-1.078-3.96-1.04-2.04.027-3.91 1.183-4.961 3.014-2.117 3.675-.546 9.103 1.519 12.09 1.013 1.454 2.208 3.09 3.792 3.039 1.52-.065 2.09-.987 3.935-.987 1.831 0 2.35.987 3.96.948 1.637-.026 2.676-1.48 3.676-2.948 1.156-1.688 1.636-3.325 1.662-3.415-.039-.013-3.182-1.221-3.22-4.857-.026-3.04 2.48-4.494 2.597-4.559-1.429-2.09-3.623-2.324-4.39-2.376-2-.156-3.675 1.09-4.61 1.09zM15.53 3.83c.843-1.012 1.4-2.427 1.245-3.83-1.207.052-2.662.805-3.532 1.818-.78.896-1.454 2.338-1.273 3.714 1.338.104 2.715-.688 3.559-1.701"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
<span className="sr-only">Login with Apple</span>
|
||||
</Button>
|
||||
<Button variant="outline" type="button" className="w-full">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path
|
||||
d="M12.48 10.92v3.28h7.84c-.24 1.84-.853 3.187-1.787 4.133-1.147 1.147-2.933 2.4-6.053 2.4-4.827 0-8.6-3.893-8.6-8.72s3.773-8.72 8.6-8.72c2.6 0 4.507 1.027 5.907 2.347l2.307-2.307C18.747 1.44 16.133 0 12.48 0 5.867 0 .307 5.387.307 12s5.56 12 12.173 12c3.573 0 6.267-1.173 8.373-3.36 2.16-2.16 2.84-5.213 2.84-7.667 0-.76-.053-1.467-.173-2.053H12.48z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
<span className="sr-only">Login with Google</span>
|
||||
</Button>
|
||||
<Button variant="outline" type="button" className="w-full">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path
|
||||
d="M6.915 4.03c-1.968 0-3.683 1.28-4.871 3.113C.704 9.208 0 11.883 0 14.449c0 .706.07 1.369.21 1.973a6.624 6.624 0 0 0 .265.86 5.297 5.297 0 0 0 .371.761c.696 1.159 1.818 1.927 3.593 1.927 1.497 0 2.633-.671 3.965-2.444.76-1.012 1.144-1.626 2.663-4.32l.756-1.339.186-.325c.061.1.121.196.183.3l2.152 3.595c.724 1.21 1.665 2.556 2.47 3.314 1.046.987 1.992 1.22 3.06 1.22 1.075 0 1.876-.355 2.455-.843a3.743 3.743 0 0 0 .81-.973c.542-.939.861-2.127.861-3.745 0-2.72-.681-5.357-2.084-7.45-1.282-1.912-2.957-2.93-4.716-2.93-1.047 0-2.088.467-3.053 1.308-.652.57-1.257 1.29-1.82 2.05-.69-.875-1.335-1.547-1.958-2.056-1.182-.966-2.315-1.303-3.454-1.303zm10.16 2.053c1.147 0 2.188.758 2.992 1.999 1.132 1.748 1.647 4.195 1.647 6.4 0 1.548-.368 2.9-1.839 2.9-.58 0-1.027-.23-1.664-1.004-.496-.601-1.343-1.878-2.832-4.358l-.617-1.028a44.908 44.908 0 0 0-1.255-1.98c.07-.109.141-.224.211-.327 1.12-1.667 2.118-2.602 3.358-2.602zm-10.201.553c1.265 0 2.058.791 2.675 1.446.307.327.737.871 1.234 1.579l-1.02 1.566c-.757 1.163-1.882 3.017-2.837 4.338-1.191 1.649-1.81 1.817-2.486 1.817-.524 0-1.038-.237-1.383-.794-.263-.426-.464-1.13-.464-2.046 0-2.221.63-4.535 1.66-6.088.454-.687.964-1.226 1.533-1.533a2.264 2.264 0 0 1 1.088-.285z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
<span className="sr-only">Login with Meta</span>
|
||||
</Button>
|
||||
</div>
|
||||
<div className="text-center text-sm">
|
||||
Don't have an account?{" "}
|
||||
<a href="#" className="underline underline-offset-4">
|
||||
Sign up
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div className="bg-primary/50 relative hidden md:block">
|
||||
{imageUrl && (
|
||||
<Image
|
||||
fill
|
||||
src={imageUrl}
|
||||
alt="Image"
|
||||
className="absolute inset-0 h-full w-full object-cover"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className="text-muted-foreground *:[a]:hover:text-primary text-center text-xs text-balance *:[a]:underline *:[a]:underline-offset-4">
|
||||
By clicking continue, you agree to our <a href="#">Terms of Service</a>{" "}
|
||||
and <a href="#">Privacy Policy</a>.
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
130
apps/v4/components/menubar-demo.tsx
Normal file
130
apps/v4/components/menubar-demo.tsx
Normal file
@@ -0,0 +1,130 @@
|
||||
import { HelpCircleIcon, SettingsIcon, Trash2Icon } from "lucide-react"
|
||||
|
||||
import {
|
||||
Menubar,
|
||||
MenubarCheckboxItem,
|
||||
MenubarContent,
|
||||
MenubarGroup,
|
||||
MenubarItem,
|
||||
MenubarMenu,
|
||||
MenubarRadioGroup,
|
||||
MenubarRadioItem,
|
||||
MenubarSeparator,
|
||||
MenubarShortcut,
|
||||
MenubarSub,
|
||||
MenubarSubContent,
|
||||
MenubarSubTrigger,
|
||||
MenubarTrigger,
|
||||
} from "@/registry/new-york-v4/ui/menubar"
|
||||
|
||||
export function MenubarDemo() {
|
||||
return (
|
||||
<Menubar>
|
||||
<MenubarMenu>
|
||||
<MenubarTrigger>File</MenubarTrigger>
|
||||
<MenubarContent>
|
||||
<MenubarItem>
|
||||
New Tab <MenubarShortcut>⌘T</MenubarShortcut>
|
||||
</MenubarItem>
|
||||
<MenubarItem>
|
||||
New Window <MenubarShortcut>⌘N</MenubarShortcut>
|
||||
</MenubarItem>
|
||||
<MenubarItem disabled>New Incognito Window</MenubarItem>
|
||||
<MenubarSeparator />
|
||||
<MenubarSub>
|
||||
<MenubarSubTrigger>Share</MenubarSubTrigger>
|
||||
<MenubarSubContent>
|
||||
<MenubarItem>Email link</MenubarItem>
|
||||
<MenubarItem>Messages</MenubarItem>
|
||||
<MenubarItem>Notes</MenubarItem>
|
||||
</MenubarSubContent>
|
||||
</MenubarSub>
|
||||
<MenubarSeparator />
|
||||
<MenubarItem>
|
||||
Print... <MenubarShortcut>⌘P</MenubarShortcut>
|
||||
</MenubarItem>
|
||||
</MenubarContent>
|
||||
</MenubarMenu>
|
||||
<MenubarMenu>
|
||||
<MenubarTrigger>Edit</MenubarTrigger>
|
||||
<MenubarContent>
|
||||
<MenubarItem>
|
||||
Undo <MenubarShortcut>⌘Z</MenubarShortcut>
|
||||
</MenubarItem>
|
||||
<MenubarItem>
|
||||
Redo <MenubarShortcut>⇧⌘Z</MenubarShortcut>
|
||||
</MenubarItem>
|
||||
<MenubarSeparator />
|
||||
<MenubarSub>
|
||||
<MenubarSubTrigger>Find</MenubarSubTrigger>
|
||||
<MenubarSubContent>
|
||||
<MenubarItem>Search the web</MenubarItem>
|
||||
<MenubarSeparator />
|
||||
<MenubarItem>Find...</MenubarItem>
|
||||
<MenubarItem>Find Next</MenubarItem>
|
||||
<MenubarItem>Find Previous</MenubarItem>
|
||||
</MenubarSubContent>
|
||||
</MenubarSub>
|
||||
<MenubarSeparator />
|
||||
<MenubarItem>Cut</MenubarItem>
|
||||
<MenubarItem>Copy</MenubarItem>
|
||||
<MenubarItem>Paste</MenubarItem>
|
||||
</MenubarContent>
|
||||
</MenubarMenu>
|
||||
<MenubarMenu>
|
||||
<MenubarTrigger>View</MenubarTrigger>
|
||||
<MenubarContent>
|
||||
<MenubarCheckboxItem>Always Show Bookmarks Bar</MenubarCheckboxItem>
|
||||
<MenubarCheckboxItem checked>
|
||||
Always Show Full URLs
|
||||
</MenubarCheckboxItem>
|
||||
<MenubarSeparator />
|
||||
<MenubarItem inset>
|
||||
Reload <MenubarShortcut>⌘R</MenubarShortcut>
|
||||
</MenubarItem>
|
||||
<MenubarItem disabled inset>
|
||||
Force Reload <MenubarShortcut>⇧⌘R</MenubarShortcut>
|
||||
</MenubarItem>
|
||||
<MenubarSeparator />
|
||||
<MenubarItem inset>Toggle Fullscreen</MenubarItem>
|
||||
<MenubarSeparator />
|
||||
<MenubarItem inset>Hide Sidebar</MenubarItem>
|
||||
</MenubarContent>
|
||||
</MenubarMenu>
|
||||
<MenubarMenu>
|
||||
<MenubarTrigger>Profiles</MenubarTrigger>
|
||||
<MenubarContent>
|
||||
<MenubarRadioGroup value="benoit">
|
||||
<MenubarRadioItem value="andy">Andy</MenubarRadioItem>
|
||||
<MenubarRadioItem value="benoit">Benoit</MenubarRadioItem>
|
||||
<MenubarRadioItem value="Luis">Luis</MenubarRadioItem>
|
||||
</MenubarRadioGroup>
|
||||
<MenubarSeparator />
|
||||
<MenubarItem inset>Edit...</MenubarItem>
|
||||
<MenubarSeparator />
|
||||
<MenubarItem inset>Add Profile...</MenubarItem>
|
||||
</MenubarContent>
|
||||
</MenubarMenu>
|
||||
<MenubarMenu>
|
||||
<MenubarTrigger>More</MenubarTrigger>
|
||||
<MenubarContent>
|
||||
<MenubarGroup>
|
||||
<MenubarItem>
|
||||
<SettingsIcon />
|
||||
Settings
|
||||
</MenubarItem>
|
||||
<MenubarItem>
|
||||
<HelpCircleIcon />
|
||||
Help
|
||||
</MenubarItem>
|
||||
<MenubarSeparator />
|
||||
<MenubarItem variant="destructive">
|
||||
<Trash2Icon />
|
||||
Delete
|
||||
</MenubarItem>
|
||||
</MenubarGroup>
|
||||
</MenubarContent>
|
||||
</MenubarMenu>
|
||||
</Menubar>
|
||||
)
|
||||
}
|
||||
34
apps/v4/components/mode-switcher.tsx
Normal file
34
apps/v4/components/mode-switcher.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { MoonIcon, SunIcon } from "lucide-react"
|
||||
import { useTheme } from "next-themes"
|
||||
|
||||
import { META_THEME_COLORS, useMetaColor } from "@/hooks/use-meta-color"
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
|
||||
export function ModeSwitcher() {
|
||||
const { setTheme, resolvedTheme } = useTheme()
|
||||
const { setMetaColor } = useMetaColor()
|
||||
|
||||
const toggleTheme = React.useCallback(() => {
|
||||
setTheme(resolvedTheme === "dark" ? "light" : "dark")
|
||||
setMetaColor(
|
||||
resolvedTheme === "dark"
|
||||
? META_THEME_COLORS.light
|
||||
: META_THEME_COLORS.dark
|
||||
)
|
||||
}, [resolvedTheme, setTheme, setMetaColor])
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="group/toggle h-8 w-8 px-0"
|
||||
onClick={toggleTheme}
|
||||
>
|
||||
<SunIcon className="hidden [html.dark_&]:block" />
|
||||
<MoonIcon className="hidden [html.light_&]:block" />
|
||||
<span className="sr-only">Toggle theme</span>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
27
apps/v4/components/mode-toggle.tsx
Normal file
27
apps/v4/components/mode-toggle.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { MoonIcon, SunIcon } from "lucide-react"
|
||||
import { useTheme } from "next-themes"
|
||||
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
|
||||
export function ModeToggle() {
|
||||
const { setTheme, resolvedTheme } = useTheme()
|
||||
|
||||
const toggleTheme = React.useCallback(() => {
|
||||
setTheme(resolvedTheme === "dark" ? "light" : "dark")
|
||||
}, [resolvedTheme, setTheme])
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="group/toggle h-8 w-8 px-0"
|
||||
onClick={toggleTheme}
|
||||
>
|
||||
<SunIcon className="hidden [html.dark_&]:block" />
|
||||
<MoonIcon className="hidden [html.light_&]:block" />
|
||||
<span className="sr-only">Toggle theme</span>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
37
apps/v4/components/nav-header.tsx
Normal file
37
apps/v4/components/nav-header.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
"use client"
|
||||
|
||||
import Link from "next/link"
|
||||
import { usePathname } from "next/navigation"
|
||||
|
||||
import {
|
||||
NavigationMenu,
|
||||
NavigationMenuItem,
|
||||
NavigationMenuLink,
|
||||
NavigationMenuList,
|
||||
} from "@/registry/new-york-v4/ui/navigation-menu"
|
||||
|
||||
export function NavHeader() {
|
||||
const pathname = usePathname()
|
||||
|
||||
return (
|
||||
<NavigationMenu>
|
||||
<NavigationMenuList className="gap-2 *:data-[slot=navigation-menu-item]:h-7 **:data-[slot=navigation-menu-link]:py-1 **:data-[slot=navigation-menu-link]:font-medium">
|
||||
<NavigationMenuItem>
|
||||
<NavigationMenuLink asChild data-active={pathname === "/"}>
|
||||
<Link href="/">Home</Link>
|
||||
</NavigationMenuLink>
|
||||
</NavigationMenuItem>
|
||||
<NavigationMenuItem>
|
||||
<NavigationMenuLink asChild data-active={pathname === "/charts"}>
|
||||
<Link href="/charts">Charts</Link>
|
||||
</NavigationMenuLink>
|
||||
</NavigationMenuItem>
|
||||
<NavigationMenuItem>
|
||||
<NavigationMenuLink asChild data-active={pathname === "/forms"}>
|
||||
<Link href="/forms">Forms</Link>
|
||||
</NavigationMenuLink>
|
||||
</NavigationMenuItem>
|
||||
</NavigationMenuList>
|
||||
</NavigationMenu>
|
||||
)
|
||||
}
|
||||
114
apps/v4/components/nav-user.tsx
Normal file
114
apps/v4/components/nav-user.tsx
Normal file
@@ -0,0 +1,114 @@
|
||||
"use client"
|
||||
|
||||
import {
|
||||
BadgeCheck,
|
||||
Bell,
|
||||
ChevronsUpDown,
|
||||
CreditCard,
|
||||
LogOut,
|
||||
Sparkles,
|
||||
} from "lucide-react"
|
||||
|
||||
import {
|
||||
Avatar,
|
||||
AvatarFallback,
|
||||
AvatarImage,
|
||||
} from "@/registry/new-york-v4/ui/avatar"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/registry/new-york-v4/ui/dropdown-menu"
|
||||
import {
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
useSidebar,
|
||||
} from "@/registry/new-york-v4/ui/sidebar"
|
||||
|
||||
export function NavUser({
|
||||
user,
|
||||
}: {
|
||||
user: {
|
||||
name: string
|
||||
email: string
|
||||
avatar: string
|
||||
}
|
||||
}) {
|
||||
const { isMobile } = useSidebar()
|
||||
|
||||
return (
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<SidebarMenuButton
|
||||
size="lg"
|
||||
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
||||
>
|
||||
<Avatar className="h-8 w-8 rounded-lg">
|
||||
<AvatarImage src={user.avatar} alt={user.name} />
|
||||
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||
<span className="truncate font-semibold">{user.name}</span>
|
||||
<span className="truncate text-xs">{user.email}</span>
|
||||
</div>
|
||||
<ChevronsUpDown className="ml-auto size-4" />
|
||||
</SidebarMenuButton>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
className="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg"
|
||||
side={isMobile ? "bottom" : "right"}
|
||||
align="end"
|
||||
sideOffset={4}
|
||||
>
|
||||
<DropdownMenuLabel className="p-0 font-normal">
|
||||
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
||||
<Avatar className="h-8 w-8 rounded-lg">
|
||||
<AvatarImage src={user.avatar} alt={user.name} />
|
||||
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||
<span className="truncate font-semibold">{user.name}</span>
|
||||
<span className="truncate text-xs">{user.email}</span>
|
||||
</div>
|
||||
</div>
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem>
|
||||
<Sparkles />
|
||||
Upgrade to Pro
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem>
|
||||
<BadgeCheck />
|
||||
Account
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<CreditCard />
|
||||
Billing
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<Bell />
|
||||
Notifications
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>
|
||||
<LogOut />
|
||||
Log out
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
)
|
||||
}
|
||||
229
apps/v4/components/navigation-menu-demo.tsx
Normal file
229
apps/v4/components/navigation-menu-demo.tsx
Normal file
@@ -0,0 +1,229 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import Link from "next/link"
|
||||
import { CircleCheckIcon, CircleHelpIcon, CircleIcon } from "lucide-react"
|
||||
|
||||
import {
|
||||
NavigationMenu,
|
||||
NavigationMenuContent,
|
||||
NavigationMenuItem,
|
||||
NavigationMenuLink,
|
||||
NavigationMenuList,
|
||||
NavigationMenuTrigger,
|
||||
navigationMenuTriggerStyle,
|
||||
} from "@/registry/new-york-v4/ui/navigation-menu"
|
||||
|
||||
const components: { title: string; href: string; description: string }[] = [
|
||||
{
|
||||
title: "Alert Dialog",
|
||||
href: "/docs/primitives/alert-dialog",
|
||||
description:
|
||||
"A modal dialog that interrupts the user with important content and expects a response.",
|
||||
},
|
||||
{
|
||||
title: "Hover Card",
|
||||
href: "/docs/primitives/hover-card",
|
||||
description:
|
||||
"For sighted users to preview content available behind a link.",
|
||||
},
|
||||
{
|
||||
title: "Progress",
|
||||
href: "/docs/primitives/progress",
|
||||
description:
|
||||
"Displays an indicator showing the completion progress of a task, typically displayed as a progress bar.",
|
||||
},
|
||||
{
|
||||
title: "Scroll-area",
|
||||
href: "/docs/primitives/scroll-area",
|
||||
description: "Visually or semantically separates content.",
|
||||
},
|
||||
{
|
||||
title: "Tabs",
|
||||
href: "/docs/primitives/tabs",
|
||||
description:
|
||||
"A set of layered sections of content—known as tab panels—that are displayed one at a time.",
|
||||
},
|
||||
{
|
||||
title: "Tooltip",
|
||||
href: "/docs/primitives/tooltip",
|
||||
description:
|
||||
"A popup that displays information related to an element when the element receives keyboard focus or the mouse hovers over it.",
|
||||
},
|
||||
]
|
||||
|
||||
export function NavigationMenuDemo() {
|
||||
return (
|
||||
<div className="hidden w-full flex-col items-center justify-center gap-6 @xl:flex">
|
||||
<NavigationMenu>
|
||||
<NavigationMenuList>
|
||||
<NavigationMenuItem>
|
||||
<NavigationMenuTrigger>Getting started</NavigationMenuTrigger>
|
||||
<NavigationMenuContent>
|
||||
<ul className="grid gap-2 md:w-[400px] lg:w-[500px] lg:grid-cols-[.75fr_1fr]">
|
||||
<li className="row-span-3">
|
||||
<NavigationMenuLink asChild>
|
||||
<a
|
||||
className="from-muted/50 to-muted flex h-full w-full flex-col justify-end rounded-md bg-linear-to-b p-6 no-underline outline-hidden select-none focus:shadow-md"
|
||||
href="/"
|
||||
>
|
||||
<div className="mt-4 mb-2 text-lg font-medium">
|
||||
shadcn/ui
|
||||
</div>
|
||||
<p className="text-muted-foreground text-sm leading-tight">
|
||||
Beautifully designed components built with Tailwind CSS.
|
||||
</p>
|
||||
</a>
|
||||
</NavigationMenuLink>
|
||||
</li>
|
||||
<ListItem href="/docs" title="Introduction">
|
||||
Re-usable components built using Radix UI and Tailwind CSS.
|
||||
</ListItem>
|
||||
<ListItem href="/docs/installation" title="Installation">
|
||||
How to install dependencies and structure your app.
|
||||
</ListItem>
|
||||
<ListItem href="/docs/primitives/typography" title="Typography">
|
||||
Styles for headings, paragraphs, lists...etc
|
||||
</ListItem>
|
||||
</ul>
|
||||
</NavigationMenuContent>
|
||||
</NavigationMenuItem>
|
||||
<NavigationMenuItem>
|
||||
<NavigationMenuTrigger>Components</NavigationMenuTrigger>
|
||||
<NavigationMenuContent>
|
||||
<ul className="grid w-[400px] gap-2 md:w-[500px] md:grid-cols-2 lg:w-[600px]">
|
||||
{components.map((component) => (
|
||||
<ListItem
|
||||
key={component.title}
|
||||
title={component.title}
|
||||
href={component.href}
|
||||
>
|
||||
{component.description}
|
||||
</ListItem>
|
||||
))}
|
||||
</ul>
|
||||
</NavigationMenuContent>
|
||||
</NavigationMenuItem>
|
||||
<NavigationMenuItem>
|
||||
<NavigationMenuLink
|
||||
asChild
|
||||
className={navigationMenuTriggerStyle()}
|
||||
>
|
||||
<Link href="/docs">Documentation</Link>
|
||||
</NavigationMenuLink>
|
||||
</NavigationMenuItem>
|
||||
</NavigationMenuList>
|
||||
</NavigationMenu>
|
||||
<NavigationMenu viewport={false}>
|
||||
<NavigationMenuList>
|
||||
<NavigationMenuItem>
|
||||
<NavigationMenuLink
|
||||
asChild
|
||||
className={navigationMenuTriggerStyle()}
|
||||
>
|
||||
<Link href="/docs">Documentation</Link>
|
||||
</NavigationMenuLink>
|
||||
</NavigationMenuItem>
|
||||
<NavigationMenuItem>
|
||||
<NavigationMenuTrigger>List</NavigationMenuTrigger>
|
||||
<NavigationMenuContent>
|
||||
<ul className="grid w-[300px] gap-4">
|
||||
<li>
|
||||
<NavigationMenuLink asChild>
|
||||
<Link href="#">
|
||||
<div className="font-medium">Components</div>
|
||||
<div className="text-muted-foreground">
|
||||
Browse all components in the library.
|
||||
</div>
|
||||
</Link>
|
||||
</NavigationMenuLink>
|
||||
<NavigationMenuLink asChild>
|
||||
<Link href="#">
|
||||
<div className="font-medium">Documentation</div>
|
||||
<div className="text-muted-foreground">
|
||||
Learn how to use the library.
|
||||
</div>
|
||||
</Link>
|
||||
</NavigationMenuLink>
|
||||
<NavigationMenuLink asChild>
|
||||
<Link href="#">
|
||||
<div className="font-medium">Blog</div>
|
||||
<div className="text-muted-foreground">
|
||||
Read our latest blog posts.
|
||||
</div>
|
||||
</Link>
|
||||
</NavigationMenuLink>
|
||||
</li>
|
||||
</ul>
|
||||
</NavigationMenuContent>
|
||||
</NavigationMenuItem>
|
||||
<NavigationMenuItem>
|
||||
<NavigationMenuTrigger>Simple List</NavigationMenuTrigger>
|
||||
<NavigationMenuContent>
|
||||
<ul className="grid w-[200px] gap-4">
|
||||
<li>
|
||||
<NavigationMenuLink asChild>
|
||||
<Link href="#">Components</Link>
|
||||
</NavigationMenuLink>
|
||||
<NavigationMenuLink asChild>
|
||||
<Link href="#">Documentation</Link>
|
||||
</NavigationMenuLink>
|
||||
<NavigationMenuLink asChild>
|
||||
<Link href="#">Blocks</Link>
|
||||
</NavigationMenuLink>
|
||||
</li>
|
||||
</ul>
|
||||
</NavigationMenuContent>
|
||||
</NavigationMenuItem>
|
||||
<NavigationMenuItem>
|
||||
<NavigationMenuTrigger>With Icon</NavigationMenuTrigger>
|
||||
<NavigationMenuContent>
|
||||
<ul className="grid w-[200px] gap-4">
|
||||
<li>
|
||||
<NavigationMenuLink asChild>
|
||||
<Link href="#" className="flex-row items-center gap-2">
|
||||
<CircleHelpIcon />
|
||||
Backlog
|
||||
</Link>
|
||||
</NavigationMenuLink>
|
||||
<NavigationMenuLink asChild>
|
||||
<Link href="#" className="flex-row items-center gap-2">
|
||||
<CircleIcon />
|
||||
To Do
|
||||
</Link>
|
||||
</NavigationMenuLink>
|
||||
<NavigationMenuLink asChild>
|
||||
<Link href="#" className="flex-row items-center gap-2">
|
||||
<CircleCheckIcon />
|
||||
Done
|
||||
</Link>
|
||||
</NavigationMenuLink>
|
||||
</li>
|
||||
</ul>
|
||||
</NavigationMenuContent>
|
||||
</NavigationMenuItem>
|
||||
</NavigationMenuList>
|
||||
</NavigationMenu>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function ListItem({
|
||||
title,
|
||||
children,
|
||||
href,
|
||||
...props
|
||||
}: React.ComponentPropsWithoutRef<"li"> & { href: string }) {
|
||||
return (
|
||||
<li {...props}>
|
||||
<NavigationMenuLink asChild>
|
||||
<Link href={href}>
|
||||
<div className="text-sm leading-none font-medium">{title}</div>
|
||||
<p className="text-muted-foreground line-clamp-2 text-sm leading-snug">
|
||||
{children}
|
||||
</p>
|
||||
</Link>
|
||||
</NavigationMenuLink>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
40
apps/v4/components/pagination-demo.tsx
Normal file
40
apps/v4/components/pagination-demo.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import {
|
||||
Pagination,
|
||||
PaginationContent,
|
||||
PaginationEllipsis,
|
||||
PaginationItem,
|
||||
PaginationLink,
|
||||
PaginationNext,
|
||||
PaginationPrevious,
|
||||
} from "@/registry/new-york-v4/ui/pagination"
|
||||
|
||||
export function PaginationDemo() {
|
||||
return (
|
||||
<div className="flex flex-col gap-6">
|
||||
<Pagination>
|
||||
<PaginationContent>
|
||||
<PaginationItem>
|
||||
<PaginationPrevious href="#" />
|
||||
</PaginationItem>
|
||||
<PaginationItem>
|
||||
<PaginationLink href="#">1</PaginationLink>
|
||||
</PaginationItem>
|
||||
<PaginationItem>
|
||||
<PaginationLink href="#" isActive>
|
||||
2
|
||||
</PaginationLink>
|
||||
</PaginationItem>
|
||||
<PaginationItem>
|
||||
<PaginationLink href="#">3</PaginationLink>
|
||||
</PaginationItem>
|
||||
<PaginationItem>
|
||||
<PaginationEllipsis />
|
||||
</PaginationItem>
|
||||
<PaginationItem>
|
||||
<PaginationNext href="#" />
|
||||
</PaginationItem>
|
||||
</PaginationContent>
|
||||
</Pagination>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
62
apps/v4/components/popover-demo.tsx
Normal file
62
apps/v4/components/popover-demo.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
import { Input } from "@/registry/new-york-v4/ui/input"
|
||||
import { Label } from "@/registry/new-york-v4/ui/label"
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/registry/new-york-v4/ui/popover"
|
||||
|
||||
export function PopoverDemo() {
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant="outline">Open popover</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-80" align="start">
|
||||
<div className="grid gap-4">
|
||||
<div className="grid gap-1.5">
|
||||
<h4 className="leading-none font-medium">Dimensions</h4>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
Set the dimensions for the layer.
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<div className="grid grid-cols-3 items-center gap-4">
|
||||
<Label htmlFor="width">Width</Label>
|
||||
<Input
|
||||
id="width"
|
||||
defaultValue="100%"
|
||||
className="col-span-2 h-8"
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-3 items-center gap-4">
|
||||
<Label htmlFor="maxWidth">Max. width</Label>
|
||||
<Input
|
||||
id="maxWidth"
|
||||
defaultValue="300px"
|
||||
className="col-span-2 h-8"
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-3 items-center gap-4">
|
||||
<Label htmlFor="height">Height</Label>
|
||||
<Input
|
||||
id="height"
|
||||
defaultValue="25px"
|
||||
className="col-span-2 h-8"
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-3 items-center gap-4">
|
||||
<Label htmlFor="maxHeight">Max. height</Label>
|
||||
<Input
|
||||
id="maxHeight"
|
||||
defaultValue="none"
|
||||
className="col-span-2 h-8"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
16
apps/v4/components/progress-demo.tsx
Normal file
16
apps/v4/components/progress-demo.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
|
||||
import { Progress } from "@/registry/new-york-v4/ui/progress"
|
||||
|
||||
export function ProgressDemo() {
|
||||
const [progress, setProgress] = React.useState(13)
|
||||
|
||||
React.useEffect(() => {
|
||||
const timer = setTimeout(() => setProgress(66), 500)
|
||||
return () => clearTimeout(timer)
|
||||
}, [])
|
||||
|
||||
return <Progress value={progress} className="w-[60%]" />
|
||||
}
|
||||
62
apps/v4/components/radio-group-demo.tsx
Normal file
62
apps/v4/components/radio-group-demo.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
import { Label } from "@/registry/new-york-v4/ui/label"
|
||||
import {
|
||||
RadioGroup,
|
||||
RadioGroupItem,
|
||||
} from "@/registry/new-york-v4/ui/radio-group"
|
||||
|
||||
const plans = [
|
||||
{
|
||||
id: "starter",
|
||||
name: "Starter Plan",
|
||||
description:
|
||||
"Perfect for small businesses getting started with our platform",
|
||||
price: "$10",
|
||||
},
|
||||
{
|
||||
id: "pro",
|
||||
name: "Pro Plan",
|
||||
description: "Advanced features for growing businesses with higher demands",
|
||||
price: "$20",
|
||||
},
|
||||
] as const
|
||||
|
||||
export function RadioGroupDemo() {
|
||||
return (
|
||||
<div className="flex flex-col gap-6">
|
||||
<RadioGroup defaultValue="comfortable">
|
||||
<div className="flex items-center gap-3">
|
||||
<RadioGroupItem value="default" id="r1" />
|
||||
<Label htmlFor="r1">Default</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<RadioGroupItem value="comfortable" id="r2" />
|
||||
<Label htmlFor="r2">Comfortable</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<RadioGroupItem value="compact" id="r3" />
|
||||
<Label htmlFor="r3">Compact</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
<RadioGroup defaultValue="starter" className="max-w-sm">
|
||||
{plans.map((plan) => (
|
||||
<Label
|
||||
className="hover:bg-accent/50 flex items-start gap-3 rounded-lg border p-4 has-[[data-state=checked]]:border-green-600 has-[[data-state=checked]]:bg-green-50 dark:has-[[data-state=checked]]:border-green-900 dark:has-[[data-state=checked]]:bg-green-950"
|
||||
key={plan.id}
|
||||
>
|
||||
<RadioGroupItem
|
||||
value={plan.id}
|
||||
id={plan.name}
|
||||
className="shadow-none data-[state=checked]:border-green-600 data-[state=checked]:bg-green-600 *:data-[slot=radio-group-indicator]:[&>svg]:fill-white *:data-[slot=radio-group-indicator]:[&>svg]:stroke-white"
|
||||
/>
|
||||
<div className="grid gap-1 font-normal">
|
||||
<div className="font-medium">{plan.name}</div>
|
||||
<div className="text-muted-foreground leading-snug">
|
||||
{plan.description}
|
||||
</div>
|
||||
</div>
|
||||
</Label>
|
||||
))}
|
||||
</RadioGroup>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
70
apps/v4/components/resizable-demo.tsx
Normal file
70
apps/v4/components/resizable-demo.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
import {
|
||||
ResizableHandle,
|
||||
ResizablePanel,
|
||||
ResizablePanelGroup,
|
||||
} from "@/registry/new-york-v4/ui/resizable"
|
||||
|
||||
export function ResizableDemo() {
|
||||
return (
|
||||
<div className="flex w-full flex-col gap-6">
|
||||
<ResizablePanelGroup
|
||||
direction="horizontal"
|
||||
className="max-w-md rounded-lg border md:min-w-[450px]"
|
||||
>
|
||||
<ResizablePanel defaultSize={50}>
|
||||
<div className="flex h-[200px] items-center justify-center p-6">
|
||||
<span className="font-semibold">One</span>
|
||||
</div>
|
||||
</ResizablePanel>
|
||||
<ResizableHandle />
|
||||
<ResizablePanel defaultSize={50}>
|
||||
<ResizablePanelGroup direction="vertical">
|
||||
<ResizablePanel defaultSize={25}>
|
||||
<div className="flex h-full items-center justify-center p-6">
|
||||
<span className="font-semibold">Two</span>
|
||||
</div>
|
||||
</ResizablePanel>
|
||||
<ResizableHandle />
|
||||
<ResizablePanel defaultSize={75}>
|
||||
<div className="flex h-full items-center justify-center p-6">
|
||||
<span className="font-semibold">Three</span>
|
||||
</div>
|
||||
</ResizablePanel>
|
||||
</ResizablePanelGroup>
|
||||
</ResizablePanel>
|
||||
</ResizablePanelGroup>
|
||||
<ResizablePanelGroup
|
||||
direction="horizontal"
|
||||
className="min-h-[200px] max-w-md rounded-lg border md:min-w-[450px]"
|
||||
>
|
||||
<ResizablePanel defaultSize={25}>
|
||||
<div className="flex h-full items-center justify-center p-6">
|
||||
<span className="font-semibold">Sidebar</span>
|
||||
</div>
|
||||
</ResizablePanel>
|
||||
<ResizableHandle withHandle />
|
||||
<ResizablePanel defaultSize={75}>
|
||||
<div className="flex h-full items-center justify-center p-6">
|
||||
<span className="font-semibold">Content</span>
|
||||
</div>
|
||||
</ResizablePanel>
|
||||
</ResizablePanelGroup>
|
||||
<ResizablePanelGroup
|
||||
direction="vertical"
|
||||
className="min-h-[200px] max-w-md rounded-lg border md:min-w-[450px]"
|
||||
>
|
||||
<ResizablePanel defaultSize={25}>
|
||||
<div className="flex h-full items-center justify-center p-6">
|
||||
<span className="font-semibold">Header</span>
|
||||
</div>
|
||||
</ResizablePanel>
|
||||
<ResizableHandle />
|
||||
<ResizablePanel defaultSize={75}>
|
||||
<div className="flex h-full items-center justify-center p-6">
|
||||
<span className="font-semibold">Content</span>
|
||||
</div>
|
||||
</ResizablePanel>
|
||||
</ResizablePanelGroup>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
80
apps/v4/components/scroll-area-demo.tsx
Normal file
80
apps/v4/components/scroll-area-demo.tsx
Normal file
@@ -0,0 +1,80 @@
|
||||
import * as React from "react"
|
||||
import Image from "next/image"
|
||||
|
||||
import { ScrollArea, ScrollBar } from "@/registry/new-york-v4/ui/scroll-area"
|
||||
import { Separator } from "@/registry/new-york-v4/ui/separator"
|
||||
|
||||
export function ScrollAreaDemo() {
|
||||
return (
|
||||
<div className="flex flex-col gap-6">
|
||||
<ScrollAreaVertical />
|
||||
<ScrollAreaHorizontalDemo />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const tags = Array.from({ length: 50 }).map(
|
||||
(_, i, a) => `v1.2.0-beta.${a.length - i}`
|
||||
)
|
||||
|
||||
function ScrollAreaVertical() {
|
||||
return (
|
||||
<div className="flex flex-col gap-6">
|
||||
<ScrollArea className="h-72 w-48 rounded-md border">
|
||||
<div className="p-4">
|
||||
<h4 className="mb-4 text-sm leading-none font-medium">Tags</h4>
|
||||
{tags.map((tag) => (
|
||||
<React.Fragment key={tag}>
|
||||
<div className="text-sm">{tag}</div>
|
||||
<Separator className="my-2" />
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const works = [
|
||||
{
|
||||
artist: "Ornella Binni",
|
||||
art: "https://images.unsplash.com/photo-1465869185982-5a1a7522cbcb?auto=format&fit=crop&w=300&q=80",
|
||||
},
|
||||
{
|
||||
artist: "Tom Byrom",
|
||||
art: "https://images.unsplash.com/photo-1548516173-3cabfa4607e9?auto=format&fit=crop&w=300&q=80",
|
||||
},
|
||||
{
|
||||
artist: "Vladimir Malyav",
|
||||
art: "https://images.unsplash.com/photo-1494337480532-3725c85fd2ab?auto=format&fit=crop&w=300&q=80",
|
||||
},
|
||||
] as const
|
||||
|
||||
function ScrollAreaHorizontalDemo() {
|
||||
return (
|
||||
<ScrollArea className="w-full max-w-96 rounded-md border p-4">
|
||||
<div className="flex gap-4">
|
||||
{works.map((artwork) => (
|
||||
<figure key={artwork.artist} className="shrink-0">
|
||||
<div className="overflow-hidden rounded-md">
|
||||
<Image
|
||||
src={artwork.art}
|
||||
alt={`Photo by ${artwork.artist}`}
|
||||
className="aspect-[3/4] h-fit w-fit object-cover"
|
||||
width={300}
|
||||
height={400}
|
||||
/>
|
||||
</div>
|
||||
<figcaption className="text-muted-foreground pt-2 text-xs">
|
||||
Photo by{" "}
|
||||
<span className="text-foreground font-semibold">
|
||||
{artwork.artist}
|
||||
</span>
|
||||
</figcaption>
|
||||
</figure>
|
||||
))}
|
||||
</div>
|
||||
<ScrollBar orientation="horizontal" />
|
||||
</ScrollArea>
|
||||
)
|
||||
}
|
||||
93
apps/v4/components/select-demo.tsx
Normal file
93
apps/v4/components/select-demo.tsx
Normal file
@@ -0,0 +1,93 @@
|
||||
import * as React from "react"
|
||||
import {
|
||||
ChartBarIcon,
|
||||
ChartLineIcon,
|
||||
ChartPieIcon,
|
||||
CircleDashed,
|
||||
} from "lucide-react"
|
||||
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectLabel,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/registry/new-york-v4/ui/select"
|
||||
|
||||
export function SelectDemo() {
|
||||
return (
|
||||
<div className="flex flex-wrap items-start gap-4">
|
||||
<Select>
|
||||
<SelectTrigger className="w-[180px]">
|
||||
<SelectValue placeholder="Select a fruit" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectLabel>Fruits</SelectLabel>
|
||||
<SelectItem value="apple">Apple</SelectItem>
|
||||
<SelectItem value="banana">Banana</SelectItem>
|
||||
<SelectItem value="blueberry">Blueberry</SelectItem>
|
||||
<SelectItem value="grapes" disabled>
|
||||
Grapes
|
||||
</SelectItem>
|
||||
<SelectItem value="pineapple">Pineapple</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Select>
|
||||
<SelectTrigger className="w-[180px]">
|
||||
<SelectValue placeholder="Large List" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{Array.from({ length: 100 }).map((_, i) => (
|
||||
<SelectItem key={i} value={`item-${i}`}>
|
||||
Item {i}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Select disabled>
|
||||
<SelectTrigger className="w-[180px]">
|
||||
<SelectValue placeholder="Disabled" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="apple">Apple</SelectItem>
|
||||
<SelectItem value="banana">Banana</SelectItem>
|
||||
<SelectItem value="blueberry">Blueberry</SelectItem>
|
||||
<SelectItem value="grapes" disabled>
|
||||
Grapes
|
||||
</SelectItem>
|
||||
<SelectItem value="pineapple">Pineapple</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Select>
|
||||
<SelectTrigger className="w-[180px]">
|
||||
<SelectValue
|
||||
placeholder={
|
||||
<>
|
||||
<CircleDashed className="text-muted-foreground" />
|
||||
With Icon
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="line">
|
||||
<ChartLineIcon />
|
||||
Line
|
||||
</SelectItem>
|
||||
<SelectItem value="bar">
|
||||
<ChartBarIcon />
|
||||
Bar
|
||||
</SelectItem>
|
||||
<SelectItem value="pie">
|
||||
<ChartPieIcon />
|
||||
Pie
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
22
apps/v4/components/separator-demo.tsx
Normal file
22
apps/v4/components/separator-demo.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Separator } from "@/registry/new-york-v4/ui/separator"
|
||||
|
||||
export function SeparatorDemo() {
|
||||
return (
|
||||
<div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="text-sm leading-none font-medium">Tailwind CSS</div>
|
||||
<div className="text-muted-foreground text-sm">
|
||||
A utility-first CSS framework.
|
||||
</div>
|
||||
</div>
|
||||
<Separator className="my-4" />
|
||||
<div className="flex h-5 items-center gap-4 text-sm">
|
||||
<div>Blog</div>
|
||||
<Separator orientation="vertical" />
|
||||
<div>Docs</div>
|
||||
<Separator orientation="vertical" />
|
||||
<div>Source</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
95
apps/v4/components/sheet-demo.tsx
Normal file
95
apps/v4/components/sheet-demo.tsx
Normal file
@@ -0,0 +1,95 @@
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
import { Input } from "@/registry/new-york-v4/ui/input"
|
||||
import { Label } from "@/registry/new-york-v4/ui/label"
|
||||
import {
|
||||
Sheet,
|
||||
SheetClose,
|
||||
SheetContent,
|
||||
SheetDescription,
|
||||
SheetFooter,
|
||||
SheetHeader,
|
||||
SheetTitle,
|
||||
SheetTrigger,
|
||||
} from "@/registry/new-york-v4/ui/sheet"
|
||||
|
||||
const SHEET_SIDES = ["top", "right", "bottom", "left"] as const
|
||||
|
||||
export function SheetDemo() {
|
||||
return (
|
||||
<div className="flex flex-col gap-6 md:flex-row">
|
||||
<Sheet>
|
||||
<SheetTrigger asChild>
|
||||
<Button variant="outline">Open</Button>
|
||||
</SheetTrigger>
|
||||
<SheetContent>
|
||||
<SheetHeader>
|
||||
<SheetTitle>Edit profile</SheetTitle>
|
||||
<SheetDescription>
|
||||
Make changes to your profile here. Click save when you're
|
||||
done.
|
||||
</SheetDescription>
|
||||
</SheetHeader>
|
||||
<div className="grid flex-1 auto-rows-min gap-6 px-4">
|
||||
<div className="grid gap-3">
|
||||
<Label htmlFor="sheet-demo-name">Name</Label>
|
||||
<Input id="sheet-demo-name" defaultValue="Pedro Duarte" />
|
||||
</div>
|
||||
<div className="grid gap-3">
|
||||
<Label htmlFor="sheet-demo-username">Username</Label>
|
||||
<Input id="sheet-demo-username" defaultValue="@peduarte" />
|
||||
</div>
|
||||
</div>
|
||||
<SheetFooter>
|
||||
<Button type="submit">Save changes</Button>
|
||||
<SheetClose asChild>
|
||||
<Button variant="outline">Close</Button>
|
||||
</SheetClose>
|
||||
</SheetFooter>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
<div className="flex gap-2">
|
||||
{SHEET_SIDES.map((side) => (
|
||||
<Sheet key={side}>
|
||||
<SheetTrigger asChild>
|
||||
<Button variant="outline" className="capitalize">
|
||||
{side}
|
||||
</Button>
|
||||
</SheetTrigger>
|
||||
<SheetContent side={side}>
|
||||
<SheetHeader>
|
||||
<SheetTitle>Edit profile</SheetTitle>
|
||||
<SheetDescription>
|
||||
Make changes to your profile here. Click save when you're
|
||||
done.
|
||||
</SheetDescription>
|
||||
</SheetHeader>
|
||||
<div className="overflow-y-auto px-4 text-sm">
|
||||
<h4 className="mb-4 text-lg leading-none font-medium">
|
||||
Lorem Ipsum
|
||||
</h4>
|
||||
{Array.from({ length: 10 }).map((_, index) => (
|
||||
<p key={index} className="mb-4 leading-normal">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed
|
||||
do eiusmod tempor incididunt ut labore et dolore magna
|
||||
aliqua. Ut enim ad minim veniam, quis nostrud exercitation
|
||||
ullamco laboris nisi ut aliquip ex ea commodo consequat.
|
||||
Duis aute irure dolor in reprehenderit in voluptate velit
|
||||
esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
|
||||
occaecat cupidatat non proident, sunt in culpa qui officia
|
||||
deserunt mollit anim id est laborum.
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
<SheetFooter>
|
||||
<Button type="submit">Save changes</Button>
|
||||
<SheetClose asChild>
|
||||
<Button variant="outline">Cancel</Button>
|
||||
</SheetClose>
|
||||
</SheetFooter>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
29
apps/v4/components/skeleton-demo.tsx
Normal file
29
apps/v4/components/skeleton-demo.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Card, CardContent, CardHeader } from "@/registry/new-york-v4/ui/card"
|
||||
import { Skeleton } from "@/registry/new-york-v4/ui/skeleton"
|
||||
|
||||
export function SkeletonDemo() {
|
||||
return (
|
||||
<div className="flex w-full flex-wrap items-start gap-4">
|
||||
<div className="flex items-center gap-4">
|
||||
<Skeleton className="size-10 shrink-0 rounded-full" />
|
||||
<div className="grid gap-2">
|
||||
<Skeleton className="h-4 w-[150px]" />
|
||||
<Skeleton className="h-4 w-[100px]" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex w-full flex-wrap items-start gap-4">
|
||||
{Array.from({ length: 3 }).map((_, index) => (
|
||||
<Card key={index} className="w-full @md:w-auto @md:min-w-sm">
|
||||
<CardHeader>
|
||||
<Skeleton className="h-4 w-2/3" />
|
||||
<Skeleton className="h-4 w-1/2" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Skeleton className="aspect-square w-full" />
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
44
apps/v4/components/slider-demo.tsx
Normal file
44
apps/v4/components/slider-demo.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
|
||||
import { Label } from "@/registry/new-york-v4/ui/label"
|
||||
import { Slider } from "@/registry/new-york-v4/ui/slider"
|
||||
|
||||
export function SliderDemo() {
|
||||
return (
|
||||
<div className="flex w-full max-w-sm flex-col flex-wrap gap-6 md:flex-row">
|
||||
<Slider defaultValue={[50]} max={100} step={1} />
|
||||
<Slider defaultValue={[25, 50]} max={100} step={1} />
|
||||
<Slider defaultValue={[10, 20]} max={100} step={10} />
|
||||
<div className="flex w-full items-center gap-6">
|
||||
<Slider defaultValue={[50]} max={100} step={1} orientation="vertical" />
|
||||
<Slider defaultValue={[25]} max={100} step={1} orientation="vertical" />
|
||||
</div>
|
||||
<SliderControlled />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function SliderControlled() {
|
||||
const [value, setValue] = React.useState([0.3, 0.7])
|
||||
|
||||
return (
|
||||
<div className="grid w-full gap-3">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<Label htmlFor="slider-demo-temperature">Temperature</Label>
|
||||
<span className="text-muted-foreground text-sm">
|
||||
{value.join(", ")}
|
||||
</span>
|
||||
</div>
|
||||
<Slider
|
||||
id="slider-demo-temperature"
|
||||
value={value}
|
||||
onValueChange={setValue}
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.1}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user