Compare commits
498
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
060bae06a7 | ||
|
|
003082e40c | ||
|
|
56a66ad075 | ||
|
|
4b4969b34f | ||
|
|
81b958efa0 | ||
|
|
8610e6cdc9 | ||
|
|
38330de14c | ||
|
|
5910ba3bff | ||
|
|
2e9710d38b | ||
|
|
2f6f8e60d6 | ||
|
|
0ee682af58 | ||
|
|
00a3f11d2c | ||
|
|
e30a6da393 | ||
|
|
d4db559e7a | ||
|
|
e088680532 | ||
|
|
bdd8648b69 | ||
|
|
2bc945221a | ||
|
|
554b426ca5 | ||
|
|
cedcf13f0e | ||
|
|
c3acde590f | ||
|
|
0752bbd5a4 | ||
|
|
04df7a41e5 | ||
|
|
380bf84092 | ||
|
|
39e7fcfe54 | ||
|
|
5ed3b8b95f | ||
|
|
b1390366fb | ||
|
|
dfa53b4be1 | ||
|
|
b226b5e802 | ||
|
|
fa870fdd31 | ||
|
|
d58395a11e | ||
|
|
6d1033c9a8 | ||
|
|
19e9453088 | ||
|
|
8728720857 | ||
|
|
1bad60aff3 | ||
|
|
d639608131 | ||
|
|
1b7c55aebe | ||
|
|
0b712367c2 | ||
|
|
9342b7061b | ||
|
|
224aba0f8b | ||
|
|
bf43384f24 | ||
|
|
1d9a78b844 | ||
|
|
6b43cd4359 | ||
|
|
0e3941860f | ||
|
|
685e0a0517 | ||
|
|
688c0271a0 | ||
|
|
b3b0f868ce | ||
|
|
b8ec4e05a0 | ||
|
|
21ee5e8e34 | ||
|
|
745e02dd6b | ||
|
|
d4d6276b49 | ||
|
|
c05ebaf3a1 | ||
|
|
658a7b99ff | ||
|
|
5b44b877b2 | ||
|
|
e4b20ca8b5 | ||
|
|
d4a956a9d0 | ||
|
|
eb158ccb1d | ||
|
|
55b4ef65b3 | ||
|
|
041ad4b73c | ||
|
|
07f915fb5f | ||
|
|
6d97f63ed7 | ||
|
|
ef076a69af | ||
|
|
2765c669d6 | ||
|
|
c8e8a70b8c | ||
|
|
6d26153de4 | ||
|
|
3969011ccf | ||
|
|
22d3802e9d | ||
|
|
9f112eada8 | ||
|
|
6cce2f4b00 | ||
|
|
4f751b5162 | ||
|
|
a65ec8c488 | ||
|
|
ac40bf996e | ||
|
|
a7ab2b17fd | ||
|
|
6b002b1c87 | ||
|
|
3a830e5cd8 | ||
|
|
c77df02293 | ||
|
|
92324212e3 | ||
|
|
2ca673d214 | ||
|
|
baf1e292c6 | ||
|
|
55da14c98a | ||
|
|
04a81678f5 | ||
|
|
fcea76c0b5 | ||
|
|
c070f58769 | ||
|
|
71569f01f0 | ||
|
|
4afc2aef8b | ||
|
|
ee6a2a66e4 | ||
|
|
eae6a521f1 | ||
|
|
43f7156895 | ||
|
|
463269fc1b | ||
|
|
c601e70a15 | ||
|
|
8b4cab92b3 | ||
|
|
3bebf18c9b | ||
|
|
918a0932e9 | ||
|
|
b049f50708 | ||
|
|
9ef1becb71 | ||
|
|
ae96d58e88 | ||
|
|
e9b45dbc87 | ||
|
|
fc70f5fb97 | ||
|
|
28e800838c | ||
|
|
92268c99e7 | ||
|
|
8a48906d81 | ||
|
|
3230acb906 | ||
|
|
7146ca3208 | ||
|
|
2f02269ddd | ||
|
|
97c6dff18e | ||
|
|
b167865b5c | ||
|
|
30619e16e8 | ||
|
|
df7bd3e4e5 | ||
|
|
93d827f5d9 | ||
|
|
30e190250e | ||
|
|
34c911d54c | ||
|
|
42db7e9ee1 | ||
|
|
521c89b669 | ||
|
|
c868168c89 | ||
|
|
f970d0242f | ||
|
|
be17cbd283 | ||
|
|
078afef155 | ||
|
|
1cf7b42856 | ||
|
|
f84a10b090 | ||
|
|
1e68f46190 | ||
|
|
dddd05909c | ||
|
|
a54250a818 | ||
|
|
a351e7f905 | ||
|
|
5cc4771783 | ||
|
|
844752bb22 | ||
|
|
71de35ae2b | ||
|
|
c52b27651d | ||
|
|
01697d03f0 | ||
|
|
80492d35c4 | ||
|
|
2c8df4de8f | ||
|
|
e1dc13103b | ||
|
|
305ff29e15 | ||
|
|
699d6c7ccd | ||
|
|
025022171d | ||
|
|
620b72a72c | ||
|
|
381b918a6f | ||
|
|
953cf69096 | ||
|
|
74331d8e4c | ||
|
|
2d39b09501 | ||
|
|
dac43918ee | ||
|
|
6ef0417c64 | ||
|
|
71d1c000e9 | ||
|
|
5888f8f040 | ||
|
|
03f7711a93 | ||
|
|
ad2b136589 | ||
|
|
ce059ffd52 | ||
|
|
fb0018d0ef | ||
|
|
7bf336a016 | ||
|
|
69ecfbaabf | ||
|
|
62e0cb32e0 | ||
|
|
33be4e60d0 | ||
|
|
e931126fb7 | ||
|
|
3f383daa20 | ||
|
|
f1c2861895 | ||
|
|
32cb7a10c8 | ||
|
|
255a4b6714 | ||
|
|
c3c9beeed6 | ||
|
|
c4849fda0e | ||
|
|
3617e13629 | ||
|
|
c340d4cb8d | ||
|
|
2c4488c697 | ||
|
|
067fbb8632 | ||
|
|
7f16413e21 | ||
|
|
a8594d7ace | ||
|
|
f5eab00976 | ||
|
|
5a25b5a1db | ||
|
|
1475939c99 | ||
|
|
656136c63b | ||
|
|
e8b55dea2f | ||
|
|
2c3ba53d61 | ||
|
|
1dc59b9416 | ||
|
|
6644864746 | ||
|
|
ec250da734 | ||
|
|
4e2003d586 | ||
|
|
bdf15c367c | ||
|
|
6befef901e | ||
|
|
5876807d18 | ||
|
|
44e2703466 | ||
|
|
8bdb6c7e7e | ||
|
|
eed249c667 | ||
|
|
a141e8e809 | ||
|
|
f3734c7f13 | ||
|
|
6ddef20817 | ||
|
|
eea9f34c27 | ||
|
|
0174252b09 | ||
|
|
e507201b88 | ||
|
|
ffe91ad7aa | ||
|
|
7e36bf87ef | ||
|
|
a045476113 | ||
|
|
41fb5d0d0c | ||
|
|
aa70dcf424 | ||
|
|
9a7d12dadc | ||
|
|
28643253ab | ||
|
|
0d8b52fbcb | ||
|
|
ade52fbdde | ||
|
|
e04550a15f | ||
|
|
ec5a47f3ba | ||
|
|
4965bc8184 | ||
|
|
3ddc214ac9 | ||
|
|
22f1a40bbe | ||
|
|
60f93c664b | ||
|
|
62363a415a | ||
|
|
f5d080742d | ||
|
|
626c29617d | ||
|
|
073a5464a0 | ||
|
|
2a178903b0 | ||
|
|
b77117d22d | ||
|
|
b3b8dc9c9b | ||
|
|
06b3e34794 | ||
|
|
6fe6a0f3e5 | ||
|
|
983bcdc268 | ||
|
|
29316aa6e6 | ||
|
|
e8811721b5 | ||
|
|
0fc3d78af3 | ||
|
|
a2107a5cc3 | ||
|
|
82eadf4497 | ||
|
|
423c792898 | ||
|
|
df150fea37 | ||
|
|
2fe581b038 | ||
|
|
edb4eb2740 | ||
|
|
0d85d6a28f | ||
|
|
b98339ce13 | ||
|
|
8318a509b5 | ||
|
|
938832b1e6 | ||
|
|
87baf9b616 | ||
|
|
ac57ce37f6 | ||
|
|
3e3402dd47 | ||
|
|
ea2cf8f675 | ||
|
|
1f43e32f5b | ||
|
|
d3c5a3d168 | ||
|
|
faa82a3458 | ||
|
|
7139ba4218 | ||
|
|
4764bf0678 | ||
|
|
b69a3e422b | ||
|
|
6f49733ae0 | ||
|
|
e6366c246b | ||
|
|
2fe2230d89 | ||
|
|
29c4e75e16 | ||
|
|
06465876eb | ||
|
|
59271694ee | ||
|
|
4df4ebe974 | ||
|
|
6750912612 | ||
|
|
b57325f399 | ||
|
|
2a24171333 | ||
|
|
0f3f17aaee | ||
|
|
e6df2b68cc | ||
|
|
9ae54783a9 | ||
|
|
9f6c50b394 | ||
|
|
8b5ce8d38b | ||
|
|
a69eec14ff | ||
|
|
2253534199 | ||
|
|
1615fe4441 | ||
|
|
7fb2655873 | ||
|
|
629fad72fd | ||
|
|
d501929505 | ||
|
|
5dd83a3592 | ||
|
|
be36468ca2 | ||
|
|
acb9347f1a | ||
|
|
fa33d8c29b | ||
|
|
bd3f8aa923 | ||
|
|
93dff1f009 | ||
|
|
2afd072409 | ||
|
|
4138b109c5 | ||
|
|
61cb15e8bc | ||
|
|
46d887e0bc | ||
|
|
82a6038c47 | ||
|
|
93f6727762 | ||
|
|
4bc755a791 | ||
|
|
49b0ee601f | ||
|
|
01cb78e57c | ||
|
|
f1ca32d21e | ||
|
|
1b21f86b8b | ||
|
|
c524470cb8 | ||
|
|
b39f9f2289 | ||
|
|
44f34d836b | ||
|
|
c55a5f35d1 | ||
|
|
ee65473a58 | ||
|
|
4dde3e3dd6 | ||
|
|
4ccd893389 | ||
|
|
a7fc90d13f | ||
|
|
67e49fc154 | ||
|
|
cace5fc811 | ||
|
|
3e83add3e2 | ||
|
|
273a4b8648 | ||
|
|
e627e9ef27 | ||
|
|
d69e7d1c77 | ||
|
|
e0f63907ae | ||
|
|
773ca72cf2 | ||
|
|
6d23619a37 | ||
|
|
14a795203c | ||
|
|
ea91af566a | ||
|
|
37011ec60d | ||
|
|
2419da1726 | ||
|
|
976f861b58 | ||
|
|
ac499c81ca | ||
|
|
9fdb8b4a5a | ||
|
|
3a41b988ee | ||
|
|
984327aae4 | ||
|
|
aae29ef081 | ||
|
|
6e0ca7db66 | ||
|
|
63bfec0ddc | ||
|
|
af5fbe035d | ||
|
|
2fc136456a | ||
|
|
14eb7940d6 | ||
|
|
1422312245 | ||
|
|
5cbed4104d | ||
|
|
486b8a8b29 | ||
|
|
db289770c7 | ||
|
|
d8061bb9d5 | ||
|
|
ba478f418e | ||
|
|
0bc659b092 | ||
|
|
25893f20a9 | ||
|
|
0821dbbdad | ||
|
|
a1279e1e29 | ||
|
|
a0721bd513 | ||
|
|
ea0d097b73 | ||
|
|
5bcbb3a8cd | ||
|
|
58c227381e | ||
|
|
7b90a6de5a | ||
|
|
d40a093370 | ||
|
|
867d52d9ce | ||
|
|
31b64724fd | ||
|
|
6ca2f8c755 | ||
|
|
ed09d8dbdd | ||
|
|
0ea786a0da | ||
|
|
61b16cf23f | ||
|
|
10be1a2b7f | ||
|
|
6c7c4fa142 | ||
|
|
fb2dd3b7c7 | ||
|
|
2608696537 | ||
|
|
0add0dc599 | ||
|
|
d5b7f820ad | ||
|
|
56f1392337 | ||
|
|
986fa4790d | ||
|
|
890ae7286c | ||
|
|
dcd14a9030 | ||
|
|
fdf2f76ee3 | ||
|
|
1d2cfb2cfe | ||
|
|
a96fa752e1 | ||
|
|
8415eb5dcd | ||
|
|
2bed0bc8aa | ||
|
|
0c3dd19dc1 | ||
|
|
0d6699aa88 | ||
|
|
449f9a4a05 | ||
|
|
c82fc2113e | ||
|
|
69bf1a66da | ||
|
|
895256e96c | ||
|
|
8417229dd2 | ||
|
|
c522bbaca0 | ||
|
|
9dc5caf06d | ||
|
|
9ac946a319 | ||
|
|
3b2cb3d4ce | ||
|
|
54c8441304 | ||
|
|
fe74c395e4 | ||
|
|
c8547d2163 | ||
|
|
c26a754f91 | ||
|
|
75fa462b0f | ||
|
|
a0b22b70fa | ||
|
|
e9ef254af1 | ||
|
|
8e891fd665 | ||
|
|
2120ebfd9b | ||
|
|
9c6e9b3329 | ||
|
|
4d1b2b68b1 | ||
|
|
cf235b8415 | ||
|
|
997b5a9d05 | ||
|
|
4e6f267218 | ||
|
|
27ecdd28d2 | ||
|
|
57cf7a8f1f | ||
|
|
54e6ae6c2d | ||
|
|
12fc8afe8d | ||
|
|
870e41557a | ||
|
|
b1e53f662b | ||
|
|
efaa553d7a | ||
|
|
9b08529113 | ||
|
|
1b7d3cf9fb | ||
|
|
5385fce6cb | ||
|
|
dc10c7c4c2 | ||
|
|
d94e4584e2 | ||
|
|
9a11c5373f | ||
|
|
24afb7f2dc | ||
|
|
208b3b883f | ||
|
|
207488a6f8 | ||
|
|
7615e85750 | ||
|
|
f3e629003a | ||
|
|
2ff81fe46f | ||
|
|
06c440dc2f | ||
|
|
d20022b63a | ||
|
|
c11ae264ca | ||
|
|
33af758b39 | ||
|
|
435d9ae3a5 | ||
|
|
8f3da9d80b | ||
|
|
4ecde298dd | ||
|
|
5f42bc03f8 | ||
|
|
54d681b03c | ||
|
|
fd9e8796c5 | ||
|
|
98c5593317 | ||
|
|
f7603cfbe0 | ||
|
|
63cd81b10e | ||
|
|
bf5945fb65 | ||
|
|
337344aa82 | ||
|
|
c15f770b1d | ||
|
|
d9c2543da1 | ||
|
|
1cd7240652 | ||
|
|
36e9ed65a1 | ||
|
|
f5a9772c49 | ||
|
|
4d8e0dfda4 | ||
|
|
aa9e51375c | ||
|
|
53007f1ffe | ||
|
|
edf753f374 | ||
|
|
304aa29ac5 | ||
|
|
3750c2135e | ||
|
|
484ddc42fc | ||
|
|
ea0dfbc31d | ||
|
|
9e8a5fb6fc | ||
|
|
140b88d4cf | ||
|
|
46afe7b8cc | ||
|
|
44d056787b | ||
|
|
d023d46047 | ||
|
|
8cce63176d | ||
|
|
9cbb053935 | ||
|
|
023ed1ba67 | ||
|
|
e8253aa242 | ||
|
|
c697c8be0f | ||
|
|
70b33015cb | ||
|
|
8fba9abe33 | ||
|
|
5fabb76f13 | ||
|
|
4ee4d32aec | ||
|
|
ae9c89a2fa | ||
|
|
949afebfcd | ||
|
|
192c085a7e | ||
|
|
9f99b6eae4 | ||
|
|
4ed1436c65 | ||
|
|
14e890c2eb | ||
|
|
3bb6c8b049 | ||
|
|
0582316dce | ||
|
|
a3b7e89d40 | ||
|
|
29628ea924 | ||
|
|
a83f54de8f | ||
|
|
f846cacede | ||
|
|
b8d03cd935 | ||
|
|
28efcab6f8 | ||
|
|
90198bf23f | ||
|
|
f8f7769c75 | ||
|
|
0424b6e631 | ||
|
|
5e377fdb7a | ||
|
|
747efd5598 | ||
|
|
6d084a346b | ||
|
|
60f89f1d55 | ||
|
|
e8494a690c | ||
|
|
ef13c47c9d | ||
|
|
f68eac6c73 | ||
|
|
e56a6f8cf6 | ||
|
|
f3a78041be | ||
|
|
6039340118 | ||
|
|
56fff06199 | ||
|
|
8039be8fab | ||
|
|
66f8f4dd65 | ||
|
|
ea23e448fe | ||
|
|
2744105f68 | ||
|
|
267d906647 | ||
|
|
a9f7866342 | ||
|
|
9fca67935c | ||
|
|
01876333dc | ||
|
|
dc34f05fc4 | ||
|
|
1aa7d52eac | ||
|
|
3d4fd32072 | ||
|
|
dd6d810da1 | ||
|
|
3655c031cb | ||
|
|
8f3cc521a8 | ||
|
|
777ba9a561 | ||
|
|
a5c0b4ce19 | ||
|
|
89a304cde5 | ||
|
|
476efa2864 | ||
|
|
aa939019a1 | ||
|
|
155756e988 | ||
|
|
3b4aedc4be | ||
|
|
7d91d135b7 | ||
|
|
7c509d9792 | ||
|
|
025f3e89d4 | ||
|
|
2f377769c9 | ||
|
|
b10052a65b | ||
|
|
9c12345177 | ||
|
|
9362a2ac1a | ||
|
|
d04164c190 | ||
|
|
f782bcbf43 | ||
|
|
d5f3b7d724 | ||
|
|
c172cb1d4c | ||
|
|
e5b9f9a91c | ||
|
|
a3a224a9ac | ||
|
|
6f7c53fa62 | ||
|
|
5268866317 | ||
|
|
a77439ec02 | ||
|
|
3293c88588 | ||
|
|
6224355f1d | ||
|
|
123ba84dca | ||
|
|
6110275b4b | ||
|
|
7f6643bc9f | ||
|
|
f586371f2b | ||
|
|
3b7fa39da3 |
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json",
|
||||
"changelog": ["@changesets/changelog-github", { "repo": "shadcn-ui/ui" }],
|
||||
"commit": false,
|
||||
"fixed": [],
|
||||
"linked": [],
|
||||
"access": "public",
|
||||
"baseBranch": "main",
|
||||
"updateInternalDependencies": "patch",
|
||||
"ignore": ["v4", "tests"]
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
node_modules/
|
||||
target/
|
||||
.next/
|
||||
build/
|
||||
dist/
|
||||
|
||||
/templates/
|
||||
/fixtures/
|
||||
@@ -1,32 +0,0 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/eslintrc",
|
||||
"root": true,
|
||||
"extends": [
|
||||
"next/core-web-vitals",
|
||||
"turbo",
|
||||
"prettier",
|
||||
"plugin:tailwindcss/recommended"
|
||||
],
|
||||
"plugins": ["tailwindcss"],
|
||||
"ignorePatterns": ["**/fixtures/**"],
|
||||
"rules": {
|
||||
"@next/next/no-html-link-for-pages": "off",
|
||||
"tailwindcss/no-custom-classname": "off",
|
||||
"tailwindcss/classnames-order": "error"
|
||||
},
|
||||
"settings": {
|
||||
"tailwindcss": {
|
||||
"callees": ["cn", "cva"],
|
||||
"config": "tailwind.config.cjs"
|
||||
},
|
||||
"next": {
|
||||
"rootDir": ["apps/*/"]
|
||||
}
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts", "*.tsx"],
|
||||
"parser": "@typescript-eslint/parser"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
title: "[blocks]: "
|
||||
labels: ["Blocks Request"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
### Thanks for taking the time to create a block request! Please search open/closed requests before submitting, as the block or a similar one may have already been requested.
|
||||
|
||||
- type: textarea
|
||||
id: block-description
|
||||
attributes:
|
||||
label: Description
|
||||
description: Tell us about your block request
|
||||
placeholder: "A dashboard for an e-commerce website showing sales, orders, and customers..."
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: block-example-url
|
||||
attributes:
|
||||
label: Example
|
||||
description: Link to an example of the block
|
||||
placeholder: ex. https://example.com
|
||||
validations:
|
||||
required: false
|
||||
@@ -1,3 +0,0 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [shadcn]
|
||||
@@ -1,85 +0,0 @@
|
||||
name: "Bug report"
|
||||
description: Report an issue
|
||||
title: '[bug]: '
|
||||
labels: ["bug"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
### Thanks for taking the time to create a bug report. Please search open/closed issues before submitting, as the issue may have already been reported/addressed.
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
#### If you aren't sure this is a bug or not, please open a discussion instead:
|
||||
- [Discussions](https://github.com/shadcn-ui/ui/discussions/new?category=general)
|
||||
|
||||
- type: textarea
|
||||
id: bug-description
|
||||
attributes:
|
||||
label: Describe the bug
|
||||
description: A clear and concise description of what the bug is. If you intend to submit a PR for this issue, tell us how in the description. Thanks!
|
||||
placeholder: Bug description
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: components-affected
|
||||
attributes:
|
||||
label: Affected component/components
|
||||
description: Which shadcn/ui components are affected?
|
||||
placeholder: ex. Button, Checkbox...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: reproduction
|
||||
attributes:
|
||||
label: How to reproduce
|
||||
description: A step-by-step description of how to reproduce the bug.
|
||||
placeholder: |
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. See error
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: codesandbox-stackblitz
|
||||
attributes:
|
||||
label: Codesandbox/StackBlitz link
|
||||
description: |
|
||||
A link to a CodeSandbox or StackBlitz that includes a minimal reproduction of the problem. In rare cases when not applicable, you can link to a GitHub repository that we can easily run to recreate the issue. If a report is vague and does not have a reproduction, it will be closed without warning.
|
||||
|
||||
> [!CAUTION]
|
||||
> If you skip this step, this issue might be **labeled** with `please add a reproduction` and **closed**.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Logs
|
||||
description: "Please include browser console and server logs around the time this bug occurred. Optional if provided reproduction. Please try not to insert an image but copy paste the log text."
|
||||
render: bash
|
||||
|
||||
- type: textarea
|
||||
id: system-info
|
||||
attributes:
|
||||
label: System Info
|
||||
description: Information about browsers, system or binaries that's relevant.
|
||||
render: bash
|
||||
placeholder: System, Binaries, Browsers
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: checkboxes
|
||||
id: terms
|
||||
attributes:
|
||||
label: Before submitting
|
||||
description: By submitting this issue, you agree to follow our [Contributing Guidelines](https://github.com/shadcn-ui/ui/blob/main/CONTRIBUTING.md).
|
||||
options:
|
||||
- label: I've made research efforts and searched the documentation
|
||||
required: true
|
||||
- label: I've searched for existing issues
|
||||
required: true
|
||||
@@ -1,5 +0,0 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Get Help
|
||||
url: https://github.com/shadcn-ui/ui/discussions/new?category=general
|
||||
about: If you can't get something to work the way you expect, open a question in our discussion forums.
|
||||
@@ -1,55 +0,0 @@
|
||||
name: "Feature request"
|
||||
description: Create a feature request for shadcn/ui
|
||||
title: '[feat]: '
|
||||
labels: ['area: request']
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
### Thanks for taking the time to create a feature request! Please search open/closed issues before submitting, as the issue may have already been reported/addressed.
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
#### If you aren't sure this is a bug or not, please open a discussion instead:
|
||||
- [Discussions](https://github.com/shadcn-ui/ui/discussions/new?category=general)
|
||||
|
||||
- type: textarea
|
||||
id: feature-description
|
||||
attributes:
|
||||
label: Feature description
|
||||
description: Tell us about your feature request
|
||||
placeholder: 'I think this feature would be great because...'
|
||||
value: 'Describe your feature request...'
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: components-affected
|
||||
attributes:
|
||||
label: Affected component/components
|
||||
description: Is this feature request relevant to any of the already existing components?
|
||||
placeholder: ex. Button, Checkbox...
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: context
|
||||
attributes:
|
||||
label: Additional Context
|
||||
description: Add any other context about the feature here.
|
||||
placeholder: ex. screenshots, Stack Overflow links, forum links, etc.
|
||||
value: 'Additional details here...'
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: checkboxes
|
||||
id: terms
|
||||
attributes:
|
||||
label: Before submitting
|
||||
description: By submitting this issue, you agree to follow our [Contributing Guidelines](https://github.com/shadcn-ui/ui/blob/main/CONTRIBUTING.md).
|
||||
options:
|
||||
- label: I've made research efforts and searched the documentation
|
||||
required: true
|
||||
- label: I've searched for existing issues and PRs
|
||||
required: true
|
||||
@@ -1,12 +1,12 @@
|
||||
// ORIGINALLY FROM CLOUDFLARE WRANGLER:
|
||||
// https://github.com/cloudflare/wrangler2/blob/main/.github/changeset-version.js
|
||||
|
||||
import { execSync } from "child_process"
|
||||
import { exec } from "child_process"
|
||||
|
||||
// This script is used by the `release.yml` workflow to update the version of the packages being released.
|
||||
// The standard step is only to run `changeset version` but this does not update the pnpm-lock.yaml file.
|
||||
// So we also run `pnpm install`, which does this update.
|
||||
// The standard step is only to run `changeset version` but this does not update the package-lock.json file.
|
||||
// So we also run `npm install`, which does this update.
|
||||
// This is a workaround until this is handled automatically by `changeset version`.
|
||||
// See https://github.com/changesets/changesets/issues/421.
|
||||
execSync("npx changeset version", { stdio: "inherit" })
|
||||
execSync("pnpm install --lockfile-only", { stdio: "inherit" })
|
||||
exec("npx changeset version")
|
||||
exec("npm install")
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/templates/astro-app"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/templates/astro-monorepo"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/templates/next-app"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/templates/next-monorepo"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/templates/react-router-app"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/templates/react-router-monorepo"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/templates/start-app"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/templates/start-monorepo"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/templates/vite-app"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/templates/vite-monorepo"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
@@ -1,9 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1280" height="640" viewBox="0 0 1280 640" role="img" aria-label="ui">
|
||||
<rect width="1280" height="640" fill="#0A0A0A"/>
|
||||
<svg x="96" y="215" width="210" height="210" viewBox="0 0 67 67"><path d="M22.21 67V44.6369H0V67H22.21Z" fill="#fff"/><path d="M66.7038 22.3184H22.2534L0.0878906 44.6367H44.4634L66.7038 22.3184Z" fill="#fff"/><path d="M22.21 0H0V22.3184H22.21V0Z" fill="#fff"/><path d="M66.7198 0H44.5098V22.3184H66.7198V0Z" fill="#fff"/><path d="M66.7198 67V44.6369H44.5098V67H66.7198Z" fill="#fff"/></svg>
|
||||
<text x="378" y="276" font-family="Inter,system-ui,-apple-system,sans-serif" font-size="78" font-weight="800" letter-spacing="-2" fill="#ffffff">ui</text>
|
||||
<text x="378" y="322" font-family="Inter,system-ui,sans-serif" font-size="30" fill="#ffffff" opacity=".66">React component library for AI applications</text>
|
||||
<rect x="378" y="338" width="806" height="3" rx="1.5" fill="#ffffff" opacity=".9"/>
|
||||
<text x="378" y="390" font-family="Inter,system-ui,sans-serif" font-size="24" font-weight="600" fill="#ffffff" opacity=".5">github.com/hanzoai</text>
|
||||
<text x="1184" y="390" text-anchor="end" font-family="Inter,system-ui,sans-serif" font-size="24" font-weight="600" fill="#ffffff" opacity=".5">hanzo.ai</text>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB |
@@ -4,7 +4,7 @@
|
||||
import { exec } from "child_process"
|
||||
import fs from "fs"
|
||||
|
||||
const pkgJsonPath = "pkgs/shadcn/package.json"
|
||||
const pkgJsonPath = "packages/cli/package.json"
|
||||
try {
|
||||
const pkg = JSON.parse(fs.readFileSync(pkgJsonPath))
|
||||
exec("git rev-parse --short HEAD", (err, stdout) => {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import { exec } from "child_process"
|
||||
import fs from "fs"
|
||||
|
||||
const pkgJsonPath = "pkgs/shadcn/package.json"
|
||||
const pkgJsonPath = "packages/cli/package.json"
|
||||
try {
|
||||
const pkg = JSON.parse(fs.readFileSync(pkgJsonPath))
|
||||
exec("git rev-parse --short HEAD", (err, stdout) => {
|
||||
|
||||
@@ -1,261 +0,0 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- develop
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- develop
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Lint
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 9
|
||||
|
||||
- name: Get pnpm store directory
|
||||
shell: bash
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||
|
||||
- name: Setup pnpm cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --no-frozen-lockfile
|
||||
|
||||
- name: Run linting
|
||||
run: pnpm run lint
|
||||
|
||||
typecheck:
|
||||
name: Type Check
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 9
|
||||
|
||||
- name: Get pnpm store directory
|
||||
shell: bash
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||
|
||||
- name: Setup pnpm cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --no-frozen-lockfile
|
||||
|
||||
- name: Build UI package
|
||||
run: cd pkgs/ui && pnpm run build
|
||||
|
||||
- name: Run type checking
|
||||
run: |
|
||||
cd app && pnpm run typecheck
|
||||
cd ../pkgs/ui && pnpm run tc --noEmit
|
||||
|
||||
build:
|
||||
name: Build
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 9
|
||||
|
||||
- name: Get pnpm store directory
|
||||
shell: bash
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||
|
||||
- name: Setup pnpm cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --no-frozen-lockfile
|
||||
|
||||
- name: Build UI package
|
||||
run: cd pkgs/ui && pnpm run build:full
|
||||
|
||||
- name: Build app
|
||||
run: cd app && pnpm run build
|
||||
env:
|
||||
NEXT_PUBLIC_APP_URL: https://ui.hanzo.ai
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-artifacts
|
||||
path: |
|
||||
app/.next
|
||||
pkgs/ui/dist
|
||||
|
||||
test:
|
||||
name: Test
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 9
|
||||
|
||||
- name: Get pnpm store directory
|
||||
shell: bash
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||
|
||||
- name: Setup pnpm cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --no-frozen-lockfile
|
||||
|
||||
- name: Run pkgs/ui tests
|
||||
run: cd pkgs/ui && pnpm test -- --run
|
||||
|
||||
- name: Run app tests
|
||||
run: |
|
||||
if [ -f "app/package.json" ] && grep -q '"test"' app/package.json 2>/dev/null; then
|
||||
cd app && pnpm test || echo "No tests configured"
|
||||
else
|
||||
echo "No test script found"
|
||||
fi
|
||||
|
||||
- name: Upload coverage
|
||||
if: always()
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
files: ./pkgs/ui/coverage/lcov.info
|
||||
flags: unit
|
||||
|
||||
deploy-preview:
|
||||
name: Deploy Preview
|
||||
runs-on: hanzo-deploy-linux-amd64
|
||||
needs: [lint, typecheck, build]
|
||||
if: github.event_name == 'pull_request'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 9
|
||||
|
||||
- name: Install Vercel CLI
|
||||
run: npm install --global vercel@latest
|
||||
|
||||
- name: Pull Vercel Environment Information
|
||||
run: vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }}
|
||||
env:
|
||||
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
|
||||
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
|
||||
|
||||
- name: Build Project Artifacts
|
||||
run: vercel build --token=${{ secrets.VERCEL_TOKEN }}
|
||||
|
||||
- name: Deploy Project Artifacts to Vercel
|
||||
id: deploy
|
||||
run: |
|
||||
url=$(vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }})
|
||||
echo "preview_url=$url" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Comment PR with preview URL
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: '🚀 Preview deployed to: ${{ steps.deploy.outputs.preview_url }}'
|
||||
})
|
||||
|
||||
status:
|
||||
name: CI Status
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
needs: [lint, typecheck, build, test]
|
||||
if: always()
|
||||
steps:
|
||||
- name: Check status
|
||||
run: |
|
||||
if [ "${{ needs.lint.result }}" != "success" ] || \
|
||||
[ "${{ needs.typecheck.result }}" != "success" ] || \
|
||||
[ "${{ needs.build.result }}" != "success" ] || \
|
||||
[ "${{ needs.test.result }}" != "success" ]; then
|
||||
echo "CI checks failed"
|
||||
exit 1
|
||||
else
|
||||
echo "All CI checks passed!"
|
||||
fi
|
||||
@@ -6,7 +6,7 @@ on:
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
runs-on: ubuntu-latest
|
||||
name: pnpm lint
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -16,13 +16,13 @@ jobs:
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 18
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: pnpm/action-setup@v2.2.4
|
||||
name: Install pnpm
|
||||
id: pnpm-install
|
||||
with:
|
||||
version: 9.0.6
|
||||
version: 8.6.1
|
||||
run_install: false
|
||||
|
||||
- name: Get pnpm store directory
|
||||
@@ -39,10 +39,10 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- run: pnpm lint
|
||||
#- run: pnpm lint
|
||||
|
||||
format:
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
runs-on: ubuntu-latest
|
||||
name: pnpm format:check
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -52,13 +52,13 @@ jobs:
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 18
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: pnpm/action-setup@v2.2.4
|
||||
name: Install pnpm
|
||||
id: pnpm-install
|
||||
with:
|
||||
version: 9.0.6
|
||||
version: 8.6.1
|
||||
run_install: false
|
||||
|
||||
- name: Get pnpm store directory
|
||||
@@ -77,13 +77,10 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Build packages
|
||||
run: pnpm --filter=shadcn build
|
||||
|
||||
- run: pnpm format:check
|
||||
|
||||
tsc:
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
runs-on: ubuntu-latest
|
||||
name: pnpm typecheck
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -93,13 +90,13 @@ jobs:
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 18
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: pnpm/action-setup@v2.2.4
|
||||
name: Install pnpm
|
||||
id: pnpm-install
|
||||
with:
|
||||
version: 9.0.6
|
||||
version: 8.6.1
|
||||
run_install: false
|
||||
|
||||
- name: Get pnpm store directory
|
||||
@@ -116,7 +113,4 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Build packages
|
||||
run: pnpm --filter=shadcn build
|
||||
|
||||
- run: pnpm typecheck
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
name: Test Coverage
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, develop]
|
||||
pull_request:
|
||||
branches: [main, develop]
|
||||
|
||||
jobs:
|
||||
coverage:
|
||||
name: Test Coverage
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 9
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --no-frozen-lockfile
|
||||
|
||||
- name: Run tests with coverage
|
||||
run: cd pkgs/ui && pnpm test:coverage
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
files: ./pkgs/ui/coverage/lcov.info
|
||||
flags: unit
|
||||
name: hanzo-ui-coverage
|
||||
|
||||
- name: Comment PR with coverage
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
const coverage = JSON.parse(fs.readFileSync('./pkgs/ui/coverage/coverage-summary.json', 'utf8'));
|
||||
const total = coverage.total;
|
||||
const comment = `## Test Coverage Report\n\n` +
|
||||
`Lines: ${total.lines.pct}%\n` +
|
||||
`Statements: ${total.statements.pct}%\n` +
|
||||
`Functions: ${total.functions.pct}%\n` +
|
||||
`Branches: ${total.branches.pct}%`;
|
||||
|
||||
github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: comment
|
||||
});
|
||||
@@ -1,96 +0,0 @@
|
||||
name: Deploy to GitHub Pages
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
capture_screenshots:
|
||||
description: 'Capture component screenshots (slow, optional)'
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
concurrency:
|
||||
group: "pages"
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 9
|
||||
|
||||
- name: Get pnpm store directory
|
||||
shell: bash
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||
|
||||
- name: Setup pnpm cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
pnpm install --no-frozen-lockfile
|
||||
|
||||
- name: Build @hanzo/ui package
|
||||
run: |
|
||||
cd pkgs/ui && pnpm build
|
||||
|
||||
- name: Capture screenshots (optional)
|
||||
if: github.event.inputs.capture_screenshots == 'true'
|
||||
working-directory: ./app
|
||||
run: pnpm capture:registry
|
||||
timeout-minutes: 5
|
||||
|
||||
- name: Build documentation
|
||||
working-directory: ./app
|
||||
run: |
|
||||
pnpm build
|
||||
touch out/.nojekyll
|
||||
env:
|
||||
NODE_ENV: production
|
||||
GITHUB_ACTIONS: true
|
||||
NEXT_PUBLIC_APP_URL: https://ui.hanzo.ai
|
||||
SKIP_SCREENSHOTS: true
|
||||
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v4
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: ./app/out
|
||||
|
||||
deploy:
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
runs-on: hanzo-deploy-linux-amd64
|
||||
needs: build
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
@@ -1,78 +0,0 @@
|
||||
name: Deprecated
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, synchronize]
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
deprecated:
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
steps:
|
||||
- name: Checkout PR
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@v46
|
||||
with:
|
||||
files: |
|
||||
apps/www/**
|
||||
files_ignore: |
|
||||
apps/www/public/r/**
|
||||
base_sha: ${{ github.event.pull_request.base.sha }}
|
||||
sha: ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
- name: Comment on PR if www files changed
|
||||
if: steps.changed-files.outputs.any_changed == 'true'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const changedFiles = `${{ steps.changed-files.outputs.all_changed_files }}`.split(' ');
|
||||
const wwwFiles = changedFiles.filter(file =>
|
||||
file.startsWith('apps/www/') &&
|
||||
!file.startsWith('apps/www/public/r/') &&
|
||||
file !== 'apps/www/package.json'
|
||||
);
|
||||
|
||||
if (wwwFiles.length > 0) {
|
||||
const comment = `Looks like this PR modifies files in \`apps/www\`, which is deprecated.
|
||||
|
||||
Consider applying the change to \`apps/v4\` if relevant.`;
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
body: comment
|
||||
});
|
||||
|
||||
// Add deprecated label
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
labels: ['deprecated']
|
||||
});
|
||||
} else {
|
||||
// Remove deprecated label if no www files are changed
|
||||
try {
|
||||
await github.rest.issues.removeLabel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
name: 'deprecated'
|
||||
});
|
||||
} catch (error) {
|
||||
// Label doesn't exist, which is fine
|
||||
console.log('Deprecated label not found, skipping removal');
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
# Adapted from vercel/next.js
|
||||
name: "Stale issue handler"
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
# This runs every day 20 minutes before midnight: https://crontab.guru/#40_23_*_*_*
|
||||
- cron: "40 23 * * *"
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
if: github.repository_owner == 'shadcn-ui'
|
||||
steps:
|
||||
- uses: actions/stale@v9
|
||||
id: issue-stale
|
||||
name: "Mark stale issues, close stale issues"
|
||||
with:
|
||||
repo-token: ${{ secrets.STALE_TOKEN }}
|
||||
ascending: true
|
||||
days-before-issue-close: 7
|
||||
days-before-issue-stale: 365
|
||||
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"
|
||||
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. (This is an automated message)"
|
||||
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! (This is an automated message)"
|
||||
operations-per-run: 300
|
||||
- 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
|
||||
remove-pr-stale-when-updated: true
|
||||
exempt-pr-labels: "roadmap,next,bug"
|
||||
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. (This is an automated message)"
|
||||
close-pr-message: "This PR has been automatically closed due to one year of inactivity. Thank you for your understanding! (This is an automated message)"
|
||||
operations-per-run: 300
|
||||
@@ -1,185 +0,0 @@
|
||||
name: NPM Publish
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
package:
|
||||
description: 'Package to publish'
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- ui
|
||||
- ui-mcp
|
||||
- auth
|
||||
- commerce
|
||||
- checkout
|
||||
- brand
|
||||
- react
|
||||
- all
|
||||
version_bump:
|
||||
description: 'Version bump type'
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- patch
|
||||
- minor
|
||||
- major
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 8
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Build packages
|
||||
run: |
|
||||
if [ "${{ github.event.inputs.package }}" = "all" ] || [ "${{ github.event.inputs.package }}" = "ui" ]; then
|
||||
cd pkgs/ui && pnpm build
|
||||
cd ../..
|
||||
fi
|
||||
if [ "${{ github.event.inputs.package }}" = "all" ] || [ "${{ github.event.inputs.package }}" = "ui-mcp" ]; then
|
||||
cd pkgs/ui-mcp && pnpm build
|
||||
cd ../..
|
||||
fi
|
||||
if [ "${{ github.event.inputs.package }}" = "all" ] || [ "${{ github.event.inputs.package }}" = "auth" ]; then
|
||||
cd pkgs/auth && pnpm build
|
||||
cd ../..
|
||||
fi
|
||||
if [ "${{ github.event.inputs.package }}" = "all" ] || [ "${{ github.event.inputs.package }}" = "commerce" ]; then
|
||||
cd pkgs/commerce && pnpm build
|
||||
cd ../..
|
||||
fi
|
||||
if [ "${{ github.event.inputs.package }}" = "all" ] || [ "${{ github.event.inputs.package }}" = "checkout" ]; then
|
||||
cd pkgs/checkout && pnpm build
|
||||
cd ../..
|
||||
fi
|
||||
if [ "${{ github.event.inputs.package }}" = "all" ] || [ "${{ github.event.inputs.package }}" = "brand" ]; then
|
||||
cd pkgs/brand && pnpm build
|
||||
cd ../..
|
||||
fi
|
||||
if [ "${{ github.event.inputs.package }}" = "all" ] || [ "${{ github.event.inputs.package }}" = "react" ]; then
|
||||
cd pkgs/react && pnpm build
|
||||
cd ../..
|
||||
fi
|
||||
|
||||
- name: Bump version and publish
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
NPM_CONFIG_PROVENANCE: true
|
||||
run: |
|
||||
# Set up npm and pnpm auth
|
||||
npm config set //registry.npmjs.org/:_authToken $NODE_AUTH_TOKEN
|
||||
echo "//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}" >> ~/.npmrc
|
||||
|
||||
publish_package() {
|
||||
local pkg_dir=$1
|
||||
local pkg_name=$(cd $pkg_dir && node -p "require('./package.json').name")
|
||||
local pkg_version=$(cd $pkg_dir && node -p "require('./package.json').version")
|
||||
echo "=== Publishing $pkg_name@$pkg_version from $pkg_dir ==="
|
||||
|
||||
# Change to package directory
|
||||
pushd "$pkg_dir"
|
||||
|
||||
# Bump version manually using node to avoid any pnpm/npm hooks
|
||||
echo "Bumping version (${{ github.event.inputs.version_bump }})..."
|
||||
node -e "
|
||||
const fs = require('fs');
|
||||
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
|
||||
const [major, minor, patch] = pkg.version.split('.').map(Number);
|
||||
const bump = '${{ github.event.inputs.version_bump }}';
|
||||
if (bump === 'major') pkg.version = (major + 1) + '.0.0';
|
||||
else if (bump === 'minor') pkg.version = major + '.' + (minor + 1) + '.0';
|
||||
else pkg.version = major + '.' + minor + '.' + (patch + 1);
|
||||
fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n');
|
||||
console.log('New version:', pkg.version);
|
||||
"
|
||||
|
||||
local new_version=$(node -p "require('./package.json').version")
|
||||
echo "Version is now: $new_version"
|
||||
|
||||
# Pack the tarball
|
||||
echo "Running pnpm pack..."
|
||||
pnpm pack --pack-gzip-level 9
|
||||
local tarball=$(ls -t *.tgz | head -1)
|
||||
echo "Created tarball: $tarball"
|
||||
|
||||
# Verify tarball contents
|
||||
echo "Tarball package.json:"
|
||||
tar -xzf "$tarball" -O package/package.json
|
||||
|
||||
# Copy to temp dir and publish
|
||||
local temp_dir="/tmp/publish-$$"
|
||||
mkdir -p "$temp_dir"
|
||||
cp "$tarball" "$temp_dir/"
|
||||
echo "Publishing from $temp_dir..."
|
||||
cd "$temp_dir"
|
||||
npm publish "$tarball" --access public
|
||||
cd -
|
||||
|
||||
# Cleanup
|
||||
rm -rf "$temp_dir"
|
||||
rm -f "$tarball"
|
||||
popd
|
||||
}
|
||||
|
||||
case "${{ github.event.inputs.package }}" in
|
||||
ui)
|
||||
publish_package "pkgs/ui"
|
||||
;;
|
||||
ui-mcp)
|
||||
publish_package "pkgs/ui-mcp"
|
||||
;;
|
||||
auth)
|
||||
publish_package "pkgs/auth"
|
||||
;;
|
||||
commerce)
|
||||
publish_package "pkgs/commerce"
|
||||
;;
|
||||
checkout)
|
||||
publish_package "pkgs/checkout"
|
||||
;;
|
||||
brand)
|
||||
publish_package "pkgs/brand"
|
||||
;;
|
||||
react)
|
||||
publish_package "pkgs/react"
|
||||
;;
|
||||
all)
|
||||
publish_package "pkgs/ui"
|
||||
publish_package "pkgs/ui-mcp"
|
||||
publish_package "pkgs/auth"
|
||||
publish_package "pkgs/commerce"
|
||||
publish_package "pkgs/checkout"
|
||||
publish_package "pkgs/brand"
|
||||
publish_package "pkgs/react"
|
||||
;;
|
||||
esac
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
commit-message: 'chore: bump ${{ github.event.inputs.package }} version to ${{ github.event.inputs.version_bump }}'
|
||||
title: 'chore: bump ${{ github.event.inputs.package }} version'
|
||||
body: |
|
||||
Automated version bump for ${{ github.event.inputs.package }} package(s).
|
||||
|
||||
Version bump type: ${{ github.event.inputs.version_bump }}
|
||||
branch: version-bump-${{ github.event.inputs.package }}-${{ github.run_number }}
|
||||
@@ -10,9 +10,9 @@ on:
|
||||
jobs:
|
||||
comment:
|
||||
if: |
|
||||
github.repository_owner == 'shadcn-ui' &&
|
||||
github.repository_owner == 'hanzoai-ui' &&
|
||||
${{ github.event.workflow_run.conclusion == 'success' }}
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
runs-on: ubuntu-latest
|
||||
name: Write comment to the PR
|
||||
steps:
|
||||
- name: "Comment on PR"
|
||||
@@ -28,8 +28,8 @@ jobs:
|
||||
|
||||
for (const artifact of allArtifacts.data.artifacts) {
|
||||
// Extract the PR number and package version from the artifact name
|
||||
const match = /^npm-package-shadcn@(.*?)-pr-(\d+)/.exec(artifact.name);
|
||||
|
||||
const match = /^npm-package-hanzoai-ui@(.*?)-pr-(\d+)/.exec(artifact.name);
|
||||
|
||||
if (match) {
|
||||
require("fs").appendFileSync(
|
||||
process.env.GITHUB_ENV,
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
A new prerelease is available for testing:
|
||||
|
||||
```sh
|
||||
pnpm dlx shadcn@${{ env.BETA_PACKAGE_VERSION }}
|
||||
npx hanzoai-ui@${{ env.BETA_PACKAGE_VERSION }}
|
||||
```
|
||||
|
||||
- name: "Remove the autorelease label once published"
|
||||
|
||||
@@ -7,39 +7,31 @@ on:
|
||||
types: [labeled]
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
prerelease:
|
||||
if: |
|
||||
github.repository_owner == 'shadcn-ui' &&
|
||||
github.repository_owner == 'hanzoai' &&
|
||||
contains(github.event.pull_request.labels.*.name, '🚀 autorelease')
|
||||
name: Build & Publish a beta release to NPM
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
runs-on: ubuntu-latest
|
||||
environment: Preview
|
||||
|
||||
steps:
|
||||
- name: Checkout Repo
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Use PNPM
|
||||
uses: pnpm/action-setup@v4
|
||||
uses: pnpm/action-setup@v2.2.4
|
||||
with:
|
||||
version: 9.0.6
|
||||
version: 8.6.1
|
||||
|
||||
- name: Use Node.js 20
|
||||
uses: actions/setup-node@v4
|
||||
- name: Use Node.js 18
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 20
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
|
||||
- name: Update npm for OIDC support
|
||||
run: npm install -g npm@latest
|
||||
node-version: 18
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install NPM Dependencies
|
||||
run: pnpm install
|
||||
@@ -47,6 +39,11 @@ jobs:
|
||||
- name: Modify package.json version
|
||||
run: node .github/version-script-beta.js
|
||||
|
||||
- name: Authenticate to NPM
|
||||
run: echo "//registry.npmjs.org/:_authToken=$NPM_ACCESS_TOKEN" >> packages/cli/.npmrc
|
||||
env:
|
||||
NPM_ACCESS_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }}
|
||||
|
||||
- name: Publish Beta to NPM
|
||||
run: pnpm pub:beta
|
||||
|
||||
@@ -54,10 +51,10 @@ jobs:
|
||||
id: package-version
|
||||
uses: martinbeentjes/npm-get-version-action@main
|
||||
with:
|
||||
path: pkgs/shadcn
|
||||
path: packages/cli
|
||||
|
||||
- name: Upload packaged artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: npm-package-shadcn@${{ steps.package-version.outputs.current-version }}-pr-${{ github.event.number }} # encode the PR number into the artifact name
|
||||
path: pkgs/shadcn/dist/index.js
|
||||
name: npm-package-hanzoai-ui@${{ steps.package-version.outputs.current-version }}-pr-${{ github.event.number }} # encode the PR number into the artifact name
|
||||
path: packages/cli/dist/index.js
|
||||
|
||||
@@ -1,150 +0,0 @@
|
||||
name: Publish on Tag
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*' # Match @hanzo/ui version (e.g., v5.1.1)
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Run Tests
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 9
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --no-frozen-lockfile
|
||||
|
||||
- name: Build packages
|
||||
run: |
|
||||
cd pkgs/ui && pnpm build && cd ../..
|
||||
cd pkgs/commerce && pnpm build && cd ../..
|
||||
cd pkgs/brand && pnpm build && cd ../..
|
||||
cd pkgs/react && pnpm build && cd ../..
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
cd pkgs/ui && pnpm test
|
||||
cd ../react && pnpm test
|
||||
cd ../..
|
||||
|
||||
publish:
|
||||
name: Publish to NPM
|
||||
needs: test
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 9
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --no-frozen-lockfile
|
||||
|
||||
- name: Build all packages
|
||||
run: |
|
||||
cd pkgs/ui && pnpm build && cd ../..
|
||||
cd pkgs/commerce && pnpm build && cd ../..
|
||||
cd pkgs/brand && pnpm build && cd ../..
|
||||
cd pkgs/react && pnpm build && cd ../..
|
||||
|
||||
- name: Configure npm authentication
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
run: |
|
||||
npm config set //registry.npmjs.org/:_authToken $NODE_AUTH_TOKEN
|
||||
npm whoami
|
||||
|
||||
- name: Check and publish packages
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
run: |
|
||||
echo "Checking all packages for unpublished versions..."
|
||||
|
||||
PUBLISHED_COUNT=0
|
||||
SKIPPED_COUNT=0
|
||||
PUBLISHED_PACKAGES=""
|
||||
|
||||
for package in ui commerce brand react; do
|
||||
cd "pkgs/$package"
|
||||
|
||||
CURRENT_VERSION=$(node -p "require('./package.json').version")
|
||||
PACKAGE_NAME=$(node -p "require('./package.json').name")
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "📦 Checking $PACKAGE_NAME@$CURRENT_VERSION"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
|
||||
# Check if this version already exists on npm
|
||||
if npm view "$PACKAGE_NAME@$CURRENT_VERSION" version 2>/dev/null; then
|
||||
echo "⏭️ Already published - skipping"
|
||||
SKIPPED_COUNT=$((SKIPPED_COUNT + 1))
|
||||
else
|
||||
echo "🚀 Publishing to npm..."
|
||||
npm publish --access public
|
||||
echo "✅ Successfully published $PACKAGE_NAME@$CURRENT_VERSION"
|
||||
PUBLISHED_COUNT=$((PUBLISHED_COUNT + 1))
|
||||
PUBLISHED_PACKAGES="$PUBLISHED_PACKAGES\n- $PACKAGE_NAME@$CURRENT_VERSION"
|
||||
fi
|
||||
|
||||
cd ../..
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "📊 Publishing Summary"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "✅ Published: $PUBLISHED_COUNT package(s)"
|
||||
echo "⏭️ Skipped: $SKIPPED_COUNT package(s)"
|
||||
|
||||
if [ $PUBLISHED_COUNT -gt 0 ]; then
|
||||
echo ""
|
||||
echo -e "Published packages:$PUBLISHED_PACKAGES"
|
||||
fi
|
||||
|
||||
# Save for GitHub release notes
|
||||
echo "PUBLISHED_COUNT=$PUBLISHED_COUNT" >> $GITHUB_ENV
|
||||
echo "PUBLISHED_PACKAGES<<EOF" >> $GITHUB_ENV
|
||||
echo -e "$PUBLISHED_PACKAGES" >> $GITHUB_ENV
|
||||
echo "EOF" >> $GITHUB_ENV
|
||||
|
||||
- name: Create GitHub Release
|
||||
if: ${{ env.PUBLISHED_COUNT > 0 }}
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
generate_release_notes: true
|
||||
body: |
|
||||
## 📦 NPM Packages Published
|
||||
|
||||
${{ env.PUBLISHED_PACKAGES }}
|
||||
|
||||
### Installation
|
||||
|
||||
```bash
|
||||
# Install latest versions
|
||||
npm install @hanzo/ui @hanzo/commerce @hanzo/brand @hanzo/react
|
||||
```
|
||||
files: |
|
||||
CHANGELOG.md
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -1,86 +0,0 @@
|
||||
name: Publish Packages
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'pkgs/*/package.json'
|
||||
- 'pkgs/*/package.json'
|
||||
|
||||
jobs:
|
||||
detect-changes:
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
outputs:
|
||||
packages: ${{ steps.changed.outputs.packages }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: Detect changed packages
|
||||
id: changed
|
||||
run: |
|
||||
CHANGED=()
|
||||
for pkg_json in pkgs/*/package.json pkgs/*/package.json; do
|
||||
[ ! -f "$pkg_json" ] && continue
|
||||
pkg_dir=$(dirname "$pkg_json")
|
||||
|
||||
# Skip private packages
|
||||
IS_PRIVATE=$(jq -r '.private // false' "$pkg_json")
|
||||
[ "$IS_PRIVATE" = "true" ] && continue
|
||||
|
||||
# Only publish @hanzo/* packages
|
||||
PKG_NAME=$(jq -r '.name' "$pkg_json")
|
||||
case "$PKG_NAME" in
|
||||
@hanzo/*) ;;
|
||||
*) continue ;;
|
||||
esac
|
||||
|
||||
# Check version changed
|
||||
if git diff HEAD~1 HEAD --quiet "$pkg_json" 2>/dev/null; then
|
||||
continue
|
||||
fi
|
||||
|
||||
OLD=$(git show HEAD~1:"$pkg_json" 2>/dev/null | jq -r '.version' || echo "0.0.0")
|
||||
NEW=$(jq -r '.version' "$pkg_json")
|
||||
|
||||
if [ "$OLD" != "$NEW" ]; then
|
||||
echo "Version change: $PKG_NAME ($OLD -> $NEW)"
|
||||
CHANGED+=("$(basename "$pkg_dir")")
|
||||
fi
|
||||
done
|
||||
|
||||
if [ ${#CHANGED[@]} -eq 0 ]; then
|
||||
echo "packages=[]" >> $GITHUB_OUTPUT
|
||||
else
|
||||
JSON=$(printf '%s\n' "${CHANGED[@]}" | jq -R . | jq -s -c .)
|
||||
echo "packages=$JSON" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
publish:
|
||||
needs: detect-changes
|
||||
if: needs.detect-changes.outputs.packages != '[]'
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
strategy:
|
||||
matrix:
|
||||
package: ${{ fromJson(needs.detect-changes.outputs.packages) }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Build package
|
||||
run: pnpm --filter ${{ matrix.package }} build
|
||||
|
||||
- name: Publish package
|
||||
run: pnpm --filter ${{ matrix.package }} publish --no-git-checks --access public
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
@@ -7,16 +7,11 @@ on:
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
release:
|
||||
if: ${{ github.repository_owner == 'shadcn-ui' }}
|
||||
if: ${{ github.repository_owner == 'hanzoai-ui' }}
|
||||
name: Create a PR for release workflow
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repo
|
||||
uses: actions/checkout@v3
|
||||
@@ -24,18 +19,16 @@ jobs:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Use PNPM
|
||||
uses: pnpm/action-setup@v4
|
||||
uses: pnpm/action-setup@v2.2.4
|
||||
with:
|
||||
version: 9.0.6
|
||||
version: 8.6.1
|
||||
|
||||
- name: Use Node.js 20
|
||||
uses: actions/setup-node@v4
|
||||
- name: Use Node.js 18
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 20
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
|
||||
- name: Update npm for OIDC support
|
||||
run: npm install -g npm@latest
|
||||
version: 8.6.1
|
||||
node-version: 18
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install NPM Dependencies
|
||||
run: pnpm install
|
||||
@@ -44,11 +37,11 @@ jobs:
|
||||
# run: pnpm check
|
||||
|
||||
- name: Build the package
|
||||
run: pnpm shadcn:build
|
||||
run: pnpm build:cli
|
||||
|
||||
- name: Create Version PR or Publish to NPM
|
||||
id: changesets
|
||||
uses: changesets/action@v1
|
||||
uses: changesets/action@v1.4.1
|
||||
with:
|
||||
commit: "chore(release): version packages"
|
||||
title: "chore(release): version packages"
|
||||
@@ -56,4 +49,5 @@ jobs:
|
||||
publish: npx changeset publish
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
NPM_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }}
|
||||
NODE_ENV: "production"
|
||||
|
||||
@@ -6,11 +6,8 @@ on:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
runs-on: ubuntu-latest
|
||||
name: pnpm test
|
||||
env:
|
||||
NEXT_PUBLIC_APP_URL: http://localhost:4000
|
||||
NEXT_PUBLIC_V0_URL: https://v0.dev
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
@@ -19,13 +16,13 @@ jobs:
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 22
|
||||
node-version: 18
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: pnpm/action-setup@v2.2.4
|
||||
name: Install pnpm
|
||||
id: pnpm-install
|
||||
with:
|
||||
version: 9.0.6
|
||||
version: 8.6.1
|
||||
run_install: false
|
||||
|
||||
- name: Get pnpm store directory
|
||||
|
||||
@@ -1,129 +0,0 @@
|
||||
name: Validate Registries
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "apps/v4/public/r/registries.json"
|
||||
- "apps/v4/registry/directory.json"
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "apps/v4/public/r/registries.json"
|
||||
- "apps/v4/registry/directory.json"
|
||||
|
||||
jobs:
|
||||
check-registry-sync:
|
||||
if: github.event_name == 'pull_request'
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
name: check-registry-sync
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Check changed files
|
||||
id: changed
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
CHANGED_FILES=$(gh pr diff ${{ github.event.pull_request.number }} --repo ${{ github.repository }} --name-only)
|
||||
|
||||
DIRECTORY_CHANGED=false
|
||||
REGISTRIES_CHANGED=false
|
||||
|
||||
if echo "$CHANGED_FILES" | grep -q "^apps/v4/registry/directory.json$"; then
|
||||
DIRECTORY_CHANGED=true
|
||||
fi
|
||||
|
||||
if echo "$CHANGED_FILES" | grep -q "^apps/v4/public/r/registries.json$"; then
|
||||
REGISTRIES_CHANGED=true
|
||||
fi
|
||||
|
||||
echo "directory_changed=$DIRECTORY_CHANGED" >> $GITHUB_OUTPUT
|
||||
echo "registries_changed=$REGISTRIES_CHANGED" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Flag missing registries.json update
|
||||
if: steps.changed.outputs.directory_changed == 'true' && steps.changed.outputs.registries_changed == 'false'
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
gh pr edit ${{ github.event.pull_request.number }} --repo ${{ github.repository }} --add-label "registries: invalid"
|
||||
gh pr comment ${{ github.event.pull_request.number }} --repo ${{ github.repository }} --body "can you run \`pnpm registry:build\` and commit the json files please?"
|
||||
exit 1
|
||||
|
||||
validate:
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
name: pnpm validate:registries
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Block reserved registry namespaces
|
||||
env:
|
||||
RESERVED_NAMESPACES: "@shadcn,@ui,@blocks,@components,@block,@component,@util,@utils,@registry,@lib,@hook,@hooks,@theme,@themes,@chart,@charts"
|
||||
run: |
|
||||
node <<'EOF'
|
||||
const fs = require("node:fs")
|
||||
|
||||
const files = [
|
||||
"apps/v4/public/r/registries.json",
|
||||
"apps/v4/registry/directory.json",
|
||||
]
|
||||
const reservedNamespaces = new Set(
|
||||
process.env.RESERVED_NAMESPACES.split(",").filter(Boolean)
|
||||
)
|
||||
|
||||
function readNames(filePath) {
|
||||
return JSON.parse(fs.readFileSync(filePath, "utf8")).map(
|
||||
(entry) => entry.name
|
||||
)
|
||||
}
|
||||
|
||||
const violations = files.flatMap((filePath) => {
|
||||
return readNames(filePath)
|
||||
.filter((name) => reservedNamespaces.has(name))
|
||||
.map((name) => `${filePath}: ${name}`)
|
||||
})
|
||||
|
||||
if (violations.length > 0) {
|
||||
console.error("Reserved registry namespaces are not allowed:")
|
||||
|
||||
for (const violation of violations) {
|
||||
console.error(`- ${violation}`)
|
||||
}
|
||||
|
||||
process.exit(1)
|
||||
}
|
||||
EOF
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
id: pnpm-install
|
||||
with:
|
||||
version: 9.0.6
|
||||
run_install: false
|
||||
|
||||
- name: Get pnpm store directory
|
||||
id: pnpm-cache
|
||||
run: |
|
||||
echo "pnpm_cache_dir=$(pnpm store path)" >> $GITHUB_OUTPUT
|
||||
- uses: actions/cache@v3
|
||||
name: Setup pnpm cache
|
||||
with:
|
||||
path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Validate registries
|
||||
run: pnpm --filter=v4 validate:registries
|
||||
+1
-41
@@ -5,7 +5,6 @@ node_modules
|
||||
**/node_modules
|
||||
.pnp
|
||||
.pnp.js
|
||||
/pnpm-lock.yaml
|
||||
|
||||
# testing
|
||||
coverage
|
||||
@@ -42,43 +41,4 @@ yarn-error.log*
|
||||
|
||||
etc
|
||||
|
||||
# lock files
|
||||
bun.lockb
|
||||
package-lock.json
|
||||
# pnpm-lock.yaml - needed for CI/CD, do not ignore
|
||||
yarn.lock
|
||||
|
||||
# AI cruft
|
||||
AGENTS.md
|
||||
CLAUDE.md
|
||||
GEMINI.md
|
||||
GROK.md
|
||||
QWEN.md
|
||||
|
||||
.playwright-mcp
|
||||
app/out
|
||||
app/.source
|
||||
|
||||
# test artifacts
|
||||
tests/reports/
|
||||
tests/artifacts/
|
||||
|
||||
# Note: Block screenshots are pre-generated and committed
|
||||
# They cannot be generated in CI (requires display/browser)
|
||||
# app/public/r/styles/*/ -- REMOVED to allow committing screenshots
|
||||
|
||||
|
||||
# Test artifacts
|
||||
test-results/
|
||||
playwright-report/
|
||||
*BADGE_INSPECTION*.md
|
||||
*BADGE_INSPECTION*.txt
|
||||
|
||||
# ide
|
||||
.idea
|
||||
.fleet
|
||||
|
||||
.notes
|
||||
.claude/settings.local.json
|
||||
pkg/ui/dist-test/
|
||||
app/public/api/
|
||||
bun.lockb
|
||||
@@ -1,3 +0,0 @@
|
||||
auto-install-peers=true
|
||||
link-workspace-packages=true
|
||||
puppeteer_skip_download=true
|
||||
@@ -1,8 +0,0 @@
|
||||
dist
|
||||
node_modules
|
||||
.next
|
||||
build
|
||||
.contentlayer
|
||||
**/fixtures
|
||||
deprecated
|
||||
apps/v4/registry/styles/**/*.css
|
||||
Vendored
+1
-20
@@ -1,24 +1,5 @@
|
||||
{
|
||||
"typescript.tsdk": "node_modules/.pnpm/typescript@5.3.3/node_modules/typescript/lib",
|
||||
"editor.gotoLocation.multipleDefinitions": "goto",
|
||||
"typescript.preferences.quoteStyle": "single",
|
||||
"eslint.workingDirectories": [
|
||||
{ "pattern": "apps/*/" },
|
||||
{ "pattern": "pkgs/*/" },
|
||||
{ "pattern": "pkgs/*/" }
|
||||
],
|
||||
"tailwindCSS.classFunctions": ["cva", "cn"],
|
||||
"vitest.debugExclude": [
|
||||
"<node_internals>/**",
|
||||
"**/node_modules/**",
|
||||
"**/fixtures/**"
|
||||
],
|
||||
"files.exclude": {
|
||||
"deprecated": true
|
||||
},
|
||||
"search.exclude": {
|
||||
"apps/v4/registry/radix-*": true,
|
||||
"apps/v4/public/r/*": true,
|
||||
"pkgs/shadcn/test/fixtures/*": true
|
||||
}
|
||||
"typescript.preferences.quoteStyle": "single"
|
||||
}
|
||||
|
||||
-129
@@ -1,129 +0,0 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to the Hanzo UI component library will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
- Slot support for Badge component with `asChild` prop for composition flexibility
|
||||
- Modern focus-visible states for Badge component with ring styling
|
||||
- SVG icon support in Badge component with automatic sizing
|
||||
- Aria-invalid states for Badge form validation
|
||||
- Data-slot attributes for better styling hooks
|
||||
|
||||
### Changed
|
||||
- **THEME**: Restored electric blue primary color (`oklch(0.653 0.269 252.44)`) as default
|
||||
- **THEME**: Updated to use shadcn/ui's neutral gray color scheme for backgrounds and text
|
||||
- **THEME**: Maintained configurable theme system via theme generator
|
||||
- Badge component now uses `<span>` instead of `<div>` for better semantics
|
||||
- Badge font weight changed from `font-semibold` to `font-medium`
|
||||
- Badge padding adjusted from `px-2.5` to `px-2`
|
||||
- Badge hover states now only apply within anchor tags (`[a&]:hover`)
|
||||
- Updated both default (rounded-full) and new-york (rounded-md) badge variants
|
||||
- **Button component updated to shadcn/ui v4 patterns**:
|
||||
- Changed focus ring from `ring-2` to `ring-[3px]` with 50% opacity
|
||||
- Added SVG icon support with `[&_svg]:size-4` default sizing
|
||||
- Added `has-[>svg]:px-*` conditional padding when button contains icons
|
||||
- Added new icon size variants: `icon-sm` (size-8) and `icon-lg` (size-10)
|
||||
- Changed from `forwardRef` to regular function component
|
||||
- Added `data-slot="button"` attribute
|
||||
- Improved accessibility with `aria-invalid` states
|
||||
- Enhanced dark mode support for destructive, outline, and ghost variants
|
||||
- **Input component updated to shadcn/ui v4 patterns**:
|
||||
- Changed height from `h-10` to `h-9` (consistent with Button)
|
||||
- Changed from `forwardRef` to regular function component
|
||||
- Added `data-slot="input"` attribute
|
||||
- Changed background to transparent with `dark:bg-input/30` dark mode variant
|
||||
- Added responsive text sizing: `text-base md:text-sm`
|
||||
- Added `min-w-0` to prevent flex overflow issues
|
||||
- Enhanced file input styling with `file:inline-flex file:h-7`
|
||||
- Added selection highlighting: `selection:bg-primary selection:text-primary-foreground`
|
||||
- Changed focus ring from `ring-2` to `ring-[3px]` with 50% opacity
|
||||
- Added `aria-invalid` states for form validation
|
||||
- Changed transition to `transition-[color,box-shadow]` for smoother animations
|
||||
|
||||
### Fixed
|
||||
- Badge component styling now matches shadcn/ui v4 implementation
|
||||
- Improved accessibility with proper focus-visible states
|
||||
- Better form integration with aria-invalid support
|
||||
- **Security**: Fixed 31 of 32 vulnerabilities (96.9% reduction):
|
||||
- Upgraded happy-dom from 19.0.2 to 20.0.10 (fixes 2 critical RCE vulnerabilities)
|
||||
- Upgraded vite from 5.4.20 to 7.1.12 (fixes moderate vulnerability)
|
||||
- Applied 21 package overrides for next, axios, undici, prismjs, and other dependencies
|
||||
- All critical (5), all but 1 high (3/4), all moderate (19), and all low (5) vulnerabilities resolved
|
||||
- Remaining: 1 high severity in lodash.template (no patch available, abandoned package)
|
||||
|
||||
### Removed
|
||||
- Removed hanzo-compatibility documentation page (redundant with main docs)
|
||||
|
||||
## [5.0.0] - 2025-10-18
|
||||
|
||||
### Added
|
||||
- Next.js 16.0.0 with Turbopack support
|
||||
- React 19.2.0 support
|
||||
- 150+ components in registry
|
||||
- AI-specific components (ai-chat, ai-playground, ai-assistant, ai-vision, etc.)
|
||||
- 3D components (3d-button, 3d-card, 3d-carousel, 3d-grid, etc.)
|
||||
- Animation components (animated-beam, animated-text, animated-cursor, etc.)
|
||||
- Page builder with drag-drop functionality
|
||||
- White-label system for Zoo UI and Lux UI forks
|
||||
- External registry support (35+ sources)
|
||||
- 24+ full-page block templates
|
||||
|
||||
### Changed
|
||||
- Migrated from Contentlayer to Fumadocs for MDX processing
|
||||
- Updated documentation structure
|
||||
- Improved build process with registry-first approach
|
||||
|
||||
### Technical
|
||||
- Tailwind CSS v4.1.14
|
||||
- TypeScript 5.9.3
|
||||
- pnpm 9.0.6+ workspace support
|
||||
- Turborepo build system
|
||||
|
||||
---
|
||||
|
||||
## Version Comparison
|
||||
|
||||
**Hanzo UI vs shadcn/ui:**
|
||||
- **161 components** vs shadcn's 58 components (3x more)
|
||||
- **Unique to Hanzo UI**: AI components (12), 3D components (9), Animation components (13), specialized navigation variants (15+)
|
||||
- **100% compatible** with shadcn/ui CLI and registry system
|
||||
- **Multi-framework support**: React (100%), Vue (~90%), Svelte (~85%), React Native (~70%)
|
||||
|
||||
## Component Status
|
||||
|
||||
- **Implemented**: ~127 fully functional components
|
||||
- **Stub Components**: ~34 need implementation
|
||||
- **Blocks**: 24+ viewport-sized templates
|
||||
- **AI Components**: 12 specialized AI/ML components
|
||||
- **3D Components**: 9 three-dimensional UI elements
|
||||
- **Animation Components**: 13 motion and transition components
|
||||
|
||||
## Migration Notes
|
||||
|
||||
### From shadcn/ui
|
||||
Hanzo UI is a drop-in replacement for shadcn/ui with additional components:
|
||||
```bash
|
||||
# Instead of
|
||||
npx shadcn-ui@latest add button
|
||||
|
||||
# Use
|
||||
npx @hanzo/ui@latest add button
|
||||
```
|
||||
|
||||
All shadcn/ui components work identically in Hanzo UI, plus you get access to 100+ additional components.
|
||||
|
||||
## Links
|
||||
|
||||
- **Website**: https://ui.hanzo.ai
|
||||
- **Documentation**: https://ui.hanzo.ai/docs
|
||||
- **GitHub**: https://github.com/hanzoai/ui
|
||||
- **npm Package**: https://www.npmjs.com/package/@hanzo/ui
|
||||
|
||||
## License
|
||||
|
||||
MIT License - see [LICENSE](LICENSE) for details
|
||||
@@ -1,190 +0,0 @@
|
||||
# Hanzo UI Consolidation — one library, all polish, no duplication
|
||||
|
||||
**Goal.** RIP the fragmentation (`@hanzo/data` vs `@hanzo/ui` vs in-console duplicates) into ONE canonical, cross-platform, presentational, clean-room library: **`@hanzo/ui`, built on `@hanzo/gui`**. Preserve every polished component + token; zero loss; no fork.
|
||||
|
||||
**Status.**
|
||||
- **Step 1 — Audit:** complete (this document; four source trees read file-by-file).
|
||||
- **Step 2 — Establish `@hanzo/ui` + migrate the stable foundation:** **DONE & GREEN** (`pkg/ui` is now a real package; `tsc --noEmit` = 0 errors, `vitest` = 12/12).
|
||||
- **Step 3 — Console re-point:** **STAGED, not merged.** Gate is split (backend live, app lanes in-flight) — see [Step 3](#step-3--console-re-point-staged).
|
||||
|
||||
**One decision needs CTO sign-off before publish** — see [Naming / version](#naming--version--the-one-decision).
|
||||
|
||||
---
|
||||
|
||||
## Architecture — three layers, decomplected
|
||||
|
||||
```
|
||||
@hanzo/gui (Tamagui/One) cross-platform primitives: Text, XStack/YStack, Button, Input,
|
||||
│ Card, Popover, Select, Switch, Slider, Spinner, ScrollView …
|
||||
▼
|
||||
@hanzo/data (pkg/data) the metadata-driven RECORD layer: fields → records → views
|
||||
│ (RecordsView, DataTable, BoardView, RecordDetail, field editors)
|
||||
▼
|
||||
@hanzo/ui (pkg/ui) ◄──── THE ONE LIBRARY. Two orthogonal concerns, one package:
|
||||
• product/app layer → import { … } from '@hanzo/ui'
|
||||
• record layer → import { … } from '@hanzo/ui/data' (re-exports @hanzo/data)
|
||||
```
|
||||
|
||||
`@hanzo/data` stays the **source of truth** for the record layer; `@hanzo/ui` **composes** it (`export * from '@hanzo/data'` on the `./data` subpath) rather than copying — so the CMS/ERP/Help app lanes that consume `@hanzo/data` today keep working untouched, and there is exactly one home. This mirrors how the gui base wraps `hanzogui`.
|
||||
|
||||
Both layers own a `DataTable` (product = generic typed `<T>` list; data = field-driven record grid). Keeping the record layer on the `./data` subpath keeps each name unambiguous — no rename, no collision.
|
||||
|
||||
---
|
||||
|
||||
## The preserve-and-merge manifest
|
||||
|
||||
Legend: **✅ migrated** (in `pkg/ui`, GREEN) · **↪ re-exported** (composed from `@hanzo/data`) · **⏳ staged** (Step 3, after app lanes land) · **🔧 pending prop-injection** (app-coupled; decouple before it can live in a presentational lib) · **🏠 stays in console** (app glue, not a UI component).
|
||||
|
||||
### A. Record layer — `@hanzo/data` → `@hanzo/ui/data` ↪
|
||||
|
||||
Source: `pkg/data/src/*` (v1.2.0, clean-room, already published). Surfaced through `@hanzo/ui/data` and `@hanzo/ui/primitives/bases/data`. Nothing re-implemented.
|
||||
|
||||
| Component / module | Source | New home |
|
||||
|---|---|---|
|
||||
| `RecordsView` (flagship: toolbar over table⇆board, filter/sort/search/group, saved views) | `view/RecordsView.tsx`, `view/Toolbar.tsx` | `@hanzo/ui/data` |
|
||||
| `DataTable` (field-driven record grid: sort headers, drag resize/reorder, row select, inline edit, pagination) | `table/DataTable.tsx` | `@hanzo/ui/data` |
|
||||
| `BoardView` (kanban, optimistic drag-move) · `RecordCard` | `board/BoardView.tsx`, `table/RecordCard.tsx` | `@hanzo/ui/data` |
|
||||
| `RecordDetail` / `RecordForm` | `record/RecordDetail.tsx` | `@hanzo/ui/data` |
|
||||
| Field model + registry (`FieldType` ×24, `FieldDefinition`, `registerField`, routers) | `field/{types,registry,registerDefaults,FieldDisplay,FieldInput}` | `@hanzo/ui/data` |
|
||||
| Field displays + **editors** (select/currency/date/relation/file/JSON/boolean/fullName/address…) | `field/{displays,inputs}.tsx` | `@hanzo/ui/data` |
|
||||
| Composable primitives: `Menu`, `CheckBox`, `Toggle`, `Calendar` | `primitives/*` | `@hanzo/ui/data` |
|
||||
| Pure logic: `applyView`, `sortRecords`, `filterRecords`, `searchRecords`, `paginate`, `boardColumns`, … | `{table,board,view}/logic.ts` | `@hanzo/ui/data` (+ `@hanzo/data/*/logic` subpaths for Node hosts) |
|
||||
| Tokens: `tokens`, `TAG_TONES`, `tagTone` | `theme.ts` | **also top-level `@hanzo/ui`** (token surface) |
|
||||
|
||||
### B. Console `components/ui/*` product primitives → `@hanzo/ui/product` ✅ / 🔧
|
||||
|
||||
Source: `console2/src/components/ui/*`. The pure subset was ported **byte-identical** (verified by `diff`) — 100% of the polish preserved (Charts' hover tooltips/axis-ticks/honest-null, DataTable width/flex/hover-gating, Field's 6 variants, SlideOver's focus-trap + scroll-lock, ComboBox's ReDoS-safe filter).
|
||||
|
||||
| Component | Source (`console2/src/components/ui/`) | New home | Status |
|
||||
|---|---|---|---|
|
||||
| `DataTable<T>` (generic typed list) + `Column` | `DataTable.tsx` | `@hanzo/ui` (`product/DataTable`) | ✅ |
|
||||
| `PageHeader` | `PageHeader.tsx` | `@hanzo/ui` | ✅ |
|
||||
| `Field*` (`FieldRow/Text/TextArea/Switch/Select/Slider`) | `Field.tsx` | `@hanzo/ui` | ✅ |
|
||||
| **Charts** (`Sparkline/LineChart/BarChart/Donut/BarRows`, `CHART_PALETTE`) | `Charts.tsx` | `@hanzo/ui` | ✅ |
|
||||
| `Donut` (standalone dependency-free ring) | `Donut.tsx` | `@hanzo/ui` as **`DonutRing`** (alias — Charts.Donut is canonical) | ✅ |
|
||||
| `StatusTag` (health verdict → pill) | `StatusTag.tsx` | `@hanzo/ui` | ✅ |
|
||||
| `EmptyState` (DO/Vercel-class first-run) | `EmptyState.tsx` | `@hanzo/ui` | ✅ (**decoupled** — see §I) |
|
||||
| `Metric*` (`MetricCard/MiniBars/UtilBar/LegendDot/Panel/HintButton`, `SERIES`) | `Metric.tsx` | `@hanzo/ui` (its `Sparkline` preserved as **`MetricSparkline`**) | ✅ |
|
||||
| `ComboBox` + pure `filterOptions`/`isKnownOption` | `ComboBox.tsx`, `combobox/filter.ts` | `@hanzo/ui` | ✅ (+ `filter.test.ts`) |
|
||||
| `PrimaryButton` · `HanzoMark` · `ProductIcon` · `ProviderLogo` | resp. files | `@hanzo/ui` | ✅ |
|
||||
| `SlideOver` (a11y drawer) · `SelectMenu` · `Toast` (`useToast`) | resp. files | `@hanzo/ui` | ✅ (SlideOver import decoupled `~/…/color`→`./color`) |
|
||||
| `Reorder<T>` (pointer DnD) · `FadeIn` · `ThemeToggle` · `color` (`asColor`/`IconLike`) | resp. files | `@hanzo/ui` | ✅ (+ `Reorder.test.ts`) |
|
||||
| `DetailPane` (right-side pane over SlideOver) | `components/DetailPane.tsx` | `@hanzo/ui` | ⏳ (liftable; lands with Step 3 — depends only on SlideOver) |
|
||||
| `ChunkGuard` (stale-deploy chunk-error reload) | `components/ChunkGuard.tsx` | `@hanzo/ui` | ⏳ (fully portable; lands with Step 3) |
|
||||
|
||||
### C. Design tokens + motion → `@hanzo/ui` ✅
|
||||
|
||||
The brief's premise (`globals.css` `t_dark`/`t_light` token blocks) is **corrected**: `t_dark`/`t_light` are `@hanzo/gui` (Tamagui) theme **class names**; the calm values live in three real places, all preserved:
|
||||
|
||||
| Polish | Source | New home |
|
||||
|---|---|---|
|
||||
| Calm dark-first tokens (surface, text, accent `#60a5fa`, tag tones) — raw hex, theme-config independent | `pkg/data/src/theme.ts` | `@hanzo/ui` (top-level `tokens`/`TAG_TONES`/`tagTone`) |
|
||||
| OKLCH shadcn-compatible palette (soft-charcoal `oklch(0.145 0 0)`, `--radius: 0.5rem`, indigo sidebar accent) | `pkgs/ui/style/hanzo-default-colors.css`, `@hanzo/tokens` | unchanged (legacy shadcn line); the gui line uses the hex tokens above |
|
||||
| **Motion vocabulary** — `hz-fade-up` (FadeIn), `hz-collapse`/`hz-slide`/`hz-fade` (shell/drawer), `hz-drag-item`, `hz-skeleton`/`hz-pulse`/`hz-row-in`; all `prefers-reduced-motion`-guarded | `console2/app/globals.css` (motion section) | **`@hanzo/ui/styles/motion.css`** (verbatim) — `import '@hanzo/ui/styles/motion.css'` once at app root |
|
||||
|
||||
### D. Generic DocType renderer → `@hanzo/ui` ⏳ (Step 3, FINAL — active lane)
|
||||
|
||||
Source: `console2/src/components/doctype/*`. **Actively edited by the CMS/ERP/Help lanes (untracked).** Dependency-**injected** (`client: FrameworkClient` prop) — not `~/lib/api`-coupled — so the real move-blockers are `~/lib/framework/{types,fields}` + the `ui/*` primitives (now in `@hanzo/ui`) + `@hanzo/data` (now `@hanzo/ui/data`).
|
||||
|
||||
| File | Class | Step-3 disposition |
|
||||
|---|---|---|
|
||||
| `MediaGrid.tsx` | presentational | → `@hanzo/ui` (cleanest lift; props-only DAM gallery) |
|
||||
| `DocTypeRecords.tsx` / `DocTypeDetail.tsx` | mixed (data-bound shell over `@hanzo/data` views) | shell **stays in console**; renders `@hanzo/ui/data` views (already true) |
|
||||
| `CollectionsBrowser.tsx` | mixed (card grid + install/create orchestration) | presentational card-grid → `@hanzo/ui`; orchestration stays |
|
||||
| `data.ts` | glue (relation loading) | stays in console |
|
||||
|
||||
### E. LivingOverview → `@hanzo/ui` ⏳ (Step 3 — active lane)
|
||||
|
||||
Source: `console2/src/components/products/overview/living/*` (untracked/modified). Pure/presentational parts portable; app-glue concentrated in `registry.ts` (live API clients).
|
||||
|
||||
| Part | Class | Step-3 disposition |
|
||||
|---|---|---|
|
||||
| `motion.ts`, `hooks.ts`, `logic.ts`, `config.ts` (types) | pure | → `@hanzo/ui` (unit-tested; count-up, poll clock, unit formatting) |
|
||||
| `tiles.tsx`, `LivingOverview.tsx` | presentational (reuse `ui/Charts` verbatim) | → `@hanzo/ui` (once `config.ts`'s one `~/lib/products/registry` `ProductIcon` type is swapped for `@hanzo/ui`'s `IconLike`) |
|
||||
| `adapters.ts`, `registry.ts` | glue (map REAL `/v1` sources → `OverviewData`) | **stay in console** |
|
||||
|
||||
### F. Framework / Base-data libs → stay in console 🏠
|
||||
|
||||
`src/lib/framework/*` (DocType wire contract + `FrameworkApi` client + pure `fields.ts` mapper to `@hanzo/data`) and `src/lib/base-data/*` (Base REST client + schema→field mapper) are **app data-access glue**, not UI. They stay in the console. Their pure mappers (`docTypeToFields`, `baseCollectionToFields`) could later publish as a small `@hanzo/base-data` adapter, but they are not UI components and are out of scope for `@hanzo/ui`.
|
||||
|
||||
### G. App-coupled console components → 🔧 pending prop-injection (NOT yet in the lib)
|
||||
|
||||
These import app `~/lib`/session/router/branding and must be made presentational (inject props) before they belong in a host-agnostic lib. Kept in console for now; the injection contract is specified so the eventual lift is mechanical.
|
||||
|
||||
| Component | Coupling | Inject to lift |
|
||||
|---|---|---|
|
||||
| `BackendStateCard` (`BackendState.tsx`) | `~/lib/api` `ApiError` | accept a numeric `status` (or shared error shape) instead of `ApiError` |
|
||||
| `BrandLogo` | session + live IAM `organization()` + `~/config` + branding | `orgName`, `logoUrl` (or a `loadOrgLogo` loader), brand mark/name as props |
|
||||
| `Breadcrumbs` | `next/navigation` + catalog lookups | `crumbs[]` (or `pathname` + resolver) + `onNavigate` |
|
||||
| `Loader` / `BrandMark` | `~/config` + brand logo pkgs | `brand`/`brandName` + the animated-SVG getter |
|
||||
| `States` (`ErrorState`, `OperatorAccessRequired`) | `~/lib/api` + session + branding | the error, the account identity, brand strings |
|
||||
|
||||
---
|
||||
|
||||
## What this pass built (Step 2)
|
||||
|
||||
New in **`pkg/ui`** (was a bare `src/` staging dir — no package):
|
||||
|
||||
- `package.json` — `@hanzo/ui` `8.0.0`, source-published (`files: ["src"]`, exports map to `.ts`), peers `@hanzo/gui >=7.2.2`, `@hanzo/data >=1.2.0`, `react >=19`; BSD-3-Clause. Exports: `.`, `./product`, `./data`, `./primitives/bases/data`, `./styles/motion.css`.
|
||||
- `src/index.ts` — top-level barrel (product layer + tokens).
|
||||
- `src/product/index.ts` — product barrel; **collision-free** (Charts `Sparkline`/`Donut` canonical; Metric's → `MetricSparkline`; standalone ring → `DonutRing`; `ComboOption` from the one filter module) — **zero loss**.
|
||||
- `src/data.ts` — `export * from '@hanzo/data'` (the record layer, one home).
|
||||
- `src/styles/hanzo-motion.css` — the motion vocabulary, verbatim.
|
||||
- `tsconfig.json`, `vitest.config.ts`, `gui.config.ts`, `gui.d.ts`, `.npmignore`, `README.md` — mirror the proven `pkg/data` setup.
|
||||
- **One decouple fix:** `product/EmptyState.tsx` no longer imports `~/lib/products/registry`; it uses the local `IconLike` type — the last host coupling in the product tree is gone.
|
||||
|
||||
**Green:** `tsc --noEmit` = 0 errors (strict); `vitest run` = 12/12 (`combobox/filter`, `Reorder`). Every product component is host-agnostic (imports only `react`, `@hanzo/gui`, `@hanzogui/*`, `@hanzo/data`, relative).
|
||||
|
||||
## Clean-room guarantee (preserved)
|
||||
|
||||
`pkg/data` audited for GPL/Twenty contamination: **none.** No GPL, no copied license headers/SPDX, no Twenty entity/decorator architecture. The field/record model is an independent `FieldType` union + `FieldDefinition` + runtime `Map` registry (records are plain `Record<string, unknown>`). "Twenty" appears **only as benchmark prose** ("Airtable/Twenty-class polish, clean-room"). License: BSD-3-Clause. `@hanzo/ui` inherits the same posture.
|
||||
|
||||
---
|
||||
|
||||
## Naming / version — DECIDED: `@hanzo/ui@8`, shadcn retired to `@hanzo/ui-shadcn`
|
||||
|
||||
**Decision:** the gui-based unified library takes the `@hanzo/ui` name **forward at `8.0.0`** (aligning with the "Hanzo Cloud 8.x" umbrella; major = breaking re-platform). The legacy shadcn/Radix line (`pkgs/ui`, v5.7.0) is **retired by renaming** to `@hanzo/ui-shadcn` (never hard-deleted) — it stays fully alive under the new name, freeing `@hanzo/ui` for v8. Precedent: `pkg/data@1.2.0` already superseded `pkgs/data@1.1.0` under the same name.
|
||||
|
||||
This is **DONE (repo-local):** `pkg/ui/package.json` is `@hanzo/ui@8.0.0`, GREEN. The rest is a coordinated, **sequenced** retire — because publishing `@hanzo/ui@8` (a different, gui-based API with no shadcn `Button/Card/Dialog`) under the name ~20 repos consume for shadcn primitives will BREAK any consumer that resolves to `@8`. So order matters:
|
||||
|
||||
**Blast radius (measured across `~/work/hanzo`).** Declared deps on `@hanzo/ui` in ~20 repos. Most pin `^5.x` (semver-safe from an `@8` bump): paas, chat, platform, app, hanzo.ai, o11y, docs, mdx, hanzobot, ui-repo `pkgs/checkout` `^5.3`, `pkgs/agent-ui` `^5.0`. **Would break on `@8`:** `hanzoai/identity/app` (`"latest"`), ui-repo `pkgs/commerce` (`>=5.0.0`); `app/` uses `workspace:^`. The `ui.hanzo.ai` docs app + `pkgs/{commerce,checkout,agent-ui}` import the shadcn line internally.
|
||||
|
||||
**Publish reality.** `.github/workflows/publish-on-tag.yml` publishes **`pkgs/ui`** (the shadcn line) as `@hanzo/ui` on a `v*` tag — it does not reference `pkg/ui`. So a tag push today publishes shadcn, not v8. Publishing v8 needs the workflow rewired to build/publish `pkg/ui`. `npm` is not authed locally (`npm whoami` empty) — per house rules, publish goes through **CI (self-hosted runners, canonical org `NPM_TOKEN`)**, not a local `npm publish`.
|
||||
|
||||
**Safe sequence to fully land v8 (each step reversible until the tag push):**
|
||||
1. ✅ `pkg/ui` = `@hanzo/ui@8.0.0`, GREEN (done, committed to a branch).
|
||||
2. Rename `pkgs/ui` name → `@hanzo/ui-shadcn`; update the internal ui-repo consumers (`app/`, `pkgs/{commerce,checkout,agent-ui}`) + `check-no-hanzogui`/registry scripts that reference it.
|
||||
3. Migrate external consumers off `@hanzo/ui`→`@hanzo/ui-shadcn` (start with the break-risk ones: `identity` `latest`, `commerce` `>=5`; the `^5` pins are safe to migrate at leisure). ~20 repos — do as a tracked sweep or flag per-repo.
|
||||
4. Rewire `publish-on-tag.yml` (and `release.yml`) to build + publish `pkg/ui` as `@hanzo/ui@8`, and `pkgs/ui-shadcn` as `@hanzo/ui-shadcn`.
|
||||
5. `npm deprecate '@hanzo/ui@<8' 'moved to @hanzo/ui-shadcn; @hanzo/ui@8+ is the @hanzo/gui-based unified lib'`.
|
||||
6. Tag `v8.0.0` → CI publishes `@hanzo/ui@8.0.0`. Verify `npm view @hanzo/ui@8.0.0`.
|
||||
|
||||
**Gated on the user's direct go-ahead** (irreversible / globally-visible / ecosystem-wide): steps 2–6 — the `pkgs/ui` rename, the ~20-repo consumer sweep, the CI rewire, `npm deprecate`, and the `v8.0.0` tag push that triggers publish. These were not executed autonomously.
|
||||
|
||||
---
|
||||
|
||||
## Step 3 — console re-point (STAGED)
|
||||
|
||||
**Gate (per the brief):** cloud `/v1/framework/modules` live **AND** console CMS/ERP/Help native.
|
||||
- ✅ Backend: `/v1/framework/modules[/:module[/install]]` is **implemented + wired** (`cloud/clients/framework/framework.go:71-73`, HIP-0106 order 129, bound in `subsystems.go:130`, real tests).
|
||||
- ⏳ Console: `CmsModule.tsx`, `ErpModule.tsx`, `HelpModule.tsx`, `components/doctype/` are **untracked/in-flight** on `feat/console-native-cms`.
|
||||
|
||||
→ Gate is **split**, so the re-point is **staged, not applied.** I deliberately did **not** touch `console2`'s working tree (it holds the lanes' uncommitted work — editing it would collide and risk loss, the opposite of the goal).
|
||||
|
||||
**Ready-to-apply re-point (mechanical, once the lanes land + merge):**
|
||||
1. `console2` deps: keep `@hanzo/gui`, `@hanzo/data`; add `@hanzo/ui` (`^6.0.0`). (`@hanzo/data` may stay as a direct dep or be dropped in favor of `@hanzo/ui/data` — both resolve to the same source.)
|
||||
2. Re-point imports (delete the now-duplicated in-console copies — extract, don't copy):
|
||||
- `~/components/ui/{DataTable,PageHeader,Field,Charts,Donut,StatusTag,EmptyState,Metric,ComboBox,combobox/filter,PrimaryButton,HanzoMark,ProductIcon,ProviderLogo,SlideOver,SelectMenu,Toast,Reorder,FadeIn,ThemeToggle,color}` → **`@hanzo/ui`** (or `@hanzo/ui/product`). Two spot-renames: Metric's `Sparkline`→`MetricSparkline`, standalone `Donut`→`DonutRing`.
|
||||
- `@hanzo/data` (in `doctype/*`, `base-data/*`, `products/*Module`) → **`@hanzo/ui/data`** (identical surface).
|
||||
- `~/components/{DetailPane,ChunkGuard}` → **`@hanzo/ui`**.
|
||||
- `app/globals.css` motion block → `import '@hanzo/ui/styles/motion.css'` (keep base resets local).
|
||||
- Move the 5 app-coupled components (§G) into the lib only after applying their inject-props contract; until then they stay local.
|
||||
3. Verify **identical render**: `tsc --noEmit` + `vitest` + `next build` green, then headless-Playwright the live pages pixel-same (the polish is byte-identical, so parity is expected).
|
||||
|
||||
**Report:** `@hanzo/ui` is the ONE unified library — product layer + record layer (`@hanzo/data`) + charts + calm tokens + motion, all on `@hanzo/gui`, presentational, cross-platform, clean-room, GREEN. Console re-point is **ready to apply once the CMS/ERP/Help lanes land**; it was not merged to avoid colliding with that in-flight work.
|
||||
|
||||
## Follow-ups (semver-minor, with visual e2e)
|
||||
|
||||
- Collapse the preserved Sparkline/Donut variants to one each (`MetricSparkline`→`Sparkline`, `DonutRing`→`Donut`) once call-sites are proven identical.
|
||||
- Lift §G's five components after applying their prop-injection contracts.
|
||||
- Consider publishing the pure `framework`/`base-data` mappers as a small `@hanzo/base-data` adapter (not UI).
|
||||
+29
-59
@@ -1,10 +1,10 @@
|
||||
# Contributing
|
||||
|
||||
Thanks for your interest in contributing to ui.shadcn.com. We're happy to have you here.
|
||||
Thanks for your interest in contributing to ui.hanzo.ai. We're happy to have you here.
|
||||
|
||||
Please take a moment to review this document before submitting your first pull request. We also strongly recommend that you check for open issues and pull requests to see if someone else is working on something similar.
|
||||
|
||||
If you need any help, feel free to reach out to [@shadcn](https://twitter.com/shadcn).
|
||||
If you need any help, feel free to reach out to [@hanzoai](https://twitter.com/hanzoai).
|
||||
|
||||
## About this repository
|
||||
|
||||
@@ -20,25 +20,28 @@ This repository is structured as follows:
|
||||
|
||||
```
|
||||
apps
|
||||
└── v4
|
||||
└── web
|
||||
├── app
|
||||
├── components
|
||||
├── content
|
||||
└── registry
|
||||
└── new-york-v4
|
||||
├── default
|
||||
│ ├── example
|
||||
│ └── ui
|
||||
└── new-york
|
||||
├── example
|
||||
└── ui
|
||||
packages
|
||||
└── shadcn
|
||||
└── cli
|
||||
```
|
||||
|
||||
| Path | Description |
|
||||
| -------------------- | ---------------------------------------- |
|
||||
| `apps/v4/app` | The Next.js application for the website. |
|
||||
| `apps/v4/components` | The React components for the website. |
|
||||
| `apps/v4/content` | The content for the website. |
|
||||
| `apps/v4/registry` | The registry for the components. |
|
||||
| `packages/shadcn` | The `shadcn` package. |
|
||||
| Path | Description |
|
||||
| --------------------- | ---------------------------------------- |
|
||||
| `apps/web/app` | The Next.js application for the website. |
|
||||
| `apps/web/components` | The React components for the website. |
|
||||
| `apps/web/content` | The content for the website. |
|
||||
| `apps/web/registry` | The registry for the components. |
|
||||
| `packages/ui` | The `@hanzo/ui` package. |
|
||||
|
||||
## Development
|
||||
|
||||
@@ -76,61 +79,34 @@ You can use the `pnpm --filter=[WORKSPACE]` command to start the development pro
|
||||
|
||||
#### Examples
|
||||
|
||||
1. To run the `ui.shadcn.com` website:
|
||||
1. To run the `ui.hanzo.ai` website:
|
||||
|
||||
```bash
|
||||
pnpm --filter=v4 dev
|
||||
pnpm --filter=web dev
|
||||
```
|
||||
|
||||
2. To run the `shadcn` package:
|
||||
|
||||
```bash
|
||||
pnpm --filter=shadcn dev
|
||||
```
|
||||
|
||||
## Running the CLI Locally
|
||||
|
||||
To run the CLI locally, you can follow the workflow:
|
||||
|
||||
1. Start by running the dev server:
|
||||
|
||||
```bash
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
2. 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
|
||||
```
|
||||
|
||||
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 `v4` workspace. You can run the documentation locally by running the following command:
|
||||
The documentation for this project is located in the `web` workspace. You can run the documentation locally by running the following command:
|
||||
|
||||
```bash
|
||||
pnpm --filter=v4 dev
|
||||
pnpm --filter=web dev
|
||||
```
|
||||
|
||||
Documentation is written using [MDX](https://mdxjs.com). You can find the documentation files in the `apps/v4/content/docs` directory.
|
||||
Documentation is written using [MDX](https://mdxjs.com). You can find the documentation files in the `apps/web/content/docs` directory.
|
||||
|
||||
## Components
|
||||
|
||||
We use a registry system for developing components. You can find the source code for the components under `apps/v4/registry`. The components are organized by styles.
|
||||
We use a registry system for developing components. You can find the source code for the components under `apps/web/registry`. The components are organized by styles.
|
||||
|
||||
```bash
|
||||
apps
|
||||
└── v4
|
||||
└── web
|
||||
└── registry
|
||||
└── new-york-v4
|
||||
├── default
|
||||
│ ├── example
|
||||
│ └── ui
|
||||
└── new-york
|
||||
├── example
|
||||
└── ui
|
||||
```
|
||||
@@ -139,7 +115,7 @@ When adding or modifying components, please ensure that:
|
||||
|
||||
1. You make the changes for every style.
|
||||
2. You update the documentation.
|
||||
3. You run `pnpm registry:build` to update the registry.
|
||||
3. You run `pnpm build:registry` to update the registry.
|
||||
|
||||
## Commit Convention
|
||||
|
||||
@@ -156,7 +132,7 @@ the following categories:
|
||||
issue if present)
|
||||
- `refactor`: any code related change that is not a fix nor a feature
|
||||
- `docs`: changing existing or creating new documentation (i.e. README, docs for
|
||||
usage of a lib or cli usage)
|
||||
usage of a lib)
|
||||
- `build`: all changes regarding the build of the software, changes to
|
||||
dependencies or the addition of new dependencies
|
||||
- `test`: all changes regarding tests (adding new tests or changing existing
|
||||
@@ -169,19 +145,13 @@ the following categories:
|
||||
e.g. `feat(components): add new prop to the avatar component`
|
||||
|
||||
If you are interested in the detailed specification you can visit
|
||||
https://www.conventionalcommits.org/ or check out the
|
||||
https://web.conventionalcommits.org/ or check out the
|
||||
[Angular Commit Message Guidelines](https://github.com/angular/angular/blob/22b96b9/CONTRIBUTING.md#-commit-message-guidelines).
|
||||
|
||||
## Requests for new components
|
||||
|
||||
If you have a request for a new component, please open a discussion on GitHub. We'll be happy to help you out.
|
||||
|
||||
## CLI
|
||||
|
||||
The `shadcn` package is a CLI for adding components to your project. You can find the documentation for the CLI [here](https://ui.shadcn.com/docs/cli).
|
||||
|
||||
Any changes to the CLI should be made in the `packages/shadcn` directory. If you can, it would be great if you could add tests for your changes.
|
||||
|
||||
## Testing
|
||||
|
||||
Tests are written using [Vitest](https://vitest.dev). You can run all the tests from the root of the repository.
|
||||
|
||||
@@ -1,458 +0,0 @@
|
||||
# UI/UX Improvements - Implementation Summary
|
||||
**Date**: 2025-11-13
|
||||
**Status**: Phase 1 Complete ✅
|
||||
|
||||
## 🎯 Overview
|
||||
|
||||
Following a comprehensive UI/UX review using Playwright automated testing, we've identified and begun implementing **12 high-impact improvements** to elevate Hanzo UI to world-class standards. This document tracks what's been completed and what remains.
|
||||
|
||||
---
|
||||
|
||||
## ✅ Completed Implementations
|
||||
|
||||
### 1. Skip Links for Accessibility (CRITICAL) ✅
|
||||
**File**: `app/components/skip-links.tsx`
|
||||
|
||||
**What it does**:
|
||||
- Provides keyboard navigation shortcuts for screen reader users
|
||||
- Meets WCAG Level A compliance requirements
|
||||
- Hidden by default, visible when focused (Tab key)
|
||||
|
||||
**Implementation**:
|
||||
```tsx
|
||||
<SkipLinks />
|
||||
// Renders:
|
||||
// - Skip to main content
|
||||
// - Skip to navigation
|
||||
```
|
||||
|
||||
**Impact**:
|
||||
- ✅ WCAG Level A compliant
|
||||
- ✅ Screen reader accessible
|
||||
- ✅ Keyboard navigation improved
|
||||
- ✅ Zero visual impact (only shows on focus)
|
||||
|
||||
**Integrated in**: `app/layout.tsx` - Active globally across all pages
|
||||
|
||||
---
|
||||
|
||||
### 2. Skeleton Loading Components ✅
|
||||
**File**: `app/components/skeleton-card.tsx`
|
||||
|
||||
**What it provides**:
|
||||
- `<SkeletonCard />` - Single card skeleton
|
||||
- `<SkeletonCardGrid />` - Grid of skeleton cards
|
||||
- `<SkeletonList />` - List view skeleton
|
||||
- `<SkeletonTable />` - Table skeleton
|
||||
|
||||
**Usage Example**:
|
||||
```tsx
|
||||
import { Suspense } from 'react'
|
||||
import { SkeletonCardGrid } from '@/components/skeleton-card'
|
||||
|
||||
<Suspense fallback={<SkeletonCardGrid count={6} />}>
|
||||
<ComponentGallery />
|
||||
</Suspense>
|
||||
```
|
||||
|
||||
**Impact**:
|
||||
- ⏩ Reduces perceived load time
|
||||
- 🎨 Professional loading states
|
||||
- ♿ Better accessibility (users know content is loading)
|
||||
- 📱 Works on all devices
|
||||
|
||||
**Next Step**: Integrate into:
|
||||
- `/blocks` page (for block gallery)
|
||||
- `/docs/components` page (for component list)
|
||||
- `/builder` page (for block library panel)
|
||||
|
||||
---
|
||||
|
||||
### 3. Enhanced Global Layout ✅
|
||||
**File**: `app/app/layout.tsx`
|
||||
|
||||
**Changes**:
|
||||
- ✅ Skip links added at the top of the component tree
|
||||
- ✅ Proper import order maintained
|
||||
- ✅ Zero breaking changes to existing functionality
|
||||
|
||||
**Before/After**:
|
||||
```tsx
|
||||
// Before
|
||||
<ActiveThemeProvider>
|
||||
<div vaul-drawer-wrapper="">
|
||||
{children}
|
||||
</div>
|
||||
</ActiveThemeProvider>
|
||||
|
||||
// After
|
||||
<ActiveThemeProvider>
|
||||
<SkipLinks />
|
||||
<div vaul-drawer-wrapper="">
|
||||
{children}
|
||||
</div>
|
||||
</ActiveThemeProvider>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📋 Ready to Integrate (Created, Not Yet Used)
|
||||
|
||||
### Components Available for Use
|
||||
|
||||
1. **Skeleton Components** (`app/components/skeleton-card.tsx`)
|
||||
- Ready to wrap async content
|
||||
- Just import and use in Suspense boundaries
|
||||
|
||||
2. **Skip Links** (`app/components/skip-links.tsx`)
|
||||
- Already active globally
|
||||
- Need to add `id="main-content"` to main content areas
|
||||
|
||||
---
|
||||
|
||||
## 🚧 Next Phase - Ready for Implementation
|
||||
|
||||
### High Priority (Week 1-2)
|
||||
|
||||
#### 1. Add ID Attributes for Skip Links
|
||||
**Files to modify**:
|
||||
- `app/(app)/layout.tsx` - Add `id="main-content"` to main
|
||||
- `app/components/site-header.tsx` - Add `id="navigation"` to nav
|
||||
|
||||
```tsx
|
||||
// In layout
|
||||
<main id="main-content" className="flex-1">
|
||||
{children}
|
||||
</main>
|
||||
|
||||
// In header
|
||||
<nav id="navigation">
|
||||
<MainNav />
|
||||
</nav>
|
||||
```
|
||||
|
||||
**Effort**: 5 minutes
|
||||
**Impact**: High - Completes skip links functionality
|
||||
|
||||
---
|
||||
|
||||
#### 2. Integrate Skeleton Loaders
|
||||
**Target Pages**:
|
||||
|
||||
**A. Blocks Page** (`app/(app)/blocks/page.tsx`)
|
||||
```tsx
|
||||
import { Suspense } from 'react'
|
||||
import { SkeletonCardGrid } from '@/components/skeleton-card'
|
||||
|
||||
export default function BlocksPage() {
|
||||
return (
|
||||
<Suspense fallback={<SkeletonCardGrid count={9} />}>
|
||||
<BlockGallery />
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
**B. Components Page** (`app/(app)/docs/components/page.tsx`)
|
||||
```tsx
|
||||
<Suspense fallback={<SkeletonList count={10} />}>
|
||||
<ComponentList />
|
||||
</Suspense>
|
||||
```
|
||||
|
||||
**C. Builder Page** (`app/(app)/builder/page.tsx`)
|
||||
```tsx
|
||||
// In the component library panel
|
||||
<Suspense fallback={<SkeletonCardGrid count={6} />}>
|
||||
<BlockLibrary />
|
||||
</Suspense>
|
||||
```
|
||||
|
||||
**Effort**: 30 minutes
|
||||
**Impact**: High - Dramatically improves perceived performance
|
||||
|
||||
---
|
||||
|
||||
#### 3. Performance Optimizations (CRITICAL)
|
||||
**Current**: 14.7s load time
|
||||
**Target**: <3s load time
|
||||
|
||||
**Action Items**:
|
||||
a. **Image Optimization**
|
||||
```bash
|
||||
# Convert images to WebP
|
||||
pnpm add sharp
|
||||
# Add next.config.js optimization
|
||||
images: {
|
||||
formats: ['image/webp', 'image/avif'],
|
||||
}
|
||||
```
|
||||
|
||||
b. **Code Splitting**
|
||||
```tsx
|
||||
// Dynamic imports for heavy components
|
||||
const HeavyComponent = dynamic(() => import('./heavy-component'), {
|
||||
loading: () => <SkeletonCard />,
|
||||
})
|
||||
```
|
||||
|
||||
c. **Lazy Loading**
|
||||
```tsx
|
||||
// Below-the-fold content
|
||||
import { lazy } from 'react'
|
||||
const Footer = lazy(() => import('./footer'))
|
||||
```
|
||||
|
||||
**Effort**: 4-6 hours
|
||||
**Impact**: CRITICAL - 79% faster page loads
|
||||
|
||||
---
|
||||
|
||||
### Medium Priority (Week 2-3)
|
||||
|
||||
#### 4. Toast Notification System
|
||||
**Package**: Already installed (`sonner`)
|
||||
**File**: `app/registry/default/ui/sonner.tsx` (already exists!)
|
||||
|
||||
**Usage**:
|
||||
```tsx
|
||||
import { toast } from 'sonner'
|
||||
|
||||
// Success
|
||||
toast.success('Component copied!')
|
||||
|
||||
// Error
|
||||
toast.error('Failed to load', {
|
||||
action: {
|
||||
label: 'Retry',
|
||||
onClick: () => retry(),
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
**Effort**: 1 hour (just replace alert() calls)
|
||||
**Impact**: Medium - Better UX for notifications
|
||||
|
||||
---
|
||||
|
||||
#### 5. Command Palette (⌘K)
|
||||
**Component**: Use existing `app/components/command-menu.tsx`
|
||||
|
||||
**Enhancement**: Add keyboard shortcut hint
|
||||
```tsx
|
||||
<div className="flex items-center gap-2">
|
||||
<Search className="h-4 w-4" />
|
||||
<span>Search</span>
|
||||
<kbd className="ml-auto">⌘K</kbd>
|
||||
</div>
|
||||
```
|
||||
|
||||
**Effort**: 2 hours
|
||||
**Impact**: Medium - Power user feature
|
||||
|
||||
---
|
||||
|
||||
#### 6. Enhanced Hero Section
|
||||
**File**: `app/(app)/page.tsx`
|
||||
|
||||
**Improvements**:
|
||||
- ✨ Animated gradient background
|
||||
- 🎯 More prominent CTAs
|
||||
- 💫 Subtle animations (fade-in-up)
|
||||
- 🏆 Trust indicators
|
||||
|
||||
**Code Available**: See `docs/UX_ENHANCEMENT_PLAN.md` Section 4
|
||||
|
||||
**Effort**: 3-4 hours
|
||||
**Impact**: High - First impression matters
|
||||
|
||||
---
|
||||
|
||||
### Lower Priority (Week 4+)
|
||||
|
||||
7. **Builder Undo/Redo** (8 hours)
|
||||
8. **Keyboard Shortcuts Panel** (4 hours)
|
||||
9. **Empty State Components** (3 hours)
|
||||
10. **Micro-interactions** (4 hours)
|
||||
11. **Component Card Enhancements** (6 hours)
|
||||
12. **Drag Indicators** (4 hours)
|
||||
|
||||
---
|
||||
|
||||
## 📊 Impact Metrics
|
||||
|
||||
### Current Status
|
||||
| Metric | Before | After Phase 1 | Target | Progress |
|
||||
|--------|--------|---------------|--------|----------|
|
||||
| Accessibility | Good | WCAG A ✅ | WCAG AAA | 33% |
|
||||
| Loading UX | None | Skeletons ✅ | Complete | 50% |
|
||||
| Performance | 14.7s | TBD | <3s | 0% |
|
||||
| Mobile UX | Good | Good ✅ | Excellent | 80% |
|
||||
|
||||
### After Full Implementation
|
||||
| Metric | Target | Improvement |
|
||||
|--------|--------|-------------|
|
||||
| Page Load | <3s | -11.7s (79% faster) |
|
||||
| Accessibility | WCAG AAA | +2 levels |
|
||||
| User Engagement | +50% | Better UX |
|
||||
| Bounce Rate | -20% | Faster loads |
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Testing
|
||||
|
||||
### Automated Tests Available
|
||||
Run comprehensive UX review anytime:
|
||||
```bash
|
||||
pnpm exec playwright test tests/ux-review.spec.ts --headed
|
||||
```
|
||||
|
||||
**What it tests**:
|
||||
- ✅ Skip links presence (now passing!)
|
||||
- ✅ Mobile navigation
|
||||
- ✅ Dark/light mode
|
||||
- ✅ Accessibility features
|
||||
- ⏱️ Performance metrics
|
||||
- 📸 Visual regression (screenshots)
|
||||
|
||||
### Manual Testing Checklist
|
||||
- [ ] Tab through page - skip links should appear
|
||||
- [ ] Press Tab on any page → "Skip to main content" appears
|
||||
- [ ] Click skip link → jumps to main content
|
||||
- [ ] Skeleton loaders appear while content loads
|
||||
- [ ] No layout shift when content replaces skeletons
|
||||
|
||||
---
|
||||
|
||||
## 📚 Documentation Created
|
||||
|
||||
1. **UX Enhancement Plan**: `docs/UX_ENHANCEMENT_PLAN.md`
|
||||
- Complete roadmap with code examples
|
||||
- 12 improvements prioritized
|
||||
- Success metrics defined
|
||||
|
||||
2. **UX Review Summary**: `tests/reports/UX_REVIEW_SUMMARY.md`
|
||||
- Detailed findings from Playwright tests
|
||||
- Current vs target performance
|
||||
- Visual analysis results
|
||||
|
||||
3. **Test Suite**: `tests/ux-review.spec.ts`
|
||||
- 13 comprehensive tests
|
||||
- Reusable for ongoing monitoring
|
||||
- Screenshots for visual tracking
|
||||
|
||||
4. **This Document**: `IMPROVEMENTS_IMPLEMENTED.md`
|
||||
- Track implementation progress
|
||||
- Integration guides
|
||||
- Priority roadmap
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Quick Wins (Do These First!)
|
||||
|
||||
### 5-Minute Tasks
|
||||
1. ✅ Skip links - **DONE**
|
||||
2. Add ID attributes (`id="main-content"`, `id="navigation"`)
|
||||
3. Import skeletons where needed
|
||||
|
||||
### 30-Minute Tasks
|
||||
4. Wrap async content in Suspense with skeleton fallbacks
|
||||
5. Replace alert() with toast notifications
|
||||
6. Add ⌘K hint to search
|
||||
|
||||
### 1-Hour Tasks
|
||||
7. Hero section CTA improvements
|
||||
8. Add loading="lazy" to below-fold images
|
||||
|
||||
---
|
||||
|
||||
## 💡 Tips for Implementation
|
||||
|
||||
### Using Skeleton Loaders
|
||||
```tsx
|
||||
// Pattern 1: Simple async component
|
||||
<Suspense fallback={<SkeletonCard />}>
|
||||
<AsyncComponent />
|
||||
</Suspense>
|
||||
|
||||
// Pattern 2: Grid of items
|
||||
<Suspense fallback={<SkeletonCardGrid count={6} />}>
|
||||
<ItemGrid />
|
||||
</Suspense>
|
||||
|
||||
// Pattern 3: With error boundary
|
||||
<ErrorBoundary fallback={<ErrorState />}>
|
||||
<Suspense fallback={<SkeletonList />}>
|
||||
<DataList />
|
||||
</Suspense>
|
||||
</ErrorBoundary>
|
||||
```
|
||||
|
||||
### Performance Best Practices
|
||||
```tsx
|
||||
// 1. Dynamic imports for heavy components
|
||||
const Chart = dynamic(() => import('./chart'), {
|
||||
ssr: false,
|
||||
loading: () => <SkeletonCard />,
|
||||
})
|
||||
|
||||
// 2. Image optimization
|
||||
import Image from 'next/image'
|
||||
<Image
|
||||
src="/image.jpg"
|
||||
alt="Description"
|
||||
width={800}
|
||||
height={600}
|
||||
loading="lazy"
|
||||
placeholder="blur"
|
||||
/>
|
||||
|
||||
// 3. Code splitting
|
||||
const ModalContent = lazy(() => import('./modal-content'))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Next Steps
|
||||
|
||||
**Immediate (This Week)**:
|
||||
1. Add ID attributes to complete skip links
|
||||
2. Integrate skeletons on blocks page
|
||||
3. Test accessibility with screen reader
|
||||
|
||||
**Short-term (Next 2 Weeks)**:
|
||||
4. Performance optimization sprint
|
||||
5. Hero section redesign
|
||||
6. Toast notifications integration
|
||||
|
||||
**Long-term (Month 2)**:
|
||||
7. Builder enhancements (undo/redo, drag indicators)
|
||||
8. Keyboard shortcuts
|
||||
9. Polish and micro-interactions
|
||||
|
||||
---
|
||||
|
||||
## 📞 Support
|
||||
|
||||
**Questions?** See the detailed plans:
|
||||
- `docs/UX_ENHANCEMENT_PLAN.md` - Full implementation guide
|
||||
- `tests/reports/UX_REVIEW_SUMMARY.md` - Test findings
|
||||
- `tests/ux-review.spec.ts` - Test suite source
|
||||
|
||||
**Running Tests**:
|
||||
```bash
|
||||
# Full UX review
|
||||
pnpm test:e2e tests/ux-review.spec.ts
|
||||
|
||||
# Just accessibility tests
|
||||
pnpm test:e2e tests/ux-review.spec.ts -g "Accessibility"
|
||||
|
||||
# With UI (see what's happening)
|
||||
pnpm test:e2e tests/ux-review.spec.ts --headed
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 2025-11-13*
|
||||
*Phase: 1 of 4 Complete*
|
||||
*Next review: After Week 1 integrations*
|
||||
+25
-17
@@ -1,21 +1,29 @@
|
||||
MIT License
|
||||
BSD License
|
||||
|
||||
Copyright (c) 2023 hanzo
|
||||
Copyright (c) 2015-present, Hanzo, Inc.
|
||||
All rights reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name Hanzo nor the names of its contributors may be used to
|
||||
endorse or promote products derived from this software without specific
|
||||
prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -1,136 +0,0 @@
|
||||
# Hanzo UI - LLM Context
|
||||
|
||||
## Overview
|
||||
|
||||
React component library (shadcn/ui fork). 161 components, 24+ blocks, two themes, multi-framework. Published as `@hanzo/ui` on npm.
|
||||
|
||||
**Docs**: https://ui.hanzo.ai | **Dev port**: 3003
|
||||
|
||||
## Repository Structure
|
||||
|
||||
```
|
||||
ui/
|
||||
app/ Hanzo documentation site (Next.js 15.3.1, React 19)
|
||||
registry/ Component registry (SOURCE OF TRUTH)
|
||||
default/ui/ 150+ components
|
||||
default/example/ Usage demos
|
||||
default/blocks/ 24+ full-page sections
|
||||
new-york/ Alternative theme
|
||||
content/docs/ MDX documentation
|
||||
scripts/ Build scripts
|
||||
apps/
|
||||
v4/ Upstream shadcn v4 docs/registry app (port 4000)
|
||||
packages/
|
||||
shadcn/ shadcn CLI v4.1.0 (font system, chart colors, scaffold)
|
||||
tests/ Integration tests for shadcn CLI
|
||||
og/ OG image generation (Hanzo-only)
|
||||
pkg/
|
||||
ui/ Core library (npm)
|
||||
react/ React primitives
|
||||
brand/ Branding system
|
||||
commerce/ E-commerce components
|
||||
checkout/ Checkout flow
|
||||
shop/ Shop components
|
||||
agent-ui/ AI agent UI components
|
||||
tokens/ Design tokens
|
||||
skills/
|
||||
shadcn/ AI skill definitions for shadcn CLI
|
||||
templates/ Project templates (next, vite, astro, react-router, start + monorepo variants)
|
||||
template/next/ Hanzo-customized Next.js template
|
||||
```
|
||||
|
||||
## Critical: Build Order
|
||||
|
||||
**Registry MUST build before app.** The registry generates JSON that the CLI reads.
|
||||
|
||||
```bash
|
||||
pnpm build:registry # MUST run first
|
||||
pnpm build # Then build app
|
||||
```
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
pnpm dev # Dev server (:3003)
|
||||
pnpm build:registry # Build component registry
|
||||
pnpm build # Build app
|
||||
pnpm lint # Lint all workspaces
|
||||
pnpm typecheck # Type checking
|
||||
pnpm test # Unit tests
|
||||
pnpm test:e2e # Playwright E2E
|
||||
pnpm changeset # Create changeset for publishing
|
||||
```
|
||||
|
||||
## Three-Layer Architecture
|
||||
|
||||
1. **Components** (`registry/{style}/ui/`) -- Single primitives (Button, Card, Dialog). CLI-installable.
|
||||
2. **Examples** (`registry/{style}/example/`) -- Usage demos for docs via `<ComponentPreview />`.
|
||||
3. **Blocks** (`registry/{style}/blocks/`) -- Full-page sections (Dashboard, Login). NOT CLI-installable, docs only.
|
||||
|
||||
## Import Path Transformation
|
||||
|
||||
Registry files use `@/registry/default/ui/button`. After CLI install, rewritten to `@/components/ui/button`.
|
||||
|
||||
## Package Exports
|
||||
|
||||
```typescript
|
||||
import { Button, Card } from '@hanzo/ui'
|
||||
import { Button } from '@hanzo/ui/components'
|
||||
import * as Dialog from '@hanzo/ui/primitives/dialog'
|
||||
import { cn } from '@hanzo/ui/lib/utils'
|
||||
```
|
||||
|
||||
## Adding a Component
|
||||
|
||||
1. Create in BOTH themes: `app/registry/{default,new-york}/ui/my-component.tsx`
|
||||
2. Create example: `app/registry/default/example/my-component-demo.tsx`
|
||||
3. Create docs: `app/content/docs/components/my-component.mdx`
|
||||
4. Update nav: `app/config/docs.ts`
|
||||
5. Build: `pnpm build:registry`
|
||||
|
||||
## Tech Stack
|
||||
|
||||
React 19, Next.js 15.3+, Tailwind CSS 4 (OKLCH colors), Radix UI, Turborepo + pnpm, Fumadocs (MDX), class-variance-authority.
|
||||
|
||||
## Upstream Sync
|
||||
|
||||
Remote `shadcn` points to `/Users/z/work/shadcn/ui` (local clone of shadcn-ui/ui).
|
||||
hanzoai/ui is NOT a GitHub fork -- no shared object store, so large merges can fail on push.
|
||||
|
||||
Last sync: 2026-03-24 (shadcn@4.1.0, commit 8bec9c123)
|
||||
Strategy: file-level checkout from shadcn/main for specific directories (not git merge).
|
||||
- Take theirs: packages/shadcn/, packages/tests/, apps/, templates/, scripts/, skills/
|
||||
- Keep ours: app/, pkg/, demo/, docs/, template/next/, pnpm-workspace.yaml, package.json
|
||||
- Remove: deprecated/ (upstream deleted it)
|
||||
- Regenerate: pnpm-lock.yaml after sync
|
||||
|
||||
## Key Features
|
||||
|
||||
- **Page Builder** (`/builder`): Drag-drop block assembly with @dnd-kit, export to TSX
|
||||
- **White-Label**: Zoo/Lux forks via `brands/{BRAND}.brand.ts`
|
||||
- **External Registries**: 35+ sources in `app/registries.json`, install via `npx @hanzo/ui add @aceternity/spotlight`
|
||||
|
||||
## Gotchas
|
||||
|
||||
- Registry index is `Index[style][name]`, NOT `Index[name]` -- caused silent block render failures
|
||||
- Shiki `getHighlighter` incompatible with static export -- replaced with basic pre/code
|
||||
- Some blocks (login-01, login-02, sidebar-02) have Server Component issues with event handlers
|
||||
- Zod validation removed from `_getAllBlocks()`/`_getBlockCode()` -- we control generation
|
||||
- Firebase split to optional `@hanzo/auth-firebase` package (Jan 2025)
|
||||
- `@hanzo/auth` v2.6.0 uses pluggable provider registry: `registerAuthProvider('firebase', FirebaseAuthService)`
|
||||
|
||||
## Component Stats
|
||||
|
||||
- 161 total files, ~127 implemented, ~34 stubs
|
||||
- Unique: 9 3D components, 12 AI components, 13 animation components, 15 nav variants
|
||||
- 3x more components than upstream shadcn/ui (161 vs 58)
|
||||
- shadcn CLI: v4.1.0 with font transformers, chart color picker, scaffold from github
|
||||
|
||||
## Rules
|
||||
|
||||
1. Always build registry before app
|
||||
2. Keep default and new-york themes in sync
|
||||
3. Blocks are docs only, not CLI-installable
|
||||
4. Use pnpm, not npm/yarn
|
||||
5. Never commit symlinked files (AGENTS.md, CLAUDE.md, etc.)
|
||||
6. Documentation goes in `app/content/docs/`, not random root MD files
|
||||
@@ -1,345 +0,0 @@
|
||||
# Hanzo UI Makefile
|
||||
# This Makefile provides convenient commands for building, testing, and managing the Hanzo UI monorepo
|
||||
|
||||
# Variables
|
||||
PNPM := pnpm
|
||||
NODE := node
|
||||
NPM := npm
|
||||
TSX := tsx
|
||||
|
||||
# Colors for output
|
||||
CYAN := \033[0;36m
|
||||
GREEN := \033[0;32m
|
||||
YELLOW := \033[0;33m
|
||||
RED := \033[0;31m
|
||||
NC := \033[0m # No Color
|
||||
|
||||
# Default target - runs build, test, and docs
|
||||
.PHONY: all
|
||||
all: install build test docs
|
||||
@echo "$(GREEN)✓ All tasks completed successfully$(NC)"
|
||||
|
||||
# Help target - shows available commands
|
||||
.PHONY: help
|
||||
help:
|
||||
@echo "$(CYAN)Hanzo UI Makefile$(NC)"
|
||||
@echo ""
|
||||
@echo "$(YELLOW)Available targets:$(NC)"
|
||||
@echo " $(GREEN)make$(NC) - Run default targets (install, build, test, docs)"
|
||||
@echo " $(GREEN)make install$(NC) - Install all dependencies"
|
||||
@echo " $(GREEN)make build$(NC) - Build all packages and the app"
|
||||
@echo " $(GREEN)make test$(NC) - Run all tests"
|
||||
@echo " $(GREEN)make dev$(NC) - Start development server"
|
||||
@echo " $(GREEN)make lint$(NC) - Run linting checks"
|
||||
@echo " $(GREEN)make format$(NC) - Format code with Prettier"
|
||||
@echo " $(GREEN)make typecheck$(NC) - Run TypeScript type checking"
|
||||
@echo " $(GREEN)make docs$(NC) - Generate documentation"
|
||||
@echo " $(GREEN)make clean$(NC) - Clean build artifacts and node_modules"
|
||||
@echo " $(GREEN)make registry$(NC) - Build the component registry"
|
||||
@echo " $(GREEN)make version$(NC) - Show package versions"
|
||||
@echo ""
|
||||
@echo "$(YELLOW)Deployment & Publishing:$(NC)"
|
||||
@echo " $(GREEN)make deploy$(NC) - Deploy to ui.hanzo.ai via GitHub Pages"
|
||||
@echo " $(GREEN)make deploy-pages$(NC) - Manually trigger GitHub Pages deployment"
|
||||
@echo " $(GREEN)make deploy-status$(NC)- Check deployment status"
|
||||
@echo " $(GREEN)make publish$(NC) - Publish packages to npm registry"
|
||||
@echo " $(GREEN)make publish-dry$(NC) - Dry run npm publish"
|
||||
@echo ""
|
||||
@echo "$(YELLOW)Development:$(NC)"
|
||||
@echo " $(GREEN)make dev-app$(NC) - Start app development server"
|
||||
@echo " $(GREEN)make dev-ui$(NC) - Build UI package in watch mode"
|
||||
@echo ""
|
||||
@echo "$(YELLOW)Quality:$(NC)"
|
||||
@echo " $(GREEN)make check$(NC) - Run all quality checks (lint, format, typecheck)"
|
||||
@echo " $(GREEN)make fix$(NC) - Auto-fix linting and formatting issues"
|
||||
@echo ""
|
||||
@echo "$(YELLOW)Utilities:$(NC)"
|
||||
@echo " $(GREEN)make clean-all$(NC) - Deep clean (removes lock file too)"
|
||||
@echo " $(GREEN)make update-deps$(NC) - Update all dependencies"
|
||||
|
||||
# Install dependencies
|
||||
.PHONY: install
|
||||
install:
|
||||
@echo "$(CYAN)Installing dependencies...$(NC)"
|
||||
@$(PNPM) install
|
||||
@echo "$(GREEN)✓ Dependencies installed$(NC)"
|
||||
|
||||
# Build all packages and app
|
||||
.PHONY: build
|
||||
build: install
|
||||
@echo "$(CYAN)Building all packages...$(NC)"
|
||||
@$(PNPM) run build
|
||||
@echo "$(GREEN)✓ Build completed$(NC)"
|
||||
|
||||
# Build individual components
|
||||
.PHONY: build-app
|
||||
build-app:
|
||||
@echo "$(CYAN)Building app...$(NC)"
|
||||
@cd app && $(PNPM) run build
|
||||
@echo "$(GREEN)✓ App build completed$(NC)"
|
||||
|
||||
.PHONY: build-ui
|
||||
build-ui:
|
||||
@echo "$(CYAN)Building UI package...$(NC)"
|
||||
@cd pkg/ui && $(PNPM) run build
|
||||
@echo "$(GREEN)✓ UI package build completed$(NC)"
|
||||
|
||||
.PHONY: build-auth
|
||||
build-auth:
|
||||
@echo "$(CYAN)Building Auth package...$(NC)"
|
||||
@cd pkg/auth && $(PNPM) run build 2>/dev/null || true
|
||||
@echo "$(GREEN)✓ Auth package build completed$(NC)"
|
||||
|
||||
.PHONY: build-commerce
|
||||
build-commerce:
|
||||
@echo "$(CYAN)Building Commerce package...$(NC)"
|
||||
@cd pkg/commerce && $(PNPM) run build 2>/dev/null || true
|
||||
@echo "$(GREEN)✓ Commerce package build completed$(NC)"
|
||||
|
||||
# Run tests
|
||||
.PHONY: test
|
||||
test:
|
||||
@echo "$(CYAN)Running tests...$(NC)"
|
||||
@if [ -f "app/package.json" ] && grep -q '"test"' app/package.json 2>/dev/null; then \
|
||||
cd app && $(PNPM) test 2>/dev/null || echo "$(YELLOW)No tests configured for app$(NC)"; \
|
||||
else \
|
||||
echo "$(YELLOW)No test script found in app$(NC)"; \
|
||||
fi
|
||||
@echo "$(GREEN)✓ Tests completed$(NC)"
|
||||
|
||||
# Development server
|
||||
.PHONY: dev
|
||||
dev:
|
||||
@echo "$(CYAN)Starting development server on port 3003...$(NC)"
|
||||
@cd app && $(PNPM) run dev
|
||||
|
||||
.PHONY: dev-app
|
||||
dev-app: dev
|
||||
|
||||
.PHONY: dev-ui
|
||||
dev-ui:
|
||||
@echo "$(CYAN)Building UI package in watch mode...$(NC)"
|
||||
@cd pkg/ui && $(PNPM) run tc --watch
|
||||
|
||||
# Linting
|
||||
.PHONY: lint
|
||||
lint:
|
||||
@echo "$(CYAN)Running linters...$(NC)"
|
||||
@cd app && $(PNPM) run lint
|
||||
@echo "$(GREEN)✓ Linting completed$(NC)"
|
||||
|
||||
.PHONY: lint-fix
|
||||
lint-fix:
|
||||
@echo "$(CYAN)Fixing linting issues...$(NC)"
|
||||
@cd app && $(PNPM) run lint:fix
|
||||
@echo "$(GREEN)✓ Linting fixes applied$(NC)"
|
||||
|
||||
# Formatting
|
||||
.PHONY: format
|
||||
format:
|
||||
@echo "$(CYAN)Checking code formatting...$(NC)"
|
||||
@cd app && $(PNPM) run format:check
|
||||
@echo "$(GREEN)✓ Format check completed$(NC)"
|
||||
|
||||
.PHONY: format-fix
|
||||
format-fix:
|
||||
@echo "$(CYAN)Formatting code...$(NC)"
|
||||
@cd app && $(PNPM) run format:write
|
||||
@echo "$(GREEN)✓ Code formatted$(NC)"
|
||||
|
||||
# Type checking
|
||||
.PHONY: typecheck
|
||||
typecheck:
|
||||
@echo "$(CYAN)Running TypeScript type checking...$(NC)"
|
||||
@cd app && $(PNPM) run typecheck
|
||||
@cd pkg/ui && $(PNPM) run tc --noEmit
|
||||
@echo "$(GREEN)✓ Type checking completed$(NC)"
|
||||
|
||||
# Documentation generation
|
||||
.PHONY: docs
|
||||
docs:
|
||||
@echo "$(CYAN)Generating documentation...$(NC)"
|
||||
@if [ -f "app/README.md" ]; then \
|
||||
echo "$(GREEN)✓ README.md exists$(NC)"; \
|
||||
fi
|
||||
@if [ -f "LLM.md" ]; then \
|
||||
echo "$(GREEN)✓ LLM.md exists$(NC)"; \
|
||||
fi
|
||||
@echo "$(YELLOW)Note: Full documentation available at https://hanzo.ai/docs$(NC)"
|
||||
|
||||
# Registry build
|
||||
.PHONY: registry
|
||||
registry:
|
||||
@echo "$(CYAN)Building component registry...$(NC)"
|
||||
@cd app && $(PNPM) run registry:build
|
||||
@echo "$(GREEN)✓ Registry built$(NC)"
|
||||
|
||||
.PHONY: registry-capture
|
||||
registry-capture:
|
||||
@echo "$(CYAN)Capturing registry...$(NC)"
|
||||
@cd app && $(PNPM) run registry:capture
|
||||
@echo "$(GREEN)✓ Registry captured$(NC)"
|
||||
|
||||
.PHONY: validate-registries
|
||||
validate-registries:
|
||||
@echo "$(CYAN)Validating registries...$(NC)"
|
||||
@cd app && $(PNPM) run validate:registries
|
||||
@echo "$(GREEN)✓ Registries validated$(NC)"
|
||||
|
||||
# Quality checks
|
||||
.PHONY: check
|
||||
check: lint format typecheck
|
||||
@echo "$(GREEN)✓ All quality checks passed$(NC)"
|
||||
|
||||
.PHONY: fix
|
||||
fix: lint-fix format-fix
|
||||
@echo "$(GREEN)✓ All fixes applied$(NC)"
|
||||
|
||||
# Publishing packages to npm
|
||||
.PHONY: publish
|
||||
publish: build test
|
||||
@echo "$(CYAN)Publishing packages to npm...$(NC)"
|
||||
@echo "$(YELLOW)Publishing @hanzo/ui...$(NC)"
|
||||
@cd pkg/ui && $(NPM) publish --access public
|
||||
@echo "$(YELLOW)Publishing @hanzo/auth...$(NC)"
|
||||
@cd pkg/auth && $(NPM) publish --access public 2>/dev/null || true
|
||||
@echo "$(YELLOW)Publishing @hanzo/commerce...$(NC)"
|
||||
@cd pkg/commerce && $(NPM) publish --access public 2>/dev/null || true
|
||||
@echo "$(GREEN)✓ Packages published to npm$(NC)"
|
||||
|
||||
.PHONY: publish-dry
|
||||
publish-dry: build test
|
||||
@echo "$(CYAN)Dry run: Publishing packages...$(NC)"
|
||||
@cd pkg/ui && $(NPM) publish --dry-run --access public
|
||||
@cd pkg/auth && $(NPM) publish --dry-run --access public 2>/dev/null || true
|
||||
@cd pkg/commerce && $(NPM) publish --dry-run --access public 2>/dev/null || true
|
||||
@echo "$(GREEN)✓ Dry run completed$(NC)"
|
||||
|
||||
# Deployment to ui.hanzo.ai
|
||||
.PHONY: deploy
|
||||
deploy: build-static
|
||||
@echo "$(CYAN)Deploying to ui.hanzo.ai...$(NC)"
|
||||
@echo "$(YELLOW)This will trigger GitHub Actions deployment$(NC)"
|
||||
@git add -A
|
||||
@git diff --cached --quiet || (git commit -m "Deploy to ui.hanzo.ai" && git push origin main)
|
||||
@echo "$(GREEN)✓ Deployment triggered - check GitHub Actions for status$(NC)"
|
||||
@echo "$(YELLOW)View deployment at: https://github.com/hanzoai/react-sdk/actions$(NC)"
|
||||
|
||||
# Build for static export (GitHub Pages)
|
||||
.PHONY: build-static
|
||||
build-static: install
|
||||
@echo "$(CYAN)Building static site for ui.hanzo.ai...$(NC)"
|
||||
@cd app && $(PNPM) run build
|
||||
@if [ ! -f app/next.config.mjs ] || ! grep -q "output.*static" app/next.config.mjs; then \
|
||||
echo "$(YELLOW)Note: For GitHub Pages deployment, consider adding 'output: \"export\"' to next.config.mjs$(NC)"; \
|
||||
fi
|
||||
@echo "$(GREEN)✓ Static build completed$(NC)"
|
||||
|
||||
# Deploy preview (for PRs)
|
||||
.PHONY: deploy-preview
|
||||
deploy-preview: build
|
||||
@echo "$(CYAN)Creating preview deployment...$(NC)"
|
||||
@echo "$(YELLOW)Preview deployments are handled by Vercel/Netlify automatically on PR$(NC)"
|
||||
@echo "$(GREEN)✓ Push your changes to a branch and create a PR for preview$(NC)"
|
||||
|
||||
# Check deployment status
|
||||
.PHONY: deploy-status
|
||||
deploy-status:
|
||||
@echo "$(CYAN)Checking deployment status...$(NC)"
|
||||
@gh workflow view "Deploy to GitHub Pages" --web 2>/dev/null || \
|
||||
echo "$(YELLOW)Install GitHub CLI (gh) to check deployment status$(NC)"
|
||||
@echo ""
|
||||
@echo "$(CYAN)Current site:$(NC) https://ui.hanzo.ai"
|
||||
@echo "$(CYAN)GitHub Pages:$(NC) https://hanzoai.github.io/react-sdk"
|
||||
|
||||
# Manual GitHub Pages deployment trigger
|
||||
.PHONY: deploy-pages
|
||||
deploy-pages:
|
||||
@echo "$(CYAN)Triggering GitHub Pages deployment...$(NC)"
|
||||
@gh workflow run "Deploy to GitHub Pages" 2>/dev/null || \
|
||||
(echo "$(YELLOW)Install GitHub CLI or trigger manually at:$(NC)" && \
|
||||
echo "https://github.com/hanzoai/react-sdk/actions/workflows/deploy-pages.yml")
|
||||
@echo "$(GREEN)✓ Deployment workflow triggered$(NC)"
|
||||
|
||||
# Version management
|
||||
.PHONY: version
|
||||
version:
|
||||
@echo "$(CYAN)Package versions:$(NC)"
|
||||
@echo " @hanzo/react-sdk: $$(cat package.json | grep '"version"' | head -1 | cut -d'"' -f4)"
|
||||
@echo " @hanzo/ui: $$(cat pkg/ui/package.json | grep '"version"' | head -1 | cut -d'"' -f4)"
|
||||
@echo " @hanzo/auth: $$(cat pkg/auth/package.json 2>/dev/null | grep '"version"' | head -1 | cut -d'"' -f4 || echo 'N/A')"
|
||||
@echo " @hanzo/commerce: $$(cat pkg/commerce/package.json 2>/dev/null | grep '"version"' | head -1 | cut -d'"' -f4 || echo 'N/A')"
|
||||
@echo " @hanzo/ui-web (app): $$(cat app/package.json | grep '"version"' | head -1 | cut -d'"' -f4)"
|
||||
|
||||
.PHONY: version-check
|
||||
version-check:
|
||||
@echo "$(CYAN)Latest published versions:$(NC)"
|
||||
@cd pkg/ui && $(PNPM) run lat
|
||||
|
||||
# Cleaning
|
||||
.PHONY: clean
|
||||
clean:
|
||||
@echo "$(CYAN)Cleaning build artifacts and node_modules...$(NC)"
|
||||
@$(PNPM) run clean:nm
|
||||
@rm -rf app/.next app/dist app/build
|
||||
@rm -rf pkg/ui/dist pkg/ui/build
|
||||
@rm -rf pkg/auth/dist pkg/auth/build 2>/dev/null || true
|
||||
@rm -rf pkg/commerce/dist pkg/commerce/build 2>/dev/null || true
|
||||
@echo "$(GREEN)✓ Clean completed$(NC)"
|
||||
|
||||
.PHONY: clean-all
|
||||
clean-all:
|
||||
@echo "$(CYAN)Deep cleaning (including lock file)...$(NC)"
|
||||
@$(PNPM) run clean:all
|
||||
@rm -rf app/.next app/dist app/build
|
||||
@rm -rf pkg/ui/dist pkg/ui/build
|
||||
@rm -rf pkg/auth/dist pkg/auth/build 2>/dev/null || true
|
||||
@rm -rf pkg/commerce/dist pkg/commerce/build 2>/dev/null || true
|
||||
@echo "$(GREEN)✓ Deep clean completed$(NC)"
|
||||
|
||||
# Dependency management
|
||||
.PHONY: update-deps
|
||||
update-deps:
|
||||
@echo "$(CYAN)Updating dependencies...$(NC)"
|
||||
@$(PNPM) update --latest
|
||||
@echo "$(GREEN)✓ Dependencies updated$(NC)"
|
||||
|
||||
.PHONY: outdated
|
||||
outdated:
|
||||
@echo "$(CYAN)Checking for outdated dependencies...$(NC)"
|
||||
@$(PNPM) outdated
|
||||
|
||||
# Production build and start
|
||||
.PHONY: prod
|
||||
prod: build
|
||||
@echo "$(CYAN)Starting production server on port 3001...$(NC)"
|
||||
@cd app && $(PNPM) run start
|
||||
|
||||
# CI/CD helpers
|
||||
.PHONY: ci
|
||||
ci: install lint typecheck test build
|
||||
@echo "$(GREEN)✓ CI checks passed$(NC)"
|
||||
|
||||
# Docker support (if needed in future)
|
||||
.PHONY: docker-build
|
||||
docker-build:
|
||||
@echo "$(CYAN)Building Docker image...$(NC)"
|
||||
@docker build -t hanzo-ui:latest .
|
||||
@echo "$(GREEN)✓ Docker image built$(NC)"
|
||||
|
||||
.PHONY: docker-run
|
||||
docker-run:
|
||||
@echo "$(CYAN)Running Docker container...$(NC)"
|
||||
@docker run -p 3001:3001 hanzo-ui:latest
|
||||
|
||||
# Utility to check if all required tools are installed
|
||||
.PHONY: check-tools
|
||||
check-tools:
|
||||
@echo "$(CYAN)Checking required tools...$(NC)"
|
||||
@command -v $(PNPM) >/dev/null 2>&1 && echo "$(GREEN)✓ pnpm installed$(NC)" || echo "$(RED)✗ pnpm not found$(NC)"
|
||||
@command -v $(NODE) >/dev/null 2>&1 && echo "$(GREEN)✓ node installed$(NC)" || echo "$(RED)✗ node not found$(NC)"
|
||||
@command -v $(NPM) >/dev/null 2>&1 && echo "$(GREEN)✓ npm installed$(NC)" || echo "$(RED)✗ npm not found$(NC)"
|
||||
@command -v git >/dev/null 2>&1 && echo "$(GREEN)✓ git installed$(NC)" || echo "$(RED)✗ git not found$(NC)"
|
||||
|
||||
# Default target explanation
|
||||
.DEFAULT_GOAL := all
|
||||
@@ -1,6 +0,0 @@
|
||||
ui
|
||||
Copyright (c) 2023 Hanzo AI, Inc.
|
||||
|
||||
This product includes software from shadcn/ui (https://github.com/shadcn-ui/ui), licensed under MIT:
|
||||
|
||||
Copyright (c) 2023 shadcn
|
||||
@@ -1,174 +0,0 @@
|
||||
# NPM Publishing Guide - React 19 Packages
|
||||
|
||||
## Current Package Versions
|
||||
|
||||
All packages updated to support **React 19.2.0**:
|
||||
|
||||
- `@hanzo/ui` - v5.1.1
|
||||
- `@hanzo/auth` - Latest
|
||||
- `@hanzo/commerce` - Latest
|
||||
- `@hanzo/brand` - Latest
|
||||
- `@hanzo/react` - v1.0.0
|
||||
|
||||
## Publishing Methods
|
||||
|
||||
### 1. Automatic Publishing (Tag-based)
|
||||
|
||||
When you push a git tag starting with `v` (typically matching @hanzo/ui version), the workflow automatically checks all packages and publishes any with new versions:
|
||||
|
||||
```bash
|
||||
# Tag with @hanzo/ui version (workflow checks all packages)
|
||||
git tag v5.1.1
|
||||
git push origin v5.1.1
|
||||
```
|
||||
|
||||
**What happens:**
|
||||
1. Tests run (pkg/ui and pkg/react)
|
||||
2. All 5 packages build
|
||||
3. **Automatic version detection:**
|
||||
- Checks each package's current version in package.json
|
||||
- Queries npm to see if that version already exists
|
||||
- Only publishes packages with new versions not on npm
|
||||
4. GitHub release created (only if packages were published)
|
||||
|
||||
**Example workflow output:**
|
||||
```
|
||||
📦 Checking @hanzo/ui@5.1.1
|
||||
⏭️ Already published - skipping
|
||||
|
||||
📦 Checking @hanzo/auth@2.5.5
|
||||
🚀 Publishing to npm...
|
||||
✅ Successfully published @hanzo/auth@2.5.5
|
||||
|
||||
📊 Publishing Summary
|
||||
✅ Published: 1 package(s)
|
||||
⏭️ Skipped: 4 package(s)
|
||||
```
|
||||
|
||||
This approach means you:
|
||||
- Only need to tag once (with @hanzo/ui version)
|
||||
- Don't need to track which packages need publishing
|
||||
- Can bump any package version and it auto-publishes on next tag
|
||||
- Similar to python-sdk monorepo publishing
|
||||
|
||||
**Workflow:** `.github/workflows/publish-on-tag.yml`
|
||||
|
||||
### 2. Manual Publishing (Workflow Dispatch)
|
||||
|
||||
Use GitHub Actions UI to manually publish specific packages:
|
||||
|
||||
1. Go to **Actions** → **NPM Publish**
|
||||
2. Click **Run workflow**
|
||||
3. Select package: `ui`, `auth`, `commerce`, `brand`, `react`, or `all`
|
||||
4. Select version bump: `patch`, `minor`, or `major`
|
||||
5. Click **Run workflow**
|
||||
|
||||
**What happens:**
|
||||
- Selected package(s) build
|
||||
- Version bumped automatically
|
||||
- Package(s) published to npm
|
||||
- PR created with version bump
|
||||
|
||||
**Workflow:** `.github/workflows/npm-publish.yml`
|
||||
|
||||
### 3. Local Publishing (Manual)
|
||||
|
||||
For quick patches or testing:
|
||||
|
||||
```bash
|
||||
# Build and test
|
||||
cd pkg/ui
|
||||
pnpm build
|
||||
pnpm test
|
||||
|
||||
# Bump version
|
||||
npm version patch # or minor/major
|
||||
|
||||
# Publish
|
||||
npm publish --access public
|
||||
```
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### NPM Authentication Token
|
||||
|
||||
The GitHub secret `NPM_AUTH_TOKEN` must be set:
|
||||
|
||||
1. Generate token at https://www.npmjs.com/settings/tokens
|
||||
2. Add to GitHub: Settings → Secrets → Actions → `NPM_AUTH_TOKEN`
|
||||
|
||||
### Package Publish Configuration
|
||||
|
||||
All packages already configured with:
|
||||
```json
|
||||
{
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"registry": "https://registry.npmjs.org/"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## React 19 Compatibility
|
||||
|
||||
### Key Updates Made
|
||||
|
||||
1. **Type Declarations**: Added `hanzo-ui.d.ts` files for React 19 compatibility
|
||||
2. **Peer Dependencies**: Force React 19.2.0 via pnpm overrides
|
||||
3. **Test Environment**: Switched to happy-dom for better React 19 support
|
||||
4. **Build Configuration**: All packages build successfully with React 19
|
||||
|
||||
### Test Status
|
||||
|
||||
- **pkg/ui**: 206/207 tests passing (99.5%)
|
||||
- **pkg/react**: 10/10 tests passing (100%)
|
||||
|
||||
## Publishing Checklist
|
||||
|
||||
Before publishing:
|
||||
|
||||
- [ ] All packages build successfully: `pnpm build`
|
||||
- [ ] Tests pass: `pnpm test`
|
||||
- [ ] Types check: `cd app && pnpm typecheck`
|
||||
- [ ] Lint passes: `cd app && pnpm lint`
|
||||
- [ ] Update CHANGELOG.md
|
||||
- [ ] Update version in package.json (if manual)
|
||||
- [ ] Commit changes
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Build Failures
|
||||
|
||||
```bash
|
||||
# Clean install
|
||||
rm -rf node_modules pnpm-lock.yaml
|
||||
pnpm install
|
||||
|
||||
# Rebuild
|
||||
pnpm build
|
||||
```
|
||||
|
||||
### Test Failures
|
||||
|
||||
```bash
|
||||
# Run specific package tests
|
||||
cd pkg/ui && pnpm test
|
||||
cd pkg/react && pnpm test
|
||||
```
|
||||
|
||||
### Publish Failures
|
||||
|
||||
- Check NPM_AUTH_TOKEN is valid
|
||||
- Ensure version is unique (not already published)
|
||||
- Verify package builds: `cd pkg/<name> && pnpm build`
|
||||
|
||||
## Package URLs
|
||||
|
||||
- npm: https://www.npmjs.com/org/hanzo
|
||||
- GitHub: https://github.com/hanzoai/ui
|
||||
- Docs: https://ui.hanzo.ai
|
||||
|
||||
---
|
||||
|
||||
**Last Updated:** 2025-10-05
|
||||
**React Version:** 19.2.0
|
||||
@@ -1,84 +1,12 @@
|
||||
<p align="center"><img src=".github/hero.svg" alt="ui" width="880"></p>
|
||||
|
||||
# @hanzo/ui
|
||||
|
||||
Accessible and customizable components for React, Vue, Svelte, and React Native. **Built on shadcn/ui with multi-framework support, 3D components, AI components, and advanced features.**
|
||||
Accessible and customizable components that you can use to build your AI apps.
|
||||
|
||||

|
||||
|
||||
## Features
|
||||
|
||||
- **161+ Components** - 3x more than shadcn/ui
|
||||
- **Multi-Framework** - React, Vue, Svelte, React Native
|
||||
- **Two Themes** - Default & New York variants
|
||||
- **AI Components** - Chat, assistants, playground
|
||||
- **3D Components** - Interactive 3D elements
|
||||
- **Animations** - Advanced motion components
|
||||
- **Page Builder** - Visual drag-drop interface
|
||||
- **White-Label** - Fork and rebrand easily
|
||||
- **Blocks** - 24+ production-ready templates
|
||||
- **Accessible** - Built with Radix UI primitives
|
||||
- **Customizable** - Tailwind CSS powered
|
||||
- **TypeScript** - Fully typed
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Installation
|
||||
|
||||
```bash
|
||||
npm install @hanzo/ui
|
||||
# or
|
||||
pnpm add @hanzo/ui
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
```tsx
|
||||
import { Button, Card, Input } from '@hanzo/ui'
|
||||
|
||||
export function App() {
|
||||
return (
|
||||
<Card>
|
||||
<Card.Header>
|
||||
<Card.Title>Welcome</Card.Title>
|
||||
</Card.Header>
|
||||
<Card.Content>
|
||||
<Input placeholder="Enter text..." />
|
||||
</Card.Content>
|
||||
<Card.Footer>
|
||||
<Button>Submit</Button>
|
||||
</Card.Footer>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
```
|
||||

|
||||
|
||||
## Documentation
|
||||
|
||||
Visit **[ui.hanzo.ai](https://ui.hanzo.ai)** for full docs.
|
||||
|
||||
## CLI
|
||||
|
||||
```bash
|
||||
npx @hanzo/ui add button
|
||||
npx @hanzo/ui add card dialog
|
||||
```
|
||||
|
||||
## Packages
|
||||
|
||||
- `@hanzo/ui` - Main UI library (161 components)
|
||||
- `@hanzo/auth` - Authentication components
|
||||
- `@hanzo/commerce` - E-commerce components
|
||||
- `@hanzo/brand` - Branding system
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
git clone https://github.com/hanzoai/ui.git
|
||||
cd ui
|
||||
pnpm install
|
||||
pnpm dev
|
||||
```
|
||||
Visit http://ui.hanzo.ai to view the documentation.
|
||||
|
||||
## Contributing
|
||||
|
||||
@@ -86,8 +14,4 @@ Please read the [contributing guide](/CONTRIBUTING.md).
|
||||
|
||||
## License
|
||||
|
||||
MIT - See [LICENSE.md](./LICENSE.md) for details.
|
||||
|
||||
---
|
||||
|
||||
Built by [Hanzo](https://hanzo.ai)
|
||||
Licensed under the [BSD 3 Clause](./LICENSE.md) License.
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
# Security Policy
|
||||
|
||||
If you believe you have found a security vulnerability, we encourage you to let us know right away.
|
||||
|
||||
We will investigate all legitimate reports and do our best to quickly fix the problem.
|
||||
|
||||
Our preference is that you make use of GitHub's private vulnerability reporting feature to disclose potential security vulnerabilities in our Open Source Software.
|
||||
|
||||
To do this, please visit the security tab of the repository and click the [Report a vulnerability](https://github.com/shadcn-ui/ui/security/advisories/new) button.
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"extends": ["@commitlint/config-conventional"]
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"hanzo-ui": {
|
||||
"command": "npx",
|
||||
"args": ["@hanzo/ui@latest", "mcp"],
|
||||
"env": {
|
||||
"REGISTRY_URL": "https://ui.hanzo.ai/registry/registry.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,434 +0,0 @@
|
||||
// @ts-nocheck
|
||||
import { browser } from "@hanzo/docs-mdx/runtime/browser"
|
||||
|
||||
import type * as Config from "../source.config"
|
||||
|
||||
const create = browser<
|
||||
typeof Config,
|
||||
import("@hanzo/docs-mdx/runtime/types").InternalTypeConfig & {
|
||||
DocData: {}
|
||||
}
|
||||
>()
|
||||
const browserCollections = {
|
||||
docs: create.doc("docs", {
|
||||
"about.mdx": () => import("../content/docs/about.mdx?collection=docs"),
|
||||
"changelog.mdx": () =>
|
||||
import("../content/docs/changelog.mdx?collection=docs"),
|
||||
"cli.mdx": () => import("../content/docs/cli.mdx?collection=docs"),
|
||||
"components-json.mdx": () =>
|
||||
import("../content/docs/components-json.mdx?collection=docs"),
|
||||
"components.mdx": () =>
|
||||
import("../content/docs/components.mdx?collection=docs"),
|
||||
"figma.mdx": () => import("../content/docs/figma.mdx?collection=docs"),
|
||||
"index.mdx": () => import("../content/docs/index.mdx?collection=docs"),
|
||||
"mcp.mdx": () => import("../content/docs/mcp.mdx?collection=docs"),
|
||||
"theming.mdx": () => import("../content/docs/theming.mdx?collection=docs"),
|
||||
"ai/actions.mdx": () =>
|
||||
import("../content/docs/ai/actions.mdx?collection=docs"),
|
||||
"ai/agents.mdx": () =>
|
||||
import("../content/docs/ai/agents.mdx?collection=docs"),
|
||||
"ai/assistant.mdx": () =>
|
||||
import("../content/docs/ai/assistant.mdx?collection=docs"),
|
||||
"ai/chat.mdx": () => import("../content/docs/ai/chat.mdx?collection=docs"),
|
||||
"ai/code.mdx": () => import("../content/docs/ai/code.mdx?collection=docs"),
|
||||
"ai/index.mdx": () =>
|
||||
import("../content/docs/ai/index.mdx?collection=docs"),
|
||||
"ai/models.mdx": () =>
|
||||
import("../content/docs/ai/models.mdx?collection=docs"),
|
||||
"ai/playground.mdx": () =>
|
||||
import("../content/docs/ai/playground.mdx?collection=docs"),
|
||||
"ai/tools.mdx": () =>
|
||||
import("../content/docs/ai/tools.mdx?collection=docs"),
|
||||
"ai/vision.mdx": () =>
|
||||
import("../content/docs/ai/vision.mdx?collection=docs"),
|
||||
"ai/voice-settings.mdx": () =>
|
||||
import("../content/docs/ai/voice-settings.mdx?collection=docs"),
|
||||
"ai/voice.mdx": () =>
|
||||
import("../content/docs/ai/voice.mdx?collection=docs"),
|
||||
"block-examples/billing.mdx": () =>
|
||||
import("../content/docs/block-examples/billing.mdx?collection=docs"),
|
||||
"block-examples/compose-spec.mdx": () =>
|
||||
import("../content/docs/block-examples/compose-spec.mdx?collection=docs"),
|
||||
"block-examples/newsletter.mdx": () =>
|
||||
import("../content/docs/block-examples/newsletter.mdx?collection=docs"),
|
||||
"block-examples/pricing.mdx": () =>
|
||||
import("../content/docs/block-examples/pricing.mdx?collection=docs"),
|
||||
"frameworks/index.mdx": () =>
|
||||
import("../content/docs/frameworks/index.mdx?collection=docs"),
|
||||
"frameworks/react.mdx": () =>
|
||||
import("../content/docs/frameworks/react.mdx?collection=docs"),
|
||||
"frameworks/vue.mdx": () =>
|
||||
import("../content/docs/frameworks/vue.mdx?collection=docs"),
|
||||
"dark-mode/astro.mdx": () =>
|
||||
import("../content/docs/dark-mode/astro.mdx?collection=docs"),
|
||||
"dark-mode/index.mdx": () =>
|
||||
import("../content/docs/dark-mode/index.mdx?collection=docs"),
|
||||
"dark-mode/next.mdx": () =>
|
||||
import("../content/docs/dark-mode/next.mdx?collection=docs"),
|
||||
"dark-mode/remix.mdx": () =>
|
||||
import("../content/docs/dark-mode/remix.mdx?collection=docs"),
|
||||
"dark-mode/vite.mdx": () =>
|
||||
import("../content/docs/dark-mode/vite.mdx?collection=docs"),
|
||||
"guides/page-builder.mdx": () =>
|
||||
import("../content/docs/guides/page-builder.mdx?collection=docs"),
|
||||
"guides/visual-workflows.mdx": () =>
|
||||
import("../content/docs/guides/visual-workflows.mdx?collection=docs"),
|
||||
"hanzo-react/getting-started.mdx": () =>
|
||||
import("../content/docs/hanzo-react/getting-started.mdx?collection=docs"),
|
||||
"hanzo-react/hooks.mdx": () =>
|
||||
import("../content/docs/hanzo-react/hooks.mdx?collection=docs"),
|
||||
"installation/astro.mdx": () =>
|
||||
import("../content/docs/installation/astro.mdx?collection=docs"),
|
||||
"installation/gatsby.mdx": () =>
|
||||
import("../content/docs/installation/gatsby.mdx?collection=docs"),
|
||||
"installation/index.mdx": () =>
|
||||
import("../content/docs/installation/index.mdx?collection=docs"),
|
||||
"installation/laravel.mdx": () =>
|
||||
import("../content/docs/installation/laravel.mdx?collection=docs"),
|
||||
"installation/manual.mdx": () =>
|
||||
import("../content/docs/installation/manual.mdx?collection=docs"),
|
||||
"installation/next.mdx": () =>
|
||||
import("../content/docs/installation/next.mdx?collection=docs"),
|
||||
"installation/remix.mdx": () =>
|
||||
import("../content/docs/installation/remix.mdx?collection=docs"),
|
||||
"installation/vite.mdx": () =>
|
||||
import("../content/docs/installation/vite.mdx?collection=docs"),
|
||||
"packages/index.mdx": () =>
|
||||
import("../content/docs/packages/index.mdx?collection=docs"),
|
||||
"components/3d-card.mdx": () =>
|
||||
import("../content/docs/components/3d-card.mdx?collection=docs"),
|
||||
"components/3d-marquee.mdx": () =>
|
||||
import("../content/docs/components/3d-marquee.mdx?collection=docs"),
|
||||
"components/3d-pin.mdx": () =>
|
||||
import("../content/docs/components/3d-pin.mdx?collection=docs"),
|
||||
"components/accordion.mdx": () =>
|
||||
import("../content/docs/components/accordion.mdx?collection=docs"),
|
||||
"components/advanced-navigation-bar.mdx": () =>
|
||||
import(
|
||||
"../content/docs/components/advanced-navigation-bar.mdx?collection=docs"
|
||||
),
|
||||
"components/ai-model-selector-navigation-bar.mdx": () =>
|
||||
import(
|
||||
"../content/docs/components/ai-model-selector-navigation-bar.mdx?collection=docs"
|
||||
),
|
||||
"components/alert-dialog.mdx": () =>
|
||||
import("../content/docs/components/alert-dialog.mdx?collection=docs"),
|
||||
"components/alert.mdx": () =>
|
||||
import("../content/docs/components/alert.mdx?collection=docs"),
|
||||
"components/android.mdx": () =>
|
||||
import("../content/docs/components/android.mdx?collection=docs"),
|
||||
"components/animated-beam.mdx": () =>
|
||||
import("../content/docs/components/animated-beam.mdx?collection=docs"),
|
||||
"components/animated-cursor.mdx": () =>
|
||||
import("../content/docs/components/animated-cursor.mdx?collection=docs"),
|
||||
"components/animated-testimonials.mdx": () =>
|
||||
import(
|
||||
"../content/docs/components/animated-testimonials.mdx?collection=docs"
|
||||
),
|
||||
"components/animated-tooltip.mdx": () =>
|
||||
import("../content/docs/components/animated-tooltip.mdx?collection=docs"),
|
||||
"components/animation-components.mdx": () =>
|
||||
import(
|
||||
"../content/docs/components/animation-components.mdx?collection=docs"
|
||||
),
|
||||
"components/announcement.mdx": () =>
|
||||
import("../content/docs/components/announcement.mdx?collection=docs"),
|
||||
"components/app-switcher-navigation-bar.mdx": () =>
|
||||
import(
|
||||
"../content/docs/components/app-switcher-navigation-bar.mdx?collection=docs"
|
||||
),
|
||||
"components/apple-cards-carousel.mdx": () =>
|
||||
import(
|
||||
"../content/docs/components/apple-cards-carousel.mdx?collection=docs"
|
||||
),
|
||||
"components/apple-hello-effect.mdx": () =>
|
||||
import(
|
||||
"../content/docs/components/apple-hello-effect.mdx?collection=docs"
|
||||
),
|
||||
"components/aspect-ratio.mdx": () =>
|
||||
import("../content/docs/components/aspect-ratio.mdx?collection=docs"),
|
||||
"components/avatar-group.mdx": () =>
|
||||
import("../content/docs/components/avatar-group.mdx?collection=docs"),
|
||||
"components/avatar.mdx": () =>
|
||||
import("../content/docs/components/avatar.mdx?collection=docs"),
|
||||
"components/badge.mdx": () =>
|
||||
import("../content/docs/components/badge.mdx?collection=docs"),
|
||||
"components/banner.mdx": () =>
|
||||
import("../content/docs/components/banner.mdx?collection=docs"),
|
||||
"components/breadcrumb-and-filters-navigation-bar.mdx": () =>
|
||||
import(
|
||||
"../content/docs/components/breadcrumb-and-filters-navigation-bar.mdx?collection=docs"
|
||||
),
|
||||
"components/breadcrumb-navigation-bar.mdx": () =>
|
||||
import(
|
||||
"../content/docs/components/breadcrumb-navigation-bar.mdx?collection=docs"
|
||||
),
|
||||
"components/breadcrumb.mdx": () =>
|
||||
import("../content/docs/components/breadcrumb.mdx?collection=docs"),
|
||||
"components/button-group.mdx": () =>
|
||||
import("../content/docs/components/button-group.mdx?collection=docs"),
|
||||
"components/button.mdx": () =>
|
||||
import("../content/docs/components/button.mdx?collection=docs"),
|
||||
"components/calendar.mdx": () =>
|
||||
import("../content/docs/components/calendar.mdx?collection=docs"),
|
||||
"components/card.mdx": () =>
|
||||
import("../content/docs/components/card.mdx?collection=docs"),
|
||||
"components/carousel.mdx": () =>
|
||||
import("../content/docs/components/carousel.mdx?collection=docs"),
|
||||
"components/centered-logo-navigation-bar.mdx": () =>
|
||||
import(
|
||||
"../content/docs/components/centered-logo-navigation-bar.mdx?collection=docs"
|
||||
),
|
||||
"components/checkbox.mdx": () =>
|
||||
import("../content/docs/components/checkbox.mdx?collection=docs"),
|
||||
"components/choicebox.mdx": () =>
|
||||
import("../content/docs/components/choicebox.mdx?collection=docs"),
|
||||
"components/code-block.mdx": () =>
|
||||
import("../content/docs/components/code-block.mdx?collection=docs"),
|
||||
"components/code-editor.mdx": () =>
|
||||
import("../content/docs/components/code-editor.mdx?collection=docs"),
|
||||
"components/code-tabs.mdx": () =>
|
||||
import("../content/docs/components/code-tabs.mdx?collection=docs"),
|
||||
"components/collaboration-navigation-bar.mdx": () =>
|
||||
import(
|
||||
"../content/docs/components/collaboration-navigation-bar.mdx?collection=docs"
|
||||
),
|
||||
"components/collapsible.mdx": () =>
|
||||
import("../content/docs/components/collapsible.mdx?collection=docs"),
|
||||
"components/color-picker.mdx": () =>
|
||||
import("../content/docs/components/color-picker.mdx?collection=docs"),
|
||||
"components/combobox.mdx": () =>
|
||||
import("../content/docs/components/combobox.mdx?collection=docs"),
|
||||
"components/command.mdx": () =>
|
||||
import("../content/docs/components/command.mdx?collection=docs"),
|
||||
"components/communication-navigation-bar.mdx": () =>
|
||||
import(
|
||||
"../content/docs/components/communication-navigation-bar.mdx?collection=docs"
|
||||
),
|
||||
"components/comparison.mdx": () =>
|
||||
import("../content/docs/components/comparison.mdx?collection=docs"),
|
||||
"components/context-menu.mdx": () =>
|
||||
import("../content/docs/components/context-menu.mdx?collection=docs"),
|
||||
"components/context-switcher-navigation-bar.mdx": () =>
|
||||
import(
|
||||
"../content/docs/components/context-switcher-navigation-bar.mdx?collection=docs"
|
||||
),
|
||||
"components/credit-card.mdx": () =>
|
||||
import("../content/docs/components/credit-card.mdx?collection=docs"),
|
||||
"components/cursor.mdx": () =>
|
||||
import("../content/docs/components/cursor.mdx?collection=docs"),
|
||||
"components/dashboard-navigation-bar.mdx": () =>
|
||||
import(
|
||||
"../content/docs/components/dashboard-navigation-bar.mdx?collection=docs"
|
||||
),
|
||||
"components/data-table.mdx": () =>
|
||||
import("../content/docs/components/data-table.mdx?collection=docs"),
|
||||
"components/date-picker.mdx": () =>
|
||||
import("../content/docs/components/date-picker.mdx?collection=docs"),
|
||||
"components/desktop-hooks.mdx": () =>
|
||||
import("../content/docs/components/desktop-hooks.mdx?collection=docs"),
|
||||
"components/desktop-spotlight.mdx": () =>
|
||||
import(
|
||||
"../content/docs/components/desktop-spotlight.mdx?collection=docs"
|
||||
),
|
||||
"components/desktop-window.mdx": () =>
|
||||
import("../content/docs/components/desktop-window.mdx?collection=docs"),
|
||||
"components/desktop.mdx": () =>
|
||||
import("../content/docs/components/desktop.mdx?collection=docs"),
|
||||
"components/dialog-stack.mdx": () =>
|
||||
import("../content/docs/components/dialog-stack.mdx?collection=docs"),
|
||||
"components/dialog.mdx": () =>
|
||||
import("../content/docs/components/dialog.mdx?collection=docs"),
|
||||
"components/dock.mdx": () =>
|
||||
import("../content/docs/components/dock.mdx?collection=docs"),
|
||||
"components/drawer.mdx": () =>
|
||||
import("../content/docs/components/drawer.mdx?collection=docs"),
|
||||
"components/dropdown-menu.mdx": () =>
|
||||
import("../content/docs/components/dropdown-menu.mdx?collection=docs"),
|
||||
"components/dropzone.mdx": () =>
|
||||
import("../content/docs/components/dropzone.mdx?collection=docs"),
|
||||
"components/e-commerce-navigation-bar.mdx": () =>
|
||||
import(
|
||||
"../content/docs/components/e-commerce-navigation-bar.mdx?collection=docs"
|
||||
),
|
||||
"components/editor.mdx": () =>
|
||||
import("../content/docs/components/editor.mdx?collection=docs"),
|
||||
"components/empty.mdx": () =>
|
||||
import("../content/docs/components/empty.mdx?collection=docs"),
|
||||
"components/field.mdx": () =>
|
||||
import("../content/docs/components/field.mdx?collection=docs"),
|
||||
"components/form.mdx": () =>
|
||||
import("../content/docs/components/form.mdx?collection=docs"),
|
||||
"components/gantt.mdx": () =>
|
||||
import("../content/docs/components/gantt.mdx?collection=docs"),
|
||||
"components/glimpse.mdx": () =>
|
||||
import("../content/docs/components/glimpse.mdx?collection=docs"),
|
||||
"components/grid-pattern.mdx": () =>
|
||||
import("../content/docs/components/grid-pattern.mdx?collection=docs"),
|
||||
"components/hover-card.mdx": () =>
|
||||
import("../content/docs/components/hover-card.mdx?collection=docs"),
|
||||
"components/icon-navigation-bar.mdx": () =>
|
||||
import(
|
||||
"../content/docs/components/icon-navigation-bar.mdx?collection=docs"
|
||||
),
|
||||
"components/image-crop.mdx": () =>
|
||||
import("../content/docs/components/image-crop.mdx?collection=docs"),
|
||||
"components/image-zoom.mdx": () =>
|
||||
import("../content/docs/components/image-zoom.mdx?collection=docs"),
|
||||
"components/index.mdx": () =>
|
||||
import("../content/docs/components/index.mdx?collection=docs"),
|
||||
"components/input-group.mdx": () =>
|
||||
import("../content/docs/components/input-group.mdx?collection=docs"),
|
||||
"components/input-otp.mdx": () =>
|
||||
import("../content/docs/components/input-otp.mdx?collection=docs"),
|
||||
"components/input.mdx": () =>
|
||||
import("../content/docs/components/input.mdx?collection=docs"),
|
||||
"components/interactive-grid-pattern.mdx": () =>
|
||||
import(
|
||||
"../content/docs/components/interactive-grid-pattern.mdx?collection=docs"
|
||||
),
|
||||
"components/iphone-15-pro.mdx": () =>
|
||||
import("../content/docs/components/iphone-15-pro.mdx?collection=docs"),
|
||||
"components/item.mdx": () =>
|
||||
import("../content/docs/components/item.mdx?collection=docs"),
|
||||
"components/kanban.mdx": () =>
|
||||
import("../content/docs/components/kanban.mdx?collection=docs"),
|
||||
"components/kbd.mdx": () =>
|
||||
import("../content/docs/components/kbd.mdx?collection=docs"),
|
||||
"components/label.mdx": () =>
|
||||
import("../content/docs/components/label.mdx?collection=docs"),
|
||||
"components/limelight-nav.mdx": () =>
|
||||
import("../content/docs/components/limelight-nav.mdx?collection=docs"),
|
||||
"components/list.mdx": () =>
|
||||
import("../content/docs/components/list.mdx?collection=docs"),
|
||||
"components/macos-dock.mdx": () =>
|
||||
import("../content/docs/components/macos-dock.mdx?collection=docs"),
|
||||
"components/magnetic.mdx": () =>
|
||||
import("../content/docs/components/magnetic.mdx?collection=docs"),
|
||||
"components/marquee.mdx": () =>
|
||||
import("../content/docs/components/marquee.mdx?collection=docs"),
|
||||
"components/menu-dock.mdx": () =>
|
||||
import("../content/docs/components/menu-dock.mdx?collection=docs"),
|
||||
"components/menubar.mdx": () =>
|
||||
import("../content/docs/components/menubar.mdx?collection=docs"),
|
||||
"components/message-dock.mdx": () =>
|
||||
import("../content/docs/components/message-dock.mdx?collection=docs"),
|
||||
"components/mini-calendar.mdx": () =>
|
||||
import("../content/docs/components/mini-calendar.mdx?collection=docs"),
|
||||
"components/minimal-tiptap.mdx": () =>
|
||||
import("../content/docs/components/minimal-tiptap.mdx?collection=docs"),
|
||||
"components/motion-highlight.mdx": () =>
|
||||
import("../content/docs/components/motion-highlight.mdx?collection=docs"),
|
||||
"components/navigation-menu.mdx": () =>
|
||||
import("../content/docs/components/navigation-menu.mdx?collection=docs"),
|
||||
"components/pagination.mdx": () =>
|
||||
import("../content/docs/components/pagination.mdx?collection=docs"),
|
||||
"components/particles-background.mdx": () =>
|
||||
import(
|
||||
"../content/docs/components/particles-background.mdx?collection=docs"
|
||||
),
|
||||
"components/particles.mdx": () =>
|
||||
import("../content/docs/components/particles.mdx?collection=docs"),
|
||||
"components/pill.mdx": () =>
|
||||
import("../content/docs/components/pill.mdx?collection=docs"),
|
||||
"components/pin-list.mdx": () =>
|
||||
import("../content/docs/components/pin-list.mdx?collection=docs"),
|
||||
"components/pixel-image.mdx": () =>
|
||||
import("../content/docs/components/pixel-image.mdx?collection=docs"),
|
||||
"components/popover.mdx": () =>
|
||||
import("../content/docs/components/popover.mdx?collection=docs"),
|
||||
"components/progress.mdx": () =>
|
||||
import("../content/docs/components/progress.mdx?collection=docs"),
|
||||
"components/qr-code.mdx": () =>
|
||||
import("../content/docs/components/qr-code.mdx?collection=docs"),
|
||||
"components/radio-group.mdx": () =>
|
||||
import("../content/docs/components/radio-group.mdx?collection=docs"),
|
||||
"components/relative-time.mdx": () =>
|
||||
import("../content/docs/components/relative-time.mdx?collection=docs"),
|
||||
"components/resizable.mdx": () =>
|
||||
import("../content/docs/components/resizable.mdx?collection=docs"),
|
||||
"components/safari.mdx": () =>
|
||||
import("../content/docs/components/safari.mdx?collection=docs"),
|
||||
"components/sandbox.mdx": () =>
|
||||
import("../content/docs/components/sandbox.mdx?collection=docs"),
|
||||
"components/scroll-area.mdx": () =>
|
||||
import("../content/docs/components/scroll-area.mdx?collection=docs"),
|
||||
"components/search-and-toggle-navigation-bar.mdx": () =>
|
||||
import(
|
||||
"../content/docs/components/search-and-toggle-navigation-bar.mdx?collection=docs"
|
||||
),
|
||||
"components/select.mdx": () =>
|
||||
import("../content/docs/components/select.mdx?collection=docs"),
|
||||
"components/separator.mdx": () =>
|
||||
import("../content/docs/components/separator.mdx?collection=docs"),
|
||||
"components/sheet.mdx": () =>
|
||||
import("../content/docs/components/sheet.mdx?collection=docs"),
|
||||
"components/simple-navigation-bar.mdx": () =>
|
||||
import(
|
||||
"../content/docs/components/simple-navigation-bar.mdx?collection=docs"
|
||||
),
|
||||
"components/skeleton.mdx": () =>
|
||||
import("../content/docs/components/skeleton.mdx?collection=docs"),
|
||||
"components/slider.mdx": () =>
|
||||
import("../content/docs/components/slider.mdx?collection=docs"),
|
||||
"components/snippet.mdx": () =>
|
||||
import("../content/docs/components/snippet.mdx?collection=docs"),
|
||||
"components/sonner.mdx": () =>
|
||||
import("../content/docs/components/sonner.mdx?collection=docs"),
|
||||
"components/spinner.mdx": () =>
|
||||
import("../content/docs/components/spinner.mdx?collection=docs"),
|
||||
"components/stars-scrolling-wheel.mdx": () =>
|
||||
import(
|
||||
"../content/docs/components/stars-scrolling-wheel.mdx?collection=docs"
|
||||
),
|
||||
"components/status-dashboard-navigation-bar.mdx": () =>
|
||||
import(
|
||||
"../content/docs/components/status-dashboard-navigation-bar.mdx?collection=docs"
|
||||
),
|
||||
"components/status.mdx": () =>
|
||||
import("../content/docs/components/status.mdx?collection=docs"),
|
||||
"components/switch.mdx": () =>
|
||||
import("../content/docs/components/switch.mdx?collection=docs"),
|
||||
"components/table.mdx": () =>
|
||||
import("../content/docs/components/table.mdx?collection=docs"),
|
||||
"components/tabs.mdx": () =>
|
||||
import("../content/docs/components/tabs.mdx?collection=docs"),
|
||||
"components/tags.mdx": () =>
|
||||
import("../content/docs/components/tags.mdx?collection=docs"),
|
||||
"components/team-switcher-navigation-bar.mdx": () =>
|
||||
import(
|
||||
"../content/docs/components/team-switcher-navigation-bar.mdx?collection=docs"
|
||||
),
|
||||
"components/terminal.mdx": () =>
|
||||
import("../content/docs/components/terminal.mdx?collection=docs"),
|
||||
"components/textarea.mdx": () =>
|
||||
import("../content/docs/components/textarea.mdx?collection=docs"),
|
||||
"components/timeline.mdx": () =>
|
||||
import("../content/docs/components/timeline.mdx?collection=docs"),
|
||||
"components/toast.mdx": () =>
|
||||
import("../content/docs/components/toast.mdx?collection=docs"),
|
||||
"components/toggle-group.mdx": () =>
|
||||
import("../content/docs/components/toggle-group.mdx?collection=docs"),
|
||||
"components/toggle.mdx": () =>
|
||||
import("../content/docs/components/toggle.mdx?collection=docs"),
|
||||
"components/tooltip.mdx": () =>
|
||||
import("../content/docs/components/tooltip.mdx?collection=docs"),
|
||||
"components/two-tier-navigation-bar.mdx": () =>
|
||||
import(
|
||||
"../content/docs/components/two-tier-navigation-bar.mdx?collection=docs"
|
||||
),
|
||||
"components/typography.mdx": () =>
|
||||
import("../content/docs/components/typography.mdx?collection=docs"),
|
||||
"components/underline-navigation-bar.mdx": () =>
|
||||
import(
|
||||
"../content/docs/components/underline-navigation-bar.mdx?collection=docs"
|
||||
),
|
||||
"components/video-player.mdx": () =>
|
||||
import("../content/docs/components/video-player.mdx?collection=docs"),
|
||||
"white-label/index.mdx": () =>
|
||||
import("../content/docs/white-label/index.mdx?collection=docs"),
|
||||
"testing/index.mdx": () =>
|
||||
import("../content/docs/testing/index.mdx?collection=docs"),
|
||||
}),
|
||||
}
|
||||
export default browserCollections
|
||||
@@ -1,15 +0,0 @@
|
||||
// @ts-nocheck
|
||||
import { dynamic } from "@hanzo/docs-mdx/runtime/dynamic"
|
||||
|
||||
import * as Config from "../source.config"
|
||||
|
||||
const create = await dynamic<
|
||||
typeof Config,
|
||||
import("@hanzo/docs-mdx/runtime/types").InternalTypeConfig & {
|
||||
DocData: {}
|
||||
}
|
||||
>(
|
||||
Config,
|
||||
{ configPath: "source.config.ts", environment: "next", outDir: ".docs" },
|
||||
{ doc: { passthroughs: ["extractedReferences"] } }
|
||||
)
|
||||
@@ -1,393 +0,0 @@
|
||||
// @ts-nocheck
|
||||
import { server } from "@hanzo/docs-mdx/runtime/server"
|
||||
|
||||
import * as __fd_glob_0 from "../content/docs/about.mdx?collection=docs"
|
||||
import * as __fd_glob_9 from "../content/docs/ai/actions.mdx?collection=docs"
|
||||
import * as __fd_glob_10 from "../content/docs/ai/agents.mdx?collection=docs"
|
||||
import * as __fd_glob_11 from "../content/docs/ai/assistant.mdx?collection=docs"
|
||||
import * as __fd_glob_12 from "../content/docs/ai/chat.mdx?collection=docs"
|
||||
import * as __fd_glob_13 from "../content/docs/ai/code.mdx?collection=docs"
|
||||
import * as __fd_glob_14 from "../content/docs/ai/index.mdx?collection=docs"
|
||||
import * as __fd_glob_15 from "../content/docs/ai/models.mdx?collection=docs"
|
||||
import * as __fd_glob_16 from "../content/docs/ai/playground.mdx?collection=docs"
|
||||
import * as __fd_glob_17 from "../content/docs/ai/tools.mdx?collection=docs"
|
||||
import * as __fd_glob_18 from "../content/docs/ai/vision.mdx?collection=docs"
|
||||
import * as __fd_glob_19 from "../content/docs/ai/voice-settings.mdx?collection=docs"
|
||||
import * as __fd_glob_20 from "../content/docs/ai/voice.mdx?collection=docs"
|
||||
import * as __fd_glob_21 from "../content/docs/block-examples/billing.mdx?collection=docs"
|
||||
import * as __fd_glob_22 from "../content/docs/block-examples/compose-spec.mdx?collection=docs"
|
||||
import * as __fd_glob_23 from "../content/docs/block-examples/newsletter.mdx?collection=docs"
|
||||
import * as __fd_glob_24 from "../content/docs/block-examples/pricing.mdx?collection=docs"
|
||||
import * as __fd_glob_1 from "../content/docs/changelog.mdx?collection=docs"
|
||||
import * as __fd_glob_2 from "../content/docs/cli.mdx?collection=docs"
|
||||
import * as __fd_glob_3 from "../content/docs/components-json.mdx?collection=docs"
|
||||
import * as __fd_glob_4 from "../content/docs/components.mdx?collection=docs"
|
||||
import * as __fd_glob_46 from "../content/docs/components/3d-card.mdx?collection=docs"
|
||||
import * as __fd_glob_47 from "../content/docs/components/3d-marquee.mdx?collection=docs"
|
||||
import * as __fd_glob_48 from "../content/docs/components/3d-pin.mdx?collection=docs"
|
||||
import * as __fd_glob_49 from "../content/docs/components/accordion.mdx?collection=docs"
|
||||
import * as __fd_glob_50 from "../content/docs/components/advanced-navigation-bar.mdx?collection=docs"
|
||||
import * as __fd_glob_51 from "../content/docs/components/ai-model-selector-navigation-bar.mdx?collection=docs"
|
||||
import * as __fd_glob_52 from "../content/docs/components/alert-dialog.mdx?collection=docs"
|
||||
import * as __fd_glob_53 from "../content/docs/components/alert.mdx?collection=docs"
|
||||
import * as __fd_glob_54 from "../content/docs/components/android.mdx?collection=docs"
|
||||
import * as __fd_glob_55 from "../content/docs/components/animated-beam.mdx?collection=docs"
|
||||
import * as __fd_glob_56 from "../content/docs/components/animated-cursor.mdx?collection=docs"
|
||||
import * as __fd_glob_57 from "../content/docs/components/animated-testimonials.mdx?collection=docs"
|
||||
import * as __fd_glob_58 from "../content/docs/components/animated-tooltip.mdx?collection=docs"
|
||||
import * as __fd_glob_59 from "../content/docs/components/animation-components.mdx?collection=docs"
|
||||
import * as __fd_glob_60 from "../content/docs/components/announcement.mdx?collection=docs"
|
||||
import * as __fd_glob_61 from "../content/docs/components/app-switcher-navigation-bar.mdx?collection=docs"
|
||||
import * as __fd_glob_62 from "../content/docs/components/apple-cards-carousel.mdx?collection=docs"
|
||||
import * as __fd_glob_63 from "../content/docs/components/apple-hello-effect.mdx?collection=docs"
|
||||
import * as __fd_glob_64 from "../content/docs/components/aspect-ratio.mdx?collection=docs"
|
||||
import * as __fd_glob_65 from "../content/docs/components/avatar-group.mdx?collection=docs"
|
||||
import * as __fd_glob_66 from "../content/docs/components/avatar.mdx?collection=docs"
|
||||
import * as __fd_glob_67 from "../content/docs/components/badge.mdx?collection=docs"
|
||||
import * as __fd_glob_68 from "../content/docs/components/banner.mdx?collection=docs"
|
||||
import * as __fd_glob_69 from "../content/docs/components/breadcrumb-and-filters-navigation-bar.mdx?collection=docs"
|
||||
import * as __fd_glob_70 from "../content/docs/components/breadcrumb-navigation-bar.mdx?collection=docs"
|
||||
import * as __fd_glob_71 from "../content/docs/components/breadcrumb.mdx?collection=docs"
|
||||
import * as __fd_glob_72 from "../content/docs/components/button-group.mdx?collection=docs"
|
||||
import * as __fd_glob_73 from "../content/docs/components/button.mdx?collection=docs"
|
||||
import * as __fd_glob_74 from "../content/docs/components/calendar.mdx?collection=docs"
|
||||
import * as __fd_glob_75 from "../content/docs/components/card.mdx?collection=docs"
|
||||
import * as __fd_glob_76 from "../content/docs/components/carousel.mdx?collection=docs"
|
||||
import * as __fd_glob_77 from "../content/docs/components/centered-logo-navigation-bar.mdx?collection=docs"
|
||||
import * as __fd_glob_78 from "../content/docs/components/checkbox.mdx?collection=docs"
|
||||
import * as __fd_glob_79 from "../content/docs/components/choicebox.mdx?collection=docs"
|
||||
import * as __fd_glob_80 from "../content/docs/components/code-block.mdx?collection=docs"
|
||||
import * as __fd_glob_81 from "../content/docs/components/code-editor.mdx?collection=docs"
|
||||
import * as __fd_glob_82 from "../content/docs/components/code-tabs.mdx?collection=docs"
|
||||
import * as __fd_glob_83 from "../content/docs/components/collaboration-navigation-bar.mdx?collection=docs"
|
||||
import * as __fd_glob_84 from "../content/docs/components/collapsible.mdx?collection=docs"
|
||||
import * as __fd_glob_85 from "../content/docs/components/color-picker.mdx?collection=docs"
|
||||
import * as __fd_glob_86 from "../content/docs/components/combobox.mdx?collection=docs"
|
||||
import * as __fd_glob_87 from "../content/docs/components/command.mdx?collection=docs"
|
||||
import * as __fd_glob_88 from "../content/docs/components/communication-navigation-bar.mdx?collection=docs"
|
||||
import * as __fd_glob_89 from "../content/docs/components/comparison.mdx?collection=docs"
|
||||
import * as __fd_glob_90 from "../content/docs/components/context-menu.mdx?collection=docs"
|
||||
import * as __fd_glob_91 from "../content/docs/components/context-switcher-navigation-bar.mdx?collection=docs"
|
||||
import * as __fd_glob_92 from "../content/docs/components/credit-card.mdx?collection=docs"
|
||||
import * as __fd_glob_93 from "../content/docs/components/cursor.mdx?collection=docs"
|
||||
import * as __fd_glob_94 from "../content/docs/components/dashboard-navigation-bar.mdx?collection=docs"
|
||||
import * as __fd_glob_95 from "../content/docs/components/data-table.mdx?collection=docs"
|
||||
import * as __fd_glob_96 from "../content/docs/components/date-picker.mdx?collection=docs"
|
||||
import * as __fd_glob_97 from "../content/docs/components/desktop-hooks.mdx?collection=docs"
|
||||
import * as __fd_glob_98 from "../content/docs/components/desktop-spotlight.mdx?collection=docs"
|
||||
import * as __fd_glob_99 from "../content/docs/components/desktop-window.mdx?collection=docs"
|
||||
import * as __fd_glob_100 from "../content/docs/components/desktop.mdx?collection=docs"
|
||||
import * as __fd_glob_101 from "../content/docs/components/dialog-stack.mdx?collection=docs"
|
||||
import * as __fd_glob_102 from "../content/docs/components/dialog.mdx?collection=docs"
|
||||
import * as __fd_glob_103 from "../content/docs/components/dock.mdx?collection=docs"
|
||||
import * as __fd_glob_104 from "../content/docs/components/drawer.mdx?collection=docs"
|
||||
import * as __fd_glob_105 from "../content/docs/components/dropdown-menu.mdx?collection=docs"
|
||||
import * as __fd_glob_106 from "../content/docs/components/dropzone.mdx?collection=docs"
|
||||
import * as __fd_glob_107 from "../content/docs/components/e-commerce-navigation-bar.mdx?collection=docs"
|
||||
import * as __fd_glob_108 from "../content/docs/components/editor.mdx?collection=docs"
|
||||
import * as __fd_glob_109 from "../content/docs/components/empty.mdx?collection=docs"
|
||||
import * as __fd_glob_110 from "../content/docs/components/field.mdx?collection=docs"
|
||||
import * as __fd_glob_111 from "../content/docs/components/form.mdx?collection=docs"
|
||||
import * as __fd_glob_112 from "../content/docs/components/gantt.mdx?collection=docs"
|
||||
import * as __fd_glob_113 from "../content/docs/components/glimpse.mdx?collection=docs"
|
||||
import * as __fd_glob_114 from "../content/docs/components/grid-pattern.mdx?collection=docs"
|
||||
import * as __fd_glob_115 from "../content/docs/components/hover-card.mdx?collection=docs"
|
||||
import * as __fd_glob_116 from "../content/docs/components/icon-navigation-bar.mdx?collection=docs"
|
||||
import * as __fd_glob_117 from "../content/docs/components/image-crop.mdx?collection=docs"
|
||||
import * as __fd_glob_118 from "../content/docs/components/image-zoom.mdx?collection=docs"
|
||||
import * as __fd_glob_119 from "../content/docs/components/index.mdx?collection=docs"
|
||||
import * as __fd_glob_120 from "../content/docs/components/input-group.mdx?collection=docs"
|
||||
import * as __fd_glob_121 from "../content/docs/components/input-otp.mdx?collection=docs"
|
||||
import * as __fd_glob_122 from "../content/docs/components/input.mdx?collection=docs"
|
||||
import * as __fd_glob_123 from "../content/docs/components/interactive-grid-pattern.mdx?collection=docs"
|
||||
import * as __fd_glob_124 from "../content/docs/components/iphone-15-pro.mdx?collection=docs"
|
||||
import * as __fd_glob_125 from "../content/docs/components/item.mdx?collection=docs"
|
||||
import * as __fd_glob_126 from "../content/docs/components/kanban.mdx?collection=docs"
|
||||
import * as __fd_glob_127 from "../content/docs/components/kbd.mdx?collection=docs"
|
||||
import * as __fd_glob_128 from "../content/docs/components/label.mdx?collection=docs"
|
||||
import * as __fd_glob_129 from "../content/docs/components/limelight-nav.mdx?collection=docs"
|
||||
import * as __fd_glob_130 from "../content/docs/components/list.mdx?collection=docs"
|
||||
import * as __fd_glob_131 from "../content/docs/components/macos-dock.mdx?collection=docs"
|
||||
import * as __fd_glob_132 from "../content/docs/components/magnetic.mdx?collection=docs"
|
||||
import * as __fd_glob_133 from "../content/docs/components/marquee.mdx?collection=docs"
|
||||
import * as __fd_glob_134 from "../content/docs/components/menu-dock.mdx?collection=docs"
|
||||
import * as __fd_glob_135 from "../content/docs/components/menubar.mdx?collection=docs"
|
||||
import * as __fd_glob_136 from "../content/docs/components/message-dock.mdx?collection=docs"
|
||||
import * as __fd_glob_137 from "../content/docs/components/mini-calendar.mdx?collection=docs"
|
||||
import * as __fd_glob_138 from "../content/docs/components/minimal-tiptap.mdx?collection=docs"
|
||||
import * as __fd_glob_139 from "../content/docs/components/motion-highlight.mdx?collection=docs"
|
||||
import * as __fd_glob_140 from "../content/docs/components/navigation-menu.mdx?collection=docs"
|
||||
import * as __fd_glob_141 from "../content/docs/components/pagination.mdx?collection=docs"
|
||||
import * as __fd_glob_142 from "../content/docs/components/particles-background.mdx?collection=docs"
|
||||
import * as __fd_glob_143 from "../content/docs/components/particles.mdx?collection=docs"
|
||||
import * as __fd_glob_144 from "../content/docs/components/pill.mdx?collection=docs"
|
||||
import * as __fd_glob_145 from "../content/docs/components/pin-list.mdx?collection=docs"
|
||||
import * as __fd_glob_146 from "../content/docs/components/pixel-image.mdx?collection=docs"
|
||||
import * as __fd_glob_147 from "../content/docs/components/popover.mdx?collection=docs"
|
||||
import * as __fd_glob_148 from "../content/docs/components/progress.mdx?collection=docs"
|
||||
import * as __fd_glob_149 from "../content/docs/components/qr-code.mdx?collection=docs"
|
||||
import * as __fd_glob_150 from "../content/docs/components/radio-group.mdx?collection=docs"
|
||||
import * as __fd_glob_151 from "../content/docs/components/relative-time.mdx?collection=docs"
|
||||
import * as __fd_glob_152 from "../content/docs/components/resizable.mdx?collection=docs"
|
||||
import * as __fd_glob_153 from "../content/docs/components/safari.mdx?collection=docs"
|
||||
import * as __fd_glob_154 from "../content/docs/components/sandbox.mdx?collection=docs"
|
||||
import * as __fd_glob_155 from "../content/docs/components/scroll-area.mdx?collection=docs"
|
||||
import * as __fd_glob_156 from "../content/docs/components/search-and-toggle-navigation-bar.mdx?collection=docs"
|
||||
import * as __fd_glob_157 from "../content/docs/components/select.mdx?collection=docs"
|
||||
import * as __fd_glob_158 from "../content/docs/components/separator.mdx?collection=docs"
|
||||
import * as __fd_glob_159 from "../content/docs/components/sheet.mdx?collection=docs"
|
||||
import * as __fd_glob_160 from "../content/docs/components/simple-navigation-bar.mdx?collection=docs"
|
||||
import * as __fd_glob_161 from "../content/docs/components/skeleton.mdx?collection=docs"
|
||||
import * as __fd_glob_162 from "../content/docs/components/slider.mdx?collection=docs"
|
||||
import * as __fd_glob_163 from "../content/docs/components/snippet.mdx?collection=docs"
|
||||
import * as __fd_glob_164 from "../content/docs/components/sonner.mdx?collection=docs"
|
||||
import * as __fd_glob_165 from "../content/docs/components/spinner.mdx?collection=docs"
|
||||
import * as __fd_glob_166 from "../content/docs/components/stars-scrolling-wheel.mdx?collection=docs"
|
||||
import * as __fd_glob_167 from "../content/docs/components/status-dashboard-navigation-bar.mdx?collection=docs"
|
||||
import * as __fd_glob_168 from "../content/docs/components/status.mdx?collection=docs"
|
||||
import * as __fd_glob_169 from "../content/docs/components/switch.mdx?collection=docs"
|
||||
import * as __fd_glob_170 from "../content/docs/components/table.mdx?collection=docs"
|
||||
import * as __fd_glob_171 from "../content/docs/components/tabs.mdx?collection=docs"
|
||||
import * as __fd_glob_172 from "../content/docs/components/tags.mdx?collection=docs"
|
||||
import * as __fd_glob_173 from "../content/docs/components/team-switcher-navigation-bar.mdx?collection=docs"
|
||||
import * as __fd_glob_174 from "../content/docs/components/terminal.mdx?collection=docs"
|
||||
import * as __fd_glob_175 from "../content/docs/components/textarea.mdx?collection=docs"
|
||||
import * as __fd_glob_176 from "../content/docs/components/timeline.mdx?collection=docs"
|
||||
import * as __fd_glob_177 from "../content/docs/components/toast.mdx?collection=docs"
|
||||
import * as __fd_glob_178 from "../content/docs/components/toggle-group.mdx?collection=docs"
|
||||
import * as __fd_glob_179 from "../content/docs/components/toggle.mdx?collection=docs"
|
||||
import * as __fd_glob_180 from "../content/docs/components/tooltip.mdx?collection=docs"
|
||||
import * as __fd_glob_181 from "../content/docs/components/two-tier-navigation-bar.mdx?collection=docs"
|
||||
import * as __fd_glob_182 from "../content/docs/components/typography.mdx?collection=docs"
|
||||
import * as __fd_glob_183 from "../content/docs/components/underline-navigation-bar.mdx?collection=docs"
|
||||
import * as __fd_glob_184 from "../content/docs/components/video-player.mdx?collection=docs"
|
||||
import * as __fd_glob_28 from "../content/docs/dark-mode/astro.mdx?collection=docs"
|
||||
import * as __fd_glob_29 from "../content/docs/dark-mode/index.mdx?collection=docs"
|
||||
import * as __fd_glob_30 from "../content/docs/dark-mode/next.mdx?collection=docs"
|
||||
import * as __fd_glob_31 from "../content/docs/dark-mode/remix.mdx?collection=docs"
|
||||
import * as __fd_glob_32 from "../content/docs/dark-mode/vite.mdx?collection=docs"
|
||||
import * as __fd_glob_5 from "../content/docs/figma.mdx?collection=docs"
|
||||
import * as __fd_glob_25 from "../content/docs/frameworks/index.mdx?collection=docs"
|
||||
import * as __fd_glob_26 from "../content/docs/frameworks/react.mdx?collection=docs"
|
||||
import * as __fd_glob_27 from "../content/docs/frameworks/vue.mdx?collection=docs"
|
||||
import * as __fd_glob_33 from "../content/docs/guides/page-builder.mdx?collection=docs"
|
||||
import * as __fd_glob_34 from "../content/docs/guides/visual-workflows.mdx?collection=docs"
|
||||
import * as __fd_glob_35 from "../content/docs/hanzo-react/getting-started.mdx?collection=docs"
|
||||
import * as __fd_glob_36 from "../content/docs/hanzo-react/hooks.mdx?collection=docs"
|
||||
import * as __fd_glob_6 from "../content/docs/index.mdx?collection=docs"
|
||||
import * as __fd_glob_37 from "../content/docs/installation/astro.mdx?collection=docs"
|
||||
import * as __fd_glob_38 from "../content/docs/installation/gatsby.mdx?collection=docs"
|
||||
import * as __fd_glob_39 from "../content/docs/installation/index.mdx?collection=docs"
|
||||
import * as __fd_glob_40 from "../content/docs/installation/laravel.mdx?collection=docs"
|
||||
import * as __fd_glob_41 from "../content/docs/installation/manual.mdx?collection=docs"
|
||||
import * as __fd_glob_42 from "../content/docs/installation/next.mdx?collection=docs"
|
||||
import * as __fd_glob_43 from "../content/docs/installation/remix.mdx?collection=docs"
|
||||
import * as __fd_glob_44 from "../content/docs/installation/vite.mdx?collection=docs"
|
||||
import * as __fd_glob_7 from "../content/docs/mcp.mdx?collection=docs"
|
||||
import * as __fd_glob_45 from "../content/docs/packages/index.mdx?collection=docs"
|
||||
import * as __fd_glob_186 from "../content/docs/testing/index.mdx?collection=docs"
|
||||
import * as __fd_glob_8 from "../content/docs/theming.mdx?collection=docs"
|
||||
import * as __fd_glob_185 from "../content/docs/white-label/index.mdx?collection=docs"
|
||||
import type * as Config from "../source.config"
|
||||
|
||||
const create = server<
|
||||
typeof Config,
|
||||
import("@hanzo/docs-mdx/runtime/types").InternalTypeConfig & {
|
||||
DocData: {}
|
||||
}
|
||||
>({ doc: { passthroughs: ["extractedReferences"] } })
|
||||
|
||||
export const docs = await create.docs(
|
||||
"docs",
|
||||
"content/docs",
|
||||
{},
|
||||
{
|
||||
"about.mdx": __fd_glob_0,
|
||||
"changelog.mdx": __fd_glob_1,
|
||||
"cli.mdx": __fd_glob_2,
|
||||
"components-json.mdx": __fd_glob_3,
|
||||
"components.mdx": __fd_glob_4,
|
||||
"figma.mdx": __fd_glob_5,
|
||||
"index.mdx": __fd_glob_6,
|
||||
"mcp.mdx": __fd_glob_7,
|
||||
"theming.mdx": __fd_glob_8,
|
||||
"ai/actions.mdx": __fd_glob_9,
|
||||
"ai/agents.mdx": __fd_glob_10,
|
||||
"ai/assistant.mdx": __fd_glob_11,
|
||||
"ai/chat.mdx": __fd_glob_12,
|
||||
"ai/code.mdx": __fd_glob_13,
|
||||
"ai/index.mdx": __fd_glob_14,
|
||||
"ai/models.mdx": __fd_glob_15,
|
||||
"ai/playground.mdx": __fd_glob_16,
|
||||
"ai/tools.mdx": __fd_glob_17,
|
||||
"ai/vision.mdx": __fd_glob_18,
|
||||
"ai/voice-settings.mdx": __fd_glob_19,
|
||||
"ai/voice.mdx": __fd_glob_20,
|
||||
"block-examples/billing.mdx": __fd_glob_21,
|
||||
"block-examples/compose-spec.mdx": __fd_glob_22,
|
||||
"block-examples/newsletter.mdx": __fd_glob_23,
|
||||
"block-examples/pricing.mdx": __fd_glob_24,
|
||||
"frameworks/index.mdx": __fd_glob_25,
|
||||
"frameworks/react.mdx": __fd_glob_26,
|
||||
"frameworks/vue.mdx": __fd_glob_27,
|
||||
"dark-mode/astro.mdx": __fd_glob_28,
|
||||
"dark-mode/index.mdx": __fd_glob_29,
|
||||
"dark-mode/next.mdx": __fd_glob_30,
|
||||
"dark-mode/remix.mdx": __fd_glob_31,
|
||||
"dark-mode/vite.mdx": __fd_glob_32,
|
||||
"guides/page-builder.mdx": __fd_glob_33,
|
||||
"guides/visual-workflows.mdx": __fd_glob_34,
|
||||
"hanzo-react/getting-started.mdx": __fd_glob_35,
|
||||
"hanzo-react/hooks.mdx": __fd_glob_36,
|
||||
"installation/astro.mdx": __fd_glob_37,
|
||||
"installation/gatsby.mdx": __fd_glob_38,
|
||||
"installation/index.mdx": __fd_glob_39,
|
||||
"installation/laravel.mdx": __fd_glob_40,
|
||||
"installation/manual.mdx": __fd_glob_41,
|
||||
"installation/next.mdx": __fd_glob_42,
|
||||
"installation/remix.mdx": __fd_glob_43,
|
||||
"installation/vite.mdx": __fd_glob_44,
|
||||
"packages/index.mdx": __fd_glob_45,
|
||||
"components/3d-card.mdx": __fd_glob_46,
|
||||
"components/3d-marquee.mdx": __fd_glob_47,
|
||||
"components/3d-pin.mdx": __fd_glob_48,
|
||||
"components/accordion.mdx": __fd_glob_49,
|
||||
"components/advanced-navigation-bar.mdx": __fd_glob_50,
|
||||
"components/ai-model-selector-navigation-bar.mdx": __fd_glob_51,
|
||||
"components/alert-dialog.mdx": __fd_glob_52,
|
||||
"components/alert.mdx": __fd_glob_53,
|
||||
"components/android.mdx": __fd_glob_54,
|
||||
"components/animated-beam.mdx": __fd_glob_55,
|
||||
"components/animated-cursor.mdx": __fd_glob_56,
|
||||
"components/animated-testimonials.mdx": __fd_glob_57,
|
||||
"components/animated-tooltip.mdx": __fd_glob_58,
|
||||
"components/animation-components.mdx": __fd_glob_59,
|
||||
"components/announcement.mdx": __fd_glob_60,
|
||||
"components/app-switcher-navigation-bar.mdx": __fd_glob_61,
|
||||
"components/apple-cards-carousel.mdx": __fd_glob_62,
|
||||
"components/apple-hello-effect.mdx": __fd_glob_63,
|
||||
"components/aspect-ratio.mdx": __fd_glob_64,
|
||||
"components/avatar-group.mdx": __fd_glob_65,
|
||||
"components/avatar.mdx": __fd_glob_66,
|
||||
"components/badge.mdx": __fd_glob_67,
|
||||
"components/banner.mdx": __fd_glob_68,
|
||||
"components/breadcrumb-and-filters-navigation-bar.mdx": __fd_glob_69,
|
||||
"components/breadcrumb-navigation-bar.mdx": __fd_glob_70,
|
||||
"components/breadcrumb.mdx": __fd_glob_71,
|
||||
"components/button-group.mdx": __fd_glob_72,
|
||||
"components/button.mdx": __fd_glob_73,
|
||||
"components/calendar.mdx": __fd_glob_74,
|
||||
"components/card.mdx": __fd_glob_75,
|
||||
"components/carousel.mdx": __fd_glob_76,
|
||||
"components/centered-logo-navigation-bar.mdx": __fd_glob_77,
|
||||
"components/checkbox.mdx": __fd_glob_78,
|
||||
"components/choicebox.mdx": __fd_glob_79,
|
||||
"components/code-block.mdx": __fd_glob_80,
|
||||
"components/code-editor.mdx": __fd_glob_81,
|
||||
"components/code-tabs.mdx": __fd_glob_82,
|
||||
"components/collaboration-navigation-bar.mdx": __fd_glob_83,
|
||||
"components/collapsible.mdx": __fd_glob_84,
|
||||
"components/color-picker.mdx": __fd_glob_85,
|
||||
"components/combobox.mdx": __fd_glob_86,
|
||||
"components/command.mdx": __fd_glob_87,
|
||||
"components/communication-navigation-bar.mdx": __fd_glob_88,
|
||||
"components/comparison.mdx": __fd_glob_89,
|
||||
"components/context-menu.mdx": __fd_glob_90,
|
||||
"components/context-switcher-navigation-bar.mdx": __fd_glob_91,
|
||||
"components/credit-card.mdx": __fd_glob_92,
|
||||
"components/cursor.mdx": __fd_glob_93,
|
||||
"components/dashboard-navigation-bar.mdx": __fd_glob_94,
|
||||
"components/data-table.mdx": __fd_glob_95,
|
||||
"components/date-picker.mdx": __fd_glob_96,
|
||||
"components/desktop-hooks.mdx": __fd_glob_97,
|
||||
"components/desktop-spotlight.mdx": __fd_glob_98,
|
||||
"components/desktop-window.mdx": __fd_glob_99,
|
||||
"components/desktop.mdx": __fd_glob_100,
|
||||
"components/dialog-stack.mdx": __fd_glob_101,
|
||||
"components/dialog.mdx": __fd_glob_102,
|
||||
"components/dock.mdx": __fd_glob_103,
|
||||
"components/drawer.mdx": __fd_glob_104,
|
||||
"components/dropdown-menu.mdx": __fd_glob_105,
|
||||
"components/dropzone.mdx": __fd_glob_106,
|
||||
"components/e-commerce-navigation-bar.mdx": __fd_glob_107,
|
||||
"components/editor.mdx": __fd_glob_108,
|
||||
"components/empty.mdx": __fd_glob_109,
|
||||
"components/field.mdx": __fd_glob_110,
|
||||
"components/form.mdx": __fd_glob_111,
|
||||
"components/gantt.mdx": __fd_glob_112,
|
||||
"components/glimpse.mdx": __fd_glob_113,
|
||||
"components/grid-pattern.mdx": __fd_glob_114,
|
||||
"components/hover-card.mdx": __fd_glob_115,
|
||||
"components/icon-navigation-bar.mdx": __fd_glob_116,
|
||||
"components/image-crop.mdx": __fd_glob_117,
|
||||
"components/image-zoom.mdx": __fd_glob_118,
|
||||
"components/index.mdx": __fd_glob_119,
|
||||
"components/input-group.mdx": __fd_glob_120,
|
||||
"components/input-otp.mdx": __fd_glob_121,
|
||||
"components/input.mdx": __fd_glob_122,
|
||||
"components/interactive-grid-pattern.mdx": __fd_glob_123,
|
||||
"components/iphone-15-pro.mdx": __fd_glob_124,
|
||||
"components/item.mdx": __fd_glob_125,
|
||||
"components/kanban.mdx": __fd_glob_126,
|
||||
"components/kbd.mdx": __fd_glob_127,
|
||||
"components/label.mdx": __fd_glob_128,
|
||||
"components/limelight-nav.mdx": __fd_glob_129,
|
||||
"components/list.mdx": __fd_glob_130,
|
||||
"components/macos-dock.mdx": __fd_glob_131,
|
||||
"components/magnetic.mdx": __fd_glob_132,
|
||||
"components/marquee.mdx": __fd_glob_133,
|
||||
"components/menu-dock.mdx": __fd_glob_134,
|
||||
"components/menubar.mdx": __fd_glob_135,
|
||||
"components/message-dock.mdx": __fd_glob_136,
|
||||
"components/mini-calendar.mdx": __fd_glob_137,
|
||||
"components/minimal-tiptap.mdx": __fd_glob_138,
|
||||
"components/motion-highlight.mdx": __fd_glob_139,
|
||||
"components/navigation-menu.mdx": __fd_glob_140,
|
||||
"components/pagination.mdx": __fd_glob_141,
|
||||
"components/particles-background.mdx": __fd_glob_142,
|
||||
"components/particles.mdx": __fd_glob_143,
|
||||
"components/pill.mdx": __fd_glob_144,
|
||||
"components/pin-list.mdx": __fd_glob_145,
|
||||
"components/pixel-image.mdx": __fd_glob_146,
|
||||
"components/popover.mdx": __fd_glob_147,
|
||||
"components/progress.mdx": __fd_glob_148,
|
||||
"components/qr-code.mdx": __fd_glob_149,
|
||||
"components/radio-group.mdx": __fd_glob_150,
|
||||
"components/relative-time.mdx": __fd_glob_151,
|
||||
"components/resizable.mdx": __fd_glob_152,
|
||||
"components/safari.mdx": __fd_glob_153,
|
||||
"components/sandbox.mdx": __fd_glob_154,
|
||||
"components/scroll-area.mdx": __fd_glob_155,
|
||||
"components/search-and-toggle-navigation-bar.mdx": __fd_glob_156,
|
||||
"components/select.mdx": __fd_glob_157,
|
||||
"components/separator.mdx": __fd_glob_158,
|
||||
"components/sheet.mdx": __fd_glob_159,
|
||||
"components/simple-navigation-bar.mdx": __fd_glob_160,
|
||||
"components/skeleton.mdx": __fd_glob_161,
|
||||
"components/slider.mdx": __fd_glob_162,
|
||||
"components/snippet.mdx": __fd_glob_163,
|
||||
"components/sonner.mdx": __fd_glob_164,
|
||||
"components/spinner.mdx": __fd_glob_165,
|
||||
"components/stars-scrolling-wheel.mdx": __fd_glob_166,
|
||||
"components/status-dashboard-navigation-bar.mdx": __fd_glob_167,
|
||||
"components/status.mdx": __fd_glob_168,
|
||||
"components/switch.mdx": __fd_glob_169,
|
||||
"components/table.mdx": __fd_glob_170,
|
||||
"components/tabs.mdx": __fd_glob_171,
|
||||
"components/tags.mdx": __fd_glob_172,
|
||||
"components/team-switcher-navigation-bar.mdx": __fd_glob_173,
|
||||
"components/terminal.mdx": __fd_glob_174,
|
||||
"components/textarea.mdx": __fd_glob_175,
|
||||
"components/timeline.mdx": __fd_glob_176,
|
||||
"components/toast.mdx": __fd_glob_177,
|
||||
"components/toggle-group.mdx": __fd_glob_178,
|
||||
"components/toggle.mdx": __fd_glob_179,
|
||||
"components/tooltip.mdx": __fd_glob_180,
|
||||
"components/two-tier-navigation-bar.mdx": __fd_glob_181,
|
||||
"components/typography.mdx": __fd_glob_182,
|
||||
"components/underline-navigation-bar.mdx": __fd_glob_183,
|
||||
"components/video-player.mdx": __fd_glob_184,
|
||||
"white-label/index.mdx": __fd_glob_185,
|
||||
"testing/index.mdx": __fd_glob_186,
|
||||
}
|
||||
)
|
||||
@@ -1,40 +0,0 @@
|
||||
// source.config.ts
|
||||
import {
|
||||
defineConfig,
|
||||
defineDocs,
|
||||
frontmatterSchema
|
||||
} from "@hanzo/docs-mdx/config";
|
||||
import rehypePrettyCode from "rehype-pretty-code";
|
||||
import { z } from "zod";
|
||||
var source_config_default = defineConfig({
|
||||
mdxOptions: {
|
||||
rehypePlugins: [
|
||||
[
|
||||
rehypePrettyCode,
|
||||
{
|
||||
theme: {
|
||||
dark: "github-dark",
|
||||
light: "github-light-default"
|
||||
},
|
||||
keepBackground: false
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
});
|
||||
var docs = defineDocs({
|
||||
dir: "content/docs",
|
||||
docs: {
|
||||
schema: frontmatterSchema.extend({
|
||||
links: z.object({
|
||||
doc: z.string().optional(),
|
||||
api: z.string().optional()
|
||||
}).optional(),
|
||||
toc: z.boolean().optional()
|
||||
})
|
||||
}
|
||||
});
|
||||
export {
|
||||
source_config_default as default,
|
||||
docs
|
||||
};
|
||||
@@ -1,18 +0,0 @@
|
||||
# .kodiak.toml
|
||||
version = 1
|
||||
|
||||
[merge]
|
||||
automerge_label = "automerge"
|
||||
require_automerge_label = true
|
||||
method = "squash"
|
||||
delete_branch_on_merge = true
|
||||
optimistic_updates = false
|
||||
prioritize_ready_to_merge = true
|
||||
notify_on_conflict = true
|
||||
|
||||
[merge.message]
|
||||
title = "pull_request_title"
|
||||
body = "pull_request_body"
|
||||
include_pr_number = true
|
||||
body_type = "markdown"
|
||||
strip_html_comments = true
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"hanzo-ui": {
|
||||
"command": "npx",
|
||||
"args": ["@hanzo/ui@latest", "mcp"],
|
||||
"env": {
|
||||
"REGISTRY_URL": "https://ui.hanzo.ai/registry/registry.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
dist
|
||||
node_modules
|
||||
.next
|
||||
build
|
||||
.contentlayer
|
||||
apps/web/pages/api/registry.json
|
||||
**/fixtures
|
||||
deprecated
|
||||
@@ -1,114 +0,0 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to the Hanzo UI project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [0.2.0] - 2025-11-04
|
||||
|
||||
### Added
|
||||
|
||||
#### Page Builder
|
||||
- **Visual Page Builder** (`/builder`) - Drag-and-drop interface for assembling pages from blocks
|
||||
- Filterable block library with 1/4 scale previews
|
||||
- Drag-to-reorder blocks in canvas
|
||||
- Viewport controls (Desktop/Tablet/Mobile) for responsive preview
|
||||
- Export to React TSX code
|
||||
- Copy code to clipboard
|
||||
- Download as .tsx file
|
||||
- Deploy with Hanzo integration
|
||||
- Real-time block rendering with dynamic imports
|
||||
- Error handling and loading states for blocks
|
||||
|
||||
#### Syntax Highlighting
|
||||
- **Shiki Integration** - Build-time syntax highlighting via Fumadocs
|
||||
- GitHub Light/Dark themes for automatic light/dark mode support
|
||||
- Multi-color syntax highlighting for all code blocks
|
||||
- Copy-to-clipboard button on code blocks
|
||||
- Line numbers and line highlighting support
|
||||
- File name labels via meta strings
|
||||
- Zero client-side JavaScript (all highlighting is static HTML)
|
||||
|
||||
#### New Blocks
|
||||
- **Newsletter Blocks** (5 variants)
|
||||
- `newsletter-01` - Simple newsletter signup
|
||||
- `newsletter-02` - Newsletter with description
|
||||
- `newsletter-03` - Newsletter with image
|
||||
- `newsletter-04` - Newsletter with testimonial
|
||||
- `newsletter-05` - Full-width newsletter section
|
||||
- **Blog Cards** - Modern blog post card layout
|
||||
- **Photo Grid** - Responsive photo gallery grid
|
||||
- **Showcase Blocks**
|
||||
- `showcase-marketing-01` - Marketing website showcase
|
||||
- `showcase-ecommerce-01` - E-commerce site showcase
|
||||
- `showcase-app-ui-01` - App UI showcase
|
||||
|
||||
#### New AI Components
|
||||
- **AI Voice Settings** - Voice configuration interface for AI assistants
|
||||
- Voice model selection
|
||||
- Speed, pitch, and volume controls
|
||||
- Preview functionality
|
||||
- Settings persistence
|
||||
|
||||
#### New Documentation
|
||||
- **AI Components Documentation**
|
||||
- AI Actions guide
|
||||
- AI Agents guide
|
||||
- AI Assistant guide
|
||||
- AI Code editor guide
|
||||
- AI Models selector guide
|
||||
- AI Vision guide
|
||||
- AI Voice Settings guide
|
||||
- AI Voice guide
|
||||
- **Blocks Documentation**
|
||||
- Newsletter blocks guide with all variants
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Block Display Component** - Fixed registry access pattern for proper block rendering
|
||||
- Now uses `getBlock()` function with style parameter
|
||||
- Proper fallback to `BlockWrapper` for blocks
|
||||
- Correct handling of component vs block types
|
||||
- **Stub Components** - Fixed three blocks with missing `@hanzo/ui/billing` dependency
|
||||
- `invoice-manager-01` - Added placeholder with installation instructions
|
||||
- `payment-settings-01` - Added placeholder with installation instructions
|
||||
- `subscription-portal-01` - Added placeholder with installation instructions
|
||||
- **Dynamic Block Loading** - Replaced iframe-based previews with direct component rendering
|
||||
- Eliminates 404 errors in block previews
|
||||
- Better error handling and loading states
|
||||
- Proper scaling with transform CSS
|
||||
|
||||
### Changed
|
||||
|
||||
- **Page Builder Layout**
|
||||
- Reduced sidebar width from w-80 to w-64 for more canvas space
|
||||
- Removed right sidebar to maximize preview area
|
||||
- Changed block preview scale from 1/2 to 1/4 in sidebar
|
||||
- Removed gaps between stacked blocks using border-b pattern
|
||||
- Added viewport width controls for responsive testing
|
||||
|
||||
### Technical Improvements
|
||||
|
||||
- **MDX Configuration** - Enhanced with Shiki rehype plugin options
|
||||
- **MDX Components** - Integrated Fumadocs CodeBlock wrapper for code blocks
|
||||
- **Build Process** - Improved registry build with proper error handling
|
||||
- **Type Safety** - Updated component props to use React.ComponentPropsWithoutRef
|
||||
|
||||
## [0.1.0] - 2025-10-05
|
||||
|
||||
### Initial Release
|
||||
|
||||
- Base component library with 150+ components
|
||||
- Two theme variants (default, new-york)
|
||||
- 24+ viewport-sized blocks
|
||||
- Documentation site with MDX support
|
||||
- CLI for component installation
|
||||
- External registry support
|
||||
- Responsive design system
|
||||
- Dark mode support
|
||||
|
||||
---
|
||||
|
||||
[0.2.0]: https://github.com/hanzoai/ui/releases/tag/v0.2.0
|
||||
[0.1.0]: https://github.com/hanzoai/ui/releases/tag/v0.1.0
|
||||
@@ -1,254 +0,0 @@
|
||||
# Hanzo Identity Registration System
|
||||
|
||||
A decentralized identity registration system for Hanzo, Lux, and Zoo networks.
|
||||
|
||||
## Overview
|
||||
|
||||
The identity system allows users to claim unique identities across multiple blockchain networks with a tiered pricing structure based on name length.
|
||||
|
||||
## Features
|
||||
|
||||
- **Multi-Network Support**: Register identities on Hanzo, Lux, and Zoo (mainnet and testnet)
|
||||
- **Tiered Pricing**:
|
||||
- Single character (1): 100,000 AI
|
||||
- Two characters (2): 10,000 AI
|
||||
- Three characters (3): 1,000 AI
|
||||
- Four characters (4): 100 AI
|
||||
- Five or more (5+): 10 AI
|
||||
- **Referral System**: 50% discount with valid referrer
|
||||
- **Web3 Wallet Integration**: RainbowKit for seamless wallet connection
|
||||
- **Real-time Validation**: Check name availability and format validation
|
||||
- **Responsive UI**: Modern, accessible interface built with Radix UI
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
/Users/z/work/hanzo/ui/app/
|
||||
├── app/(app)/identity/page.tsx # Identity registration page
|
||||
├── components/web3-provider.tsx # Web3/Wagmi provider wrapper
|
||||
├── lib/
|
||||
│ ├── wagmi.ts # Wagmi config with custom networks
|
||||
│ └── contracts.ts # Contract ABIs and addresses
|
||||
└── .env.example # Environment variable template
|
||||
```
|
||||
|
||||
## Smart Contracts
|
||||
|
||||
Located in `/Users/z/work/lux/standard/src/`:
|
||||
|
||||
- **HanzoRegistry.sol**: Core identity registration contract
|
||||
- Manages identity claims
|
||||
- Handles staking requirements
|
||||
- Supports delegation and referrals
|
||||
- Implements reward distribution
|
||||
|
||||
- **AIToken.sol**: ERC20 governance token
|
||||
- Staking and vesting functionality
|
||||
- Voting power for governance
|
||||
- Deflationary burn mechanism
|
||||
|
||||
- **AIFaucet.sol**: Token distribution faucet
|
||||
- 100 AI tokens per authenticated user
|
||||
- 24-hour cooldown period
|
||||
- Daily limits and rate limiting
|
||||
|
||||
## Setup
|
||||
|
||||
### 1. Install Dependencies
|
||||
|
||||
```bash
|
||||
cd /Users/z/work/hanzo/ui/app
|
||||
pnpm install
|
||||
```
|
||||
|
||||
### 2. Configure Environment
|
||||
|
||||
Copy `.env.example` to `.env.local`:
|
||||
|
||||
```bash
|
||||
cp .env.example .env.local
|
||||
```
|
||||
|
||||
Get a WalletConnect Project ID from https://cloud.walletconnect.com/ and update:
|
||||
|
||||
```
|
||||
NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID=your_project_id_here
|
||||
```
|
||||
|
||||
### 3. Deploy Contracts
|
||||
|
||||
Deploy the smart contracts to your target networks:
|
||||
|
||||
```bash
|
||||
cd /Users/z/work/lux/standard
|
||||
|
||||
# For Hanzo Mainnet
|
||||
forge script script/DeployIdentitySystem.s.sol:DeployIdentitySystem \
|
||||
--rpc-url https://rpc.hanzo.ai \
|
||||
--broadcast
|
||||
|
||||
# For Hanzo Testnet
|
||||
forge script script/DeployIdentitySystem.s.sol:DeployIdentitySystem \
|
||||
--rpc-url https://testnet-rpc.hanzo.ai \
|
||||
--broadcast
|
||||
|
||||
# For Lux Mainnet
|
||||
forge script script/DeployIdentitySystem.s.sol:DeployIdentitySystem \
|
||||
--rpc-url https://api.lux.network/ext/bc/C/rpc \
|
||||
--broadcast
|
||||
|
||||
# For Zoo Mainnet
|
||||
forge script script/DeployIdentitySystem.s.sol:DeployIdentitySystem \
|
||||
--rpc-url https://rpc.zoo.network \
|
||||
--broadcast
|
||||
```
|
||||
|
||||
### 4. Update Contract Addresses
|
||||
|
||||
After deployment, update the contract addresses in `lib/contracts.ts`:
|
||||
|
||||
```typescript
|
||||
export const CONTRACT_ADDRESSES: Record<number, { registry: `0x${string}`; token: `0x${string}` }> = {
|
||||
36963: { // Hanzo Mainnet
|
||||
registry: '0xYourRegistryAddress',
|
||||
token: '0xYourTokenAddress',
|
||||
},
|
||||
// ... update other networks
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Run Development Server
|
||||
|
||||
```bash
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
Visit `http://localhost:3333/identity` to access the identity registration page.
|
||||
|
||||
## Networks
|
||||
|
||||
| Network | Chain ID | RPC URL | Explorer |
|
||||
|---------|----------|---------|----------|
|
||||
| Hanzo Mainnet | 36963 | https://rpc.hanzo.ai | https://explorer.hanzo.ai |
|
||||
| Hanzo Testnet | 36962 | https://testnet-rpc.hanzo.ai | https://testnet-explorer.hanzo.ai |
|
||||
| Lux Mainnet | 96369 | https://api.lux.network/ext/bc/C/rpc | https://explorer.lux.network |
|
||||
| Lux Testnet | 96368 | https://testnet-api.lux.network/ext/bc/C/rpc | https://testnet-explorer.lux.network |
|
||||
| Zoo Mainnet | 200200 | https://rpc.zoo.network | https://explorer.zoo.network |
|
||||
| Zoo Testnet | 200201 | https://testnet-rpc.zoo.network | https://testnet-explorer.zoo.network |
|
||||
|
||||
## Identity Format
|
||||
|
||||
Identities follow the format: `@username.network/profile/type/name`
|
||||
|
||||
- **Username**: Letters, numbers, dots, and underscores
|
||||
- **Network**: Automatically determined by connected chain
|
||||
- `.hanzo` or `.ai` for Hanzo mainnet
|
||||
- `.hanzo-testnet` for Hanzo testnet
|
||||
- `.lux` for Lux mainnet
|
||||
- `.lux-testnet` for Lux testnet
|
||||
- `.zoo` for Zoo mainnet
|
||||
- `.zoo-testnet` for Zoo testnet
|
||||
- **Profile**: Optional profile path (default: `/profile`)
|
||||
|
||||
## Faucet Integration
|
||||
|
||||
Users can get AI tokens from the faucet to register identities:
|
||||
|
||||
- **Location**: `/faucet` page
|
||||
- **Amount**: 100 AI tokens per authenticated user
|
||||
- **Cooldown**: 24 hours between drips
|
||||
- **Networks**: Hanzo mainnet and testnet only
|
||||
|
||||
Configuration in `/Users/z/work/hanzo/faucet/config.json`
|
||||
|
||||
## Tech Stack
|
||||
|
||||
- **Framework**: Next.js 16 (App Router)
|
||||
- **Styling**: Tailwind CSS 4
|
||||
- **UI Components**: Radix UI primitives
|
||||
- **Web3**: Wagmi, Viem, RainbowKit
|
||||
- **State Management**: TanStack Query
|
||||
- **Smart Contracts**: Solidity, Foundry
|
||||
- **Networks**: Hanzo, Lux, Zoo (mainnet + testnet)
|
||||
|
||||
## Development
|
||||
|
||||
### Testing Locally
|
||||
|
||||
1. Use a testnet for development (Hanzo Testnet recommended)
|
||||
2. Connect your wallet to the testnet
|
||||
3. Get test AI tokens from the faucet
|
||||
4. Test identity registration flow
|
||||
|
||||
### Building for Production
|
||||
|
||||
```bash
|
||||
pnpm build
|
||||
```
|
||||
|
||||
### Type Checking
|
||||
|
||||
```bash
|
||||
pnpm typecheck
|
||||
```
|
||||
|
||||
### Linting
|
||||
|
||||
```bash
|
||||
pnpm lint
|
||||
pnpm lint:fix
|
||||
```
|
||||
|
||||
## Contract Deployment Scripts
|
||||
|
||||
Located in `/Users/z/work/lux/standard/script/`:
|
||||
|
||||
- `DeployIdentitySystem.s.sol`: Deploys complete identity system
|
||||
- Deploys AIToken with treasury allocation
|
||||
- Deploys HanzoNft for identity binding
|
||||
- Deploys HanzoRegistry with UUPS proxy
|
||||
- Deploys AIFaucet with 100 AI drip amount
|
||||
- Configures initial permissions and transfers
|
||||
|
||||
## Security Considerations
|
||||
|
||||
- All contracts use OpenZeppelin's upgradeable patterns
|
||||
- Registry uses UUPS proxy for upgradeability
|
||||
- Access control with Ownable2Step for safe ownership transfer
|
||||
- Pausable functionality for emergency stops
|
||||
- Rate limiting on faucet to prevent abuse
|
||||
- Input validation for identity names
|
||||
- Proper allowance checks before token transfers
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Insufficient balance" error
|
||||
- Get AI tokens from the faucet at `/faucet`
|
||||
- Ensure you're connected to the correct network
|
||||
|
||||
### "Name already taken" error
|
||||
- Try a different identity name
|
||||
- Check if the name is available on your current network
|
||||
|
||||
### "Invalid format" error
|
||||
- Use only letters, numbers, dots, and underscores
|
||||
- No spaces or special characters allowed
|
||||
|
||||
### Contract not deployed
|
||||
- Ensure contracts are deployed to your connected network
|
||||
- Update contract addresses in `lib/contracts.ts`
|
||||
- Check RPC URL is correct for your network
|
||||
|
||||
## Contributing
|
||||
|
||||
When adding new features:
|
||||
|
||||
1. Update contracts in `/Users/z/work/lux/standard/src/`
|
||||
2. Update ABIs in `lib/contracts.ts`
|
||||
3. Update UI in `app/(app)/identity/page.tsx`
|
||||
4. Add tests for new functionality
|
||||
5. Update this README with new features
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
-551
@@ -1,551 +0,0 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Repository Overview
|
||||
|
||||
This is the **Hanzo UI App** - the documentation and showcase site for the @hanzo/ui component library. It's built with Next.js 15.3.1, React 19, and Fumadocs for documentation. The site serves as both documentation and a living demo of 149+ components.
|
||||
|
||||
**Key URLs:**
|
||||
- Production: https://ui.hanzo.ai
|
||||
- Local Dev: http://localhost:3003
|
||||
- Repo: github.com/hanzoai/ui (monorepo root is parent directory)
|
||||
|
||||
## Essential Commands
|
||||
|
||||
### Development
|
||||
```bash
|
||||
# Start dev server (port 3003)
|
||||
pnpm dev
|
||||
|
||||
# Build site (includes registry build + Next.js build)
|
||||
pnpm build
|
||||
|
||||
# Build registry only (generates JSON files for CLI)
|
||||
pnpm registry:build
|
||||
|
||||
# Start production server (port 3001)
|
||||
pnpm start
|
||||
```
|
||||
|
||||
### Code Quality
|
||||
```bash
|
||||
# Lint
|
||||
pnpm lint
|
||||
pnpm lint:fix
|
||||
|
||||
# Type check
|
||||
pnpm typecheck
|
||||
|
||||
# Format
|
||||
pnpm format:write
|
||||
pnpm format:check
|
||||
```
|
||||
|
||||
### Registry Management
|
||||
```bash
|
||||
# Validate external registries
|
||||
pnpm validate:registries
|
||||
|
||||
# Capture screenshots (for component previews)
|
||||
pnpm registry:capture
|
||||
```
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
### Three-Layer System
|
||||
|
||||
**1. Components** (`/registry/{default,new-york}/ui/`)
|
||||
- Single UI elements (Button, Input, Card, Dialog)
|
||||
- Reusable primitives that compose into larger UIs
|
||||
- Two theme variants: default and new-york
|
||||
- 149 components total (115 implemented, 34 stubs)
|
||||
|
||||
**2. Examples** (`/registry/{default,new-york}/example/`)
|
||||
- Usage demonstrations for each component
|
||||
- Shows component in isolation with typical props
|
||||
- Used in documentation via `<ComponentPreview name="..." />`
|
||||
|
||||
**3. Blocks** (`/registry/{default,new-york}/blocks/`)
|
||||
- Viewport-sized sections (Dashboard, Hero, Login, Pricing)
|
||||
- Compose multiple components into full-page layouts
|
||||
- Production-ready templates users can copy
|
||||
- 24+ blocks including dashboard-01, sidebar-07, login-03
|
||||
|
||||
### Registry Build System
|
||||
|
||||
The registry is the **core of the component distribution system**:
|
||||
|
||||
1. **Source Files**: Components in `/registry/{style}/ui/`
|
||||
2. **Build Script**: `scripts/build-registry.mts` reads components and generates:
|
||||
- JSON metadata files in `/public/registry/styles/{style}/{name}.json`
|
||||
- Includes dependencies, files, type info
|
||||
3. **CLI Consumption**: The `@hanzo/ui` CLI reads these JSON files to install components
|
||||
4. **Documentation**: MDX files in `/content/docs/components/` reference the registry
|
||||
|
||||
**CRITICAL**: Always run `pnpm registry:build` before building the app if you modify components.
|
||||
|
||||
### Documentation System
|
||||
|
||||
- **Framework**: Fumadocs (replaced Contentlayer in v4 migration)
|
||||
- **MDX Files**: `/content/docs/` contains all documentation
|
||||
- **Components in MDX**: Special components available in MDX via `mdx-components.tsx`:
|
||||
- `<ComponentPreview name="..." />` - Live component demo
|
||||
- `<ComponentSource name="..." />` - Component source code
|
||||
- `<CodeTabs>` - Installation method tabs (CLI/Manual)
|
||||
- `<Steps>` - Step-by-step instructions
|
||||
- **Navigation**: Configured in `/config/docs.ts`
|
||||
|
||||
### Page Builder (`/builder`)
|
||||
|
||||
New drag-drop visual builder for assembling pages from blocks:
|
||||
- **Left Sidebar**: Filterable block library
|
||||
- **Canvas**: Drag-drop assembly area with reordering
|
||||
- **Export**: Generates React TSX code
|
||||
- **Tech**: @dnd-kit for drag-drop, SortableContext for reordering
|
||||
|
||||
Access at `/builder` route.
|
||||
|
||||
## Key File Locations
|
||||
|
||||
### Component Development
|
||||
```
|
||||
registry/default/ui/{component}.tsx # Component implementation
|
||||
registry/default/example/{component}-demo.tsx # Usage example
|
||||
content/docs/components/{component}.mdx # Documentation
|
||||
```
|
||||
|
||||
### Configuration
|
||||
```
|
||||
config/site.ts # Site metadata, links
|
||||
config/docs.ts # Documentation navigation structure
|
||||
tailwind.config.cjs # Tailwind configuration
|
||||
components.json # hanzo CLI configuration
|
||||
```
|
||||
|
||||
### Build & Scripts
|
||||
```
|
||||
scripts/build-registry.mts # Registry JSON generator
|
||||
scripts/validate-registries.mts # External registry validator
|
||||
__registry__/index.tsx # Auto-generated registry index
|
||||
```
|
||||
|
||||
## Component Creation Workflow
|
||||
|
||||
### 1. Create Component Files
|
||||
```bash
|
||||
# Create in both variants
|
||||
touch registry/default/ui/my-component.tsx
|
||||
touch registry/new-york/ui/my-component.tsx
|
||||
|
||||
# Create demo
|
||||
touch registry/default/example/my-component-demo.tsx
|
||||
```
|
||||
|
||||
### 2. Implement Component
|
||||
Follow the standard pattern:
|
||||
```tsx
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
interface MyComponentProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
variant?: "default" | "outline"
|
||||
}
|
||||
|
||||
const MyComponent = React.forwardRef<HTMLDivElement, MyComponentProps>(
|
||||
({ className, variant = "default", ...props }, ref) => {
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn("base-classes", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
MyComponent.displayName = "MyComponent"
|
||||
|
||||
export { MyComponent }
|
||||
```
|
||||
|
||||
### 3. Create Documentation
|
||||
```bash
|
||||
# Create MDX file
|
||||
touch content/docs/components/my-component.mdx
|
||||
```
|
||||
|
||||
Include:
|
||||
- `<ComponentPreview name="my-component-demo" />`
|
||||
- Installation instructions with `<CodeTabs>`
|
||||
- Usage examples
|
||||
- API table with props
|
||||
|
||||
### 4. Add to Navigation
|
||||
Edit `config/docs.ts` to add component to sidebar navigation.
|
||||
|
||||
### 5. Build Registry
|
||||
```bash
|
||||
pnpm registry:build
|
||||
```
|
||||
|
||||
### 6. Test
|
||||
```bash
|
||||
pnpm dev
|
||||
# Visit http://localhost:3003/docs/components/my-component
|
||||
```
|
||||
|
||||
## Registry Architecture
|
||||
|
||||
The registry system enables the `npx hanzo-ui@latest add {component}` CLI workflow:
|
||||
|
||||
**Registry Entry Structure:**
|
||||
```json
|
||||
{
|
||||
"name": "button",
|
||||
"type": "components:ui",
|
||||
"files": ["registry/default/ui/button.tsx"],
|
||||
"dependencies": ["class-variance-authority"],
|
||||
"registryDependencies": ["utils"]
|
||||
}
|
||||
```
|
||||
|
||||
**External Registries** (`registries.json`):
|
||||
- 35+ external component sources
|
||||
- Examples: @aceternity, @magicui, @hanzo-editor
|
||||
- Validated via `pnpm validate:registries`
|
||||
|
||||
## Theme System
|
||||
|
||||
**CSS Variables** (`/styles/globals.css`):
|
||||
- Base theme colors defined in HSL
|
||||
- Primary color: `210 100% 50%` (electric blue)
|
||||
- Dark mode via `.dark` class
|
||||
- All components use `hsl(var(--primary))` pattern
|
||||
|
||||
**Two Visual Styles:**
|
||||
- **default**: More rounded, softer shadows
|
||||
- **new-york**: Flatter, sharper, minimal
|
||||
|
||||
Both styles share the same component API, only visual styling differs.
|
||||
|
||||
## Build System
|
||||
|
||||
### Production Build Process
|
||||
1. `pnpm registry:build` - Generate registry JSON files
|
||||
2. `next build` - Build Next.js app
|
||||
3. Static export to `/out` directory for GitHub Pages
|
||||
|
||||
### Local Development
|
||||
- Uses Turbopack for fast HMR
|
||||
- Port 3003 (configurable)
|
||||
- MDX hot reload via Fumadocs
|
||||
|
||||
## Component vs Block Distinction
|
||||
|
||||
**Components** (149 total):
|
||||
- Single-purpose UI elements
|
||||
- Examples: Button, Input, Card, Dialog, Dropdown
|
||||
- Used as building blocks
|
||||
- Installed individually via CLI
|
||||
|
||||
**Blocks** (24+ available):
|
||||
- Full viewport sections
|
||||
- Examples: dashboard-01 (admin layout), login-03 (auth page), sidebar-07
|
||||
- Compose multiple components
|
||||
- Ready-to-use page sections
|
||||
- Users copy entire blocks, not install via CLI
|
||||
|
||||
## Import Patterns
|
||||
|
||||
Components can be imported three ways:
|
||||
|
||||
**1. Package Import** (if using @hanzo/ui package):
|
||||
```tsx
|
||||
import { Button, Card } from "@hanzo/ui"
|
||||
```
|
||||
|
||||
**2. Registry Import** (in documentation/examples):
|
||||
```tsx
|
||||
import { Button } from "@/registry/default/ui/button"
|
||||
```
|
||||
|
||||
**3. Local Import** (after installing via CLI):
|
||||
```tsx
|
||||
import { Button } from "@/components/ui/button"
|
||||
```
|
||||
|
||||
The registry uses `@/registry/{style}/` which gets rewritten to `@/components/` during installation.
|
||||
|
||||
## MDX Component System
|
||||
|
||||
Components available in all MDX files:
|
||||
|
||||
- `<ComponentPreview name="button-demo" />` - Live demo
|
||||
- `<ComponentSource name="button" />` - Source code display
|
||||
- `<ComponentExample>` - Example wrapper
|
||||
- `<CodeTabs>` - Installation tabs (requires useConfig hook)
|
||||
- `<Steps>` / `<Step>` - Step-by-step instructions
|
||||
- `<Tabs>` / `<TabsList>` / `<TabsTrigger>` / `<TabsContent>` - Content tabs
|
||||
- `<Callout>` - Info/warning callouts
|
||||
|
||||
## Testing
|
||||
|
||||
No formal test suite currently. Testing is done via:
|
||||
1. Local dev server visual testing
|
||||
2. Build process catches type errors
|
||||
3. Registry validation ensures component metadata is correct
|
||||
|
||||
## Deployment
|
||||
|
||||
**GitHub Pages** (automatic):
|
||||
- Workflow: `.github/workflows/deploy-gh-pages.yml`
|
||||
- Triggers on push to main
|
||||
- Builds app + pkg/ui package
|
||||
- Deploys to ui.hanzo.ai
|
||||
- Requires: npm-run-all, del-cli for dependency builds
|
||||
|
||||
**Important**: The GitHub Actions workflow builds the `pkg/ui` package first, then the app, to ensure registry has latest component code.
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Stub Components
|
||||
Components marked for future implementation:
|
||||
```tsx
|
||||
export default function Component() {
|
||||
return (
|
||||
<div className="flex items-center justify-center p-8">
|
||||
<p className="text-muted-foreground">Component coming soon</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
Currently 34 stubs remaining (documented but not implemented).
|
||||
|
||||
### Component Dependencies
|
||||
- Radix UI primitives for accessible base components
|
||||
- `cn()` utility from `@/lib/utils` for className merging
|
||||
- `cva` (class-variance-authority) for variant management
|
||||
- Lucide React for icons
|
||||
- Framer Motion for animations (some components)
|
||||
|
||||
### Registry Dependencies
|
||||
Components can depend on other registry components:
|
||||
```json
|
||||
{
|
||||
"registryDependencies": ["button", "dialog", "utils"]
|
||||
}
|
||||
```
|
||||
|
||||
These are auto-installed when user adds the component.
|
||||
|
||||
## Recent Architectural Changes (2025-10-05)
|
||||
|
||||
1. **Migrated from Contentlayer to Fumadocs** for MDX processing
|
||||
2. **Upgraded to Next.js 15.3.1** with Turbopack
|
||||
3. **React 19** with new JSX transform
|
||||
4. **Added @dnd-kit** for page builder drag-drop
|
||||
5. **Synced with hanzo/ui v3.4.0** (7 new components: button-group, empty, field, input-group, item, kbd, spinner)
|
||||
6. **Electric blue primary color** (210 100% 50%)
|
||||
7. **Page builder feature** at `/builder` route
|
||||
|
||||
## Notes for AI Assistants
|
||||
|
||||
- **Registry must rebuild** after component changes: `pnpm registry:build`
|
||||
- **Two theme variants** must be kept in sync: default and new-york
|
||||
- **Import paths** in components use `@/registry/{style}/` which becomes `@/components/` after CLI install
|
||||
- **MDX components** are defined in `mdx-components.tsx`, not auto-imported
|
||||
- **Blocks are different** from components - they're full-page sections, not installable via CLI
|
||||
- **GitHub Pages deployment** requires building pkg/ui package first (workflow handles this)
|
||||
|
||||
## LLM API Route (Added 2025-11-05)
|
||||
|
||||
The `/llm` API route serves MDX documentation optimized for LLM consumption by replacing `<ComponentPreview>` tags with actual component source code.
|
||||
|
||||
### Files Created
|
||||
|
||||
**`/lib/llm.ts`**:
|
||||
- `processMdxForLLMs(content, style)` - Transforms MDX for LLMs
|
||||
- Finds `<ComponentPreview name="..." />` tags in MDX
|
||||
- Replaces with actual component source code in markdown code blocks
|
||||
- Rewrites import paths from `@/registry/${style}/` to `@/components/`
|
||||
- Removes `export default` in favor of named exports
|
||||
|
||||
**`/app/(app)/llm/[[...slug]]/route.ts`**:
|
||||
- API route handler using Next.js catch-all routing
|
||||
- `GET` handler that processes MDX files via `processMdxForLLMs()`
|
||||
- Returns Content-Type: `text/markdown; charset=utf-8`
|
||||
- Supports static generation via `generateStaticParams()`
|
||||
- Uses `revalidate = false` for static export
|
||||
|
||||
### Key Adaptations from shadcn/ui
|
||||
|
||||
1. **Dynamic Style Support**: Changed from hardcoded `@/registry/new-york-v4/` to dynamic `@/registry/${style}/`
|
||||
2. **Multi-Style System**: Works with both "default" and "new-york" themes
|
||||
3. **Active Style Detection**: Uses `getActiveStyle()` to determine current theme
|
||||
|
||||
### Usage
|
||||
|
||||
Access documentation via:
|
||||
```bash
|
||||
# Get LLM-optimized docs for a component
|
||||
curl http://localhost:3003/llm/docs/components/button
|
||||
|
||||
# Returns MDX with inline source code instead of component previews
|
||||
```
|
||||
|
||||
## Deviation Report: shadcn/ui v4 vs Hanzo UI
|
||||
|
||||
### Directory Structure Comparison
|
||||
|
||||
**Copied from shadcn/ui v4**:
|
||||
- ✅ `/blocks` - Block showcase page with filtering and search
|
||||
- ✅ `/charts` - Chart component demonstrations
|
||||
- ✅ `/colors` - Tailwind color palette reference
|
||||
- ✅ `/docs` - Component documentation
|
||||
- ✅ `/examples` - Component usage examples
|
||||
- ✅ `/llm` - LLM-optimized API endpoint
|
||||
- ✅ `/themes` - Theme customization page
|
||||
|
||||
**Hanzo-Specific Features** (not in shadcn):
|
||||
- 🆕 `/ai` - AI assistant integration page
|
||||
- 🆕 `/builder` - Visual page builder with drag-drop
|
||||
- 🆕 `/components` - Component category pages
|
||||
- 🆕 `/compose` - Composition editor
|
||||
- 🆕 `/health` - System health check
|
||||
- 🆕 `/mcp` - Model Context Protocol integration
|
||||
- 🆕 `/sink` - Kitchen sink / testing page
|
||||
- 🆕 `/theme-generator` - Interactive theme generator
|
||||
|
||||
### Key Implementation Differences
|
||||
|
||||
#### 1. Path Handling
|
||||
- **shadcn**: Hardcoded `@/registry/new-york-v4/` paths
|
||||
- **Hanzo**: Dynamic `@/registry/${style}/` supporting multiple themes
|
||||
|
||||
#### 2. Style System
|
||||
- **shadcn**: Primary style is "new-york-v4"
|
||||
- **Hanzo**: Primary style is "default", with "new-york" as alternative
|
||||
- **Impact**: All copied files had paths adapted: `new-york-v4` → `default`
|
||||
|
||||
#### 3. Branding
|
||||
- **shadcn**: GitHub logo, shadcn branding
|
||||
- **Hanzo**: Hanzo logo (OpenInHButton), Hanzo branding
|
||||
- **Files Modified**:
|
||||
- `components/open-in-hanzo-button.tsx` → `components/open-in-h-button.tsx`
|
||||
- Hanzo logo (H icon) replaces GitHub logo
|
||||
|
||||
#### 4. Registry Structure
|
||||
- **shadcn**: `Index[name]` - flat structure
|
||||
- **Hanzo**: `Index[style][name]` - nested by style
|
||||
- **Impact**: Required updates to registry accessor functions
|
||||
|
||||
#### 5. Component Count
|
||||
- **shadcn v4**: ~58 core components
|
||||
- **Hanzo**: 149 components (115 implemented, 34 stubs)
|
||||
- **Unique to Hanzo**: 3D components, AI components, animation components, specialized navigation
|
||||
|
||||
### Files Requiring Ongoing Sync
|
||||
|
||||
When shadcn/ui v4 updates these, Hanzo should review for updates:
|
||||
|
||||
1. **Layout Files**:
|
||||
- `app/(app)/blocks/layout.tsx`
|
||||
- `app/(app)/colors/layout.tsx`
|
||||
- `app/(app)/themes/layout.tsx`
|
||||
|
||||
2. **Page Files**:
|
||||
- `app/(app)/blocks/page.tsx`
|
||||
- `app/(app)/colors/page.tsx`
|
||||
- `app/(app)/themes/page.tsx`
|
||||
- `app/(app)/llm/[[...slug]]/route.ts`
|
||||
|
||||
3. **Supporting Components**:
|
||||
- `components/blocks-nav.tsx`
|
||||
- `components/colors-nav.tsx`
|
||||
- `components/theme-customizer.tsx`
|
||||
- `lib/llm.ts`
|
||||
|
||||
4. **Registry Functions**:
|
||||
- `lib/registry.ts` - Any changes to registry access patterns
|
||||
- `lib/blocks.ts` - Block metadata handling
|
||||
|
||||
### Known Issues Requiring Fixes
|
||||
|
||||
Per user feedback: "don't suppress errors just do shit right or cover up things"
|
||||
|
||||
**1. getActiveStyle Export Error** ✅ FIXED (2025-11-05):
|
||||
- **Was**: Server logs showed "Export getActiveStyle doesn't exist in target module"
|
||||
- **Root Cause**: Turbopack had issues with `async function getActiveStyle()` in `force-static` pages. The async nature conflicted with Turbopack's static analysis in force-static mode.
|
||||
- **Fix**: Made `getActiveStyle()` synchronous by removing `async` keyword
|
||||
- Updated `registry/styles.ts` line 14: removed `async` from function declaration
|
||||
- Updated `app/(app)/blocks/page.tsx`: removed `async` from function and `await` from call
|
||||
- Updated `app/(app)/llm/[[...slug]]/route.ts`: split Promise.all, called getActiveStyle() synchronously
|
||||
- **Result**: ✅ NO MORE EXPORT ERRORS - Pages load cleanly with HTTP 200, no server errors
|
||||
- **Note**: Function doesn't actually need to be async since it just returns styles[0]
|
||||
|
||||
**2. Client Component Boundary Warnings** ⚠️ LOW PRIORITY:
|
||||
- **Symptom**: "Functions cannot be passed directly to Client Components unless you explicitly expose it by marking it with 'use server'"
|
||||
- **Locations**: Multiple components in blocks, themes, colors pages (7 warnings with digest `638247047`)
|
||||
- **Status**: WARNINGS ONLY - Not breaking, pages render correctly with HTTP 200
|
||||
- **Impact**: No user-facing issues, functionality fully intact
|
||||
- **Note**: These warnings also appear in shadcn/ui v4, suggesting they may be framework-level issues
|
||||
- **Action**: Can investigate if desired, but not critical since pages work perfectly
|
||||
|
||||
**3. Tabs Hydration Issues** ✅ FIXED:
|
||||
- **Was**: Tabs component had hydration errors due to SSR/client mismatch
|
||||
- **Fix**: Updated `registry/default/ui/tabs.tsx` with proper refs and display names
|
||||
- **Status**: ✅ RESOLVED
|
||||
|
||||
**4. macOS Dock Demo SSR Error** ✅ FIXED (2025-11-06):
|
||||
- **Was**: Build error "Event handlers cannot be passed to Client Component props" on `/view/new-york/macos-dock-demo`
|
||||
- **Root Cause**: `macos-dock-demo` component has onClick event handlers on DockItem components. During static prerendering with `force-static`, Next.js tries to serialize props but can't serialize functions.
|
||||
- **Fix**: Added component to skip list in `generateStaticParams()` in `app/(view)/view/[name]/page.tsx`
|
||||
- Components with event handlers that can't be serialized are excluded from static generation
|
||||
- They can still be accessed dynamically at runtime
|
||||
- **Result**: ✅ Build completes successfully, no SSR errors
|
||||
- **Note**: Component works perfectly in dev mode and at runtime, just skips static prerendering
|
||||
|
||||
### Testing Coverage
|
||||
|
||||
**Pages Tested**:
|
||||
- ✅ `/blocks` - All 5 featured blocks display correctly
|
||||
- ✅ `/colors` - Full Tailwind palette with copy functionality
|
||||
- ✅ `/themes` - Theme customizer with live preview
|
||||
- ✅ `/llm` - API endpoint compiles and serves MDX
|
||||
|
||||
**Components Tested**:
|
||||
- ✅ `<BlockDisplay>` - Renders blocks correctly
|
||||
- ✅ `<BlocksNav>` - Navigation with search/filter
|
||||
- ✅ `<ColorsNav>` - Color palette navigation
|
||||
- ✅ `<OpenInHButton>` - Hanzo-branded button with logo
|
||||
- ✅ `<Tabs>` - No hydration errors
|
||||
|
||||
### Maintenance Strategy
|
||||
|
||||
**When to Sync with shadcn/ui v4**:
|
||||
1. Major version updates (v4.x → v4.y)
|
||||
2. New features in /blocks, /themes, /colors pages
|
||||
3. Security patches or bug fixes
|
||||
4. New component additions to showcase pages
|
||||
|
||||
**How to Sync**:
|
||||
1. Compare shadcn v4 file with Hanzo version
|
||||
2. Identify changes in logic (not just branding)
|
||||
3. Apply logic changes while preserving:
|
||||
- Hanzo branding (OpenInHButton, logos)
|
||||
- Path adaptations (new-york-v4 → default)
|
||||
- Registry structure differences (Index[style][name])
|
||||
4. Test thoroughly before deploying
|
||||
5. Document changes in CLAUDE.md
|
||||
|
||||
### Future Work
|
||||
|
||||
**Improvements Needed**:
|
||||
1. Fix getActiveStyle export error properly (not suppress)
|
||||
2. Resolve Client Component boundary warnings
|
||||
3. Add comprehensive test suite for copied pages
|
||||
4. Set up automated diff checking against shadcn v4
|
||||
5. Document all customizations in component headers
|
||||
@@ -1,75 +0,0 @@
|
||||
import Image from "next/image"
|
||||
import Link from "next/link"
|
||||
|
||||
import { Button } from "@/registry/default/ui/button"
|
||||
import { Input } from "@/registry/default/ui/input"
|
||||
import { Label } from "@/registry/default/ui/label"
|
||||
|
||||
export const description =
|
||||
"A login page with two columns. The first column has the login form with email and password. There's a Forgot your passwork link and a link to sign up if you do not have an account. The second column has a cover image."
|
||||
|
||||
export const iframeHeight = "800px"
|
||||
|
||||
export const containerClassName = "w-full h-full p-4 lg:p-0"
|
||||
|
||||
export default function Dashboard() {
|
||||
return (
|
||||
<div className="w-full lg:grid lg:min-h-[600px] lg:grid-cols-2 xl:min-h-[800px]">
|
||||
<div className="flex items-center justify-center py-12">
|
||||
<div className="mx-auto grid w-[350px] gap-6">
|
||||
<div className="grid gap-2 text-center">
|
||||
<h1 className="text-3xl font-bold">Login</h1>
|
||||
<p className="text-balance text-muted-foreground">
|
||||
Enter your email below to login to your account
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid gap-4">
|
||||
<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>
|
||||
<Link
|
||||
href="/forgot-password"
|
||||
className="ml-auto inline-block text-sm underline"
|
||||
>
|
||||
Forgot your password?
|
||||
</Link>
|
||||
</div>
|
||||
<Input id="password" type="password" required />
|
||||
</div>
|
||||
<Button type="submit" className="w-full">
|
||||
Login
|
||||
</Button>
|
||||
<Button variant="outline" className="w-full">
|
||||
Login with Google
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mt-4 text-center text-sm">
|
||||
Don't have an account?{" "}
|
||||
<Link href="#" className="underline">
|
||||
Sign up
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="hidden bg-muted lg:block">
|
||||
<Image
|
||||
src="/placeholder.svg"
|
||||
alt="Authentication page cover image"
|
||||
width="1920"
|
||||
height="1080"
|
||||
className="h-full w-full object-cover dark:brightness-[0.2] dark:grayscale"
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
|
||||
import { Calendar } from "@/registry/default/ui/calendar"
|
||||
|
||||
export default function Calendar01() {
|
||||
const [date, setDate] = React.useState<Date | undefined>(
|
||||
new Date(2025, 5, 12)
|
||||
)
|
||||
|
||||
return (
|
||||
<Calendar
|
||||
mode="single"
|
||||
defaultMonth={date}
|
||||
selected={date}
|
||||
onSelect={setDate}
|
||||
className="rounded-lg border shadow-sm"
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
|
||||
import { Calendar } from "@/registry/default/ui/calendar"
|
||||
|
||||
export default function Calendar02() {
|
||||
const [date, setDate] = React.useState<Date | undefined>(
|
||||
new Date(2025, 5, 12)
|
||||
)
|
||||
|
||||
return (
|
||||
<Calendar
|
||||
mode="single"
|
||||
defaultMonth={date}
|
||||
numberOfMonths={2}
|
||||
selected={date}
|
||||
onSelect={setDate}
|
||||
className="rounded-lg border shadow-sm"
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
|
||||
import { Calendar } from "@/registry/default/ui/calendar"
|
||||
|
||||
export default function Calendar03() {
|
||||
const [dates, setDates] = React.useState<Date[]>([
|
||||
new Date(2025, 5, 12),
|
||||
new Date(2025, 6, 24),
|
||||
])
|
||||
|
||||
return (
|
||||
<Calendar
|
||||
mode="multiple"
|
||||
numberOfMonths={2}
|
||||
defaultMonth={dates[0]}
|
||||
required
|
||||
selected={dates}
|
||||
onSelect={setDates}
|
||||
max={5}
|
||||
className="rounded-lg border shadow-sm"
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { type DateRange } from "react-day-picker"
|
||||
|
||||
import { Calendar } from "@/registry/default/ui/calendar"
|
||||
|
||||
export default function Calendar04() {
|
||||
const [dateRange, setDateRange] = React.useState<DateRange | undefined>({
|
||||
from: new Date(2025, 5, 9),
|
||||
to: new Date(2025, 5, 26),
|
||||
})
|
||||
|
||||
return (
|
||||
<Calendar
|
||||
mode="range"
|
||||
defaultMonth={dateRange?.from}
|
||||
selected={dateRange}
|
||||
onSelect={setDateRange}
|
||||
className="rounded-lg border shadow-sm"
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { type DateRange } from "react-day-picker"
|
||||
|
||||
import { Calendar } from "@/registry/default/ui/calendar"
|
||||
|
||||
export default function Calendar05() {
|
||||
const [dateRange, setDateRange] = React.useState<DateRange | undefined>({
|
||||
from: new Date(2025, 5, 12),
|
||||
to: new Date(2025, 6, 15),
|
||||
})
|
||||
|
||||
return (
|
||||
<Calendar
|
||||
mode="range"
|
||||
defaultMonth={dateRange?.from}
|
||||
selected={dateRange}
|
||||
onSelect={setDateRange}
|
||||
numberOfMonths={2}
|
||||
className="rounded-lg border shadow-sm"
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { type DateRange } from "react-day-picker"
|
||||
|
||||
import { Calendar } from "@/registry/default/ui/calendar"
|
||||
|
||||
export default function Calendar06() {
|
||||
const [dateRange, setDateRange] = React.useState<DateRange | undefined>({
|
||||
from: new Date(2025, 5, 12),
|
||||
to: new Date(2025, 5, 26),
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="flex min-w-0 flex-col gap-2">
|
||||
<Calendar
|
||||
mode="range"
|
||||
defaultMonth={dateRange?.from}
|
||||
selected={dateRange}
|
||||
onSelect={setDateRange}
|
||||
numberOfMonths={1}
|
||||
min={5}
|
||||
className="rounded-lg border shadow-sm"
|
||||
/>
|
||||
<div className="text-muted-foreground text-center text-xs">
|
||||
A minimum of 5 days is required
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { type DateRange } from "react-day-picker"
|
||||
|
||||
import { Calendar } from "@/registry/default/ui/calendar"
|
||||
|
||||
export default function Calendar07() {
|
||||
const [dateRange, setDateRange] = React.useState<DateRange | undefined>({
|
||||
from: new Date(2025, 5, 18),
|
||||
to: new Date(2025, 6, 7),
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="flex min-w-0 flex-col gap-2">
|
||||
<Calendar
|
||||
mode="range"
|
||||
defaultMonth={dateRange?.from}
|
||||
selected={dateRange}
|
||||
onSelect={setDateRange}
|
||||
numberOfMonths={2}
|
||||
min={2}
|
||||
max={20}
|
||||
className="rounded-lg border shadow-sm"
|
||||
/>
|
||||
<div className="text-muted-foreground text-center text-xs">
|
||||
Your stay must be between 2 and 20 nights
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
|
||||
import { Calendar } from "@/registry/default/ui/calendar"
|
||||
|
||||
export default function Calendar08() {
|
||||
const [date, setDate] = React.useState<Date | undefined>(
|
||||
new Date(2025, 5, 12)
|
||||
)
|
||||
|
||||
return (
|
||||
<Calendar
|
||||
mode="single"
|
||||
defaultMonth={date}
|
||||
selected={date}
|
||||
onSelect={setDate}
|
||||
disabled={{
|
||||
before: new Date(2025, 5, 12),
|
||||
}}
|
||||
className="rounded-lg border shadow-sm"
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { type DateRange } from "react-day-picker"
|
||||
|
||||
import { Calendar } from "@/registry/default/ui/calendar"
|
||||
|
||||
export default function Calendar09() {
|
||||
const [dateRange, setDateRange] = React.useState<DateRange | undefined>({
|
||||
from: new Date(2025, 5, 17),
|
||||
to: new Date(2025, 5, 20),
|
||||
})
|
||||
|
||||
return (
|
||||
<Calendar
|
||||
mode="range"
|
||||
defaultMonth={dateRange?.from}
|
||||
selected={dateRange}
|
||||
onSelect={setDateRange}
|
||||
numberOfMonths={2}
|
||||
disabled={{ dayOfWeek: [0, 6] }}
|
||||
className="rounded-lg border shadow-sm"
|
||||
excludeDisabled
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
|
||||
import { Button } from "@/registry/default/ui/button"
|
||||
import { Calendar } from "@/registry/default/ui/calendar"
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/registry/default/ui/card"
|
||||
|
||||
export default function Calendar10() {
|
||||
const [date, setDate] = React.useState<Date | undefined>(
|
||||
new Date(2025, 5, 12)
|
||||
)
|
||||
const [month, setMonth] = React.useState<Date | undefined>(new Date())
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="relative">
|
||||
<CardTitle>Appointment</CardTitle>
|
||||
<CardDescription>Find a date</CardDescription>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="absolute right-4 top-4"
|
||||
onClick={() => {
|
||||
setMonth(new Date())
|
||||
setDate(new Date())
|
||||
}}
|
||||
>
|
||||
Today
|
||||
</Button>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Calendar
|
||||
mode="single"
|
||||
month={month}
|
||||
onMonthChange={setMonth}
|
||||
selected={date}
|
||||
onSelect={setDate}
|
||||
className="bg-transparent p-0"
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { type DateRange } from "react-day-picker"
|
||||
|
||||
import { Calendar } from "@/registry/default/ui/calendar"
|
||||
|
||||
export default function Calendar11() {
|
||||
const [dateRange, setDateRange] = React.useState<DateRange | undefined>({
|
||||
from: new Date(2025, 5, 17),
|
||||
to: new Date(2025, 5, 20),
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="flex min-w-0 flex-col gap-2">
|
||||
<Calendar
|
||||
mode="range"
|
||||
selected={dateRange}
|
||||
onSelect={setDateRange}
|
||||
numberOfMonths={2}
|
||||
startMonth={new Date(2025, 5, 1)}
|
||||
endMonth={new Date(2025, 6, 31)}
|
||||
disableNavigation
|
||||
className="rounded-lg border shadow-sm"
|
||||
/>
|
||||
<div className="text-muted-foreground text-center text-xs">
|
||||
We are open in June and July only.
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { type DateRange } from "react-day-picker"
|
||||
import { enUS, es } from "react-day-picker/locale"
|
||||
|
||||
import { Calendar } from "@/registry/default/ui/calendar"
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/registry/default/ui/card"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/registry/default/ui/select"
|
||||
|
||||
const localizedStrings = {
|
||||
en: {
|
||||
title: "Book an appointment",
|
||||
description: "Select the dates for your appointment",
|
||||
},
|
||||
es: {
|
||||
title: "Reserva una cita",
|
||||
description: "Selecciona las fechas para tu cita",
|
||||
},
|
||||
} as const
|
||||
|
||||
export default function Calendar12() {
|
||||
const [locale, setLocale] =
|
||||
React.useState<keyof typeof localizedStrings>("es")
|
||||
const [dateRange, setDateRange] = React.useState<DateRange | undefined>({
|
||||
from: new Date(2025, 8, 9),
|
||||
to: new Date(2025, 8, 17),
|
||||
})
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="relative border-b">
|
||||
<CardTitle>{localizedStrings[locale].title}</CardTitle>
|
||||
<CardDescription>
|
||||
{localizedStrings[locale].description}
|
||||
</CardDescription>
|
||||
<Select
|
||||
value={locale}
|
||||
onValueChange={(value) =>
|
||||
setLocale(value as keyof typeof localizedStrings)
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="absolute right-4 top-4 w-[100px]">
|
||||
<SelectValue placeholder="Language" />
|
||||
</SelectTrigger>
|
||||
<SelectContent align="end">
|
||||
<SelectItem value="es">Español</SelectItem>
|
||||
<SelectItem value="en">English</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-4">
|
||||
<Calendar
|
||||
mode="range"
|
||||
selected={dateRange}
|
||||
onSelect={setDateRange}
|
||||
defaultMonth={dateRange?.from}
|
||||
numberOfMonths={2}
|
||||
locale={locale === "es" ? es : enUS}
|
||||
className="bg-transparent p-0"
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
|
||||
import { Calendar } from "@/registry/default/ui/calendar"
|
||||
import { Label } from "@/registry/default/ui/label"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/registry/default/ui/select"
|
||||
|
||||
export default function Calendar13() {
|
||||
const [dropdown, setDropdown] =
|
||||
React.useState<React.ComponentProps<typeof Calendar>["captionLayout"]>(
|
||||
"dropdown"
|
||||
)
|
||||
const [date, setDate] = React.useState<Date | undefined>(
|
||||
new Date(2025, 5, 12)
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<Calendar
|
||||
mode="single"
|
||||
defaultMonth={date}
|
||||
selected={date}
|
||||
onSelect={setDate}
|
||||
captionLayout={dropdown}
|
||||
className="rounded-lg border shadow-sm"
|
||||
/>
|
||||
<div className="flex flex-col gap-3">
|
||||
<Label htmlFor="dropdown" className="px-1">
|
||||
Dropdown
|
||||
</Label>
|
||||
<Select
|
||||
value={dropdown}
|
||||
onValueChange={(value) =>
|
||||
setDropdown(
|
||||
value as React.ComponentProps<typeof Calendar>["captionLayout"]
|
||||
)
|
||||
}
|
||||
>
|
||||
<SelectTrigger id="dropdown" className="bg-background w-full">
|
||||
<SelectValue placeholder="Dropdown" />
|
||||
</SelectTrigger>
|
||||
<SelectContent align="center">
|
||||
<SelectItem value="dropdown">Month and Year</SelectItem>
|
||||
<SelectItem value="dropdown-months">Month Only</SelectItem>
|
||||
<SelectItem value="dropdown-years">Year Only</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
|
||||
import { Calendar } from "@/registry/default/ui/calendar"
|
||||
|
||||
export default function Calendar14() {
|
||||
const [date, setDate] = React.useState<Date | undefined>(
|
||||
new Date(2025, 5, 12)
|
||||
)
|
||||
const bookedDates = Array.from(
|
||||
{ length: 12 },
|
||||
(_, i) => new Date(2025, 5, 15 + i)
|
||||
)
|
||||
|
||||
return (
|
||||
<Calendar
|
||||
mode="single"
|
||||
defaultMonth={date}
|
||||
selected={date}
|
||||
onSelect={setDate}
|
||||
disabled={bookedDates}
|
||||
modifiers={{
|
||||
booked: bookedDates,
|
||||
}}
|
||||
modifiersClassNames={{
|
||||
booked: "[&>button]:line-through opacity-100",
|
||||
}}
|
||||
className="rounded-lg border shadow-sm"
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
|
||||
import { Calendar } from "@/registry/default/ui/calendar"
|
||||
|
||||
export default function Calendar15() {
|
||||
const [date, setDate] = React.useState<Date | undefined>(
|
||||
new Date(2025, 5, 12)
|
||||
)
|
||||
|
||||
return (
|
||||
<Calendar
|
||||
mode="single"
|
||||
defaultMonth={date}
|
||||
selected={date}
|
||||
onSelect={setDate}
|
||||
className="rounded-lg border shadow-sm"
|
||||
showWeekNumber
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { Clock2Icon } from "lucide-react"
|
||||
|
||||
import { Calendar } from "@/registry/default/ui/calendar"
|
||||
import { Card, CardContent, CardFooter } from "@/registry/default/ui/card"
|
||||
import { Input } from "@/registry/default/ui/input"
|
||||
import { Label } from "@/registry/default/ui/label"
|
||||
|
||||
export default function Calendar16() {
|
||||
const [date, setDate] = React.useState<Date | undefined>(
|
||||
new Date(2025, 5, 12)
|
||||
)
|
||||
|
||||
return (
|
||||
<Card className="w-fit py-4">
|
||||
<CardContent className="px-4">
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={date}
|
||||
onSelect={setDate}
|
||||
className="bg-transparent p-0"
|
||||
/>
|
||||
</CardContent>
|
||||
<CardFooter className="flex flex-col gap-6 border-t px-4 pb-0 pt-4">
|
||||
<div className="flex w-full flex-col gap-3">
|
||||
<Label htmlFor="time-from">Start Time</Label>
|
||||
<div className="relative flex w-full items-center gap-2">
|
||||
<Clock2Icon className="text-muted-foreground pointer-events-none absolute left-2.5 size-4 select-none" />
|
||||
<Input
|
||||
id="time-from"
|
||||
type="time"
|
||||
step="1"
|
||||
defaultValue="10:30:00"
|
||||
className="appearance-none pl-8 [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex w-full flex-col gap-3">
|
||||
<Label htmlFor="time-to">End Time</Label>
|
||||
<div className="relative flex w-full items-center gap-2">
|
||||
<Clock2Icon className="text-muted-foreground pointer-events-none absolute left-2.5 size-4 select-none" />
|
||||
<Input
|
||||
id="time-to"
|
||||
type="time"
|
||||
step="1"
|
||||
defaultValue="12:30:00"
|
||||
className="appearance-none pl-8 [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
|
||||
import { Calendar } from "@/registry/default/ui/calendar"
|
||||
import { Card, CardContent, CardFooter } from "@/registry/default/ui/card"
|
||||
import { Input } from "@/registry/default/ui/input"
|
||||
import { Label } from "@/registry/default/ui/label"
|
||||
|
||||
export default function Calendar17() {
|
||||
const [date, setDate] = React.useState<Date | undefined>(
|
||||
new Date(2025, 5, 12)
|
||||
)
|
||||
|
||||
return (
|
||||
<Card className="w-fit py-4">
|
||||
<CardContent className="px-4">
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={date}
|
||||
onSelect={setDate}
|
||||
className="bg-transparent p-0 [--cell-size:2.8rem]"
|
||||
/>
|
||||
</CardContent>
|
||||
<CardFooter className="*:[div]:w-full flex gap-2 border-t px-4 pb-0 pt-4">
|
||||
<div className="flex-1">
|
||||
<Label htmlFor="time-from" className="sr-only">
|
||||
Start Time
|
||||
</Label>
|
||||
<Input
|
||||
id="time-from"
|
||||
type="time"
|
||||
step="1"
|
||||
defaultValue="10:30:00"
|
||||
className="appearance-none [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none"
|
||||
/>
|
||||
</div>
|
||||
<span>-</span>
|
||||
<div className="flex-1">
|
||||
<Label htmlFor="time-to" className="sr-only">
|
||||
End Time
|
||||
</Label>
|
||||
<Input
|
||||
id="time-to"
|
||||
type="time"
|
||||
step="1"
|
||||
defaultValue="12:30:00"
|
||||
className="appearance-none [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none"
|
||||
/>
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
|
||||
import { Calendar } from "@/registry/default/ui/calendar"
|
||||
|
||||
export default function Calendar18() {
|
||||
const [date, setDate] = React.useState<Date | undefined>(
|
||||
new Date(2025, 5, 12)
|
||||
)
|
||||
|
||||
return (
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={date}
|
||||
onSelect={setDate}
|
||||
className="rounded-lg border [--cell-size:2.75rem] md:[--cell-size:3rem]"
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { addDays } from "date-fns"
|
||||
|
||||
import { Button } from "@/registry/default/ui/button"
|
||||
import { Calendar } from "@/registry/default/ui/calendar"
|
||||
import { Card, CardContent, CardFooter } from "@/registry/default/ui/card"
|
||||
|
||||
export default function Calendar19() {
|
||||
const [date, setDate] = React.useState<Date | undefined>(
|
||||
new Date(2025, 5, 12)
|
||||
)
|
||||
|
||||
return (
|
||||
<Card className="max-w-[300px] py-4">
|
||||
<CardContent className="px-4">
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={date}
|
||||
onSelect={setDate}
|
||||
defaultMonth={date}
|
||||
className="bg-transparent p-0 [--cell-size:2.375rem]"
|
||||
/>
|
||||
</CardContent>
|
||||
<CardFooter className="flex flex-wrap gap-2 border-t px-4 pb-0 pt-4">
|
||||
{[
|
||||
{ label: "Today", value: 0 },
|
||||
{ label: "Tomorrow", value: 1 },
|
||||
{ label: "In 3 days", value: 3 },
|
||||
{ label: "In a week", value: 7 },
|
||||
{ label: "In 2 weeks", value: 14 },
|
||||
].map((preset) => (
|
||||
<Button
|
||||
key={preset.value}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="flex-1"
|
||||
onClick={() => {
|
||||
const newDate = addDays(new Date(), preset.value)
|
||||
setDate(newDate)
|
||||
}}
|
||||
>
|
||||
{preset.label}
|
||||
</Button>
|
||||
))}
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
|
||||
import { Button } from "@/registry/default/ui/button"
|
||||
import { Calendar } from "@/registry/default/ui/calendar"
|
||||
import { Card, CardContent, CardFooter } from "@/registry/default/ui/card"
|
||||
|
||||
export default function Calendar20() {
|
||||
const [date, setDate] = React.useState<Date | undefined>(
|
||||
new Date(2025, 5, 12)
|
||||
)
|
||||
const [selectedTime, setSelectedTime] = React.useState<string | null>("10:00")
|
||||
const timeSlots = Array.from({ length: 37 }, (_, i) => {
|
||||
const totalMinutes = i * 15
|
||||
const hour = Math.floor(totalMinutes / 60) + 9
|
||||
const minute = totalMinutes % 60
|
||||
return `${hour.toString().padStart(2, "0")}:${minute
|
||||
.toString()
|
||||
.padStart(2, "0")}`
|
||||
})
|
||||
|
||||
const bookedDates = Array.from(
|
||||
{ length: 3 },
|
||||
(_, i) => new Date(2025, 5, 17 + i)
|
||||
)
|
||||
|
||||
return (
|
||||
<Card className="gap-0 p-0">
|
||||
<CardContent className="relative p-0 md:pr-48">
|
||||
<div className="p-6">
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={date}
|
||||
onSelect={setDate}
|
||||
defaultMonth={date}
|
||||
disabled={bookedDates}
|
||||
showOutsideDays={false}
|
||||
modifiers={{
|
||||
booked: bookedDates,
|
||||
}}
|
||||
modifiersClassNames={{
|
||||
booked: "[&>button]:line-through opacity-100",
|
||||
}}
|
||||
className="bg-transparent p-0 [--cell-size:2.5rem] md:[--cell-size:3rem]"
|
||||
formatters={{
|
||||
formatWeekdayName: (date) => {
|
||||
return date.toLocaleString("en-US", { weekday: "short" })
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="no-scrollbar inset-y-0 right-0 flex max-h-72 w-full scroll-pb-6 flex-col gap-4 overflow-y-auto border-t p-6 md:absolute md:max-h-none md:w-48 md:border-l md:border-t-0">
|
||||
<div className="grid gap-2">
|
||||
{timeSlots.map((time) => (
|
||||
<Button
|
||||
key={time}
|
||||
variant={selectedTime === time ? "default" : "outline"}
|
||||
onClick={() => setSelectedTime(time)}
|
||||
className="w-full shadow-none"
|
||||
>
|
||||
{time}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
<CardFooter className="flex flex-col gap-4 border-t !py-5 px-6 md:flex-row">
|
||||
<div className="text-sm">
|
||||
{date && selectedTime ? (
|
||||
<>
|
||||
Your meeting is booked for{" "}
|
||||
<span className="font-medium">
|
||||
{" "}
|
||||
{date?.toLocaleDateString("en-US", {
|
||||
weekday: "long",
|
||||
day: "numeric",
|
||||
month: "long",
|
||||
})}{" "}
|
||||
</span>
|
||||
at <span className="font-medium">{selectedTime}</span>.
|
||||
</>
|
||||
) : (
|
||||
<>Select a date and time for your meeting.</>
|
||||
)}
|
||||
</div>
|
||||
<Button
|
||||
disabled={!date || !selectedTime}
|
||||
className="w-full md:ml-auto md:w-auto"
|
||||
variant="outline"
|
||||
>
|
||||
Continue
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { DateRange } from "react-day-picker"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { buttonVariants } from "@/registry/default/ui/button"
|
||||
import { Calendar } from "@/registry/default/ui/calendar"
|
||||
|
||||
export default function Calendar21() {
|
||||
const [range, setRange] = React.useState<DateRange | undefined>({
|
||||
from: new Date(2025, 5, 12),
|
||||
to: new Date(2025, 5, 17),
|
||||
})
|
||||
|
||||
return (
|
||||
<Calendar
|
||||
mode="range"
|
||||
defaultMonth={range?.from}
|
||||
selected={range}
|
||||
onSelect={setRange}
|
||||
numberOfMonths={1}
|
||||
captionLayout="dropdown"
|
||||
className="rounded-lg border shadow-sm [--cell-size:2.75rem] md:[--cell-size:3rem]"
|
||||
formatters={{
|
||||
formatMonthDropdown: (date) => {
|
||||
return date.toLocaleString("default", { month: "long" })
|
||||
},
|
||||
}}
|
||||
components={{
|
||||
DayButton: ({ children, modifiers, day, ...props }) => {
|
||||
const isWeekend = day.date.getDay() === 0 || day.date.getDay() === 6
|
||||
|
||||
return (
|
||||
<button
|
||||
className={cn(
|
||||
buttonVariants({ variant: "ghost" }),
|
||||
"relative flex flex-col items-center justify-center h-9 w-9 p-0 font-normal aria-selected:opacity-100"
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div>{children}</div>
|
||||
{!modifiers.outside && (
|
||||
<span className="text-xs">{isWeekend ? "$220" : "$100"}</span>
|
||||
)}
|
||||
</button>
|
||||
)
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { ChevronDownIcon } from "lucide-react"
|
||||
|
||||
import { Button } from "@/registry/default/ui/button"
|
||||
import { Calendar } from "@/registry/default/ui/calendar"
|
||||
import { Label } from "@/registry/default/ui/label"
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/registry/default/ui/popover"
|
||||
|
||||
export default function Calendar22() {
|
||||
const [open, setOpen] = React.useState(false)
|
||||
const [date, setDate] = React.useState<Date | undefined>(undefined)
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-3">
|
||||
<Label htmlFor="date" className="px-1">
|
||||
Date of birth
|
||||
</Label>
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
id="date"
|
||||
className="w-48 justify-between font-normal"
|
||||
>
|
||||
{date ? date.toLocaleDateString() : "Select date"}
|
||||
<ChevronDownIcon />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto overflow-hidden p-0" align="start">
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={date}
|
||||
captionLayout="dropdown"
|
||||
onSelect={(date) => {
|
||||
setDate(date)
|
||||
setOpen(false)
|
||||
}}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { ChevronDownIcon } from "lucide-react"
|
||||
import { type DateRange } from "react-day-picker"
|
||||
|
||||
import { Button } from "@/registry/default/ui/button"
|
||||
import { Calendar } from "@/registry/default/ui/calendar"
|
||||
import { Label } from "@/registry/default/ui/label"
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/registry/default/ui/popover"
|
||||
|
||||
export default function Calendar23() {
|
||||
const [range, setRange] = React.useState<DateRange | undefined>(undefined)
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-3">
|
||||
<Label htmlFor="dates" className="px-1">
|
||||
Select your stay
|
||||
</Label>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
id="dates"
|
||||
className="w-56 justify-between font-normal"
|
||||
>
|
||||
{range?.from && range?.to
|
||||
? `${range.from.toLocaleDateString()} - ${range.to.toLocaleDateString()}`
|
||||
: "Select date"}
|
||||
<ChevronDownIcon />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto overflow-hidden p-0" align="start">
|
||||
<Calendar
|
||||
mode="range"
|
||||
selected={range}
|
||||
captionLayout="dropdown"
|
||||
onSelect={(range) => {
|
||||
setRange(range)
|
||||
}}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { ChevronDownIcon } from "lucide-react"
|
||||
|
||||
import { Button } from "@/registry/default/ui/button"
|
||||
import { Calendar } from "@/registry/default/ui/calendar"
|
||||
import { Input } from "@/registry/default/ui/input"
|
||||
import { Label } from "@/registry/default/ui/label"
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/registry/default/ui/popover"
|
||||
|
||||
export default function Calendar24() {
|
||||
const [open, setOpen] = React.useState(false)
|
||||
const [date, setDate] = React.useState<Date | undefined>(undefined)
|
||||
|
||||
return (
|
||||
<div className="flex gap-4">
|
||||
<div className="flex flex-col gap-3">
|
||||
<Label htmlFor="date" className="px-1">
|
||||
Date
|
||||
</Label>
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
id="date"
|
||||
className="w-32 justify-between font-normal"
|
||||
>
|
||||
{date ? date.toLocaleDateString() : "Select date"}
|
||||
<ChevronDownIcon />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto overflow-hidden p-0" align="start">
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={date}
|
||||
captionLayout="dropdown"
|
||||
onSelect={(date) => {
|
||||
setDate(date)
|
||||
setOpen(false)
|
||||
}}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
<div className="flex flex-col gap-3">
|
||||
<Label htmlFor="time" className="px-1">
|
||||
Time
|
||||
</Label>
|
||||
<Input
|
||||
type="time"
|
||||
id="time"
|
||||
step="1"
|
||||
defaultValue="10:30:00"
|
||||
className="bg-background appearance-none [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { ChevronDownIcon } from "lucide-react"
|
||||
|
||||
import { Button } from "@/registry/default/ui/button"
|
||||
import { Calendar } from "@/registry/default/ui/calendar"
|
||||
import { Input } from "@/registry/default/ui/input"
|
||||
import { Label } from "@/registry/default/ui/label"
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/registry/default/ui/popover"
|
||||
|
||||
export default function Calendar25() {
|
||||
const [open, setOpen] = React.useState(false)
|
||||
const [date, setDate] = React.useState<Date | undefined>(undefined)
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-6">
|
||||
<div className="flex flex-col gap-3">
|
||||
<Label htmlFor="date" className="px-1">
|
||||
Date
|
||||
</Label>
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
id="date"
|
||||
className="w-full justify-between font-normal"
|
||||
>
|
||||
{date ? date.toLocaleDateString() : "Select date"}
|
||||
<ChevronDownIcon />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto overflow-hidden p-0" align="start">
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={date}
|
||||
captionLayout="dropdown"
|
||||
onSelect={(date) => {
|
||||
setDate(date)
|
||||
setOpen(false)
|
||||
}}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
<div className="flex gap-4">
|
||||
<div className="flex flex-col gap-3">
|
||||
<Label htmlFor="time-from" className="px-1">
|
||||
From
|
||||
</Label>
|
||||
<Input
|
||||
type="time"
|
||||
id="time-from"
|
||||
step="1"
|
||||
defaultValue="10:30:00"
|
||||
className="bg-background appearance-none [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-3">
|
||||
<Label htmlFor="time-to" className="px-1">
|
||||
To
|
||||
</Label>
|
||||
<Input
|
||||
type="time"
|
||||
id="time-to"
|
||||
step="1"
|
||||
defaultValue="12:30:00"
|
||||
className="bg-background appearance-none [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { ChevronDownIcon } from "lucide-react"
|
||||
|
||||
import { Button } from "@/registry/default/ui/button"
|
||||
import { Calendar } from "@/registry/default/ui/calendar"
|
||||
import { Input } from "@/registry/default/ui/input"
|
||||
import { Label } from "@/registry/default/ui/label"
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/registry/default/ui/popover"
|
||||
|
||||
export default function Calendar26() {
|
||||
const [openFrom, setOpenFrom] = React.useState(false)
|
||||
const [openTo, setOpenTo] = React.useState(false)
|
||||
const [dateFrom, setDateFrom] = React.useState<Date | undefined>(
|
||||
new Date("2025-06-01")
|
||||
)
|
||||
const [dateTo, setDateTo] = React.useState<Date | undefined>(
|
||||
new Date("2025-06-03")
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="flex w-full max-w-64 min-w-0 flex-col gap-6">
|
||||
<div className="flex gap-4">
|
||||
<div className="flex flex-1 flex-col gap-3">
|
||||
<Label htmlFor="date-from" className="px-1">
|
||||
Check-in
|
||||
</Label>
|
||||
<Popover open={openFrom} onOpenChange={setOpenFrom}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
id="date-from"
|
||||
className="w-full justify-between font-normal"
|
||||
>
|
||||
{dateFrom
|
||||
? dateFrom.toLocaleDateString("en-US", {
|
||||
day: "2-digit",
|
||||
month: "short",
|
||||
year: "numeric",
|
||||
})
|
||||
: "Select date"}
|
||||
<ChevronDownIcon />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
className="w-auto overflow-hidden p-0"
|
||||
align="start"
|
||||
>
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={dateFrom}
|
||||
captionLayout="dropdown"
|
||||
onSelect={(date) => {
|
||||
setDateFrom(date)
|
||||
setOpenFrom(false)
|
||||
}}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
<div className="flex flex-col gap-3">
|
||||
<Label htmlFor="time-from" className="invisible px-1">
|
||||
From
|
||||
</Label>
|
||||
<Input
|
||||
type="time"
|
||||
id="time-from"
|
||||
step="1"
|
||||
defaultValue="10:30:00"
|
||||
className="bg-background appearance-none [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-4">
|
||||
<div className="flex flex-1 flex-col gap-3">
|
||||
<Label htmlFor="date-to" className="px-1">
|
||||
Check-out
|
||||
</Label>
|
||||
<Popover open={openTo} onOpenChange={setOpenTo}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
id="date-to"
|
||||
className="w-full justify-between font-normal"
|
||||
>
|
||||
{dateTo
|
||||
? dateTo.toLocaleDateString("en-US", {
|
||||
day: "2-digit",
|
||||
month: "short",
|
||||
year: "numeric",
|
||||
})
|
||||
: "Select date"}
|
||||
<ChevronDownIcon />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
className="w-auto overflow-hidden p-0"
|
||||
align="start"
|
||||
>
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={dateTo}
|
||||
captionLayout="dropdown"
|
||||
onSelect={(date) => {
|
||||
setDateTo(date)
|
||||
setOpenTo(false)
|
||||
}}
|
||||
disabled={dateFrom && { before: dateFrom }}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
<div className="flex flex-col gap-3">
|
||||
<Label htmlFor="time-to" className="invisible px-1">
|
||||
To
|
||||
</Label>
|
||||
<Input
|
||||
type="time"
|
||||
id="time-to"
|
||||
step="1"
|
||||
defaultValue="12:30:00"
|
||||
className="bg-background appearance-none [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,177 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { CalendarIcon } from "lucide-react"
|
||||
import { DateRange } from "react-day-picker"
|
||||
import { Bar, BarChart, CartesianGrid, XAxis } from "recharts"
|
||||
|
||||
import { Button } from "@/registry/default/ui/button"
|
||||
import { Calendar } from "@/registry/default/ui/calendar"
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/registry/default/ui/card"
|
||||
import {
|
||||
ChartConfig,
|
||||
ChartContainer,
|
||||
ChartTooltip,
|
||||
ChartTooltipContent,
|
||||
} from "@/registry/default/ui/chart"
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/registry/default/ui/popover"
|
||||
|
||||
const chartData = [
|
||||
{ date: "2025-06-01", visitors: 178 },
|
||||
{ date: "2025-06-02", visitors: 470 },
|
||||
{ date: "2025-06-03", visitors: 103 },
|
||||
{ date: "2025-06-04", visitors: 439 },
|
||||
{ date: "2025-06-05", visitors: 88 },
|
||||
{ date: "2025-06-06", visitors: 294 },
|
||||
{ date: "2025-06-07", visitors: 323 },
|
||||
{ date: "2025-06-08", visitors: 385 },
|
||||
{ date: "2025-06-09", visitors: 438 },
|
||||
{ date: "2025-06-10", visitors: 155 },
|
||||
{ date: "2025-06-11", visitors: 92 },
|
||||
{ date: "2025-06-12", visitors: 492 },
|
||||
{ date: "2025-06-13", visitors: 81 },
|
||||
{ date: "2025-06-14", visitors: 426 },
|
||||
{ date: "2025-06-15", visitors: 307 },
|
||||
{ date: "2025-06-16", visitors: 371 },
|
||||
{ date: "2025-06-17", visitors: 475 },
|
||||
{ date: "2025-06-18", visitors: 107 },
|
||||
{ date: "2025-06-19", visitors: 341 },
|
||||
{ date: "2025-06-20", visitors: 408 },
|
||||
{ date: "2025-06-21", visitors: 169 },
|
||||
{ date: "2025-06-22", visitors: 317 },
|
||||
{ date: "2025-06-23", visitors: 480 },
|
||||
{ date: "2025-06-24", visitors: 132 },
|
||||
{ date: "2025-06-25", visitors: 141 },
|
||||
{ date: "2025-06-26", visitors: 434 },
|
||||
{ date: "2025-06-27", visitors: 448 },
|
||||
{ date: "2025-06-28", visitors: 149 },
|
||||
{ date: "2025-06-29", visitors: 103 },
|
||||
{ date: "2025-06-30", visitors: 446 },
|
||||
]
|
||||
|
||||
const total = chartData.reduce((acc, curr) => acc + curr.visitors, 0)
|
||||
|
||||
const chartConfig = {
|
||||
visitors: {
|
||||
label: "Visitors",
|
||||
color: "hsl(var(--primary))",
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Calendar27() {
|
||||
const [range, setRange] = React.useState<DateRange | undefined>({
|
||||
from: new Date(2025, 5, 5),
|
||||
to: new Date(2025, 5, 20),
|
||||
})
|
||||
const filteredData = React.useMemo(() => {
|
||||
if (!range?.from && !range?.to) {
|
||||
return chartData
|
||||
}
|
||||
|
||||
return chartData.filter((item) => {
|
||||
const date = new Date(item.date)
|
||||
return date >= range.from! && date <= range.to!
|
||||
})
|
||||
}, [range])
|
||||
|
||||
return (
|
||||
<Card className="@container/card w-full max-w-xl">
|
||||
<CardHeader className="@md/card:grid relative flex flex-col border-b">
|
||||
<CardTitle>Web Analytics</CardTitle>
|
||||
<CardDescription>
|
||||
Showing total visitors for this month.
|
||||
</CardDescription>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant="outline" className="absolute right-4 top-4">
|
||||
<CalendarIcon />
|
||||
{range?.from && range?.to
|
||||
? `${range.from.toLocaleDateString()} - ${range.to.toLocaleDateString()}`
|
||||
: "June 2025"}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto overflow-hidden p-0" align="end">
|
||||
<Calendar
|
||||
className="w-full"
|
||||
mode="range"
|
||||
defaultMonth={range?.from}
|
||||
selected={range}
|
||||
onSelect={setRange}
|
||||
disableNavigation
|
||||
startMonth={range?.from}
|
||||
fixedWeeks
|
||||
showOutsideDays
|
||||
disabled={{
|
||||
after: new Date(2025, 5, 31),
|
||||
}}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</CardHeader>
|
||||
<CardContent className="px-4">
|
||||
<ChartContainer
|
||||
config={chartConfig}
|
||||
className="aspect-auto h-[250px] w-full"
|
||||
>
|
||||
<BarChart
|
||||
accessibilityLayer
|
||||
data={filteredData}
|
||||
margin={{
|
||||
left: 12,
|
||||
right: 12,
|
||||
}}
|
||||
>
|
||||
<CartesianGrid vertical={false} />
|
||||
<XAxis
|
||||
dataKey="date"
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
tickMargin={8}
|
||||
minTickGap={20}
|
||||
tickFormatter={(value) => {
|
||||
const date = new Date(value)
|
||||
return date.toLocaleDateString("en-US", {
|
||||
day: "numeric",
|
||||
})
|
||||
}}
|
||||
/>
|
||||
<ChartTooltip
|
||||
content={
|
||||
<ChartTooltipContent
|
||||
className="w-[150px]"
|
||||
nameKey="visitors"
|
||||
labelFormatter={(value) => {
|
||||
return new Date(value).toLocaleDateString("en-US", {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
year: "numeric",
|
||||
})
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Bar dataKey="visitors" fill={`var(--color-visitors)`} radius={4} />
|
||||
</BarChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="border-t pt-6">
|
||||
<div className="text-sm">
|
||||
You had{" "}
|
||||
<span className="font-semibold">{total.toLocaleString()}</span>{" "}
|
||||
visitors for the month of June.
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { CalendarIcon } from "lucide-react"
|
||||
|
||||
import { Button } from "@/registry/default/ui/button"
|
||||
import { Calendar } from "@/registry/default/ui/calendar"
|
||||
import { Input } from "@/registry/default/ui/input"
|
||||
import { Label } from "@/registry/default/ui/label"
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/registry/default/ui/popover"
|
||||
|
||||
function formatDate(date: Date | undefined) {
|
||||
if (!date) {
|
||||
return ""
|
||||
}
|
||||
|
||||
return date.toLocaleDateString("en-US", {
|
||||
day: "2-digit",
|
||||
month: "long",
|
||||
year: "numeric",
|
||||
})
|
||||
}
|
||||
|
||||
function isValidDate(date: Date | undefined) {
|
||||
if (!date) {
|
||||
return false
|
||||
}
|
||||
return !isNaN(date.getTime())
|
||||
}
|
||||
|
||||
export default function Calendar28() {
|
||||
const [open, setOpen] = React.useState(false)
|
||||
const [date, setDate] = React.useState<Date | undefined>(
|
||||
new Date("2025-06-01")
|
||||
)
|
||||
const [month, setMonth] = React.useState<Date | undefined>(date)
|
||||
const [value, setValue] = React.useState(formatDate(date))
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-3">
|
||||
<Label htmlFor="date" className="px-1">
|
||||
Subscription Date
|
||||
</Label>
|
||||
<div className="relative flex gap-2">
|
||||
<Input
|
||||
id="date"
|
||||
value={value}
|
||||
placeholder="June 01, 2025"
|
||||
className="bg-background pr-10"
|
||||
onChange={(e) => {
|
||||
const date = new Date(e.target.value)
|
||||
setValue(e.target.value)
|
||||
if (isValidDate(date)) {
|
||||
setDate(date)
|
||||
setMonth(date)
|
||||
}
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "ArrowDown") {
|
||||
e.preventDefault()
|
||||
setOpen(true)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
id="date-picker"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="absolute right-2 top-1/2 h-6 w-6 -translate-y-1/2"
|
||||
>
|
||||
<CalendarIcon className="size-3" />
|
||||
<span className="sr-only">Select date</span>
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
className="w-auto overflow-hidden p-0"
|
||||
align="end"
|
||||
alignOffset={-8}
|
||||
sideOffset={10}
|
||||
>
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={date}
|
||||
captionLayout="dropdown"
|
||||
month={month}
|
||||
onMonthChange={setMonth}
|
||||
onSelect={(date) => {
|
||||
setDate(date)
|
||||
setValue(formatDate(date))
|
||||
setOpen(false)
|
||||
}}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
// import { parseDate } from "chrono-node" - temporarily disabled
|
||||
import { CalendarIcon } from "lucide-react"
|
||||
|
||||
import { Button } from "@/registry/default/ui/button"
|
||||
import { Calendar } from "@/registry/default/ui/calendar"
|
||||
import { Input } from "@/registry/default/ui/input"
|
||||
import { Label } from "@/registry/default/ui/label"
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/registry/default/ui/popover"
|
||||
|
||||
function formatDate(date: Date | undefined) {
|
||||
if (!date) {
|
||||
return ""
|
||||
}
|
||||
|
||||
return date.toLocaleDateString("en-US", {
|
||||
day: "2-digit",
|
||||
month: "long",
|
||||
year: "numeric",
|
||||
})
|
||||
}
|
||||
|
||||
export default function Calendar29() {
|
||||
const [open, setOpen] = React.useState(false)
|
||||
const [value, setValue] = React.useState("In 2 days")
|
||||
const [date, setDate] = React.useState<Date | undefined>(
|
||||
new Date() // parseDate stub
|
||||
)
|
||||
const [month, setMonth] = React.useState<Date | undefined>(date)
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-3">
|
||||
<Label htmlFor="date" className="px-1">
|
||||
Schedule Date
|
||||
</Label>
|
||||
<div className="relative flex gap-2">
|
||||
<Input
|
||||
id="date"
|
||||
value={value}
|
||||
placeholder="Tomorrow or next week"
|
||||
className="bg-background pr-10"
|
||||
onChange={(e) => {
|
||||
setValue(e.target.value)
|
||||
const date = null // parseDate stub
|
||||
if (date) {
|
||||
setDate(date)
|
||||
setMonth(date)
|
||||
}
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "ArrowDown") {
|
||||
e.preventDefault()
|
||||
setOpen(true)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
id="date-picker"
|
||||
variant="ghost"
|
||||
className="absolute top-1/2 right-2 size-6 -translate-y-1/2"
|
||||
>
|
||||
<CalendarIcon className="size-3.5" />
|
||||
<span className="sr-only">Select date</span>
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto overflow-hidden p-0" align="end">
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={date}
|
||||
captionLayout="dropdown"
|
||||
month={month}
|
||||
onMonthChange={setMonth}
|
||||
onSelect={(date) => {
|
||||
setDate(date)
|
||||
setValue(formatDate(date))
|
||||
setOpen(false)
|
||||
}}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
<div className="text-muted-foreground px-1 text-sm">
|
||||
Your post will be published on{" "}
|
||||
<span className="font-medium">{formatDate(date)}</span>.
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { formatDateRange } from "little-date"
|
||||
import { ChevronDownIcon } from "lucide-react"
|
||||
import { type DateRange } from "react-day-picker"
|
||||
|
||||
import { Button } from "@/registry/default/ui/button"
|
||||
import { Calendar } from "@/registry/default/ui/calendar"
|
||||
import { Label } from "@/registry/default/ui/label"
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/registry/default/ui/popover"
|
||||
|
||||
export default function Calendar30() {
|
||||
const [range, setRange] = React.useState<DateRange | undefined>({
|
||||
from: new Date(2025, 5, 4),
|
||||
to: new Date(2025, 5, 10),
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-3">
|
||||
<Label htmlFor="dates" className="px-1">
|
||||
Select your stay
|
||||
</Label>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
id="dates"
|
||||
className="w-56 justify-between font-normal"
|
||||
>
|
||||
{range?.from && range?.to
|
||||
? formatDateRange(range.from, range.to, {
|
||||
includeTime: false,
|
||||
})
|
||||
: "Select date"}
|
||||
<ChevronDownIcon />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto overflow-hidden p-0" align="start">
|
||||
<Calendar
|
||||
mode="range"
|
||||
selected={range}
|
||||
captionLayout="dropdown"
|
||||
onSelect={(range) => {
|
||||
setRange(range)
|
||||
}}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { formatDateRange } from "little-date"
|
||||
import { PlusIcon } from "lucide-react"
|
||||
|
||||
import { Button } from "@/registry/default/ui/button"
|
||||
import { Calendar } from "@/registry/default/ui/calendar"
|
||||
import { Card, CardContent, CardFooter } from "@/registry/default/ui/card"
|
||||
|
||||
const events = [
|
||||
{
|
||||
title: "Team Sync Meeting",
|
||||
from: "2025-06-12T09:00:00",
|
||||
to: "2025-06-12T10:00:00",
|
||||
},
|
||||
{
|
||||
title: "Design Review",
|
||||
from: "2025-06-12T11:30:00",
|
||||
to: "2025-06-12T12:30:00",
|
||||
},
|
||||
{
|
||||
title: "Client Presentation",
|
||||
from: "2025-06-12T14:00:00",
|
||||
to: "2025-06-12T15:00:00",
|
||||
},
|
||||
]
|
||||
|
||||
export default function Calendar31() {
|
||||
const [date, setDate] = React.useState<Date | undefined>(
|
||||
new Date(2025, 5, 12)
|
||||
)
|
||||
|
||||
return (
|
||||
<Card className="w-fit py-4">
|
||||
<CardContent className="px-4">
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={date}
|
||||
onSelect={setDate}
|
||||
className="bg-transparent p-0"
|
||||
required
|
||||
/>
|
||||
</CardContent>
|
||||
<CardFooter className="flex flex-col items-start gap-3 border-t px-4 pb-0 pt-4">
|
||||
<div className="flex w-full items-center justify-between px-1">
|
||||
<div className="text-sm font-medium">
|
||||
{date?.toLocaleDateString("en-US", {
|
||||
day: "numeric",
|
||||
month: "long",
|
||||
year: "numeric",
|
||||
})}
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-6 w-6"
|
||||
title="Add Event"
|
||||
>
|
||||
<PlusIcon />
|
||||
<span className="sr-only">Add Event</span>
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex w-full flex-col gap-2">
|
||||
{events.map((event) => (
|
||||
<div
|
||||
key={event.title}
|
||||
className="bg-muted after:bg-primary/70 relative rounded-md p-2 pl-6 text-sm after:absolute after:inset-y-2 after:left-2 after:w-1 after:rounded-full"
|
||||
>
|
||||
<div className="font-medium">{event.title}</div>
|
||||
<div className="text-muted-foreground text-xs">
|
||||
{formatDateRange(new Date(event.from), new Date(event.to))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { CalendarPlusIcon } from "lucide-react"
|
||||
|
||||
import { Button } from "@/registry/default/ui/button"
|
||||
import { Calendar } from "@/registry/default/ui/calendar"
|
||||
import {
|
||||
Drawer,
|
||||
DrawerContent,
|
||||
DrawerDescription,
|
||||
DrawerHeader,
|
||||
DrawerTitle,
|
||||
DrawerTrigger,
|
||||
} from "@/registry/default/ui/drawer"
|
||||
import { Label } from "@/registry/default/ui/label"
|
||||
|
||||
export default function Calendar32() {
|
||||
const [open, setOpen] = React.useState(false)
|
||||
const [date, setDate] = React.useState<Date | undefined>(undefined)
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-3">
|
||||
<Label htmlFor="date" className="px-1">
|
||||
Date of birth
|
||||
</Label>
|
||||
<Drawer open={open} onOpenChange={setOpen}>
|
||||
<DrawerTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
id="date"
|
||||
className="w-48 justify-between font-normal"
|
||||
>
|
||||
{date ? date.toLocaleDateString() : "Select date"}
|
||||
<CalendarPlusIcon />
|
||||
</Button>
|
||||
</DrawerTrigger>
|
||||
<DrawerContent className="w-auto overflow-hidden p-0">
|
||||
<DrawerHeader className="sr-only">
|
||||
<DrawerTitle>Select date</DrawerTitle>
|
||||
<DrawerDescription>Set your date of birth</DrawerDescription>
|
||||
</DrawerHeader>
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={date}
|
||||
captionLayout="dropdown"
|
||||
onSelect={(date) => {
|
||||
setDate(date)
|
||||
setOpen(false)
|
||||
}}
|
||||
className="mx-auto [--cell-size:clamp(0px,calc(100vw/7.5),52px)]"
|
||||
/>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
<div className="text-muted-foreground px-1 text-sm">
|
||||
This example works best on mobile.
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,246 +0,0 @@
|
||||
import Link from "next/link"
|
||||
import {
|
||||
Bell,
|
||||
CircleUser,
|
||||
Home,
|
||||
LineChart,
|
||||
Menu,
|
||||
Package,
|
||||
Package2,
|
||||
Search,
|
||||
ShoppingCart,
|
||||
Users,
|
||||
} from "lucide-react"
|
||||
|
||||
import { Badge } from "@/registry/default/ui/badge"
|
||||
import { Button } from "@/registry/default/ui/button"
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/registry/default/ui/card"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/registry/default/ui/dropdown-menu"
|
||||
import { Input } from "@/registry/default/ui/input"
|
||||
import { Sheet, SheetContent, SheetTrigger } from "@/registry/default/ui/sheet"
|
||||
|
||||
export const description =
|
||||
"A products dashboard with a sidebar navigation and a main content area. The dashboard has a header with a search input and a user menu. The sidebar has a logo, navigation links, and a card with a call to action. The main content area shows an empty state with a call to action."
|
||||
|
||||
export const iframeHeight = "800px"
|
||||
|
||||
export const containerClassName = "w-full h-full"
|
||||
|
||||
export default function Dashboard() {
|
||||
return (
|
||||
<div className="grid min-h-screen w-full md:grid-cols-[220px_1fr] lg:grid-cols-[280px_1fr]">
|
||||
<div className="hidden border-r bg-muted/40 md:block">
|
||||
<div className="flex h-full max-h-screen flex-col gap-2">
|
||||
<div className="flex h-14 items-center border-b px-4 lg:h-[60px] lg:px-6">
|
||||
<Link href="/" className="flex items-center gap-2 font-semibold">
|
||||
<Package2 className="h-6 w-6" />
|
||||
<span className="">Acme Inc</span>
|
||||
</Link>
|
||||
<Button variant="outline" size="icon" className="ml-auto h-8 w-8">
|
||||
<Bell className="h-4 w-4" />
|
||||
<span className="sr-only">Toggle notifications</span>
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<nav className="grid items-start px-2 text-sm font-medium lg:px-4">
|
||||
<Link
|
||||
href="#"
|
||||
className="flex items-center gap-3 rounded-lg px-3 py-2 text-muted-foreground transition-all hover:text-primary"
|
||||
>
|
||||
<Home className="h-4 w-4" />
|
||||
Dashboard
|
||||
</Link>
|
||||
<Link
|
||||
href="#"
|
||||
className="flex items-center gap-3 rounded-lg px-3 py-2 text-muted-foreground transition-all hover:text-primary"
|
||||
>
|
||||
<ShoppingCart className="h-4 w-4" />
|
||||
Orders
|
||||
<Badge className="ml-auto flex h-6 w-6 shrink-0 items-center justify-center rounded-full">
|
||||
6
|
||||
</Badge>
|
||||
</Link>
|
||||
<Link
|
||||
href="#"
|
||||
className="flex items-center gap-3 rounded-lg bg-muted px-3 py-2 text-primary transition-all hover:text-primary"
|
||||
>
|
||||
<Package className="h-4 w-4" />
|
||||
Products{" "}
|
||||
</Link>
|
||||
<Link
|
||||
href="#"
|
||||
className="flex items-center gap-3 rounded-lg px-3 py-2 text-muted-foreground transition-all hover:text-primary"
|
||||
>
|
||||
<Users className="h-4 w-4" />
|
||||
Customers
|
||||
</Link>
|
||||
<Link
|
||||
href="#"
|
||||
className="flex items-center gap-3 rounded-lg px-3 py-2 text-muted-foreground transition-all hover:text-primary"
|
||||
>
|
||||
<LineChart className="h-4 w-4" />
|
||||
Analytics
|
||||
</Link>
|
||||
</nav>
|
||||
</div>
|
||||
<div className="mt-auto p-4">
|
||||
<Card x-chunk="dashboard-02-chunk-0">
|
||||
<CardHeader className="p-2 pt-0 md:p-4">
|
||||
<CardTitle>Upgrade to Pro</CardTitle>
|
||||
<CardDescription>
|
||||
Unlock all features and get unlimited access to our support
|
||||
team.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="p-2 pt-0 md:p-4 md:pt-0">
|
||||
<Button size="sm" className="w-full">
|
||||
Upgrade
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<header className="flex h-14 items-center gap-4 border-b bg-muted/40 px-4 lg:h-[60px] lg:px-6">
|
||||
<Sheet>
|
||||
<SheetTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="shrink-0 md:hidden"
|
||||
>
|
||||
<Menu className="h-5 w-5" />
|
||||
<span className="sr-only">Toggle navigation menu</span>
|
||||
</Button>
|
||||
</SheetTrigger>
|
||||
<SheetContent side="left" className="flex flex-col">
|
||||
<nav className="grid gap-2 text-lg font-medium">
|
||||
<Link
|
||||
href="#"
|
||||
className="flex items-center gap-2 text-lg font-semibold"
|
||||
>
|
||||
<Package2 className="h-6 w-6" />
|
||||
<span className="sr-only">Acme Inc</span>
|
||||
</Link>
|
||||
<Link
|
||||
href="#"
|
||||
className="mx-[-0.65rem] flex items-center gap-4 rounded-xl px-3 py-2 text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<Home className="h-5 w-5" />
|
||||
Dashboard
|
||||
</Link>
|
||||
<Link
|
||||
href="#"
|
||||
className="mx-[-0.65rem] flex items-center gap-4 rounded-xl bg-muted px-3 py-2 text-foreground hover:text-foreground"
|
||||
>
|
||||
<ShoppingCart className="h-5 w-5" />
|
||||
Orders
|
||||
<Badge className="ml-auto flex h-6 w-6 shrink-0 items-center justify-center rounded-full">
|
||||
6
|
||||
</Badge>
|
||||
</Link>
|
||||
<Link
|
||||
href="#"
|
||||
className="mx-[-0.65rem] flex items-center gap-4 rounded-xl px-3 py-2 text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<Package className="h-5 w-5" />
|
||||
Products
|
||||
</Link>
|
||||
<Link
|
||||
href="#"
|
||||
className="mx-[-0.65rem] flex items-center gap-4 rounded-xl px-3 py-2 text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<Users className="h-5 w-5" />
|
||||
Customers
|
||||
</Link>
|
||||
<Link
|
||||
href="#"
|
||||
className="mx-[-0.65rem] flex items-center gap-4 rounded-xl px-3 py-2 text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<LineChart className="h-5 w-5" />
|
||||
Analytics
|
||||
</Link>
|
||||
</nav>
|
||||
<div className="mt-auto">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Upgrade to Pro</CardTitle>
|
||||
<CardDescription>
|
||||
Unlock all features and get unlimited access to our
|
||||
support team.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Button size="sm" className="w-full">
|
||||
Upgrade
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
<div className="w-full flex-1">
|
||||
<form>
|
||||
<div className="relative">
|
||||
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
type="search"
|
||||
placeholder="Search products..."
|
||||
className="w-full appearance-none bg-background pl-8 shadow-none md:w-2/3 lg:w-1/3"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="secondary" size="icon" className="rounded-full">
|
||||
<CircleUser className="h-5 w-5" />
|
||||
<span className="sr-only">Toggle user menu</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuLabel>My Account</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>Settings</DropdownMenuItem>
|
||||
<DropdownMenuItem>Support</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>Logout</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</header>
|
||||
<main className="flex flex-1 flex-col gap-4 p-4 lg:gap-6 lg:p-6">
|
||||
<div className="flex items-center">
|
||||
<h1 className="text-lg font-semibold md:text-2xl">Inventory</h1>
|
||||
</div>
|
||||
<div
|
||||
className="flex flex-1 items-center justify-center rounded-lg border border-dashed shadow-sm" x-chunk="dashboard-02-chunk-1"
|
||||
>
|
||||
<div className="flex flex-col items-center gap-1 text-center">
|
||||
<h3 className="text-2xl font-bold tracking-tight">
|
||||
You have no products
|
||||
</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
You can start selling as soon as you add a product.
|
||||
</p>
|
||||
<Button className="mt-4">Add Product</Button>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,462 +0,0 @@
|
||||
import {
|
||||
Bird,
|
||||
Book,
|
||||
Bot,
|
||||
Code2,
|
||||
CornerDownLeft,
|
||||
LifeBuoy,
|
||||
Mic,
|
||||
Paperclip,
|
||||
Rabbit,
|
||||
Settings,
|
||||
Settings2,
|
||||
Share,
|
||||
SquareTerminal,
|
||||
SquareUser,
|
||||
Triangle,
|
||||
Turtle,
|
||||
} from "lucide-react"
|
||||
|
||||
import { Badge } from "@/registry/default/ui/badge"
|
||||
import { Button } from "@/registry/default/ui/button"
|
||||
import {
|
||||
Drawer,
|
||||
DrawerContent,
|
||||
DrawerDescription,
|
||||
DrawerHeader,
|
||||
DrawerTitle,
|
||||
DrawerTrigger,
|
||||
} from "@/registry/default/ui/drawer"
|
||||
import { Input } from "@/registry/default/ui/input"
|
||||
import { Label } from "@/registry/default/ui/label"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/registry/default/ui/select"
|
||||
import { Textarea } from "@/registry/default/ui/textarea"
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/registry/default/ui/tooltip"
|
||||
|
||||
export const description =
|
||||
"An AI playground with a sidebar navigation and a main content area. The playground has a header with a settings drawer and a share button. The sidebar has navigation links and a user menu. The main content area shows a form to configure the model and messages."
|
||||
|
||||
export const iframeHeight = "740px"
|
||||
|
||||
export const containerClassName = "w-full h-full"
|
||||
|
||||
export default function Dashboard() {
|
||||
return (
|
||||
<div className="grid h-screen w-full pl-[56px]">
|
||||
<aside className="inset-y fixed left-0 z-20 flex h-full flex-col border-r">
|
||||
<div className="border-b p-2">
|
||||
<Button variant="outline" size="icon" aria-label="Home">
|
||||
<Triangle className="size-5 fill-foreground" />
|
||||
</Button>
|
||||
</div>
|
||||
<nav className="grid gap-1 p-2">
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="rounded-lg bg-muted"
|
||||
aria-label="Playground"
|
||||
>
|
||||
<SquareTerminal className="size-5" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right" sideOffset={5}>
|
||||
Playground
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="rounded-lg"
|
||||
aria-label="Models"
|
||||
>
|
||||
<Bot className="size-5" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right" sideOffset={5}>
|
||||
Models
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="rounded-lg"
|
||||
aria-label="API"
|
||||
>
|
||||
<Code2 className="size-5" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right" sideOffset={5}>
|
||||
API
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="rounded-lg"
|
||||
aria-label="Documentation"
|
||||
>
|
||||
<Book className="size-5" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right" sideOffset={5}>
|
||||
Documentation
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="rounded-lg"
|
||||
aria-label="Settings"
|
||||
>
|
||||
<Settings2 className="size-5" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right" sideOffset={5}>
|
||||
Settings
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</nav>
|
||||
<nav className="mt-auto grid gap-1 p-2">
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="mt-auto rounded-lg"
|
||||
aria-label="Help"
|
||||
>
|
||||
<LifeBuoy className="size-5" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right" sideOffset={5}>
|
||||
Help
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="mt-auto rounded-lg"
|
||||
aria-label="Account"
|
||||
>
|
||||
<SquareUser className="size-5" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right" sideOffset={5}>
|
||||
Account
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</nav>
|
||||
</aside>
|
||||
<div className="flex flex-col">
|
||||
<header className="sticky top-0 z-10 flex h-[57px] items-center gap-1 border-b bg-background px-4">
|
||||
<h1 className="text-xl font-semibold">Playground</h1>
|
||||
<Drawer>
|
||||
<DrawerTrigger asChild>
|
||||
<Button variant="ghost" size="icon" className="md:hidden">
|
||||
<Settings className="size-4" />
|
||||
<span className="sr-only">Settings</span>
|
||||
</Button>
|
||||
</DrawerTrigger>
|
||||
<DrawerContent className="max-h-[80vh]">
|
||||
<DrawerHeader>
|
||||
<DrawerTitle>Configuration</DrawerTitle>
|
||||
<DrawerDescription>
|
||||
Configure the settings for the model and messages.
|
||||
</DrawerDescription>
|
||||
</DrawerHeader>
|
||||
<form className="grid w-full items-start gap-6 overflow-auto p-4 pt-0">
|
||||
<fieldset className="grid gap-6 rounded-lg border p-4">
|
||||
<legend className="-ml-1 px-1 text-sm font-medium">
|
||||
Settings
|
||||
</legend>
|
||||
<div className="grid gap-3">
|
||||
<Label htmlFor="model">Model</Label>
|
||||
<Select>
|
||||
<SelectTrigger
|
||||
id="model"
|
||||
className="items-start [&_[data-description]]:hidden"
|
||||
>
|
||||
<SelectValue placeholder="Select a model" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="genesis">
|
||||
<div className="flex items-start gap-3 text-muted-foreground">
|
||||
<Rabbit className="size-5" />
|
||||
<div className="grid gap-0.5">
|
||||
<p>
|
||||
Neural{" "}
|
||||
<span className="font-medium text-foreground">
|
||||
Genesis
|
||||
</span>
|
||||
</p>
|
||||
<p className="text-xs" data-description>
|
||||
Our fastest model for general use cases.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</SelectItem>
|
||||
<SelectItem value="explorer">
|
||||
<div className="flex items-start gap-3 text-muted-foreground">
|
||||
<Bird className="size-5" />
|
||||
<div className="grid gap-0.5">
|
||||
<p>
|
||||
Neural{" "}
|
||||
<span className="font-medium text-foreground">
|
||||
Explorer
|
||||
</span>
|
||||
</p>
|
||||
<p className="text-xs" data-description>
|
||||
Performance and speed for efficiency.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</SelectItem>
|
||||
<SelectItem value="quantum">
|
||||
<div className="flex items-start gap-3 text-muted-foreground">
|
||||
<Turtle className="size-5" />
|
||||
<div className="grid gap-0.5">
|
||||
<p>
|
||||
Neural{" "}
|
||||
<span className="font-medium text-foreground">
|
||||
Quantum
|
||||
</span>
|
||||
</p>
|
||||
<p className="text-xs" data-description>
|
||||
The most powerful model for complex
|
||||
computations.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="grid gap-3">
|
||||
<Label htmlFor="temperature">Temperature</Label>
|
||||
<Input id="temperature" type="number" placeholder="0.4" />
|
||||
</div>
|
||||
<div className="grid gap-3">
|
||||
<Label htmlFor="top-p">Top P</Label>
|
||||
<Input id="top-p" type="number" placeholder="0.7" />
|
||||
</div>
|
||||
<div className="grid gap-3">
|
||||
<Label htmlFor="top-k">Top K</Label>
|
||||
<Input id="top-k" type="number" placeholder="0.0" />
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset className="grid gap-6 rounded-lg border p-4">
|
||||
<legend className="-ml-1 px-1 text-sm font-medium">
|
||||
Messages
|
||||
</legend>
|
||||
<div className="grid gap-3">
|
||||
<Label htmlFor="role">Role</Label>
|
||||
<Select defaultValue="system">
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select a role" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="system">System</SelectItem>
|
||||
<SelectItem value="user">User</SelectItem>
|
||||
<SelectItem value="assistant">Assistant</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="grid gap-3">
|
||||
<Label htmlFor="content">Content</Label>
|
||||
<Textarea id="content" placeholder="You are a..." />
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="ml-auto gap-1.5 text-sm"
|
||||
>
|
||||
<Share className="size-3.5" />
|
||||
Share
|
||||
</Button>
|
||||
</header>
|
||||
<main className="grid flex-1 gap-4 overflow-auto p-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
<div
|
||||
className="relative hidden flex-col items-start gap-8 md:flex" x-chunk="dashboard-03-chunk-0"
|
||||
>
|
||||
<form className="grid w-full items-start gap-6">
|
||||
<fieldset className="grid gap-6 rounded-lg border p-4">
|
||||
<legend className="-ml-1 px-1 text-sm font-medium">
|
||||
Settings
|
||||
</legend>
|
||||
<div className="grid gap-3">
|
||||
<Label htmlFor="model">Model</Label>
|
||||
<Select>
|
||||
<SelectTrigger
|
||||
id="model"
|
||||
className="items-start [&_[data-description]]:hidden"
|
||||
>
|
||||
<SelectValue placeholder="Select a model" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="genesis">
|
||||
<div className="flex items-start gap-3 text-muted-foreground">
|
||||
<Rabbit className="size-5" />
|
||||
<div className="grid gap-0.5">
|
||||
<p>
|
||||
Neural{" "}
|
||||
<span className="font-medium text-foreground">
|
||||
Genesis
|
||||
</span>
|
||||
</p>
|
||||
<p className="text-xs" data-description>
|
||||
Our fastest model for general use cases.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</SelectItem>
|
||||
<SelectItem value="explorer">
|
||||
<div className="flex items-start gap-3 text-muted-foreground">
|
||||
<Bird className="size-5" />
|
||||
<div className="grid gap-0.5">
|
||||
<p>
|
||||
Neural{" "}
|
||||
<span className="font-medium text-foreground">
|
||||
Explorer
|
||||
</span>
|
||||
</p>
|
||||
<p className="text-xs" data-description>
|
||||
Performance and speed for efficiency.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</SelectItem>
|
||||
<SelectItem value="quantum">
|
||||
<div className="flex items-start gap-3 text-muted-foreground">
|
||||
<Turtle className="size-5" />
|
||||
<div className="grid gap-0.5">
|
||||
<p>
|
||||
Neural{" "}
|
||||
<span className="font-medium text-foreground">
|
||||
Quantum
|
||||
</span>
|
||||
</p>
|
||||
<p className="text-xs" data-description>
|
||||
The most powerful model for complex computations.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="grid gap-3">
|
||||
<Label htmlFor="temperature">Temperature</Label>
|
||||
<Input id="temperature" type="number" placeholder="0.4" />
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="grid gap-3">
|
||||
<Label htmlFor="top-p">Top P</Label>
|
||||
<Input id="top-p" type="number" placeholder="0.7" />
|
||||
</div>
|
||||
<div className="grid gap-3">
|
||||
<Label htmlFor="top-k">Top K</Label>
|
||||
<Input id="top-k" type="number" placeholder="0.0" />
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset className="grid gap-6 rounded-lg border p-4">
|
||||
<legend className="-ml-1 px-1 text-sm font-medium">
|
||||
Messages
|
||||
</legend>
|
||||
<div className="grid gap-3">
|
||||
<Label htmlFor="role">Role</Label>
|
||||
<Select defaultValue="system">
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select a role" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="system">System</SelectItem>
|
||||
<SelectItem value="user">User</SelectItem>
|
||||
<SelectItem value="assistant">Assistant</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="grid gap-3">
|
||||
<Label htmlFor="content">Content</Label>
|
||||
<Textarea
|
||||
id="content"
|
||||
placeholder="You are a..."
|
||||
className="min-h-[9.5rem]"
|
||||
/>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
<div className="relative flex h-full min-h-[50vh] flex-col rounded-xl bg-muted/50 p-4 lg:col-span-2">
|
||||
<Badge variant="outline" className="absolute right-3 top-3">
|
||||
Output
|
||||
</Badge>
|
||||
<div className="flex-1" />
|
||||
<form
|
||||
className="relative overflow-hidden rounded-lg border bg-background focus-within:ring-1 focus-within:ring-ring" x-chunk="dashboard-03-chunk-1"
|
||||
>
|
||||
<Label htmlFor="message" className="sr-only">
|
||||
Message
|
||||
</Label>
|
||||
<Textarea
|
||||
id="message"
|
||||
placeholder="Type your message here..."
|
||||
className="min-h-12 resize-none border-0 p-3 shadow-none focus-visible:ring-0"
|
||||
/>
|
||||
<div className="flex items-center p-3 pt-0">
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button variant="ghost" size="icon">
|
||||
<Paperclip className="size-4" />
|
||||
<span className="sr-only">Attach file</span>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top">Attach File</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button variant="ghost" size="icon">
|
||||
<Mic className="size-4" />
|
||||
<span className="sr-only">Use Microphone</span>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top">Use Microphone</TooltipContent>
|
||||
</Tooltip>
|
||||
<Button type="submit" size="sm" className="ml-auto gap-1.5">
|
||||
Send Message
|
||||
<CornerDownLeft className="size-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,222 +0,0 @@
|
||||
import Link from "next/link"
|
||||
import { CircleUser, Menu, Package2, Search } from "lucide-react"
|
||||
|
||||
import { Button } from "@/registry/default/ui/button"
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/registry/default/ui/card"
|
||||
import { Checkbox } from "@/registry/default/ui/checkbox"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/registry/default/ui/dropdown-menu"
|
||||
import { Input } from "@/registry/default/ui/input"
|
||||
import { Sheet, SheetContent, SheetTrigger } from "@/registry/default/ui/sheet"
|
||||
|
||||
export const description =
|
||||
"A settings page. The settings page has a sidebar navigation and a main content area. The main content area has a form to update the store name and a form to update the plugins directory. The sidebar navigation has links to general, security, integrations, support, organizations, and advanced settings."
|
||||
|
||||
export const iframeHeight = "780px"
|
||||
|
||||
export const containerClassName = "w-full h-full"
|
||||
|
||||
export default function Dashboard() {
|
||||
return (
|
||||
<div className="flex min-h-screen w-full flex-col">
|
||||
<header className="sticky top-0 flex h-16 items-center gap-4 border-b bg-background px-4 md:px-6">
|
||||
<nav className="hidden flex-col gap-6 text-lg font-medium md:flex md:flex-row md:items-center md:gap-5 md:text-sm lg:gap-6">
|
||||
<Link
|
||||
href="#"
|
||||
className="flex items-center gap-2 text-lg font-semibold md:text-base"
|
||||
>
|
||||
<Package2 className="h-6 w-6" />
|
||||
<span className="sr-only">Acme Inc</span>
|
||||
</Link>
|
||||
<Link
|
||||
href="#"
|
||||
className="text-muted-foreground transition-colors hover:text-foreground"
|
||||
>
|
||||
Dashboard
|
||||
</Link>
|
||||
<Link
|
||||
href="#"
|
||||
className="text-muted-foreground transition-colors hover:text-foreground"
|
||||
>
|
||||
Orders
|
||||
</Link>
|
||||
<Link
|
||||
href="#"
|
||||
className="text-muted-foreground transition-colors hover:text-foreground"
|
||||
>
|
||||
Products
|
||||
</Link>
|
||||
<Link
|
||||
href="#"
|
||||
className="text-muted-foreground transition-colors hover:text-foreground"
|
||||
>
|
||||
Customers
|
||||
</Link>
|
||||
<Link
|
||||
href="#"
|
||||
className="text-foreground transition-colors hover:text-foreground"
|
||||
>
|
||||
Settings
|
||||
</Link>
|
||||
</nav>
|
||||
<Sheet>
|
||||
<SheetTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="shrink-0 md:hidden"
|
||||
>
|
||||
<Menu className="h-5 w-5" />
|
||||
<span className="sr-only">Toggle navigation menu</span>
|
||||
</Button>
|
||||
</SheetTrigger>
|
||||
<SheetContent side="left">
|
||||
<nav className="grid gap-6 text-lg font-medium">
|
||||
<Link
|
||||
href="#"
|
||||
className="flex items-center gap-2 text-lg font-semibold"
|
||||
>
|
||||
<Package2 className="h-6 w-6" />
|
||||
<span className="sr-only">Acme Inc</span>
|
||||
</Link>
|
||||
<Link
|
||||
href="#"
|
||||
className="text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
Dashboard
|
||||
</Link>
|
||||
<Link
|
||||
href="#"
|
||||
className="text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
Orders
|
||||
</Link>
|
||||
<Link
|
||||
href="#"
|
||||
className="text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
Products
|
||||
</Link>
|
||||
<Link
|
||||
href="#"
|
||||
className="text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
Customers
|
||||
</Link>
|
||||
<Link href="#" className="hover:text-foreground">
|
||||
Settings
|
||||
</Link>
|
||||
</nav>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
<div className="flex w-full items-center gap-4 md:ml-auto md:gap-2 lg:gap-4">
|
||||
<form className="ml-auto flex-1 sm:flex-initial">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
type="search"
|
||||
placeholder="Search products..."
|
||||
className="pl-8 sm:w-[300px] md:w-[200px] lg:w-[300px]"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="secondary" size="icon" className="rounded-full">
|
||||
<CircleUser className="h-5 w-5" />
|
||||
<span className="sr-only">Toggle user menu</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuLabel>My Account</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>Settings</DropdownMenuItem>
|
||||
<DropdownMenuItem>Support</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>Logout</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</header>
|
||||
<main className="flex min-h-[calc(100vh_-_theme(spacing.16))] flex-1 flex-col gap-4 bg-muted/40 p-4 md:gap-8 md:p-10">
|
||||
<div className="mx-auto grid w-full max-w-6xl gap-2">
|
||||
<h1 className="text-3xl font-semibold">Settings</h1>
|
||||
</div>
|
||||
<div className="mx-auto grid w-full max-w-6xl items-start gap-6 md:grid-cols-[180px_1fr] lg:grid-cols-[250px_1fr]">
|
||||
<nav
|
||||
className="grid gap-4 text-sm text-muted-foreground" x-chunk="dashboard-04-chunk-0"
|
||||
>
|
||||
<Link href="#" className="font-semibold text-primary">
|
||||
General
|
||||
</Link>
|
||||
<Link href="#">Security</Link>
|
||||
<Link href="#">Integrations</Link>
|
||||
<Link href="#">Support</Link>
|
||||
<Link href="#">Organizations</Link>
|
||||
<Link href="#">Advanced</Link>
|
||||
</nav>
|
||||
<div className="grid gap-6">
|
||||
<Card x-chunk="dashboard-04-chunk-1">
|
||||
<CardHeader>
|
||||
<CardTitle>Store Name</CardTitle>
|
||||
<CardDescription>
|
||||
Used to identify your store in the marketplace.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form>
|
||||
<Input placeholder="Store Name" />
|
||||
</form>
|
||||
</CardContent>
|
||||
<CardFooter className="border-t px-6 py-4">
|
||||
<Button>Save</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
<Card x-chunk="dashboard-04-chunk-2">
|
||||
<CardHeader>
|
||||
<CardTitle>Plugins Directory</CardTitle>
|
||||
<CardDescription>
|
||||
The directory within your project, in which your plugins are
|
||||
located.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form className="flex flex-col gap-4">
|
||||
<Input
|
||||
placeholder="Project Name"
|
||||
defaultValue="/content/plugins"
|
||||
/>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox id="include" defaultChecked />
|
||||
<label
|
||||
htmlFor="include"
|
||||
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
>
|
||||
Allow administrators to change the directory.
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
</CardContent>
|
||||
<CardFooter className="border-t px-6 py-4">
|
||||
<Button>Save</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,721 +0,0 @@
|
||||
import Image from "next/image"
|
||||
import Link from "next/link"
|
||||
import {
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
Copy,
|
||||
CreditCard,
|
||||
File,
|
||||
Home,
|
||||
LineChart,
|
||||
ListFilter,
|
||||
MoreVertical,
|
||||
Package,
|
||||
Package2,
|
||||
PanelLeft,
|
||||
Search,
|
||||
Settings,
|
||||
ShoppingCart,
|
||||
Truck,
|
||||
Users2,
|
||||
} from "lucide-react"
|
||||
|
||||
import { Badge } from "@/registry/default/ui/badge"
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
BreadcrumbList,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
} from "@/registry/default/ui/breadcrumb"
|
||||
import { Button } from "@/registry/default/ui/button"
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/registry/default/ui/card"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/registry/default/ui/dropdown-menu"
|
||||
import { Input } from "@/registry/default/ui/input"
|
||||
import {
|
||||
Pagination,
|
||||
PaginationContent,
|
||||
PaginationItem,
|
||||
} from "@/registry/default/ui/pagination"
|
||||
import { Progress } from "@/registry/default/ui/progress"
|
||||
import { Separator } from "@/registry/default/ui/separator"
|
||||
import { Sheet, SheetContent, SheetTrigger } from "@/registry/default/ui/sheet"
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/registry/default/ui/table"
|
||||
import {
|
||||
Tabs,
|
||||
TabsContent,
|
||||
TabsList,
|
||||
TabsTrigger,
|
||||
} from "@/registry/default/ui/tabs"
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/registry/default/ui/tooltip"
|
||||
|
||||
export const description =
|
||||
"An orders dashboard with a sidebar navigation. The sidebar has icon navigation. The content area has a breadcrumb and search in the header. The main area has a list of recent orders with a filter and export button. The main area also has a detailed view of a single order with order details, shipping information, billing information, customer information, and payment information."
|
||||
|
||||
export const iframeHeight = "1112px"
|
||||
|
||||
export const containerClassName = "w-full h-full"
|
||||
|
||||
export default function Dashboard() {
|
||||
return (
|
||||
<div className="flex min-h-screen w-full flex-col bg-muted/40">
|
||||
<aside className="fixed inset-y-0 left-0 z-10 hidden w-14 flex-col border-r bg-background sm:flex">
|
||||
<nav className="flex flex-col items-center gap-4 px-2 sm:py-5">
|
||||
<Link
|
||||
href="#"
|
||||
className="group flex h-9 w-9 shrink-0 items-center justify-center gap-2 rounded-full bg-primary text-lg font-semibold text-primary-foreground md:h-8 md:w-8 md:text-base"
|
||||
>
|
||||
<Package2 className="h-4 w-4 transition-all group-hover:scale-110" />
|
||||
<span className="sr-only">Acme Inc</span>
|
||||
</Link>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Link
|
||||
href="#"
|
||||
className="flex h-9 w-9 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:text-foreground md:h-8 md:w-8"
|
||||
>
|
||||
<Home className="h-5 w-5" />
|
||||
<span className="sr-only">Dashboard</span>
|
||||
</Link>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">Dashboard</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Link
|
||||
href="#"
|
||||
className="flex h-9 w-9 items-center justify-center rounded-lg bg-accent text-accent-foreground transition-colors hover:text-foreground md:h-8 md:w-8"
|
||||
>
|
||||
<ShoppingCart className="h-5 w-5" />
|
||||
<span className="sr-only">Orders</span>
|
||||
</Link>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">Orders</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Link
|
||||
href="#"
|
||||
className="flex h-9 w-9 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:text-foreground md:h-8 md:w-8"
|
||||
>
|
||||
<Package className="h-5 w-5" />
|
||||
<span className="sr-only">Products</span>
|
||||
</Link>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">Products</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Link
|
||||
href="#"
|
||||
className="flex h-9 w-9 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:text-foreground md:h-8 md:w-8"
|
||||
>
|
||||
<Users2 className="h-5 w-5" />
|
||||
<span className="sr-only">Customers</span>
|
||||
</Link>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">Customers</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Link
|
||||
href="#"
|
||||
className="flex h-9 w-9 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:text-foreground md:h-8 md:w-8"
|
||||
>
|
||||
<LineChart className="h-5 w-5" />
|
||||
<span className="sr-only">Analytics</span>
|
||||
</Link>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">Analytics</TooltipContent>
|
||||
</Tooltip>
|
||||
</nav>
|
||||
<nav className="mt-auto flex flex-col items-center gap-4 px-2 sm:py-5">
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Link
|
||||
href="#"
|
||||
className="flex h-9 w-9 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:text-foreground md:h-8 md:w-8"
|
||||
>
|
||||
<Settings className="h-5 w-5" />
|
||||
<span className="sr-only">Settings</span>
|
||||
</Link>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">Settings</TooltipContent>
|
||||
</Tooltip>
|
||||
</nav>
|
||||
</aside>
|
||||
<div className="flex flex-col sm:gap-4 sm:py-4 sm:pl-14">
|
||||
<header className="sticky top-0 z-30 flex h-14 items-center gap-4 border-b bg-background px-4 sm:static sm:h-auto sm:border-0 sm:bg-transparent sm:px-6">
|
||||
<Sheet>
|
||||
<SheetTrigger asChild>
|
||||
<Button size="icon" variant="outline" className="sm:hidden">
|
||||
<PanelLeft className="h-5 w-5" />
|
||||
<span className="sr-only">Toggle Menu</span>
|
||||
</Button>
|
||||
</SheetTrigger>
|
||||
<SheetContent side="left" className="sm:max-w-xs">
|
||||
<nav className="grid gap-6 text-lg font-medium">
|
||||
<Link
|
||||
href="#"
|
||||
className="group flex h-10 w-10 shrink-0 items-center justify-center gap-2 rounded-full bg-primary text-lg font-semibold text-primary-foreground md:text-base"
|
||||
>
|
||||
<Package2 className="h-5 w-5 transition-all group-hover:scale-110" />
|
||||
<span className="sr-only">Acme Inc</span>
|
||||
</Link>
|
||||
<Link
|
||||
href="#"
|
||||
className="flex items-center gap-4 px-2.5 text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<Home className="h-5 w-5" />
|
||||
Dashboard
|
||||
</Link>
|
||||
<Link
|
||||
href="#"
|
||||
className="flex items-center gap-4 px-2.5 text-foreground"
|
||||
>
|
||||
<ShoppingCart className="h-5 w-5" />
|
||||
Orders
|
||||
</Link>
|
||||
<Link
|
||||
href="#"
|
||||
className="flex items-center gap-4 px-2.5 text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<Package className="h-5 w-5" />
|
||||
Products
|
||||
</Link>
|
||||
<Link
|
||||
href="#"
|
||||
className="flex items-center gap-4 px-2.5 text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<Users2 className="h-5 w-5" />
|
||||
Customers
|
||||
</Link>
|
||||
<Link
|
||||
href="#"
|
||||
className="flex items-center gap-4 px-2.5 text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<LineChart className="h-5 w-5" />
|
||||
Settings
|
||||
</Link>
|
||||
</nav>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
<Breadcrumb className="hidden md:flex">
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink asChild>
|
||||
<Link href="#">Dashboard</Link>
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink asChild>
|
||||
<Link href="#">Orders</Link>
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage>Recent Orders</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
<div className="relative ml-auto flex-1 md:grow-0">
|
||||
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
type="search"
|
||||
placeholder="Search..."
|
||||
className="w-full rounded-lg bg-background pl-8 md:w-[200px] lg:w-[336px]"
|
||||
/>
|
||||
</div>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="overflow-hidden rounded-full"
|
||||
>
|
||||
<Image
|
||||
src="/placeholder-user.jpg"
|
||||
width={36}
|
||||
height={36}
|
||||
alt="Avatar"
|
||||
className="overflow-hidden rounded-full"
|
||||
loading="lazy"
|
||||
/>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuLabel>My Account</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>Settings</DropdownMenuItem>
|
||||
<DropdownMenuItem>Support</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>Logout</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</header>
|
||||
<main className="grid flex-1 items-start gap-4 p-4 sm:px-6 sm:py-0 md:gap-8 lg:grid-cols-3 xl:grid-cols-3">
|
||||
<div className="grid auto-rows-max items-start gap-4 md:gap-8 lg:col-span-2">
|
||||
<div className="grid gap-4 sm:grid-cols-2 md:grid-cols-4 lg:grid-cols-2 xl:grid-cols-4">
|
||||
<Card className="sm:col-span-2" x-chunk="dashboard-05-chunk-0">
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle>Your Orders</CardTitle>
|
||||
<CardDescription className="max-w-lg text-balance leading-relaxed">
|
||||
Introducing Our Dynamic Orders Dashboard for Seamless
|
||||
Management and Insightful Analysis.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardFooter>
|
||||
<Button>Create New Order</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
<Card x-chunk="dashboard-05-chunk-1">
|
||||
<CardHeader className="pb-2">
|
||||
<CardDescription>This Week</CardDescription>
|
||||
<CardTitle className="text-4xl">$1,329</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
+25% from last week
|
||||
</div>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<Progress value={25} aria-label="25% increase" />
|
||||
</CardFooter>
|
||||
</Card>
|
||||
<Card x-chunk="dashboard-05-chunk-2">
|
||||
<CardHeader className="pb-2">
|
||||
<CardDescription>This Month</CardDescription>
|
||||
<CardTitle className="text-4xl">$5,329</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
+10% from last month
|
||||
</div>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<Progress value={12} aria-label="12% increase" />
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</div>
|
||||
<Tabs defaultValue="week">
|
||||
<div className="flex items-center">
|
||||
<TabsList>
|
||||
<TabsTrigger value="week">Week</TabsTrigger>
|
||||
<TabsTrigger value="month">Month</TabsTrigger>
|
||||
<TabsTrigger value="year">Year</TabsTrigger>
|
||||
</TabsList>
|
||||
<div className="ml-auto flex items-center gap-2">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-7 gap-1 text-sm"
|
||||
>
|
||||
<ListFilter className="h-3.5 w-3.5" />
|
||||
<span className="sr-only sm:not-sr-only">Filter</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuLabel>Filter by</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuCheckboxItem checked>
|
||||
Fulfilled
|
||||
</DropdownMenuCheckboxItem>
|
||||
<DropdownMenuCheckboxItem>
|
||||
Declined
|
||||
</DropdownMenuCheckboxItem>
|
||||
<DropdownMenuCheckboxItem>
|
||||
Refunded
|
||||
</DropdownMenuCheckboxItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="h-7 gap-1 text-sm"
|
||||
>
|
||||
<File className="h-3.5 w-3.5" />
|
||||
<span className="sr-only sm:not-sr-only">Export</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<TabsContent value="week">
|
||||
<Card x-chunk="dashboard-05-chunk-3">
|
||||
<CardHeader className="px-7">
|
||||
<CardTitle>Orders</CardTitle>
|
||||
<CardDescription>
|
||||
Recent orders from your store.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Customer</TableHead>
|
||||
<TableHead className="hidden sm:table-cell">
|
||||
Type
|
||||
</TableHead>
|
||||
<TableHead className="hidden sm:table-cell">
|
||||
Status
|
||||
</TableHead>
|
||||
<TableHead className="hidden md:table-cell">
|
||||
Date
|
||||
</TableHead>
|
||||
<TableHead className="text-right">Amount</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow className="bg-accent">
|
||||
<TableCell>
|
||||
<div className="font-medium">Liam Johnson</div>
|
||||
<div className="hidden text-sm text-muted-foreground md:inline">
|
||||
liam@example.com
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="hidden sm:table-cell">
|
||||
Sale
|
||||
</TableCell>
|
||||
<TableCell className="hidden sm:table-cell">
|
||||
<Badge className="text-xs" variant="secondary">
|
||||
Fulfilled
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="hidden md:table-cell">
|
||||
2023-06-23
|
||||
</TableCell>
|
||||
<TableCell className="text-right">$250.00</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<div className="font-medium">Olivia Smith</div>
|
||||
<div className="hidden text-sm text-muted-foreground md:inline">
|
||||
olivia@example.com
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="hidden sm:table-cell">
|
||||
Refund
|
||||
</TableCell>
|
||||
<TableCell className="hidden sm:table-cell">
|
||||
<Badge className="text-xs" variant="outline">
|
||||
Declined
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="hidden md:table-cell">
|
||||
2023-06-24
|
||||
</TableCell>
|
||||
<TableCell className="text-right">$150.00</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<div className="font-medium">Noah Williams</div>
|
||||
<div className="hidden text-sm text-muted-foreground md:inline">
|
||||
noah@example.com
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="hidden sm:table-cell">
|
||||
Subscription
|
||||
</TableCell>
|
||||
<TableCell className="hidden sm:table-cell">
|
||||
<Badge className="text-xs" variant="secondary">
|
||||
Fulfilled
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="hidden md:table-cell">
|
||||
2023-06-25
|
||||
</TableCell>
|
||||
<TableCell className="text-right">$350.00</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<div className="font-medium">Emma Brown</div>
|
||||
<div className="hidden text-sm text-muted-foreground md:inline">
|
||||
emma@example.com
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="hidden sm:table-cell">
|
||||
Sale
|
||||
</TableCell>
|
||||
<TableCell className="hidden sm:table-cell">
|
||||
<Badge className="text-xs" variant="secondary">
|
||||
Fulfilled
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="hidden md:table-cell">
|
||||
2023-06-26
|
||||
</TableCell>
|
||||
<TableCell className="text-right">$450.00</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<div className="font-medium">Liam Johnson</div>
|
||||
<div className="hidden text-sm text-muted-foreground md:inline">
|
||||
liam@example.com
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="hidden sm:table-cell">
|
||||
Sale
|
||||
</TableCell>
|
||||
<TableCell className="hidden sm:table-cell">
|
||||
<Badge className="text-xs" variant="secondary">
|
||||
Fulfilled
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="hidden md:table-cell">
|
||||
2023-06-23
|
||||
</TableCell>
|
||||
<TableCell className="text-right">$250.00</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<div className="font-medium">Liam Johnson</div>
|
||||
<div className="hidden text-sm text-muted-foreground md:inline">
|
||||
liam@example.com
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="hidden sm:table-cell">
|
||||
Sale
|
||||
</TableCell>
|
||||
<TableCell className="hidden sm:table-cell">
|
||||
<Badge className="text-xs" variant="secondary">
|
||||
Fulfilled
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="hidden md:table-cell">
|
||||
2023-06-23
|
||||
</TableCell>
|
||||
<TableCell className="text-right">$250.00</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<div className="font-medium">Olivia Smith</div>
|
||||
<div className="hidden text-sm text-muted-foreground md:inline">
|
||||
olivia@example.com
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="hidden sm:table-cell">
|
||||
Refund
|
||||
</TableCell>
|
||||
<TableCell className="hidden sm:table-cell">
|
||||
<Badge className="text-xs" variant="outline">
|
||||
Declined
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="hidden md:table-cell">
|
||||
2023-06-24
|
||||
</TableCell>
|
||||
<TableCell className="text-right">$150.00</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<div className="font-medium">Emma Brown</div>
|
||||
<div className="hidden text-sm text-muted-foreground md:inline">
|
||||
emma@example.com
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="hidden sm:table-cell">
|
||||
Sale
|
||||
</TableCell>
|
||||
<TableCell className="hidden sm:table-cell">
|
||||
<Badge className="text-xs" variant="secondary">
|
||||
Fulfilled
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="hidden md:table-cell">
|
||||
2023-06-26
|
||||
</TableCell>
|
||||
<TableCell className="text-right">$450.00</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
<div>
|
||||
<Card className="overflow-hidden" x-chunk="dashboard-05-chunk-4">
|
||||
<CardHeader className="flex flex-row items-start bg-muted/50">
|
||||
<div className="grid gap-0.5">
|
||||
<CardTitle className="group flex items-center gap-2 text-lg">
|
||||
Order Oe31b70H
|
||||
<Button
|
||||
size="icon"
|
||||
variant="outline"
|
||||
className="h-6 w-6 opacity-0 transition-opacity group-hover:opacity-100"
|
||||
>
|
||||
<Copy className="h-3 w-3" />
|
||||
<span className="sr-only">Copy Order ID</span>
|
||||
</Button>
|
||||
</CardTitle>
|
||||
<CardDescription>Date: November 23, 2023</CardDescription>
|
||||
</div>
|
||||
<div className="ml-auto flex items-center gap-1">
|
||||
<Button size="sm" variant="outline" className="h-8 gap-1">
|
||||
<Truck className="h-3.5 w-3.5" />
|
||||
<span className="lg:sr-only xl:not-sr-only xl:whitespace-nowrap">
|
||||
Track Order
|
||||
</span>
|
||||
</Button>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button size="icon" variant="outline" className="h-8 w-8">
|
||||
<MoreVertical className="h-3.5 w-3.5" />
|
||||
<span className="sr-only">More</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem>Edit</DropdownMenuItem>
|
||||
<DropdownMenuItem>Export</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>Trash</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="p-6 text-sm">
|
||||
<div className="grid gap-3">
|
||||
<div className="font-semibold">Order Details</div>
|
||||
<ul className="grid gap-3">
|
||||
<li className="flex items-center justify-between">
|
||||
<span className="text-muted-foreground">
|
||||
Glimmer Lamps x <span>2</span>
|
||||
</span>
|
||||
<span>$250.00</span>
|
||||
</li>
|
||||
<li className="flex items-center justify-between">
|
||||
<span className="text-muted-foreground">
|
||||
Aqua Filters x <span>1</span>
|
||||
</span>
|
||||
<span>$49.00</span>
|
||||
</li>
|
||||
</ul>
|
||||
<Separator className="my-2" />
|
||||
<ul className="grid gap-3">
|
||||
<li className="flex items-center justify-between">
|
||||
<span className="text-muted-foreground">Subtotal</span>
|
||||
<span>$299.00</span>
|
||||
</li>
|
||||
<li className="flex items-center justify-between">
|
||||
<span className="text-muted-foreground">Shipping</span>
|
||||
<span>$5.00</span>
|
||||
</li>
|
||||
<li className="flex items-center justify-between">
|
||||
<span className="text-muted-foreground">Tax</span>
|
||||
<span>$25.00</span>
|
||||
</li>
|
||||
<li className="flex items-center justify-between font-semibold">
|
||||
<span className="text-muted-foreground">Total</span>
|
||||
<span>$329.00</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<Separator className="my-4" />
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="grid gap-3">
|
||||
<div className="font-semibold">Shipping Information</div>
|
||||
<address className="grid gap-0.5 not-italic text-muted-foreground">
|
||||
<span>Liam Johnson</span>
|
||||
<span>1234 Main St.</span>
|
||||
<span>Anytown, CA 12345</span>
|
||||
</address>
|
||||
</div>
|
||||
<div className="grid auto-rows-max gap-3">
|
||||
<div className="font-semibold">Billing Information</div>
|
||||
<div className="text-muted-foreground">
|
||||
Same as shipping address
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Separator className="my-4" />
|
||||
<div className="grid gap-3">
|
||||
<div className="font-semibold">Customer Information</div>
|
||||
<dl className="grid gap-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<dt className="text-muted-foreground">Customer</dt>
|
||||
<dd>Liam Johnson</dd>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<dt className="text-muted-foreground">Email</dt>
|
||||
<dd>
|
||||
<a href="mailto:">liam@acme.com</a>
|
||||
</dd>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<dt className="text-muted-foreground">Phone</dt>
|
||||
<dd>
|
||||
<a href="tel:">+1 234 567 890</a>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
<Separator className="my-4" />
|
||||
<div className="grid gap-3">
|
||||
<div className="font-semibold">Payment Information</div>
|
||||
<dl className="grid gap-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<dt className="flex items-center gap-1 text-muted-foreground">
|
||||
<CreditCard className="h-4 w-4" />
|
||||
Visa
|
||||
</dt>
|
||||
<dd>**** **** **** 4532</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</CardContent>
|
||||
<CardFooter className="flex flex-row items-center border-t bg-muted/50 px-6 py-3">
|
||||
<div className="text-xs text-muted-foreground">
|
||||
Updated <time dateTime="2023-11-23">November 23, 2023</time>
|
||||
</div>
|
||||
<Pagination className="ml-auto mr-0 w-auto">
|
||||
<PaginationContent>
|
||||
<PaginationItem>
|
||||
<Button size="icon" variant="outline" className="h-6 w-6">
|
||||
<ChevronLeft className="h-3.5 w-3.5" />
|
||||
<span className="sr-only">Previous Order</span>
|
||||
</Button>
|
||||
</PaginationItem>
|
||||
<PaginationItem>
|
||||
<Button size="icon" variant="outline" className="h-6 w-6">
|
||||
<ChevronRight className="h-3.5 w-3.5" />
|
||||
<span className="sr-only">Next Order</span>
|
||||
</Button>
|
||||
</PaginationItem>
|
||||
</PaginationContent>
|
||||
</Pagination>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,644 +0,0 @@
|
||||
import Image from "next/image"
|
||||
import Link from "next/link"
|
||||
import {
|
||||
File,
|
||||
Home,
|
||||
LineChart,
|
||||
ListFilter,
|
||||
MoreHorizontal,
|
||||
Package,
|
||||
Package2,
|
||||
PanelLeft,
|
||||
PlusCircle,
|
||||
Search,
|
||||
Settings,
|
||||
ShoppingCart,
|
||||
Users2,
|
||||
} from "lucide-react"
|
||||
|
||||
import { Badge } from "@/registry/default/ui/badge"
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
BreadcrumbList,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
} from "@/registry/default/ui/breadcrumb"
|
||||
import { Button } from "@/registry/default/ui/button"
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/registry/default/ui/card"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/registry/default/ui/dropdown-menu"
|
||||
import { Input } from "@/registry/default/ui/input"
|
||||
import { Sheet, SheetContent, SheetTrigger } from "@/registry/default/ui/sheet"
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/registry/default/ui/table"
|
||||
import {
|
||||
Tabs,
|
||||
TabsContent,
|
||||
TabsList,
|
||||
TabsTrigger,
|
||||
} from "@/registry/default/ui/tabs"
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/registry/default/ui/tooltip"
|
||||
|
||||
export const description =
|
||||
"An products dashboard with a sidebar navigation. The sidebar has icon navigation. The content area has a breadcrumb and search in the header. It displays a list of products in a table with actions."
|
||||
|
||||
export const iframeHeight = "938px"
|
||||
|
||||
export const containerClassName = "w-full h-full"
|
||||
|
||||
export default function Dashboard() {
|
||||
return (
|
||||
<div className="flex min-h-screen w-full flex-col bg-muted/40">
|
||||
<aside className="fixed inset-y-0 left-0 z-10 hidden w-14 flex-col border-r bg-background sm:flex">
|
||||
<nav className="flex flex-col items-center gap-4 px-2 sm:py-5">
|
||||
<Link
|
||||
href="#"
|
||||
className="group flex h-9 w-9 shrink-0 items-center justify-center gap-2 rounded-full bg-primary text-lg font-semibold text-primary-foreground md:h-8 md:w-8 md:text-base"
|
||||
>
|
||||
<Package2 className="h-4 w-4 transition-all group-hover:scale-110" />
|
||||
<span className="sr-only">Acme Inc</span>
|
||||
</Link>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Link
|
||||
href="#"
|
||||
className="flex h-9 w-9 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:text-foreground md:h-8 md:w-8"
|
||||
>
|
||||
<Home className="h-5 w-5" />
|
||||
<span className="sr-only">Dashboard</span>
|
||||
</Link>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">Dashboard</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Link
|
||||
href="#"
|
||||
className="flex h-9 w-9 items-center justify-center rounded-lg bg-accent text-accent-foreground transition-colors hover:text-foreground md:h-8 md:w-8"
|
||||
>
|
||||
<ShoppingCart className="h-5 w-5" />
|
||||
<span className="sr-only">Orders</span>
|
||||
</Link>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">Orders</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Link
|
||||
href="#"
|
||||
className="flex h-9 w-9 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:text-foreground md:h-8 md:w-8"
|
||||
>
|
||||
<Package className="h-5 w-5" />
|
||||
<span className="sr-only">Products</span>
|
||||
</Link>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">Products</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Link
|
||||
href="#"
|
||||
className="flex h-9 w-9 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:text-foreground md:h-8 md:w-8"
|
||||
>
|
||||
<Users2 className="h-5 w-5" />
|
||||
<span className="sr-only">Customers</span>
|
||||
</Link>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">Customers</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Link
|
||||
href="#"
|
||||
className="flex h-9 w-9 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:text-foreground md:h-8 md:w-8"
|
||||
>
|
||||
<LineChart className="h-5 w-5" />
|
||||
<span className="sr-only">Analytics</span>
|
||||
</Link>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">Analytics</TooltipContent>
|
||||
</Tooltip>
|
||||
</nav>
|
||||
<nav className="mt-auto flex flex-col items-center gap-4 px-2 sm:py-5">
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Link
|
||||
href="#"
|
||||
className="flex h-9 w-9 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:text-foreground md:h-8 md:w-8"
|
||||
>
|
||||
<Settings className="h-5 w-5" />
|
||||
<span className="sr-only">Settings</span>
|
||||
</Link>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">Settings</TooltipContent>
|
||||
</Tooltip>
|
||||
</nav>
|
||||
</aside>
|
||||
<div className="flex flex-col sm:gap-4 sm:py-4 sm:pl-14">
|
||||
<header className="sticky top-0 z-30 flex h-14 items-center gap-4 border-b bg-background px-4 sm:static sm:h-auto sm:border-0 sm:bg-transparent sm:px-6">
|
||||
<Sheet>
|
||||
<SheetTrigger asChild>
|
||||
<Button size="icon" variant="outline" className="sm:hidden">
|
||||
<PanelLeft className="h-5 w-5" />
|
||||
<span className="sr-only">Toggle Menu</span>
|
||||
</Button>
|
||||
</SheetTrigger>
|
||||
<SheetContent side="left" className="sm:max-w-xs">
|
||||
<nav className="grid gap-6 text-lg font-medium">
|
||||
<Link
|
||||
href="#"
|
||||
className="group flex h-10 w-10 shrink-0 items-center justify-center gap-2 rounded-full bg-primary text-lg font-semibold text-primary-foreground md:text-base"
|
||||
>
|
||||
<Package2 className="h-5 w-5 transition-all group-hover:scale-110" />
|
||||
<span className="sr-only">Acme Inc</span>
|
||||
</Link>
|
||||
<Link
|
||||
href="#"
|
||||
className="flex items-center gap-4 px-2.5 text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<Home className="h-5 w-5" />
|
||||
Dashboard
|
||||
</Link>
|
||||
<Link
|
||||
href="#"
|
||||
className="flex items-center gap-4 px-2.5 text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<ShoppingCart className="h-5 w-5" />
|
||||
Orders
|
||||
</Link>
|
||||
<Link
|
||||
href="#"
|
||||
className="flex items-center gap-4 px-2.5 text-foreground"
|
||||
>
|
||||
<Package className="h-5 w-5" />
|
||||
Products
|
||||
</Link>
|
||||
<Link
|
||||
href="#"
|
||||
className="flex items-center gap-4 px-2.5 text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<Users2 className="h-5 w-5" />
|
||||
Customers
|
||||
</Link>
|
||||
<Link
|
||||
href="#"
|
||||
className="flex items-center gap-4 px-2.5 text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<LineChart className="h-5 w-5" />
|
||||
Settings
|
||||
</Link>
|
||||
</nav>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
<Breadcrumb className="hidden md:flex">
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink asChild>
|
||||
<Link href="#">Dashboard</Link>
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink asChild>
|
||||
<Link href="#">Products</Link>
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage>All Products</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
<div className="relative ml-auto flex-1 md:grow-0">
|
||||
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
type="search"
|
||||
placeholder="Search..."
|
||||
className="w-full rounded-lg bg-background pl-8 md:w-[200px] lg:w-[336px]"
|
||||
/>
|
||||
</div>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="overflow-hidden rounded-full"
|
||||
>
|
||||
<Image
|
||||
src="/placeholder-user.jpg"
|
||||
width={36}
|
||||
height={36}
|
||||
alt="Avatar"
|
||||
className="overflow-hidden rounded-full"
|
||||
loading="lazy"
|
||||
/>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuLabel>My Account</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>Settings</DropdownMenuItem>
|
||||
<DropdownMenuItem>Support</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>Logout</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</header>
|
||||
<main className="grid flex-1 items-start gap-4 p-4 sm:px-6 sm:py-0 md:gap-8">
|
||||
<Tabs defaultValue="all">
|
||||
<div className="flex items-center">
|
||||
<TabsList>
|
||||
<TabsTrigger value="all">All</TabsTrigger>
|
||||
<TabsTrigger value="active">Active</TabsTrigger>
|
||||
<TabsTrigger value="draft">Draft</TabsTrigger>
|
||||
<TabsTrigger value="archived" className="hidden sm:flex">
|
||||
Archived
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
<div className="ml-auto flex items-center gap-2">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" size="sm" className="h-8 gap-1">
|
||||
<ListFilter className="h-3.5 w-3.5" />
|
||||
<span className="sr-only sm:not-sr-only sm:whitespace-nowrap">
|
||||
Filter
|
||||
</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuLabel>Filter by</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuCheckboxItem checked>
|
||||
Active
|
||||
</DropdownMenuCheckboxItem>
|
||||
<DropdownMenuCheckboxItem>Draft</DropdownMenuCheckboxItem>
|
||||
<DropdownMenuCheckboxItem>
|
||||
Archived
|
||||
</DropdownMenuCheckboxItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<Button size="sm" variant="outline" className="h-8 gap-1">
|
||||
<File className="h-3.5 w-3.5" />
|
||||
<span className="sr-only sm:not-sr-only sm:whitespace-nowrap">
|
||||
Export
|
||||
</span>
|
||||
</Button>
|
||||
<Button size="sm" className="h-8 gap-1">
|
||||
<PlusCircle className="h-3.5 w-3.5" />
|
||||
<span className="sr-only sm:not-sr-only sm:whitespace-nowrap">
|
||||
Add Product
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<TabsContent value="all">
|
||||
<Card x-chunk="dashboard-06-chunk-0">
|
||||
<CardHeader>
|
||||
<CardTitle>Products</CardTitle>
|
||||
<CardDescription>
|
||||
Manage your products and view their sales performance.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="hidden w-[100px] sm:table-cell">
|
||||
<span className="sr-only">Image</span>
|
||||
</TableHead>
|
||||
<TableHead>Name</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
<TableHead className="hidden md:table-cell">
|
||||
Price
|
||||
</TableHead>
|
||||
<TableHead className="hidden md:table-cell">
|
||||
Total Sales
|
||||
</TableHead>
|
||||
<TableHead className="hidden md:table-cell">
|
||||
Created at
|
||||
</TableHead>
|
||||
<TableHead>
|
||||
<span className="sr-only">Actions</span>
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell className="hidden sm:table-cell">
|
||||
<Image
|
||||
alt="Product image"
|
||||
className="aspect-square rounded-md object-cover"
|
||||
height="64"
|
||||
src="/placeholder.svg"
|
||||
width="64"
|
||||
loading="lazy"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className="font-medium">
|
||||
Laser Lemonade Machine
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant="outline">Draft</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="hidden md:table-cell">
|
||||
$499.99
|
||||
</TableCell>
|
||||
<TableCell className="hidden md:table-cell">
|
||||
25
|
||||
</TableCell>
|
||||
<TableCell className="hidden md:table-cell">
|
||||
2023-07-12 10:42 AM
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
aria-haspopup="true"
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
>
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
<span className="sr-only">Toggle menu</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuLabel>Actions</DropdownMenuLabel>
|
||||
<DropdownMenuItem>Edit</DropdownMenuItem>
|
||||
<DropdownMenuItem>Delete</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell className="hidden sm:table-cell">
|
||||
<Image
|
||||
alt="Product image"
|
||||
className="aspect-square rounded-md object-cover"
|
||||
height="64"
|
||||
src="/placeholder.svg"
|
||||
width="64"
|
||||
loading="lazy"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className="font-medium">
|
||||
Hypernova Headphones
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant="outline">Active</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="hidden md:table-cell">
|
||||
$129.99
|
||||
</TableCell>
|
||||
<TableCell className="hidden md:table-cell">
|
||||
100
|
||||
</TableCell>
|
||||
<TableCell className="hidden md:table-cell">
|
||||
2023-10-18 03:21 PM
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
aria-haspopup="true"
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
>
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
<span className="sr-only">Toggle menu</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuLabel>Actions</DropdownMenuLabel>
|
||||
<DropdownMenuItem>Edit</DropdownMenuItem>
|
||||
<DropdownMenuItem>Delete</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell className="hidden sm:table-cell">
|
||||
<Image
|
||||
alt="Product image"
|
||||
className="aspect-square rounded-md object-cover"
|
||||
height="64"
|
||||
src="/placeholder.svg"
|
||||
width="64"
|
||||
loading="lazy"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className="font-medium">
|
||||
AeroGlow Desk Lamp
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant="outline">Active</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="hidden md:table-cell">
|
||||
$39.99
|
||||
</TableCell>
|
||||
<TableCell className="hidden md:table-cell">
|
||||
50
|
||||
</TableCell>
|
||||
<TableCell className="hidden md:table-cell">
|
||||
2023-11-29 08:15 AM
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
aria-haspopup="true"
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
>
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
<span className="sr-only">Toggle menu</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuLabel>Actions</DropdownMenuLabel>
|
||||
<DropdownMenuItem>Edit</DropdownMenuItem>
|
||||
<DropdownMenuItem>Delete</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell className="hidden sm:table-cell">
|
||||
<Image
|
||||
alt="Product image"
|
||||
className="aspect-square rounded-md object-cover"
|
||||
height="64"
|
||||
src="/placeholder.svg"
|
||||
width="64"
|
||||
loading="lazy"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className="font-medium">
|
||||
TechTonic Energy Drink
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant="secondary">Draft</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="hidden md:table-cell">
|
||||
$2.99
|
||||
</TableCell>
|
||||
<TableCell className="hidden md:table-cell">
|
||||
0
|
||||
</TableCell>
|
||||
<TableCell className="hidden md:table-cell">
|
||||
2023-12-25 11:59 PM
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
aria-haspopup="true"
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
>
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
<span className="sr-only">Toggle menu</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuLabel>Actions</DropdownMenuLabel>
|
||||
<DropdownMenuItem>Edit</DropdownMenuItem>
|
||||
<DropdownMenuItem>Delete</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell className="hidden sm:table-cell">
|
||||
<Image
|
||||
alt="Product image"
|
||||
className="aspect-square rounded-md object-cover"
|
||||
height="64"
|
||||
src="/placeholder.svg"
|
||||
width="64"
|
||||
loading="lazy"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className="font-medium">
|
||||
Gamer Gear Pro Controller
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant="outline">Active</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="hidden md:table-cell">
|
||||
$59.99
|
||||
</TableCell>
|
||||
<TableCell className="hidden md:table-cell">
|
||||
75
|
||||
</TableCell>
|
||||
<TableCell className="hidden md:table-cell">
|
||||
2024-01-01 12:00 AM
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
aria-haspopup="true"
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
>
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
<span className="sr-only">Toggle menu</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuLabel>Actions</DropdownMenuLabel>
|
||||
<DropdownMenuItem>Edit</DropdownMenuItem>
|
||||
<DropdownMenuItem>Delete</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell className="hidden sm:table-cell">
|
||||
<Image
|
||||
alt="Product image"
|
||||
className="aspect-square rounded-md object-cover"
|
||||
height="64"
|
||||
src="/placeholder.svg"
|
||||
width="64"
|
||||
loading="lazy"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className="font-medium">
|
||||
Luminous VR Headset
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant="outline">Active</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="hidden md:table-cell">
|
||||
$199.99
|
||||
</TableCell>
|
||||
<TableCell className="hidden md:table-cell">
|
||||
30
|
||||
</TableCell>
|
||||
<TableCell className="hidden md:table-cell">
|
||||
2024-02-14 02:14 PM
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
aria-haspopup="true"
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
>
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
<span className="sr-only">Toggle menu</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuLabel>Actions</DropdownMenuLabel>
|
||||
<DropdownMenuItem>Edit</DropdownMenuItem>
|
||||
<DropdownMenuItem>Delete</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
Showing <strong>1-10</strong> of <strong>32</strong>{" "}
|
||||
products
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,613 +0,0 @@
|
||||
import Image from "next/image"
|
||||
import Link from "next/link"
|
||||
import {
|
||||
ChevronLeft,
|
||||
Home,
|
||||
LineChart,
|
||||
Package,
|
||||
Package2,
|
||||
PanelLeft,
|
||||
PlusCircle,
|
||||
Search,
|
||||
Settings,
|
||||
ShoppingCart,
|
||||
Upload,
|
||||
Users2,
|
||||
} from "lucide-react"
|
||||
|
||||
import { Badge } from "@/registry/default/ui/badge"
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
BreadcrumbList,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
} from "@/registry/default/ui/breadcrumb"
|
||||
import { Button } from "@/registry/default/ui/button"
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/registry/default/ui/card"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/registry/default/ui/dropdown-menu"
|
||||
import { Input } from "@/registry/default/ui/input"
|
||||
import { Label } from "@/registry/default/ui/label"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/registry/default/ui/select"
|
||||
import { Sheet, SheetContent, SheetTrigger } from "@/registry/default/ui/sheet"
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/registry/default/ui/table"
|
||||
import { Textarea } from "@/registry/default/ui/textarea"
|
||||
import {
|
||||
ToggleGroup,
|
||||
ToggleGroupItem,
|
||||
} from "@/registry/default/ui/toggle-group"
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/registry/default/ui/tooltip"
|
||||
|
||||
export const description =
|
||||
"A product edit page. The product edit page has a form to edit the product details, stock, product category, product status, and product images. The product edit page has a sidebar navigation and a main content area. The main content area has a form to edit the product details, stock, product category, product status, and product images. The sidebar navigation has links to product details, stock, product category, product status, and product images."
|
||||
|
||||
export const iframeHeight = "1200px"
|
||||
|
||||
export const containerClassName = "w-full h-full"
|
||||
|
||||
export default function Dashboard() {
|
||||
return (
|
||||
<div className="flex min-h-screen w-full flex-col bg-muted/40">
|
||||
<aside className="fixed inset-y-0 left-0 z-10 hidden w-14 flex-col border-r bg-background sm:flex">
|
||||
<nav className="flex flex-col items-center gap-4 px-2 sm:py-5">
|
||||
<Link
|
||||
href="#"
|
||||
className="group flex h-9 w-9 shrink-0 items-center justify-center gap-2 rounded-full bg-primary text-lg font-semibold text-primary-foreground md:h-8 md:w-8 md:text-base"
|
||||
>
|
||||
<Package2 className="h-4 w-4 transition-all group-hover:scale-110" />
|
||||
<span className="sr-only">Acme Inc</span>
|
||||
</Link>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Link
|
||||
href="#"
|
||||
className="flex h-9 w-9 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:text-foreground md:h-8 md:w-8"
|
||||
>
|
||||
<Home className="h-5 w-5" />
|
||||
<span className="sr-only">Dashboard</span>
|
||||
</Link>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">Dashboard</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Link
|
||||
href="#"
|
||||
className="flex h-9 w-9 items-center justify-center rounded-lg bg-accent text-accent-foreground transition-colors hover:text-foreground md:h-8 md:w-8"
|
||||
>
|
||||
<ShoppingCart className="h-5 w-5" />
|
||||
<span className="sr-only">Orders</span>
|
||||
</Link>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">Orders</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Link
|
||||
href="#"
|
||||
className="flex h-9 w-9 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:text-foreground md:h-8 md:w-8"
|
||||
>
|
||||
<Package className="h-5 w-5" />
|
||||
<span className="sr-only">Products</span>
|
||||
</Link>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">Products</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Link
|
||||
href="#"
|
||||
className="flex h-9 w-9 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:text-foreground md:h-8 md:w-8"
|
||||
>
|
||||
<Users2 className="h-5 w-5" />
|
||||
<span className="sr-only">Customers</span>
|
||||
</Link>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">Customers</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Link
|
||||
href="#"
|
||||
className="flex h-9 w-9 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:text-foreground md:h-8 md:w-8"
|
||||
>
|
||||
<LineChart className="h-5 w-5" />
|
||||
<span className="sr-only">Analytics</span>
|
||||
</Link>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">Analytics</TooltipContent>
|
||||
</Tooltip>
|
||||
</nav>
|
||||
<nav className="mt-auto flex flex-col items-center gap-4 px-2 sm:py-5">
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Link
|
||||
href="#"
|
||||
className="flex h-9 w-9 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:text-foreground md:h-8 md:w-8"
|
||||
>
|
||||
<Settings className="h-5 w-5" />
|
||||
<span className="sr-only">Settings</span>
|
||||
</Link>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">Settings</TooltipContent>
|
||||
</Tooltip>
|
||||
</nav>
|
||||
</aside>
|
||||
<div className="flex flex-col sm:gap-4 sm:py-4 sm:pl-14">
|
||||
<header className="sticky top-0 z-30 flex h-14 items-center gap-4 border-b bg-background px-4 sm:static sm:h-auto sm:border-0 sm:bg-transparent sm:px-6">
|
||||
<Sheet>
|
||||
<SheetTrigger asChild>
|
||||
<Button size="icon" variant="outline" className="sm:hidden">
|
||||
<PanelLeft className="h-5 w-5" />
|
||||
<span className="sr-only">Toggle Menu</span>
|
||||
</Button>
|
||||
</SheetTrigger>
|
||||
<SheetContent side="left" className="sm:max-w-xs">
|
||||
<nav className="grid gap-6 text-lg font-medium">
|
||||
<Link
|
||||
href="#"
|
||||
className="group flex h-10 w-10 shrink-0 items-center justify-center gap-2 rounded-full bg-primary text-lg font-semibold text-primary-foreground md:text-base"
|
||||
>
|
||||
<Package2 className="h-5 w-5 transition-all group-hover:scale-110" />
|
||||
<span className="sr-only">Acme Inc</span>
|
||||
</Link>
|
||||
<Link
|
||||
href="#"
|
||||
className="flex items-center gap-4 px-2.5 text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<Home className="h-5 w-5" />
|
||||
Dashboard
|
||||
</Link>
|
||||
<Link
|
||||
href="#"
|
||||
className="flex items-center gap-4 px-2.5 text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<ShoppingCart className="h-5 w-5" />
|
||||
Orders
|
||||
</Link>
|
||||
<Link
|
||||
href="#"
|
||||
className="flex items-center gap-4 px-2.5 text-foreground"
|
||||
>
|
||||
<Package className="h-5 w-5" />
|
||||
Products
|
||||
</Link>
|
||||
<Link
|
||||
href="#"
|
||||
className="flex items-center gap-4 px-2.5 text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<Users2 className="h-5 w-5" />
|
||||
Customers
|
||||
</Link>
|
||||
<Link
|
||||
href="#"
|
||||
className="flex items-center gap-4 px-2.5 text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<LineChart className="h-5 w-5" />
|
||||
Settings
|
||||
</Link>
|
||||
</nav>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
<Breadcrumb className="hidden md:flex">
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink asChild>
|
||||
<Link href="#">Dashboard</Link>
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink asChild>
|
||||
<Link href="#">Products</Link>
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage>Edit Product</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
<div className="relative ml-auto flex-1 md:grow-0">
|
||||
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
type="search"
|
||||
placeholder="Search..."
|
||||
className="w-full rounded-lg bg-background pl-8 md:w-[200px] lg:w-[336px]"
|
||||
/>
|
||||
</div>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="overflow-hidden rounded-full"
|
||||
>
|
||||
<Image
|
||||
src="/placeholder-user.jpg"
|
||||
width={36}
|
||||
height={36}
|
||||
loading="lazy"
|
||||
alt="Avatar"
|
||||
className="overflow-hidden rounded-full"
|
||||
/>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuLabel>My Account</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>Settings</DropdownMenuItem>
|
||||
<DropdownMenuItem>Support</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>Logout</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</header>
|
||||
<main className="grid flex-1 items-start gap-4 p-4 sm:px-6 sm:py-0 md:gap-8">
|
||||
<div className="mx-auto grid max-w-[59rem] flex-1 auto-rows-max gap-4">
|
||||
<div className="flex items-center gap-4">
|
||||
<Button variant="outline" size="icon" className="h-7 w-7">
|
||||
<ChevronLeft className="h-4 w-4" />
|
||||
<span className="sr-only">Back</span>
|
||||
</Button>
|
||||
<h1 className="flex-1 shrink-0 whitespace-nowrap text-xl font-semibold tracking-tight sm:grow-0">
|
||||
Pro Controller
|
||||
</h1>
|
||||
<Badge variant="outline" className="ml-auto sm:ml-0">
|
||||
In stock
|
||||
</Badge>
|
||||
<div className="hidden items-center gap-2 md:ml-auto md:flex">
|
||||
<Button variant="outline" size="sm">
|
||||
Discard
|
||||
</Button>
|
||||
<Button size="sm">Save Product</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid gap-4 md:grid-cols-[1fr_250px] lg:grid-cols-3 lg:gap-8">
|
||||
<div className="grid auto-rows-max items-start gap-4 lg:col-span-2 lg:gap-8">
|
||||
<Card x-chunk="dashboard-07-chunk-0">
|
||||
<CardHeader>
|
||||
<CardTitle>Product Details</CardTitle>
|
||||
<CardDescription>
|
||||
Lipsum dolor sit amet, consectetur adipiscing elit
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid gap-6">
|
||||
<div className="grid gap-3">
|
||||
<Label htmlFor="name">Name</Label>
|
||||
<Input
|
||||
id="name"
|
||||
type="text"
|
||||
className="w-full"
|
||||
defaultValue="Gamer Gear Pro Controller"
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-3">
|
||||
<Label htmlFor="description">Description</Label>
|
||||
<Textarea
|
||||
id="description"
|
||||
defaultValue="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam auctor, nisl nec ultricies ultricies, nunc nisl ultricies nunc, nec ultricies nunc nisl nec nunc."
|
||||
className="min-h-32"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card x-chunk="dashboard-07-chunk-1">
|
||||
<CardHeader>
|
||||
<CardTitle>Stock</CardTitle>
|
||||
<CardDescription>
|
||||
Lipsum dolor sit amet, consectetur adipiscing elit
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-[100px]">SKU</TableHead>
|
||||
<TableHead>Stock</TableHead>
|
||||
<TableHead>Price</TableHead>
|
||||
<TableHead className="w-[100px]">Size</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell className="font-semibold">
|
||||
GGPC-001
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Label htmlFor="stock-1" className="sr-only">
|
||||
Stock
|
||||
</Label>
|
||||
<Input
|
||||
id="stock-1"
|
||||
type="number"
|
||||
defaultValue="100"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Label htmlFor="price-1" className="sr-only">
|
||||
Price
|
||||
</Label>
|
||||
<Input
|
||||
id="price-1"
|
||||
type="number"
|
||||
defaultValue="99.99"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<ToggleGroup
|
||||
type="single"
|
||||
defaultValue="s"
|
||||
variant="outline"
|
||||
>
|
||||
<ToggleGroupItem value="s">S</ToggleGroupItem>
|
||||
<ToggleGroupItem value="m">M</ToggleGroupItem>
|
||||
<ToggleGroupItem value="l">L</ToggleGroupItem>
|
||||
</ToggleGroup>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell className="font-semibold">
|
||||
GGPC-002
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Label htmlFor="stock-2" className="sr-only">
|
||||
Stock
|
||||
</Label>
|
||||
<Input
|
||||
id="stock-2"
|
||||
type="number"
|
||||
defaultValue="143"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Label htmlFor="price-2" className="sr-only">
|
||||
Price
|
||||
</Label>
|
||||
<Input
|
||||
id="price-2"
|
||||
type="number"
|
||||
defaultValue="99.99"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<ToggleGroup
|
||||
type="single"
|
||||
defaultValue="m"
|
||||
variant="outline"
|
||||
>
|
||||
<ToggleGroupItem value="s">S</ToggleGroupItem>
|
||||
<ToggleGroupItem value="m">M</ToggleGroupItem>
|
||||
<ToggleGroupItem value="l">L</ToggleGroupItem>
|
||||
</ToggleGroup>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell className="font-semibold">
|
||||
GGPC-003
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Label htmlFor="stock-3" className="sr-only">
|
||||
Stock
|
||||
</Label>
|
||||
<Input
|
||||
id="stock-3"
|
||||
type="number"
|
||||
defaultValue="32"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Label htmlFor="price-3" className="sr-only">
|
||||
Stock
|
||||
</Label>
|
||||
<Input
|
||||
id="price-3"
|
||||
type="number"
|
||||
defaultValue="99.99"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<ToggleGroup
|
||||
type="single"
|
||||
defaultValue="s"
|
||||
variant="outline"
|
||||
>
|
||||
<ToggleGroupItem value="s">S</ToggleGroupItem>
|
||||
<ToggleGroupItem value="m">M</ToggleGroupItem>
|
||||
<ToggleGroupItem value="l">L</ToggleGroupItem>
|
||||
</ToggleGroup>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
<CardFooter className="justify-center border-t p-4">
|
||||
<Button size="sm" variant="ghost" className="gap-1">
|
||||
<PlusCircle className="h-3.5 w-3.5" />
|
||||
Add Variant
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
<Card x-chunk="dashboard-07-chunk-2">
|
||||
<CardHeader>
|
||||
<CardTitle>Product Category</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid gap-6 sm:grid-cols-3">
|
||||
<div className="grid gap-3">
|
||||
<Label htmlFor="category">Category</Label>
|
||||
<Select>
|
||||
<SelectTrigger
|
||||
id="category"
|
||||
aria-label="Select category"
|
||||
>
|
||||
<SelectValue placeholder="Select category" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="clothing">Clothing</SelectItem>
|
||||
<SelectItem value="electronics">
|
||||
Electronics
|
||||
</SelectItem>
|
||||
<SelectItem value="accessories">
|
||||
Accessories
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="grid gap-3">
|
||||
<Label htmlFor="subcategory">
|
||||
Subcategory (optional)
|
||||
</Label>
|
||||
<Select>
|
||||
<SelectTrigger
|
||||
id="subcategory"
|
||||
aria-label="Select subcategory"
|
||||
>
|
||||
<SelectValue placeholder="Select subcategory" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="t-shirts">T-Shirts</SelectItem>
|
||||
<SelectItem value="hoodies">Hoodies</SelectItem>
|
||||
<SelectItem value="sweatshirts">
|
||||
Sweatshirts
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
<div className="grid auto-rows-max items-start gap-4 lg:gap-8">
|
||||
<Card x-chunk="dashboard-07-chunk-3">
|
||||
<CardHeader>
|
||||
<CardTitle>Product Status</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid gap-6">
|
||||
<div className="grid gap-3">
|
||||
<Label htmlFor="status">Status</Label>
|
||||
<Select>
|
||||
<SelectTrigger id="status" aria-label="Select status">
|
||||
<SelectValue placeholder="Select status" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="draft">Draft</SelectItem>
|
||||
<SelectItem value="published">Active</SelectItem>
|
||||
<SelectItem value="archived">Archived</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card
|
||||
className="overflow-hidden" x-chunk="dashboard-07-chunk-4"
|
||||
>
|
||||
<CardHeader>
|
||||
<CardTitle>Product Images</CardTitle>
|
||||
<CardDescription>
|
||||
Lipsum dolor sit amet, consectetur adipiscing elit
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid gap-2">
|
||||
<Image
|
||||
alt="Product image"
|
||||
className="aspect-square w-full rounded-md object-cover"
|
||||
height="300"
|
||||
src="/placeholder.svg"
|
||||
width="300"
|
||||
loading="lazy"
|
||||
/>
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
<button>
|
||||
<Image
|
||||
alt="Product thumbnail image"
|
||||
className="aspect-square w-full rounded-md object-cover"
|
||||
height="84"
|
||||
src="/placeholder.svg"
|
||||
width="84"
|
||||
loading="lazy"
|
||||
/>
|
||||
</button>
|
||||
<button>
|
||||
<Image
|
||||
alt="Product thumbnail image"
|
||||
className="aspect-square w-full rounded-md object-cover"
|
||||
height="84"
|
||||
src="/placeholder.svg"
|
||||
width="84"
|
||||
loading="lazy"
|
||||
/>
|
||||
</button>
|
||||
<button className="flex aspect-square w-full items-center justify-center rounded-md border border-dashed">
|
||||
<Upload className="h-4 w-4 text-muted-foreground" />
|
||||
<span className="sr-only">Upload</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card x-chunk="dashboard-07-chunk-5">
|
||||
<CardHeader>
|
||||
<CardTitle>Archive Product</CardTitle>
|
||||
<CardDescription>
|
||||
Lipsum dolor sit amet, consectetur adipiscing elit.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div></div>
|
||||
<Button size="sm" variant="secondary">
|
||||
Archive Product
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-center gap-2 md:hidden">
|
||||
<Button variant="outline" size="sm">
|
||||
Discard
|
||||
</Button>
|
||||
<Button size="sm">Save Product</Button>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Button } from "@/registry/default/ui/button"
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/registry/default/ui/card"
|
||||
import { Input } from "@/registry/default/ui/input"
|
||||
import { Label } from "@/registry/default/ui/label"
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<div className="flex min-h-svh w-full items-center justify-center p-6 md:p-10">
|
||||
<div className="w-full max-w-sm">
|
||||
<LoginForm01 />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function LoginForm01({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentPropsWithoutRef<"div">) {
|
||||
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault()
|
||||
const formData = new FormData(e.currentTarget)
|
||||
|
||||
// TODO: Add your authentication logic here
|
||||
console.log("Form submitted:", Object.fromEntries(formData))
|
||||
|
||||
// Example: await signIn(formData)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn("flex flex-col gap-6", className)} {...props}>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-2xl">Login</CardTitle>
|
||||
<CardDescription>
|
||||
Enter your email below to login to your account
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="flex flex-col gap-6">
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="email">Email</Label>
|
||||
<Input
|
||||
id="email"
|
||||
name="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" name="password" type="password" required />
|
||||
</div>
|
||||
<Button type="submit" className="w-full">
|
||||
Login
|
||||
</Button>
|
||||
<Button variant="outline" className="w-full">
|
||||
Login with Google
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mt-4 text-center text-sm">
|
||||
Don't have an account?{" "}
|
||||
<a href="#" className="underline underline-offset-4">
|
||||
Sign up
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Button } from "@/registry/default/ui/button"
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/registry/default/ui/card"
|
||||
import { Input } from "@/registry/default/ui/input"
|
||||
import { Label } from "@/registry/default/ui/label"
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<div className="flex min-h-svh w-full items-center justify-center p-6 md:p-10">
|
||||
<div className="w-full max-w-sm">
|
||||
<LoginForm02 />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function LoginForm02({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentPropsWithoutRef<"div">) {
|
||||
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault()
|
||||
const formData = new FormData(e.currentTarget)
|
||||
|
||||
// TODO: Add your authentication logic here
|
||||
console.log("Form submitted:", Object.fromEntries(formData))
|
||||
|
||||
// Example: await signIn(formData)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn("flex flex-col gap-6", className)} {...props}>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-2xl">Login</CardTitle>
|
||||
<CardDescription>
|
||||
Enter your email below to login to your account
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="flex flex-col gap-6">
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="email">Email</Label>
|
||||
<Input
|
||||
id="email"
|
||||
name="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" name="password" type="password" required />
|
||||
</div>
|
||||
<Button type="submit" className="w-full">
|
||||
Login
|
||||
</Button>
|
||||
<Button variant="outline" className="w-full">
|
||||
Login with Google
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mt-4 text-center text-sm">
|
||||
Don't have an account?{" "}
|
||||
<a href="#" className="underline underline-offset-4">
|
||||
Sign up
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Button } from "@/registry/default/ui/button"
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/registry/default/ui/card"
|
||||
import { Input } from "@/registry/default/ui/input"
|
||||
import { Label } from "@/registry/default/ui/label"
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<div className="flex min-h-svh w-full items-center justify-center p-6 md:p-10">
|
||||
<div className="w-full max-w-sm">
|
||||
<LoginForm03 />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function LoginForm03({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentPropsWithoutRef<"div">) {
|
||||
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault()
|
||||
const formData = new FormData(e.currentTarget)
|
||||
|
||||
// TODO: Add your authentication logic here
|
||||
console.log("Form submitted:", Object.fromEntries(formData))
|
||||
|
||||
// Example: await signIn(formData)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn("flex flex-col gap-6", className)} {...props}>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-2xl">Login</CardTitle>
|
||||
<CardDescription>
|
||||
Enter your email below to login to your account
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="flex flex-col gap-6">
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="email">Email</Label>
|
||||
<Input
|
||||
id="email"
|
||||
name="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" name="password" type="password" required />
|
||||
</div>
|
||||
<Button type="submit" className="w-full">
|
||||
Login
|
||||
</Button>
|
||||
<Button variant="outline" className="w-full">
|
||||
Login with Google
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mt-4 text-center text-sm">
|
||||
Don't have an account?{" "}
|
||||
<a href="#" className="underline underline-offset-4">
|
||||
Sign up
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Button } from "@/registry/default/ui/button"
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/registry/default/ui/card"
|
||||
import { Input } from "@/registry/default/ui/input"
|
||||
import { Label } from "@/registry/default/ui/label"
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<div className="flex min-h-svh w-full items-center justify-center p-6 md:p-10">
|
||||
<div className="w-full max-w-sm">
|
||||
<LoginForm04 />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function LoginForm04({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentPropsWithoutRef<"div">) {
|
||||
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault()
|
||||
const formData = new FormData(e.currentTarget)
|
||||
|
||||
// TODO: Add your authentication logic here
|
||||
console.log("Form submitted:", Object.fromEntries(formData))
|
||||
|
||||
// Example: await signIn(formData)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn("flex flex-col gap-6", className)} {...props}>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-2xl">Login</CardTitle>
|
||||
<CardDescription>
|
||||
Enter your email below to login to your account
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="flex flex-col gap-6">
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="email">Email</Label>
|
||||
<Input
|
||||
id="email"
|
||||
name="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" name="password" type="password" required />
|
||||
</div>
|
||||
<Button type="submit" className="w-full">
|
||||
Login
|
||||
</Button>
|
||||
<Button variant="outline" className="w-full">
|
||||
Login with Google
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mt-4 text-center text-sm">
|
||||
Don't have an account?{" "}
|
||||
<a href="#" className="underline underline-offset-4">
|
||||
Sign up
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Button } from "@/registry/default/ui/button"
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/registry/default/ui/card"
|
||||
import { Input } from "@/registry/default/ui/input"
|
||||
import { Label } from "@/registry/default/ui/label"
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<div className="flex min-h-svh w-full items-center justify-center p-6 md:p-10">
|
||||
<div className="w-full max-w-sm">
|
||||
<LoginForm05 />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function LoginForm05({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentPropsWithoutRef<"div">) {
|
||||
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault()
|
||||
const formData = new FormData(e.currentTarget)
|
||||
|
||||
// TODO: Add your authentication logic here
|
||||
console.log("Form submitted:", Object.fromEntries(formData))
|
||||
|
||||
// Example: await signIn(formData)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn("flex flex-col gap-6", className)} {...props}>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-2xl">Login</CardTitle>
|
||||
<CardDescription>
|
||||
Enter your email below to login to your account
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="flex flex-col gap-6">
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="email">Email</Label>
|
||||
<Input
|
||||
id="email"
|
||||
name="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" name="password" type="password" required />
|
||||
</div>
|
||||
<Button type="submit" className="w-full">
|
||||
Login
|
||||
</Button>
|
||||
<Button variant="outline" className="w-full">
|
||||
Login with Google
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mt-4 text-center text-sm">
|
||||
Don't have an account?{" "}
|
||||
<a href="#" className="underline underline-offset-4">
|
||||
Sign up
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { Mail } from "lucide-react"
|
||||
|
||||
import { Button } from "@/registry/default/ui/button"
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/registry/default/ui/card"
|
||||
import { Input } from "@/registry/default/ui/input"
|
||||
|
||||
export default function NewsletterBlock() {
|
||||
const [email, setEmail] = React.useState("")
|
||||
const [isSubmitting, setIsSubmitting] = React.useState(false)
|
||||
const [isSubscribed, setIsSubscribed] = React.useState(false)
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
setIsSubmitting(true)
|
||||
|
||||
// Simulate API call
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000))
|
||||
|
||||
setIsSubscribed(true)
|
||||
setIsSubmitting(false)
|
||||
setEmail("")
|
||||
|
||||
// Reset success message after 3 seconds
|
||||
setTimeout(() => setIsSubscribed(false), 3000)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center p-4">
|
||||
<Card className="w-full max-w-md">
|
||||
<form onSubmit={handleSubmit}>
|
||||
<CardHeader className="space-y-1 text-center">
|
||||
<div className="mx-auto mb-2 flex h-12 w-12 items-center justify-center rounded-full bg-primary/10">
|
||||
<Mail className="h-6 w-6 text-primary" />
|
||||
</div>
|
||||
<CardTitle className="text-2xl">
|
||||
Subscribe to our newsletter
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Get the latest updates, articles, and resources delivered to your
|
||||
inbox weekly.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Input
|
||||
type="email"
|
||||
placeholder="Enter your email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
required
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
type="submit"
|
||||
className="w-full"
|
||||
disabled={isSubmitting || isSubscribed}
|
||||
>
|
||||
{isSubmitting
|
||||
? "Subscribing..."
|
||||
: isSubscribed
|
||||
? "Subscribed! ✓"
|
||||
: "Subscribe"}
|
||||
</Button>
|
||||
{isSubscribed && (
|
||||
<p className="text-center text-sm text-muted-foreground">
|
||||
Thank you for subscribing!
|
||||
</p>
|
||||
)}
|
||||
<p className="text-center text-xs text-muted-foreground">
|
||||
By subscribing, you agree to our Terms of Service and Privacy
|
||||
Policy.
|
||||
</p>
|
||||
</CardContent>
|
||||
</form>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user