mirror of
https://github.com/shadcn-ui/ui.git
synced 2026-06-11 09:51:40 +00:00
Compare commits
574 Commits
shadcn-ui@
...
shadcn/01-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
51e1940525 | ||
|
|
074eed5605 | ||
|
|
ca7fbc3b64 | ||
|
|
b3b2fe2755 | ||
|
|
1fcb318c56 | ||
|
|
2a783175c5 | ||
|
|
69fc8e23cc | ||
|
|
84ec2f6079 | ||
|
|
37e5192fe7 | ||
|
|
205bfc637e | ||
|
|
9eae13639c | ||
|
|
c77ddfc8b7 | ||
|
|
516ed3d17e | ||
|
|
bbda8c0b5a | ||
|
|
d5be468d32 | ||
|
|
9f094a13b9 | ||
|
|
829ef0cbf6 | ||
|
|
4ebfd6896f | ||
|
|
911c52022a | ||
|
|
61e21e3356 | ||
|
|
28d97b7fc1 | ||
|
|
5085e48241 | ||
|
|
b7afb5aa1e | ||
|
|
a6e2d3672f | ||
|
|
0f6efb9769 | ||
|
|
415b02b4bc | ||
|
|
f1dd9c6903 | ||
|
|
abde54987b | ||
|
|
79f2498b28 | ||
|
|
d999f803a8 | ||
|
|
1d8fe41d64 | ||
|
|
33b6146fde | ||
|
|
1d55420629 | ||
|
|
12d4cf2ab0 | ||
|
|
5466432b01 | ||
|
|
f9e4991f15 | ||
|
|
67e7364b3c | ||
|
|
2f6c3e74bb | ||
|
|
a106fc5412 | ||
|
|
19665adeed | ||
|
|
fc0e8c0c52 | ||
|
|
c16c58d0f9 | ||
|
|
a3fe5074c1 | ||
|
|
3baef994d7 | ||
|
|
a7b3dbf121 | ||
|
|
26dc03b545 | ||
|
|
be3c1a9a98 | ||
|
|
418b2d9e14 | ||
|
|
7eb77fb3b9 | ||
|
|
f759a25354 | ||
|
|
0b288883d2 | ||
|
|
cf4ccb34e2 | ||
|
|
dc80a326c2 | ||
|
|
92e5c7ab2c | ||
|
|
d44971b6c2 | ||
|
|
8539dd6eec | ||
|
|
bc7df68620 | ||
|
|
1832f258bd | ||
|
|
e2730f7276 | ||
|
|
22ba26152a | ||
|
|
76d6a59f9f | ||
|
|
3a2a87386f | ||
|
|
1822d95883 | ||
|
|
f90f5148eb | ||
|
|
99b0a5ac90 | ||
|
|
984cb2a1ea | ||
|
|
fed2bac1d9 | ||
|
|
1ebfcd7cd9 | ||
|
|
535a7d9220 | ||
|
|
e7f8cd4566 | ||
|
|
8506977f83 | ||
|
|
575c0214da | ||
|
|
a1ab28bf58 | ||
|
|
190ae2dcd8 | ||
|
|
d5920cc3c1 | ||
|
|
89652889db | ||
|
|
57d15bb2d5 | ||
|
|
779517a1d4 | ||
|
|
b567f7a6c1 | ||
|
|
839afa714f | ||
|
|
32f0bc0de9 | ||
|
|
7d2499c803 | ||
|
|
84b88440c3 | ||
|
|
a5122f9029 | ||
|
|
3b90317e3c | ||
|
|
9d7c7b8978 | ||
|
|
957df8997c | ||
|
|
460240a86c | ||
|
|
bfffb08f42 | ||
|
|
5282332e52 | ||
|
|
3db8a07b3f | ||
|
|
187959435e | ||
|
|
5953229417 | ||
|
|
d6159023ed | ||
|
|
b0774b0578 | ||
|
|
7cefabbe98 | ||
|
|
3740373f99 | ||
|
|
9c4419f249 | ||
|
|
ea9b81594d | ||
|
|
4810f744e3 | ||
|
|
38c5fb4ace | ||
|
|
f37425bdb0 | ||
|
|
592ef33658 | ||
|
|
1a6da427ff | ||
|
|
bd8533bd26 | ||
|
|
202131cd7b | ||
|
|
7977975c9d | ||
|
|
a23fec1c31 | ||
|
|
2af2979cf4 | ||
|
|
aee53d90e2 | ||
|
|
1a68715048 | ||
|
|
34c99c1728 | ||
|
|
86642840e2 | ||
|
|
fac0daac7b | ||
|
|
13fe24e1f8 | ||
|
|
1eb2d74d7c | ||
|
|
16d4d38f56 | ||
|
|
5234c46722 | ||
|
|
f9037239af | ||
|
|
14e4726400 | ||
|
|
9f4d65fc8f | ||
|
|
1e357cb20d | ||
|
|
6339aaa315 | ||
|
|
e2caa3cd35 | ||
|
|
c74a094f14 | ||
|
|
bfb5b6357e | ||
|
|
ac3d965c6e | ||
|
|
6e7cd11d68 | ||
|
|
a75b4ca80e | ||
|
|
475abbbb20 | ||
|
|
762cd73554 | ||
|
|
8c06932800 | ||
|
|
e516481394 | ||
|
|
d1eb24e23a | ||
|
|
9a14c1d092 | ||
|
|
5ef2bc5f45 | ||
|
|
8f6a64f176 | ||
|
|
e85920c191 | ||
|
|
c8c4027b6b | ||
|
|
699195ba77 | ||
|
|
9643db42cf | ||
|
|
dd71498762 | ||
|
|
ddf761e802 | ||
|
|
5f7957ab51 | ||
|
|
f07c7ad5d0 | ||
|
|
d5aa527f0b | ||
|
|
5ec990a474 | ||
|
|
cb742e9825 | ||
|
|
254198b4bf | ||
|
|
1081536246 | ||
|
|
811bb59a8f | ||
|
|
ea677cc74e | ||
|
|
9253b43f29 | ||
|
|
c8fda09a63 | ||
|
|
1ff01b1bb5 | ||
|
|
f10d59fee9 | ||
|
|
2f869a2590 | ||
|
|
102b0b0c62 | ||
|
|
387756c4ab | ||
|
|
05145e66d3 | ||
|
|
704991247c | ||
|
|
729b9ec8ca | ||
|
|
a1bed464f3 | ||
|
|
805ed4120a | ||
|
|
600a593c87 | ||
|
|
500dbe2664 | ||
|
|
c577ee0666 | ||
|
|
d5bf0018fd | ||
|
|
fb36ca4159 | ||
|
|
824a35ada1 | ||
|
|
8d520c8d49 | ||
|
|
0873835339 | ||
|
|
4a0d4cfdb9 | ||
|
|
c4c5d8d419 | ||
|
|
9253682b87 | ||
|
|
c7cd16a637 | ||
|
|
eff1918d41 | ||
|
|
366d6b656b | ||
|
|
e489c5e08e | ||
|
|
d87003e0a4 | ||
|
|
a8633075f7 | ||
|
|
432d5e6e28 | ||
|
|
8f0c26f22a | ||
|
|
149b321c1b | ||
|
|
c1ae5a57cc | ||
|
|
b8ed303d8c | ||
|
|
13c97acf9f | ||
|
|
bed277c54d | ||
|
|
f7c42169a6 | ||
|
|
2c2fe97eb9 | ||
|
|
d64374d009 | ||
|
|
e24e51a2fa | ||
|
|
cdfecd1d97 | ||
|
|
2c043e709f | ||
|
|
aed19aa911 | ||
|
|
9e35d229ae | ||
|
|
db1975ef4d | ||
|
|
961e0b62d7 | ||
|
|
70c684c224 | ||
|
|
4ff64ba818 | ||
|
|
500a353816 | ||
|
|
c830780d62 | ||
|
|
debd51a854 | ||
|
|
78426dd862 | ||
|
|
6e47a94a8f | ||
|
|
ab6a856930 | ||
|
|
b33d3868e9 | ||
|
|
9e0a86122a | ||
|
|
2b276de95a | ||
|
|
64739f8399 | ||
|
|
f0cff7e0eb | ||
|
|
e242adaa9c | ||
|
|
986c00ee0e | ||
|
|
d0eece06d4 | ||
|
|
0a0a566a4e | ||
|
|
bf5a79c4d4 | ||
|
|
f02b412478 | ||
|
|
0d31293c7b | ||
|
|
3d1d19fc1b | ||
|
|
e5b56c84a9 | ||
|
|
630afe836e | ||
|
|
52c12bc27a | ||
|
|
182f2083cd | ||
|
|
3febcdc523 | ||
|
|
ced2513137 | ||
|
|
93ae8bd67f | ||
|
|
3259fb7ca1 | ||
|
|
d8397d80a8 | ||
|
|
06e74fce78 | ||
|
|
bc9e5eaaab | ||
|
|
c9b69d0836 | ||
|
|
539212c49e | ||
|
|
123887c36c | ||
|
|
35c1ba57c2 | ||
|
|
b34516f471 | ||
|
|
66b95402c1 | ||
|
|
5460177a7a | ||
|
|
b0049c2266 | ||
|
|
4e6e21f094 | ||
|
|
3b808c83be | ||
|
|
0e6b37e99a | ||
|
|
9ec433838f | ||
|
|
444ff70590 | ||
|
|
27bc5deff1 | ||
|
|
cacd7c8798 | ||
|
|
f227f93742 | ||
|
|
87e099a3d7 | ||
|
|
303d65718c | ||
|
|
909219df14 | ||
|
|
e8ada4e3c7 | ||
|
|
8fc80836ff | ||
|
|
d0a308cc64 | ||
|
|
e461c02389 | ||
|
|
50c2f6045a | ||
|
|
14aca65eee | ||
|
|
14c952b594 | ||
|
|
1e9434e6f9 | ||
|
|
f3d14c48cb | ||
|
|
c668c35bb9 | ||
|
|
1297abc882 | ||
|
|
36ebbf26dc | ||
|
|
a2abc4ad95 | ||
|
|
4b546bfb13 | ||
|
|
5fc9ade413 | ||
|
|
96880e7c9a | ||
|
|
7dfdb029e7 | ||
|
|
28f34ed3c3 | ||
|
|
bd54184e60 | ||
|
|
ce3adfa075 | ||
|
|
674807c1b4 | ||
|
|
c62167a449 | ||
|
|
061083006f | ||
|
|
1af66c2d08 | ||
|
|
0993d98cc7 | ||
|
|
52d223393a | ||
|
|
207b69fe8d | ||
|
|
408760a93b | ||
|
|
a9ab7afebf | ||
|
|
b6221ea524 | ||
|
|
9ef7967b0d | ||
|
|
64b2f1a5ad | ||
|
|
f4ca57a79c | ||
|
|
99ff9caf71 | ||
|
|
cd9a55b76a | ||
|
|
49373eed96 | ||
|
|
078dfe6607 | ||
|
|
77fc5ec8db | ||
|
|
cfba3fdf70 | ||
|
|
4e4118f3cf | ||
|
|
faa7a67fb3 | ||
|
|
701e1160ea | ||
|
|
f5931f8d09 | ||
|
|
5a28937c6e | ||
|
|
0b74059d38 | ||
|
|
fab9877586 | ||
|
|
0f7591f67c | ||
|
|
81c7e44863 | ||
|
|
2fac3e40c2 | ||
|
|
5ad11ff851 | ||
|
|
6b92dd8eaf | ||
|
|
84540f551d | ||
|
|
99588fff8f | ||
|
|
f99cd2aa5d | ||
|
|
a62a155aac | ||
|
|
dc8853c8df | ||
|
|
259a9ff56a | ||
|
|
9f156a1b89 | ||
|
|
f2e33415c6 | ||
|
|
59a931055f | ||
|
|
59f2d558b6 | ||
|
|
4a77cdc41c | ||
|
|
e42f55f8e3 | ||
|
|
6cc38903b8 | ||
|
|
32e4b78da8 | ||
|
|
2cef110f45 | ||
|
|
79254d6b80 | ||
|
|
6e2d83bf42 | ||
|
|
cc70f6ef43 | ||
|
|
d3fec64031 | ||
|
|
12539b3664 | ||
|
|
210010f5ed | ||
|
|
cc5e07b60b | ||
|
|
29150e576a | ||
|
|
5b856127ee | ||
|
|
93808ab561 | ||
|
|
58637a34d1 | ||
|
|
dd9900ba0e | ||
|
|
650b3b9bda | ||
|
|
238e492181 | ||
|
|
f170784f78 | ||
|
|
03b1d783c4 | ||
|
|
36a9c1bb71 | ||
|
|
949e6f65ef | ||
|
|
dbbb2a427e | ||
|
|
1369d3ca96 | ||
|
|
984c4d8912 | ||
|
|
248347a389 | ||
|
|
f6ad10abd5 | ||
|
|
f0093d6a41 | ||
|
|
c1b955444d | ||
|
|
4ac9db98fe | ||
|
|
06cc0cdf3d | ||
|
|
4aa8b02980 | ||
|
|
13d9693808 | ||
|
|
816b654f07 | ||
|
|
9aaaf429d9 | ||
|
|
afc553d8f8 | ||
|
|
11c31af94f | ||
|
|
bebc2843f0 | ||
|
|
bf0c8b596b | ||
|
|
7590fb7636 | ||
|
|
f47bb973be | ||
|
|
9813c59886 | ||
|
|
9044d890ec | ||
|
|
3bc3d3849c | ||
|
|
ebc9f710f6 | ||
|
|
eda92749e9 | ||
|
|
c86f1bd8b8 | ||
|
|
1da3e740e4 | ||
|
|
5c50a32e8f | ||
|
|
7c3da3e348 | ||
|
|
d4872067a6 | ||
|
|
37c726e60e | ||
|
|
79c054ac7a | ||
|
|
3a4c3b2f7d | ||
|
|
f199dd3bbf | ||
|
|
5ec881d176 | ||
|
|
3f76d5fdc2 | ||
|
|
1f0a7008d6 | ||
|
|
c04f1cac2d | ||
|
|
e8856d1dea | ||
|
|
7f0af435e1 | ||
|
|
3f5f361d19 | ||
|
|
206ca548d3 | ||
|
|
59412bbb08 | ||
|
|
a9f7b8d66d | ||
|
|
2de7bbf32e | ||
|
|
0fae3fd93a | ||
|
|
f859d4857e | ||
|
|
f3ff4a4fc3 | ||
|
|
9a9c5b1faa | ||
|
|
343b20fc5c | ||
|
|
ee94767dba | ||
|
|
7df1007a5b | ||
|
|
a465432a66 | ||
|
|
7822e06904 | ||
|
|
578d2c1823 | ||
|
|
29de71d77f | ||
|
|
0374ba874d | ||
|
|
59b2cc8142 | ||
|
|
0e721be8dd | ||
|
|
7640ef7bbc | ||
|
|
8f3b28f50f | ||
|
|
73be841162 | ||
|
|
ad32fdeb7d | ||
|
|
2dd7864007 | ||
|
|
5d37bae1b8 | ||
|
|
4b59cb812e | ||
|
|
4b200ebf59 | ||
|
|
33795426dd | ||
|
|
fb614ac292 | ||
|
|
be580dbf76 | ||
|
|
98859e7b1c | ||
|
|
6b523b60db | ||
|
|
e3d5377a3e | ||
|
|
f60945c252 | ||
|
|
5eb33f7830 | ||
|
|
f6fef4a2ed | ||
|
|
f6f64ce773 | ||
|
|
7ce4414445 | ||
|
|
319c7c55cc | ||
|
|
57d404b5d3 | ||
|
|
6145dd8118 | ||
|
|
4fb98d520f | ||
|
|
1cf5fad881 | ||
|
|
6f3050248c | ||
|
|
1903eb94a8 | ||
|
|
9f3ae7746f | ||
|
|
c579e9232c | ||
|
|
08018ed623 | ||
|
|
1db90baaf2 | ||
|
|
3dc6207e97 | ||
|
|
5d2373fb7a | ||
|
|
e67c0d4507 | ||
|
|
147206c168 | ||
|
|
e6e9a6772b | ||
|
|
1ae9ffcf58 | ||
|
|
8fad64a854 | ||
|
|
7527ff490a | ||
|
|
3a279a2766 | ||
|
|
51c8c3d798 | ||
|
|
53f211b043 | ||
|
|
a2ed2883ac | ||
|
|
66c7f6d73b | ||
|
|
fc3d8288f7 | ||
|
|
6e399abdb4 | ||
|
|
3c22784a98 | ||
|
|
c82a6fab5f | ||
|
|
3fccfeb301 | ||
|
|
42e8eaf7cb | ||
|
|
d250109cc4 | ||
|
|
ef73e591c8 | ||
|
|
c6917799ce | ||
|
|
b4efc8aa4d | ||
|
|
35f776d38c | ||
|
|
5cadc5e983 | ||
|
|
e0782b328b | ||
|
|
cf0dadafce | ||
|
|
5877dcd21a | ||
|
|
95be4835b1 | ||
|
|
5a13def46d | ||
|
|
b8810caac7 | ||
|
|
24ec36ee7b | ||
|
|
dd94aa936f | ||
|
|
44f35d55b0 | ||
|
|
958a0fdb18 | ||
|
|
6b660033fb | ||
|
|
dac5a0bd2c | ||
|
|
648ddde3a2 | ||
|
|
4ec8a67dab | ||
|
|
9091dcdc1b | ||
|
|
33f89e9654 | ||
|
|
545423c93b | ||
|
|
82528a62a0 | ||
|
|
14abbd94b5 | ||
|
|
cf54b6fa71 | ||
|
|
46f247c47f | ||
|
|
beb0281ca2 | ||
|
|
a54ade1b98 | ||
|
|
11c1bc2cb9 | ||
|
|
4083876e80 | ||
|
|
0176754ff2 | ||
|
|
1be434bc64 | ||
|
|
2a346ede51 | ||
|
|
82c56f9503 | ||
|
|
f68798e50b | ||
|
|
b37fc17f04 | ||
|
|
d6063c5769 | ||
|
|
ef9fa600a5 | ||
|
|
43c4023ed8 | ||
|
|
c765635e13 | ||
|
|
95a9673b1e | ||
|
|
617cdd0e77 | ||
|
|
1536b7824e | ||
|
|
524e4b8b95 | ||
|
|
1f16cf4728 | ||
|
|
4f8d768e59 | ||
|
|
c0deeac0d0 | ||
|
|
897376329b | ||
|
|
d3d52fc687 | ||
|
|
4d0864a5c2 | ||
|
|
e8f58932bd | ||
|
|
2f0dbca221 | ||
|
|
58d012e342 | ||
|
|
963114e118 | ||
|
|
0a4286500e | ||
|
|
ae845788f6 | ||
|
|
ccb2d695a7 | ||
|
|
b838ffe8cc | ||
|
|
7ce6c495bd | ||
|
|
c9ca64d2b9 | ||
|
|
4bb9e9de53 | ||
|
|
c21ecfb665 | ||
|
|
613ec3583f | ||
|
|
170d3c087c | ||
|
|
4b0fbe27fa | ||
|
|
c34193cd34 | ||
|
|
88fdc989e9 | ||
|
|
4506822389 | ||
|
|
33a5fc7966 | ||
|
|
33b77e2f31 | ||
|
|
e3769277d8 | ||
|
|
3992a7b19c | ||
|
|
52c23746bc | ||
|
|
f68976b667 | ||
|
|
7a1f80af2c | ||
|
|
646f715388 | ||
|
|
9441130f05 | ||
|
|
48e3a4a326 | ||
|
|
98078fbe01 | ||
|
|
8be9e5d966 | ||
|
|
a8b1ea7e55 | ||
|
|
3c9f7ca0e2 | ||
|
|
c598f19845 | ||
|
|
7962cee384 | ||
|
|
de3c34845b | ||
|
|
6a1354e52d | ||
|
|
1532a15894 | ||
|
|
8e5d080900 | ||
|
|
cf95943446 | ||
|
|
3210bed755 | ||
|
|
eaa91d43df | ||
|
|
8cf0c7f3ba | ||
|
|
da7729644c | ||
|
|
aca3ef97e3 | ||
|
|
c9fecd4cdf | ||
|
|
6cf598d47f | ||
|
|
91727ec460 | ||
|
|
5e172fc34f | ||
|
|
f461ab0910 | ||
|
|
26c8d0f662 | ||
|
|
ac5c727fc9 | ||
|
|
54b1f5b661 | ||
|
|
25a41cfe2a | ||
|
|
edc653c01e | ||
|
|
f78a4aaa28 | ||
|
|
b4dda36cc9 | ||
|
|
cbe0f1959c | ||
|
|
dbd3b8f066 | ||
|
|
7abb4019c3 | ||
|
|
b14081631f | ||
|
|
0857bfe889 | ||
|
|
ea6699adbf | ||
|
|
1f004243d4 | ||
|
|
eee7ce6879 | ||
|
|
c3377530f4 | ||
|
|
a8bb2ef737 | ||
|
|
ab836d1ab3 | ||
|
|
77d6f5676e | ||
|
|
2846b2ea9a | ||
|
|
0f84973b4d | ||
|
|
f8348621f4 | ||
|
|
fbed50f4e8 | ||
|
|
1971fa7511 | ||
|
|
7b5582e5d0 | ||
|
|
2ca7476c9b | ||
|
|
aea12e9762 | ||
|
|
343acb3a51 | ||
|
|
cf139e5fa1 | ||
|
|
38fb9693d0 | ||
|
|
f1de3401a2 | ||
|
|
379d1560c3 | ||
|
|
d604b82fb3 | ||
|
|
658c710bce |
5
.changeset/brave-cheetahs-smile.md
Normal file
5
.changeset/brave-cheetahs-smile.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"shadcn": minor
|
||||
---
|
||||
|
||||
add support for TanStack Start
|
||||
5
.changeset/cold-impalas-call.md
Normal file
5
.changeset/cold-impalas-call.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"shadcn": patch
|
||||
---
|
||||
|
||||
support for version detection in monorepo
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json",
|
||||
"changelog": ["@changesets/changelog-github", { "repo": "shadcn/ui" }],
|
||||
"changelog": ["@changesets/changelog-github", { "repo": "shadcn-ui/ui" }],
|
||||
"commit": false,
|
||||
"fixed": [],
|
||||
"linked": [],
|
||||
"access": "public",
|
||||
"baseBranch": "main",
|
||||
"updateInternalDependencies": "patch",
|
||||
"ignore": ["www", "**-template"]
|
||||
"ignore": ["www", "v4"]
|
||||
}
|
||||
|
||||
5
.changeset/cool-mails-bake.md
Normal file
5
.changeset/cool-mails-bake.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"shadcn": patch
|
||||
---
|
||||
|
||||
upgrade @antfu/ni
|
||||
5
.changeset/curly-impalas-give.md
Normal file
5
.changeset/curly-impalas-give.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"shadcn": patch
|
||||
---
|
||||
|
||||
allow silent mode with npm
|
||||
5
.changeset/curvy-taxis-help.md
Normal file
5
.changeset/curvy-taxis-help.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"shadcn": patch
|
||||
---
|
||||
|
||||
do not add ring for v3
|
||||
5
.changeset/few-houses-impress.md
Normal file
5
.changeset/few-houses-impress.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"shadcn": minor
|
||||
---
|
||||
|
||||
add theme vars support
|
||||
5
.changeset/five-hounds-tell.md
Normal file
5
.changeset/five-hounds-tell.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"shadcn": minor
|
||||
---
|
||||
|
||||
add tailwind version detection
|
||||
5
.changeset/fluffy-years-knock.md
Normal file
5
.changeset/fluffy-years-knock.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"shadcn": minor
|
||||
---
|
||||
|
||||
add --base-color flag
|
||||
5
.changeset/fresh-cherries-brush.md
Normal file
5
.changeset/fresh-cherries-brush.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"shadcn": minor
|
||||
---
|
||||
|
||||
add support for tailwind v4
|
||||
5
.changeset/great-olives-flow.md
Normal file
5
.changeset/great-olives-flow.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"shadcn": minor
|
||||
---
|
||||
|
||||
default to css vars. add --no-css-variables
|
||||
5
.changeset/green-eels-shout.md
Normal file
5
.changeset/green-eels-shout.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"shadcn": patch
|
||||
---
|
||||
|
||||
cache registry calls
|
||||
5
.changeset/large-timers-kneel.md
Normal file
5
.changeset/large-timers-kneel.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"shadcn": minor
|
||||
---
|
||||
|
||||
replace tailwindcss-animate with tw-animate-css
|
||||
5
.changeset/metal-queens-hang.md
Normal file
5
.changeset/metal-queens-hang.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"shadcn": patch
|
||||
---
|
||||
|
||||
add --template flag
|
||||
5
.changeset/new-cheetahs-dance.md
Normal file
5
.changeset/new-cheetahs-dance.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"shadcn": minor
|
||||
---
|
||||
|
||||
default for new-york for v4
|
||||
5
.changeset/nine-llamas-sell.md
Normal file
5
.changeset/nine-llamas-sell.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"shadcn": minor
|
||||
---
|
||||
|
||||
fix handling of sidebar colors
|
||||
5
.changeset/ninety-needles-brake.md
Normal file
5
.changeset/ninety-needles-brake.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"shadcn": patch
|
||||
---
|
||||
|
||||
do not overwrite user defined vars
|
||||
5
.changeset/orange-papayas-relax.md
Normal file
5
.changeset/orange-papayas-relax.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"shadcn": patch
|
||||
---
|
||||
|
||||
fix cn import bug in monorepo
|
||||
5
.changeset/proud-snails-switch.md
Normal file
5
.changeset/proud-snails-switch.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"shadcn": patch
|
||||
---
|
||||
|
||||
filter out deprecated from --all
|
||||
5
.changeset/quiet-grapes-poke.md
Normal file
5
.changeset/quiet-grapes-poke.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"shadcn": minor
|
||||
---
|
||||
|
||||
add oklch base color
|
||||
5
.changeset/serious-geese-reply.md
Normal file
5
.changeset/serious-geese-reply.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"shadcn": minor
|
||||
---
|
||||
|
||||
hotswap style for v4
|
||||
5
.changeset/shaggy-months-tease.md
Normal file
5
.changeset/shaggy-months-tease.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"shadcn": patch
|
||||
---
|
||||
|
||||
check for empty css vars
|
||||
5
.changeset/six-queens-argue.md
Normal file
5
.changeset/six-queens-argue.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"shadcn": patch
|
||||
---
|
||||
|
||||
only show deprecated for new projects
|
||||
5
.changeset/slow-tools-relax.md
Normal file
5
.changeset/slow-tools-relax.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"shadcn": minor
|
||||
---
|
||||
|
||||
add warning for deprecated components
|
||||
5
.changeset/spotty-plants-juggle.md
Normal file
5
.changeset/spotty-plants-juggle.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"shadcn": patch
|
||||
---
|
||||
|
||||
fix tanstack check
|
||||
5
.changeset/tasty-walls-drum.md
Normal file
5
.changeset/tasty-walls-drum.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"shadcn": minor
|
||||
---
|
||||
|
||||
add support for route install for react-router and laravel
|
||||
5
.changeset/warm-fans-protect.md
Normal file
5
.changeset/warm-fans-protect.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"shadcn": minor
|
||||
---
|
||||
|
||||
add theme prop to registry-item schema
|
||||
8
.eslintignore
Normal file
8
.eslintignore
Normal file
@@ -0,0 +1,8 @@
|
||||
node_modules/
|
||||
target/
|
||||
.next/
|
||||
build/
|
||||
dist/
|
||||
|
||||
/templates/
|
||||
/fixtures/
|
||||
@@ -8,6 +8,7 @@
|
||||
"plugin:tailwindcss/recommended"
|
||||
],
|
||||
"plugins": ["tailwindcss"],
|
||||
"ignorePatterns": ["**/fixtures/**"],
|
||||
"rules": {
|
||||
"@next/next/no-html-link-for-pages": "off",
|
||||
"tailwindcss/no-custom-classname": "off",
|
||||
|
||||
25
.github/DISCUSSION_TEMPLATE/blocks-request.yml
vendored
Normal file
25
.github/DISCUSSION_TEMPLATE/blocks-request.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
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
|
||||
3
.github/FUNDING.yml
vendored
Normal file
3
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [shadcn]
|
||||
85
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
85
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
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
|
||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
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.
|
||||
55
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
55
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
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
|
||||
2
.github/version-script-beta.js
vendored
2
.github/version-script-beta.js
vendored
@@ -4,7 +4,7 @@
|
||||
import { exec } from "child_process"
|
||||
import fs from "fs"
|
||||
|
||||
const pkgJsonPath = "packages/cli/package.json"
|
||||
const pkgJsonPath = "packages/shadcn/package.json"
|
||||
try {
|
||||
const pkg = JSON.parse(fs.readFileSync(pkgJsonPath))
|
||||
exec("git rev-parse --short HEAD", (err, stdout) => {
|
||||
|
||||
2
.github/version-script-next.js
vendored
2
.github/version-script-next.js
vendored
@@ -4,7 +4,7 @@
|
||||
import { exec } from "child_process"
|
||||
import fs from "fs"
|
||||
|
||||
const pkgJsonPath = "packages/cli/package.json"
|
||||
const pkgJsonPath = "packages/shadcn/package.json"
|
||||
try {
|
||||
const pkg = JSON.parse(fs.readFileSync(pkgJsonPath))
|
||||
exec("git rev-parse --short HEAD", (err, stdout) => {
|
||||
|
||||
27
.github/workflows/code-check.yml
vendored
27
.github/workflows/code-check.yml
vendored
@@ -7,7 +7,7 @@ on:
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
name: Lint
|
||||
name: pnpm lint
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
@@ -16,13 +16,13 @@ jobs:
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 20
|
||||
|
||||
- uses: pnpm/action-setup@v2.2.4
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
id: pnpm-install
|
||||
with:
|
||||
version: 8.6.1
|
||||
version: 9.0.6
|
||||
run_install: false
|
||||
|
||||
- name: Get pnpm store directory
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
|
||||
format:
|
||||
runs-on: ubuntu-latest
|
||||
name: Format
|
||||
name: pnpm format:check
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
@@ -52,13 +52,13 @@ jobs:
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 20
|
||||
|
||||
- uses: pnpm/action-setup@v2.2.4
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
id: pnpm-install
|
||||
with:
|
||||
version: 8.6.1
|
||||
version: 9.0.6
|
||||
run_install: false
|
||||
|
||||
- name: Get pnpm store directory
|
||||
@@ -81,7 +81,7 @@ jobs:
|
||||
|
||||
tsc:
|
||||
runs-on: ubuntu-latest
|
||||
name: TypeScript
|
||||
name: pnpm typecheck
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
@@ -90,13 +90,13 @@ jobs:
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 20
|
||||
|
||||
- uses: pnpm/action-setup@v2.2.4
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
id: pnpm-install
|
||||
with:
|
||||
version: 8.6.1
|
||||
version: 9.0.6
|
||||
run_install: false
|
||||
|
||||
- name: Get pnpm store directory
|
||||
@@ -113,4 +113,7 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Build packages
|
||||
run: pnpm --filter=shadcn build
|
||||
|
||||
- run: pnpm typecheck
|
||||
|
||||
45
.github/workflows/issue-stale.yml
vendored
Normal file
45
.github/workflows/issue-stale.yml
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
# 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: ubuntu-latest
|
||||
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 # ~2 years
|
||||
days-before-pr-stale: -1
|
||||
days-before-pr-close: -1
|
||||
remove-issue-stale-when-updated: true
|
||||
stale-issue-label: "stale?"
|
||||
exempt-issue-labels: "roadmap,next,bug"
|
||||
stale-issue-message: "This issue has been automatically marked as stale due to one year of inactivity. It will be closed in 7 days unless there’s further input. If you believe this issue is still relevant, please leave a comment or provide updated details. Thank you."
|
||||
close-issue-message: "This issue has been automatically closed due to one year of inactivity. If you’re still experiencing a similar problem or have additional details to share, please open a new issue following our current issue template. Your updated report helps us investigate and address concerns more efficiently. Thank you for your understanding!"
|
||||
operations-per-run: 300 # 1 operation per 100 issues, the rest is to label/comment/close
|
||||
- uses: actions/stale@v9
|
||||
id: pr-state
|
||||
name: "Mark stale PRs, close stale PRs"
|
||||
with:
|
||||
repo-token: ${{ secrets.STALE_TOKEN }}
|
||||
ascending: true
|
||||
days-before-issue-close: -1
|
||||
days-before-issue-stale: -1
|
||||
days-before-pr-close: 7
|
||||
days-before-pr-stale: 365 # PRs with no activity in over 90 days will be marked as stale
|
||||
remove-pr-stale-when-updated: true
|
||||
exempt-pr-labels: "roadmap,nex,awaiting-approval,work-in-progress"
|
||||
stale-pr-label: "stale?"
|
||||
stale-pr-message: "This PR has been automatically marked as stale due to one year of inactivity. It will be closed in 7 days unless there’s further input. If you believe this PR is still relevant, please leave a comment or provide updated details. Thank you."
|
||||
close-pr-message: "This PR has been automatically closed due to one year of inactivity. Thank you for your understanding!"
|
||||
operations-per-run: 300 # 1 operation per 100 issues, the rest is to label/comment/close
|
||||
8
.github/workflows/prerelease-comment.yml
vendored
8
.github/workflows/prerelease-comment.yml
vendored
@@ -10,7 +10,7 @@ on:
|
||||
jobs:
|
||||
comment:
|
||||
if: |
|
||||
github.repository_owner == 'shadcn' &&
|
||||
github.repository_owner == 'shadcn-ui' &&
|
||||
${{ github.event.workflow_run.conclusion == 'success' }}
|
||||
runs-on: ubuntu-latest
|
||||
name: Write comment to the PR
|
||||
@@ -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-ui@(.*?)-pr-(\d+)/.exec(artifact.name);
|
||||
|
||||
const match = /^npm-package-shadcn@(.*?)-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
|
||||
npx shadcn-ui@${{ env.BETA_PACKAGE_VERSION }}
|
||||
pnpm dlx shadcn@${{ env.BETA_PACKAGE_VERSION }}
|
||||
```
|
||||
|
||||
- name: "Remove the autorelease label once published"
|
||||
|
||||
16
.github/workflows/prerelease.yml
vendored
16
.github/workflows/prerelease.yml
vendored
@@ -10,7 +10,7 @@ on:
|
||||
jobs:
|
||||
prerelease:
|
||||
if: |
|
||||
github.repository_owner == 'shadcn' &&
|
||||
github.repository_owner == 'shadcn-ui' &&
|
||||
contains(github.event.pull_request.labels.*.name, '🚀 autorelease')
|
||||
name: Build & Publish a beta release to NPM
|
||||
runs-on: ubuntu-latest
|
||||
@@ -23,9 +23,9 @@ jobs:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Use PNPM
|
||||
uses: pnpm/action-setup@v2.2.4
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 8.6.1
|
||||
version: 9.0.6
|
||||
|
||||
- name: Use Node.js 18
|
||||
uses: actions/setup-node@v3
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
run: node .github/version-script-beta.js
|
||||
|
||||
- name: Authenticate to NPM
|
||||
run: echo "//registry.npmjs.org/:_authToken=$NPM_ACCESS_TOKEN" >> packages/cli/.npmrc
|
||||
run: echo "//registry.npmjs.org/:_authToken=$NPM_ACCESS_TOKEN" >> packages/shadcn/.npmrc
|
||||
env:
|
||||
NPM_ACCESS_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }}
|
||||
|
||||
@@ -51,10 +51,10 @@ jobs:
|
||||
id: package-version
|
||||
uses: martinbeentjes/npm-get-version-action@main
|
||||
with:
|
||||
path: packages/cli
|
||||
path: packages/shadcn
|
||||
|
||||
- name: Upload packaged artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: npm-package-shadcn-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
|
||||
name: npm-package-shadcn@${{ steps.package-version.outputs.current-version }}-pr-${{ github.event.number }} # encode the PR number into the artifact name
|
||||
path: packages/shadcn/dist/index.js
|
||||
|
||||
10
.github/workflows/release.yml
vendored
10
.github/workflows/release.yml
vendored
@@ -9,7 +9,7 @@ on:
|
||||
|
||||
jobs:
|
||||
release:
|
||||
if: ${{ github.repository_owner == 'shadcn' }}
|
||||
if: ${{ github.repository_owner == 'shadcn-ui' }}
|
||||
name: Create a PR for release workflow
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -19,14 +19,14 @@ jobs:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Use PNPM
|
||||
uses: pnpm/action-setup@v2.2.4
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 8.6.1
|
||||
version: 9.0.6
|
||||
|
||||
- name: Use Node.js 18
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
version: 8.6.1
|
||||
version: 9.0.6
|
||||
node-version: 18
|
||||
cache: "pnpm"
|
||||
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
# run: pnpm check
|
||||
|
||||
- name: Build the package
|
||||
run: pnpm build:cli
|
||||
run: pnpm shadcn:build
|
||||
|
||||
- name: Create Version PR or Publish to NPM
|
||||
id: changesets
|
||||
|
||||
8
.github/workflows/test.yml
vendored
8
.github/workflows/test.yml
vendored
@@ -7,7 +7,7 @@ on:
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
name: Test
|
||||
name: pnpm test
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
@@ -16,13 +16,13 @@ jobs:
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 18
|
||||
|
||||
- uses: pnpm/action-setup@v2.2.4
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
id: pnpm-install
|
||||
with:
|
||||
version: 8.6.1
|
||||
version: 9.0.6
|
||||
run_install: false
|
||||
|
||||
- name: Get pnpm store directory
|
||||
|
||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -33,4 +33,9 @@ yarn-error.log*
|
||||
.turbo
|
||||
|
||||
.contentlayer
|
||||
tsconfig.tsbuildinfo
|
||||
tsconfig.tsbuildinfo
|
||||
|
||||
# ide
|
||||
.idea
|
||||
.fleet
|
||||
.vscode
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
npx commitlint --edit $1
|
||||
18
.kodiak.toml
Normal file
18
.kodiak.toml
Normal file
@@ -0,0 +1,18 @@
|
||||
# .kodiak.toml
|
||||
version = 1
|
||||
|
||||
[merge]
|
||||
automerge_label = "automerge"
|
||||
require_automerge_label = true
|
||||
method = "squash"
|
||||
delete_branch_on_merge = true
|
||||
optimistic_updates = false
|
||||
prioritize_ready_to_merge = true
|
||||
notify_on_conflict = true
|
||||
|
||||
[merge.message]
|
||||
title = "pull_request_title"
|
||||
body = "pull_request_body"
|
||||
include_pr_number = true
|
||||
body_type = "markdown"
|
||||
strip_html_comments = true
|
||||
@@ -3,4 +3,5 @@ node_modules
|
||||
.next
|
||||
build
|
||||
.contentlayer
|
||||
apps/www/pages/api/registry.json
|
||||
apps/www/pages/api/registry.json
|
||||
**/fixtures
|
||||
|
||||
11
.vscode/settings.json
vendored
11
.vscode/settings.json
vendored
@@ -4,7 +4,14 @@
|
||||
{ "pattern": "packages/*/" }
|
||||
],
|
||||
"tailwindCSS.experimental.classRegex": [
|
||||
["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"],
|
||||
["cn\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"]
|
||||
["cva\\(((?:[^()]|\\([^()]*\\))*)\\)", "[\"'`]?([^\"'`]+)[\"'`]?"],
|
||||
["cn\\(((?:[^()]|\\([^()]*\\))*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"]
|
||||
// "cva\\(([^)]*)\\)",
|
||||
// "[\"'`]([^\"'`]*).*?[\"'`]"
|
||||
],
|
||||
"vitest.debugExclude": [
|
||||
"<node_internals>/**",
|
||||
"**/node_modules/**",
|
||||
"**/fixtures/**"
|
||||
]
|
||||
}
|
||||
|
||||
211
CONTRIBUTING.md
Normal file
211
CONTRIBUTING.md
Normal file
@@ -0,0 +1,211 @@
|
||||
# Contributing
|
||||
|
||||
Thanks for your interest in contributing to ui.shadcn.com. We're happy to have you here.
|
||||
|
||||
Please take a moment to review this document before submitting your first pull request. We also strongly recommend that you check for open issues and pull requests to see if someone else is working on something similar.
|
||||
|
||||
If you need any help, feel free to reach out to [@shadcn](https://twitter.com/shadcn).
|
||||
|
||||
## About this repository
|
||||
|
||||
This repository is a monorepo.
|
||||
|
||||
- We use [pnpm](https://pnpm.io) and [`workspaces`](https://pnpm.io/workspaces) for development.
|
||||
- We use [Turborepo](https://turbo.build/repo) as our build system.
|
||||
- We use [changesets](https://github.com/changesets/changesets) for managing releases.
|
||||
|
||||
## Structure
|
||||
|
||||
This repository is structured as follows:
|
||||
|
||||
```
|
||||
apps
|
||||
└── www
|
||||
├── app
|
||||
├── components
|
||||
├── content
|
||||
└── registry
|
||||
├── default
|
||||
│ ├── example
|
||||
│ └── ui
|
||||
└── new-york
|
||||
├── example
|
||||
└── ui
|
||||
packages
|
||||
└── cli
|
||||
```
|
||||
|
||||
| Path | Description |
|
||||
| --------------------- | ---------------------------------------- |
|
||||
| `apps/www/app` | The Next.js application for the website. |
|
||||
| `apps/www/components` | The React components for the website. |
|
||||
| `apps/www/content` | The content for the website. |
|
||||
| `apps/www/registry` | The registry for the components. |
|
||||
| `packages/cli` | The `shadcn-ui` package. |
|
||||
|
||||
## Development
|
||||
|
||||
### Fork this repo
|
||||
|
||||
You can fork this repo by clicking the fork button in the top right corner of this page.
|
||||
|
||||
### Clone on your local machine
|
||||
|
||||
```bash
|
||||
git clone https://github.com/your-username/ui.git
|
||||
```
|
||||
|
||||
### Navigate to project directory
|
||||
|
||||
```bash
|
||||
cd ui
|
||||
```
|
||||
|
||||
### Create a new Branch
|
||||
|
||||
```bash
|
||||
git checkout -b my-new-branch
|
||||
```
|
||||
|
||||
### Install dependencies
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
```
|
||||
|
||||
### Run a workspace
|
||||
|
||||
You can use the `pnpm --filter=[WORKSPACE]` command to start the development process for a workspace.
|
||||
|
||||
#### Examples
|
||||
|
||||
1. To run the `ui.shadcn.com` website:
|
||||
|
||||
```bash
|
||||
pnpm --filter=www dev
|
||||
```
|
||||
|
||||
2. To run the `shadcn-ui` package:
|
||||
|
||||
```bash
|
||||
pnpm --filter=shadcn-ui dev
|
||||
```
|
||||
|
||||
## Running the CLI Locally
|
||||
|
||||
To run the CLI locally, you can follow the workflow:
|
||||
|
||||
1. Start by running the registry (main site) to make sure the components are up to date:
|
||||
|
||||
```bash
|
||||
pnpm www:dev
|
||||
```
|
||||
|
||||
2. Run the development script for the CLI:
|
||||
|
||||
```bash
|
||||
pnpm shadcn:dev
|
||||
```
|
||||
|
||||
3. In another terminal tab, test the CLI by running:
|
||||
|
||||
```bash
|
||||
pnpm shadcn
|
||||
```
|
||||
|
||||
To test the CLI in a specific app, use a command like:
|
||||
|
||||
```bash
|
||||
pnpm shadcn <init | add | ...> -c ~/Desktop/my-app
|
||||
```
|
||||
|
||||
4. To run the tests for the CLI:
|
||||
|
||||
```bash
|
||||
pnpm --filter=shadcn test
|
||||
```
|
||||
|
||||
This workflow ensures that you are running the most recent version of the registry and testing the CLI properly in your local environment.
|
||||
|
||||
## Documentation
|
||||
|
||||
The documentation for this project is located in the `www` workspace. You can run the documentation locally by running the following command:
|
||||
|
||||
```bash
|
||||
pnpm --filter=www dev
|
||||
```
|
||||
|
||||
Documentation is written using [MDX](https://mdxjs.com). You can find the documentation files in the `apps/www/content/docs` directory.
|
||||
|
||||
## Components
|
||||
|
||||
We use a registry system for developing components. You can find the source code for the components under `apps/www/registry`. The components are organized by styles.
|
||||
|
||||
```bash
|
||||
apps
|
||||
└── www
|
||||
└── registry
|
||||
├── default
|
||||
│ ├── example
|
||||
│ └── ui
|
||||
└── new-york
|
||||
├── example
|
||||
└── ui
|
||||
```
|
||||
|
||||
When adding or modifying components, please ensure that:
|
||||
|
||||
1. You make the changes for every style.
|
||||
2. You update the documentation.
|
||||
3. You run `pnpm build:registry` to update the registry.
|
||||
|
||||
## Commit Convention
|
||||
|
||||
Before you create a Pull Request, please check whether your commits comply with
|
||||
the commit conventions used in this repository.
|
||||
|
||||
When you create a commit we kindly ask you to follow the convention
|
||||
`category(scope or module): message` in your commit message while using one of
|
||||
the following categories:
|
||||
|
||||
- `feat / feature`: all changes that introduce completely new code or new
|
||||
features
|
||||
- `fix`: changes that fix a bug (ideally you will additionally reference an
|
||||
issue if present)
|
||||
- `refactor`: any code related change that is not a fix nor a feature
|
||||
- `docs`: changing existing or creating new documentation (i.e. README, docs for
|
||||
usage of a lib or cli usage)
|
||||
- `build`: all changes regarding the build of the software, changes to
|
||||
dependencies or the addition of new dependencies
|
||||
- `test`: all changes regarding tests (adding new tests or changing existing
|
||||
ones)
|
||||
- `ci`: all changes regarding the configuration of continuous integration (i.e.
|
||||
github actions, ci system)
|
||||
- `chore`: all changes to the repository that do not fit into any of the above
|
||||
categories
|
||||
|
||||
e.g. `feat(components): add new prop to the avatar component`
|
||||
|
||||
If you are interested in the detailed specification you can visit
|
||||
https://www.conventionalcommits.org/ or check out the
|
||||
[Angular Commit Message Guidelines](https://github.com/angular/angular/blob/22b96b9/CONTRIBUTING.md#-commit-message-guidelines).
|
||||
|
||||
## Requests for new components
|
||||
|
||||
If you have a request for a new component, please open a discussion on GitHub. We'll be happy to help you out.
|
||||
|
||||
## CLI
|
||||
|
||||
The `shadcn-ui` package is a CLI for adding components to your project. You can find the documentation for the CLI [here](https://ui.shadcn.com/docs/cli).
|
||||
|
||||
Any changes to the CLI should be made in the `packages/cli` directory. If you can, it would be great if you could add tests for your changes.
|
||||
|
||||
## Testing
|
||||
|
||||
Tests are written using [Vitest](https://vitest.dev). You can run all the tests from the root of the repository.
|
||||
|
||||
```bash
|
||||
pnpm test
|
||||
```
|
||||
|
||||
Please ensure that the tests are passing when submitting a pull request. If you're adding new features, please include tests.
|
||||
@@ -8,6 +8,10 @@ Accessible and customizable components that you can copy and paste into your app
|
||||
|
||||
Visit http://ui.shadcn.com/docs to view the documentation.
|
||||
|
||||
## Contributing
|
||||
|
||||
Please read the [contributing guide](/CONTRIBUTING.md).
|
||||
|
||||
## License
|
||||
|
||||
Licensed under the [MIT license](https://github.com/shadcn/ui/blob/main/LICENSE.md).
|
||||
|
||||
41
apps/v4/.gitignore
vendored
Normal file
41
apps/v4/.gitignore
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/versions
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# env files (can opt-in for committing if needed)
|
||||
.env*
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
6
apps/v4/.prettierignore
Normal file
6
apps/v4/.prettierignore
Normal file
@@ -0,0 +1,6 @@
|
||||
dist
|
||||
node_modules
|
||||
.next
|
||||
build
|
||||
.contentlayer
|
||||
registry/__index__.tsx
|
||||
1
apps/v4/README.md
Normal file
1
apps/v4/README.md
Normal file
@@ -0,0 +1 @@
|
||||
This is a wip registry for the `shadcn` canary version. It has React 19 and Tailwind v4 components.
|
||||
1
apps/v4/__registry__/.autogenerated
Normal file
1
apps/v4/__registry__/.autogenerated
Normal file
@@ -0,0 +1 @@
|
||||
// The content of this directory is autogenerated by the registry server.
|
||||
0
apps/v4/__registry__/.gitkeep
Normal file
0
apps/v4/__registry__/.gitkeep
Normal file
1
apps/v4/__registry__/README.md
Normal file
1
apps/v4/__registry__/README.md
Normal file
@@ -0,0 +1 @@
|
||||
> Files inside this directory is autogenerated by `./scripts/build-registry.ts`. **Do not edit them manually.** - shadcn
|
||||
3924
apps/v4/__registry__/index.tsx
Normal file
3924
apps/v4/__registry__/index.tsx
Normal file
File diff suppressed because it is too large
Load Diff
76
apps/v4/app/(app)/charts/charts.tsx
Normal file
76
apps/v4/app/(app)/charts/charts.tsx
Normal file
@@ -0,0 +1,76 @@
|
||||
export { ChartAreaDefault } from "@/registry/new-york-v4/charts/chart-area-default"
|
||||
export { ChartAreaLinear } from "@/registry/new-york-v4/charts/chart-area-linear"
|
||||
export { ChartAreaStep } from "@/registry/new-york-v4/charts/chart-area-step"
|
||||
export { ChartAreaLegend } from "@/registry/new-york-v4/charts/chart-area-legend"
|
||||
export { ChartAreaStacked } from "@/registry/new-york-v4/charts/chart-area-stacked"
|
||||
export { ChartAreaStackedExpand } from "@/registry/new-york-v4/charts/chart-area-stacked-expand"
|
||||
export { ChartAreaIcons } from "@/registry/new-york-v4/charts/chart-area-icons"
|
||||
export { ChartAreaGradient } from "@/registry/new-york-v4/charts/chart-area-gradient"
|
||||
export { ChartAreaAxes } from "@/registry/new-york-v4/charts/chart-area-axes"
|
||||
export { ChartAreaInteractive } from "@/registry/new-york-v4/charts/chart-area-interactive"
|
||||
|
||||
export { ChartBarDefault } from "@/registry/new-york-v4/charts/chart-bar-default"
|
||||
export { ChartBarHorizontal } from "@/registry/new-york-v4/charts/chart-bar-horizontal"
|
||||
export { ChartBarMultiple } from "@/registry/new-york-v4/charts/chart-bar-multiple"
|
||||
export { ChartBarStacked } from "@/registry/new-york-v4/charts/chart-bar-stacked"
|
||||
export { ChartBarLabel } from "@/registry/new-york-v4/charts/chart-bar-label"
|
||||
export { ChartBarLabelCustom } from "@/registry/new-york-v4/charts/chart-bar-label-custom"
|
||||
export { ChartBarMixed } from "@/registry/new-york-v4/charts/chart-bar-mixed"
|
||||
export { ChartBarActive } from "@/registry/new-york-v4/charts/chart-bar-active"
|
||||
export { ChartBarNegative } from "@/registry/new-york-v4/charts/chart-bar-negative"
|
||||
export { ChartBarInteractive } from "@/registry/new-york-v4/charts/chart-bar-interactive"
|
||||
|
||||
export { ChartLineDefault } from "@/registry/new-york-v4/charts/chart-line-default"
|
||||
export { ChartLineLinear } from "@/registry/new-york-v4/charts/chart-line-linear"
|
||||
export { ChartLineStep } from "@/registry/new-york-v4/charts/chart-line-step"
|
||||
export { ChartLineMultiple } from "@/registry/new-york-v4/charts/chart-line-multiple"
|
||||
export { ChartLineDots } from "@/registry/new-york-v4/charts/chart-line-dots"
|
||||
export { ChartLineDotsCustom } from "@/registry/new-york-v4/charts/chart-line-dots-custom"
|
||||
export { ChartLineDotsColors } from "@/registry/new-york-v4/charts/chart-line-dots-colors"
|
||||
export { ChartLineLabel } from "@/registry/new-york-v4/charts/chart-line-label"
|
||||
export { ChartLineLabelCustom } from "@/registry/new-york-v4/charts/chart-line-label-custom"
|
||||
export { ChartLineInteractive } from "@/registry/new-york-v4/charts/chart-line-interactive"
|
||||
|
||||
export { ChartPieSimple } from "@/registry/new-york-v4/charts/chart-pie-simple"
|
||||
export { ChartPieSeparatorNone } from "@/registry/new-york-v4/charts/chart-pie-separator-none"
|
||||
export { ChartPieLabel } from "@/registry/new-york-v4/charts/chart-pie-label"
|
||||
export { ChartPieLabelCustom } from "@/registry/new-york-v4/charts/chart-pie-label-custom"
|
||||
export { ChartPieLabelList } from "@/registry/new-york-v4/charts/chart-pie-label-list"
|
||||
export { ChartPieLegend } from "@/registry/new-york-v4/charts/chart-pie-legend"
|
||||
export { ChartPieDonut } from "@/registry/new-york-v4/charts/chart-pie-donut"
|
||||
export { ChartPieDonutActive } from "@/registry/new-york-v4/charts/chart-pie-donut-active"
|
||||
export { ChartPieDonutText } from "@/registry/new-york-v4/charts/chart-pie-donut-text"
|
||||
export { ChartPieStacked } from "@/registry/new-york-v4/charts/chart-pie-stacked"
|
||||
export { ChartPieInteractive } from "@/registry/new-york-v4/charts/chart-pie-interactive"
|
||||
|
||||
export { ChartRadarDefault } from "@/registry/new-york-v4/charts/chart-radar-default"
|
||||
export { ChartRadarDots } from "@/registry/new-york-v4/charts/chart-radar-dots"
|
||||
export { ChartRadarLinesOnly } from "@/registry/new-york-v4/charts/chart-radar-lines-only"
|
||||
export { ChartRadarLabelCustom } from "@/registry/new-york-v4/charts/chart-radar-label-custom"
|
||||
export { ChartRadarGridCustom } from "@/registry/new-york-v4/charts/chart-radar-grid-custom"
|
||||
export { ChartRadarGridNone } from "@/registry/new-york-v4/charts/chart-radar-grid-none"
|
||||
export { ChartRadarGridCircle } from "@/registry/new-york-v4/charts/chart-radar-grid-circle"
|
||||
export { ChartRadarGridCircleNoLines } from "@/registry/new-york-v4/charts/chart-radar-grid-circle-no-lines"
|
||||
export { ChartRadarGridCircleFill } from "@/registry/new-york-v4/charts/chart-radar-grid-circle-fill"
|
||||
export { ChartRadarGridFill } from "@/registry/new-york-v4/charts/chart-radar-grid-fill"
|
||||
export { ChartRadarMultiple } from "@/registry/new-york-v4/charts/chart-radar-multiple"
|
||||
export { ChartRadarLegend } from "@/registry/new-york-v4/charts/chart-radar-legend"
|
||||
export { ChartRadarIcons } from "@/registry/new-york-v4/charts/chart-radar-icons"
|
||||
export { ChartRadarRadius } from "@/registry/new-york-v4/charts/chart-radar-radius"
|
||||
|
||||
export { ChartRadialSimple } from "@/registry/new-york-v4/charts/chart-radial-simple"
|
||||
export { ChartRadialLabel } from "@/registry/new-york-v4/charts/chart-radial-label"
|
||||
export { ChartRadialGrid } from "@/registry/new-york-v4/charts/chart-radial-grid"
|
||||
export { ChartRadialText } from "@/registry/new-york-v4/charts/chart-radial-text"
|
||||
export { ChartRadialShape } from "@/registry/new-york-v4/charts/chart-radial-shape"
|
||||
export { ChartRadialStacked } from "@/registry/new-york-v4/charts/chart-radial-stacked"
|
||||
|
||||
export { ChartTooltipDefault } from "@/registry/new-york-v4/charts/chart-tooltip-default"
|
||||
export { ChartTooltipIndicatorLine } from "@/registry/new-york-v4/charts/chart-tooltip-indicator-line"
|
||||
export { ChartTooltipIndicatorNone } from "@/registry/new-york-v4/charts/chart-tooltip-indicator-none"
|
||||
export { ChartTooltipLabelCustom } from "@/registry/new-york-v4/charts/chart-tooltip-label-custom"
|
||||
export { ChartTooltipLabelFormatter } from "@/registry/new-york-v4/charts/chart-tooltip-label-formatter"
|
||||
export { ChartTooltipLabelNone } from "@/registry/new-york-v4/charts/chart-tooltip-label-none"
|
||||
export { ChartTooltipFormatter } from "@/registry/new-york-v4/charts/chart-tooltip-formatter"
|
||||
export { ChartTooltipIcons } from "@/registry/new-york-v4/charts/chart-tooltip-icons"
|
||||
export { ChartTooltipAdvanced } from "@/registry/new-york-v4/charts/chart-tooltip-advanced"
|
||||
20
apps/v4/app/(app)/charts/page.tsx
Normal file
20
apps/v4/app/(app)/charts/page.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import { ComponentWrapper } from "@/components/component-wrapper"
|
||||
import * as Charts from "@/app/(app)/charts/charts"
|
||||
|
||||
export default function ChartsPage() {
|
||||
return (
|
||||
<div className="grid flex-1 grid-cols-3 items-start gap-4 p-4 2xl:grid-cols-4">
|
||||
{Object.entries(Charts)
|
||||
.sort()
|
||||
.map(([key, Component]) => (
|
||||
<ComponentWrapper
|
||||
key={key}
|
||||
name={key}
|
||||
className="w-auto data-[name=chartareainteractive]:col-span-3 data-[name=chartbarinteractive]:col-span-3 data-[name=chartlineinteractive]:col-span-3 **:data-[slot=card]:w-full"
|
||||
>
|
||||
<Component />
|
||||
</ComponentWrapper>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
14
apps/v4/app/(app)/forms/page.tsx
Normal file
14
apps/v4/app/(app)/forms/page.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import { FormsDemo } from "@/components/forms-demo"
|
||||
|
||||
export default function FormsPage() {
|
||||
return (
|
||||
<div className="flex flex-1 flex-col items-center justify-center gap-12 p-4 lg:flex-row">
|
||||
<div className="">
|
||||
<FormsDemo />
|
||||
</div>
|
||||
<div className="theme-scaled">
|
||||
<FormsDemo />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
44
apps/v4/app/(app)/layout.tsx
Normal file
44
apps/v4/app/(app)/layout.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import { cookies } from "next/headers"
|
||||
|
||||
import { AppSidebar } from "@/components/app-sidebar"
|
||||
import { ModeSwitcher } from "@/components/mode-switcher"
|
||||
import { NavHeader } from "@/components/nav-header"
|
||||
import { ThemeSelector } from "@/components/theme-selector"
|
||||
import { Separator } from "@/registry/new-york-v4/ui/separator"
|
||||
import {
|
||||
SidebarInset,
|
||||
SidebarProvider,
|
||||
SidebarTrigger,
|
||||
} from "@/registry/new-york-v4/ui/sidebar"
|
||||
|
||||
export default async function AppLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode
|
||||
}>) {
|
||||
const cookieStore = await cookies()
|
||||
const defaultOpen = cookieStore.get("sidebar_state")?.value === "true"
|
||||
|
||||
return (
|
||||
<SidebarProvider defaultOpen={defaultOpen}>
|
||||
<AppSidebar />
|
||||
<SidebarInset>
|
||||
<header className="bg-background sticky inset-x-0 top-0 isolate z-10 flex shrink-0 items-center gap-2 border-b">
|
||||
<div className="flex h-14 w-full items-center gap-2 px-4">
|
||||
<SidebarTrigger className="-ml-1.5" />
|
||||
<Separator
|
||||
orientation="vertical"
|
||||
className="mr-2 data-[orientation=vertical]:h-4"
|
||||
/>
|
||||
<NavHeader />
|
||||
<div className="ml-auto flex items-center gap-2">
|
||||
<ThemeSelector />
|
||||
<ModeSwitcher />
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
{children}
|
||||
</SidebarInset>
|
||||
</SidebarProvider>
|
||||
)
|
||||
}
|
||||
40
apps/v4/app/(app)/login/page.tsx
Normal file
40
apps/v4/app/(app)/login/page.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import {
|
||||
Manrope as FontManrope,
|
||||
Lexend as FontSans,
|
||||
Newsreader as FontSerif,
|
||||
} from "next/font/google"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { LoginForm } from "@/components/login-form"
|
||||
|
||||
const fontSans = FontSans({ subsets: ["latin"], variable: "--font-sans" })
|
||||
const fontSerif = FontSerif({ subsets: ["latin"], variable: "--font-serif" })
|
||||
const fontManrope = FontManrope({
|
||||
subsets: ["latin"],
|
||||
variable: "--font-manrope",
|
||||
})
|
||||
export default function LoginPage() {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"bg-muted dark:bg-background flex flex-1 flex-col items-center justify-center gap-16 p-6 md:p-10",
|
||||
fontSans.variable,
|
||||
fontSerif.variable,
|
||||
fontManrope.variable
|
||||
)}
|
||||
>
|
||||
<div className="w-full max-w-sm md:max-w-3xl">
|
||||
<LoginForm />
|
||||
</div>
|
||||
<div className="theme-login-one w-full max-w-sm md:max-w-3xl">
|
||||
<LoginForm imageUrl="https://images.unsplash.com/photo-1482872376051-5ce74ebf0908?q=80&w=3050&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" />
|
||||
</div>
|
||||
<div className="theme-login-two w-full max-w-sm md:max-w-3xl">
|
||||
<LoginForm imageUrl="https://images.unsplash.com/photo-1498758536662-35b82cd15e29?q=80&w=3088&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" />
|
||||
</div>
|
||||
<div className="theme-login-three w-full max-w-sm md:max-w-3xl">
|
||||
<LoginForm imageUrl="https://images.unsplash.com/photo-1536147116438-62679a5e01f2?q=80&w=2688&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
196
apps/v4/app/(app)/page.tsx
Normal file
196
apps/v4/app/(app)/page.tsx
Normal file
@@ -0,0 +1,196 @@
|
||||
import { AccordionDemo } from "@/components/accordion-demo"
|
||||
import { AlertDemo } from "@/components/alert-demo"
|
||||
import { AlertDialogDemo } from "@/components/alert-dialog-demo"
|
||||
import { AspectRatioDemo } from "@/components/aspect-ratio-demo"
|
||||
import { AvatarDemo } from "@/components/avatar-demo"
|
||||
import { BadgeDemo } from "@/components/badge-demo"
|
||||
import { BreadcrumbDemo } from "@/components/breadcrumb-demo"
|
||||
import { ButtonDemo } from "@/components/button-demo"
|
||||
import { CalendarDemo } from "@/components/calendar-demo"
|
||||
import { CardDemo } from "@/components/card-demo"
|
||||
import { CarouselDemo } from "@/components/carousel-demo"
|
||||
import { ChartDemo } from "@/components/chart-demo"
|
||||
import { CheckboxDemo } from "@/components/checkbox-demo"
|
||||
import { CollapsibleDemo } from "@/components/collapsible-demo"
|
||||
import { ComboboxDemo } from "@/components/combobox-demo"
|
||||
import { CommandDemo } from "@/components/command-demo"
|
||||
import { ComponentWrapper } from "@/components/component-wrapper"
|
||||
import { ContextMenuDemo } from "@/components/context-menu-demo"
|
||||
import { DatePickerDemo } from "@/components/date-picker-demo"
|
||||
import { DialogDemo } from "@/components/dialog-demo"
|
||||
import { DrawerDemo } from "@/components/drawer-demo"
|
||||
import { DropdownMenuDemo } from "@/components/dropdown-menu-demo"
|
||||
import { FormDemo } from "@/components/form-demo"
|
||||
import { HoverCardDemo } from "@/components/hover-card-demo"
|
||||
import { InputDemo } from "@/components/input-demo"
|
||||
import { InputOTPDemo } from "@/components/input-otp-demo"
|
||||
import { LabelDemo } from "@/components/label-demo"
|
||||
import { MenubarDemo } from "@/components/menubar-demo"
|
||||
import { NavigationMenuDemo } from "@/components/navigation-menu-demo"
|
||||
import { PaginationDemo } from "@/components/pagination-demo"
|
||||
import { PopoverDemo } from "@/components/popover-demo"
|
||||
import { ProgressDemo } from "@/components/progress-demo"
|
||||
import { RadioGroupDemo } from "@/components/radio-group-demo"
|
||||
import { ResizableDemo } from "@/components/resizable-demo"
|
||||
import { ScrollAreaDemo } from "@/components/scroll-area-demo"
|
||||
import { SelectDemo } from "@/components/select-demo"
|
||||
import { SeparatorDemo } from "@/components/separator-demo"
|
||||
import { SheetDemo } from "@/components/sheet-demo"
|
||||
import { SkeletonDemo } from "@/components/skeleton-demo"
|
||||
import { SliderDemo } from "@/components/slider-demo"
|
||||
import { SonnerDemo } from "@/components/sonner-demo"
|
||||
import { SwitchDemo } from "@/components/switch-demo"
|
||||
import { TableDemo } from "@/components/table-demo"
|
||||
import { TabsDemo } from "@/components/tabs-demo"
|
||||
import { TextareaDemo } from "@/components/textarea-demo"
|
||||
import { ToggleDemo } from "@/components/toggle-demo"
|
||||
import { ToggleGroupDemo } from "@/components/toggle-group-demo"
|
||||
import { TooltipDemo } from "@/components/tooltip-demo"
|
||||
|
||||
export default function SinkPage() {
|
||||
return (
|
||||
<div className="@container grid flex-1 gap-4 p-4">
|
||||
<ComponentWrapper name="chart" className="w-full">
|
||||
<ChartDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="accordion">
|
||||
<AccordionDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="alert">
|
||||
<AlertDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="alert-dialog">
|
||||
<AlertDialogDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="aspect-ratio">
|
||||
<AspectRatioDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="avatar">
|
||||
<AvatarDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="badge">
|
||||
<BadgeDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="breadcrumb">
|
||||
<BreadcrumbDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="button">
|
||||
<ButtonDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="calendar">
|
||||
<CalendarDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="card">
|
||||
<CardDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="carousel">
|
||||
<CarouselDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="checkbox">
|
||||
<CheckboxDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="collapsible">
|
||||
<CollapsibleDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="combobox">
|
||||
<ComboboxDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="command">
|
||||
<CommandDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="context-menu">
|
||||
<ContextMenuDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="date-picker">
|
||||
<DatePickerDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="dialog">
|
||||
<DialogDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="drawer">
|
||||
<DrawerDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="dropdown-menu">
|
||||
<DropdownMenuDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="form">
|
||||
<FormDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="hover-card">
|
||||
<HoverCardDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="input">
|
||||
<InputDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="input-otp">
|
||||
<InputOTPDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="label">
|
||||
<LabelDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="menubar">
|
||||
<MenubarDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="navigation-menu">
|
||||
<NavigationMenuDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="pagination">
|
||||
<PaginationDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="popover">
|
||||
<PopoverDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="progress">
|
||||
<ProgressDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="radio-group">
|
||||
<RadioGroupDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="resizable">
|
||||
<ResizableDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="scroll-area">
|
||||
<ScrollAreaDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="select">
|
||||
<SelectDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="separator">
|
||||
<SeparatorDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="sheet">
|
||||
<SheetDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="skeleton">
|
||||
<SkeletonDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="slider">
|
||||
<SliderDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="sonner">
|
||||
<SonnerDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="switch">
|
||||
<SwitchDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="table">
|
||||
<TableDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="tabs">
|
||||
<TabsDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="textarea">
|
||||
<TextareaDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="toggle">
|
||||
<ToggleDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="toggle-group">
|
||||
<ToggleGroupDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="tooltip">
|
||||
<TooltipDemo />
|
||||
</ComponentWrapper>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { addDays, format } from "date-fns"
|
||||
import { CalendarIcon } from "lucide-react"
|
||||
import { DateRange } from "react-day-picker"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
import { Calendar } from "@/registry/new-york-v4/ui/calendar"
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/registry/new-york-v4/ui/popover"
|
||||
|
||||
export function AnalyticsDatePicker() {
|
||||
const [date, setDate] = React.useState<DateRange | undefined>({
|
||||
from: new Date(new Date().getFullYear(), 0, 20),
|
||||
to: addDays(new Date(new Date().getFullYear(), 0, 20), 20),
|
||||
})
|
||||
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
id="date"
|
||||
variant="outline"
|
||||
className={cn(
|
||||
"w-fit justify-start px-2 font-normal",
|
||||
!date && "text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
<CalendarIcon className="text-muted-foreground" />
|
||||
{date?.from ? (
|
||||
date.to ? (
|
||||
<>
|
||||
{format(date.from, "LLL dd, y")} -{" "}
|
||||
{format(date.to, "LLL dd, y")}
|
||||
</>
|
||||
) : (
|
||||
format(date.from, "LLL dd, y")
|
||||
)
|
||||
) : (
|
||||
<span>Pick a date</span>
|
||||
)}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0" align="end">
|
||||
<Calendar
|
||||
initialFocus
|
||||
mode="range"
|
||||
defaultMonth={date?.from}
|
||||
selected={date}
|
||||
onSelect={setDate}
|
||||
numberOfMonths={2}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import {
|
||||
ChartLineIcon,
|
||||
FileIcon,
|
||||
HomeIcon,
|
||||
LifeBuoy,
|
||||
Send,
|
||||
Settings2Icon,
|
||||
ShoppingBagIcon,
|
||||
ShoppingCartIcon,
|
||||
UserIcon,
|
||||
} from "lucide-react"
|
||||
|
||||
import { Sidebar, SidebarContent } from "@/registry/new-york-v4/ui/sidebar"
|
||||
import { NavMain } from "@/app/(examples)/dashboard-03/components/nav-main"
|
||||
import { NavSecondary } from "@/app/(examples)/dashboard-03/components/nav-secondary"
|
||||
|
||||
const data = {
|
||||
navMain: [
|
||||
{
|
||||
title: "Dashboard",
|
||||
url: "/dashboard",
|
||||
icon: HomeIcon,
|
||||
},
|
||||
{
|
||||
title: "Analytics",
|
||||
url: "/dashboard/analytics",
|
||||
icon: ChartLineIcon,
|
||||
},
|
||||
{
|
||||
title: "Orders",
|
||||
url: "/dashboard/orders",
|
||||
icon: ShoppingBagIcon,
|
||||
},
|
||||
{
|
||||
title: "Products",
|
||||
url: "/dashboard/products",
|
||||
icon: ShoppingCartIcon,
|
||||
},
|
||||
{
|
||||
title: "Invoices",
|
||||
url: "/dashboard/invoices",
|
||||
icon: FileIcon,
|
||||
},
|
||||
{
|
||||
title: "Customers",
|
||||
url: "/dashboard/customers",
|
||||
icon: UserIcon,
|
||||
},
|
||||
{
|
||||
title: "Settings",
|
||||
url: "/dashboard/settings",
|
||||
icon: Settings2Icon,
|
||||
},
|
||||
],
|
||||
navSecondary: [
|
||||
{
|
||||
title: "Support",
|
||||
url: "#",
|
||||
icon: LifeBuoy,
|
||||
},
|
||||
{
|
||||
title: "Feedback",
|
||||
url: "#",
|
||||
icon: Send,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
||||
return (
|
||||
<Sidebar
|
||||
className="top-(--header-height) h-[calc(100svh-var(--header-height))]!"
|
||||
{...props}
|
||||
>
|
||||
<SidebarContent>
|
||||
<NavMain items={data.navMain} />
|
||||
<NavSecondary items={data.navSecondary} className="mt-auto" />
|
||||
</SidebarContent>
|
||||
</Sidebar>
|
||||
)
|
||||
}
|
||||
110
apps/v4/app/(examples)/dashboard-03/components/chart-revenue.tsx
Normal file
110
apps/v4/app/(examples)/dashboard-03/components/chart-revenue.tsx
Normal file
@@ -0,0 +1,110 @@
|
||||
"use client"
|
||||
|
||||
import { TrendingUp } from "lucide-react"
|
||||
import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts"
|
||||
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/registry/new-york-v4/ui/card"
|
||||
import {
|
||||
ChartConfig,
|
||||
ChartContainer,
|
||||
ChartTooltip,
|
||||
ChartTooltipContent,
|
||||
} from "@/registry/new-york-v4/ui/chart"
|
||||
|
||||
const chartData = [
|
||||
{ month: "January", desktop: 186, mobile: 80 },
|
||||
{ month: "February", desktop: 305, mobile: 200 },
|
||||
{ month: "March", desktop: 237, mobile: 120 },
|
||||
{ month: "April", desktop: 73, mobile: 190 },
|
||||
{ month: "May", desktop: 209, mobile: 130 },
|
||||
{ month: "June", desktop: 346, mobile: 140 },
|
||||
{ month: "July", desktop: 321, mobile: 275 },
|
||||
{ month: "August", desktop: 132, mobile: 95 },
|
||||
{ month: "September", desktop: 189, mobile: 225 },
|
||||
{ month: "October", desktop: 302, mobile: 248 },
|
||||
{ month: "November", desktop: 342, mobile: 285 },
|
||||
{ month: "December", desktop: 328, mobile: 290 },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
desktop: {
|
||||
label: "Desktop",
|
||||
color: "var(--chart-1)",
|
||||
},
|
||||
mobile: {
|
||||
label: "Mobile",
|
||||
color: "var(--chart-2)",
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export function ChartRevenue() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardDescription>January - June 2024</CardDescription>
|
||||
<CardTitle className="text-3xl font-bold tracking-tight">
|
||||
$45,231.89
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ChartContainer config={chartConfig} className="aspect-[3/1]">
|
||||
<BarChart
|
||||
accessibilityLayer
|
||||
data={chartData}
|
||||
margin={{
|
||||
left: -16,
|
||||
right: 0,
|
||||
}}
|
||||
>
|
||||
<CartesianGrid vertical={false} />
|
||||
<XAxis
|
||||
dataKey="month"
|
||||
tickLine={false}
|
||||
tickMargin={10}
|
||||
axisLine={false}
|
||||
tickFormatter={(value) => value.slice(0, 3)}
|
||||
/>
|
||||
<YAxis
|
||||
tickLine={false}
|
||||
tickMargin={10}
|
||||
axisLine={false}
|
||||
tickFormatter={(value) => value.toLocaleString()}
|
||||
domain={[0, "dataMax"]}
|
||||
/>
|
||||
<ChartTooltip
|
||||
cursor={false}
|
||||
content={<ChartTooltipContent hideIndicator />}
|
||||
/>
|
||||
<Bar
|
||||
dataKey="desktop"
|
||||
fill="var(--color-desktop)"
|
||||
radius={[0, 0, 4, 4]}
|
||||
stackId={1}
|
||||
/>
|
||||
<Bar
|
||||
dataKey="mobile"
|
||||
fill="var(--color-mobile)"
|
||||
radius={[4, 4, 0, 0]}
|
||||
stackId={1}
|
||||
/>
|
||||
</BarChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col items-start gap-2 text-sm">
|
||||
<div className="flex gap-2 leading-none font-medium">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="text-muted-foreground leading-none">
|
||||
Showing total visitors for the last 6 months
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { Label, Pie, PieChart, Sector } from "recharts"
|
||||
import { PieSectorDataItem } from "recharts/types/polar/Pie"
|
||||
|
||||
import {
|
||||
Card,
|
||||
CardAction,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/registry/new-york-v4/ui/card"
|
||||
import {
|
||||
ChartConfig,
|
||||
ChartContainer,
|
||||
ChartStyle,
|
||||
ChartTooltip,
|
||||
ChartTooltipContent,
|
||||
} from "@/registry/new-york-v4/ui/chart"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/registry/new-york-v4/ui/select"
|
||||
|
||||
const desktopData = [
|
||||
{ month: "january", desktop: 186, fill: "var(--color-january)" },
|
||||
{ month: "february", desktop: 305, fill: "var(--color-february)" },
|
||||
{ month: "march", desktop: 237, fill: "var(--color-march)" },
|
||||
{ month: "april", desktop: 173, fill: "var(--color-april)" },
|
||||
{ month: "may", desktop: 209, fill: "var(--color-may)" },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
visitors: {
|
||||
label: "Visitors",
|
||||
},
|
||||
desktop: {
|
||||
label: "Desktop",
|
||||
},
|
||||
mobile: {
|
||||
label: "Mobile",
|
||||
},
|
||||
january: {
|
||||
label: "January",
|
||||
color: "var(--chart-1)",
|
||||
},
|
||||
february: {
|
||||
label: "February",
|
||||
color: "var(--chart-2)",
|
||||
},
|
||||
march: {
|
||||
label: "March",
|
||||
color: "var(--chart-3)",
|
||||
},
|
||||
april: {
|
||||
label: "April",
|
||||
color: "var(--chart-4)",
|
||||
},
|
||||
may: {
|
||||
label: "May",
|
||||
color: "var(--chart-5)",
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export function ChartVisitors() {
|
||||
const id = "pie-interactive"
|
||||
const [activeMonth, setActiveMonth] = React.useState(desktopData[0].month)
|
||||
|
||||
const activeIndex = React.useMemo(
|
||||
() => desktopData.findIndex((item) => item.month === activeMonth),
|
||||
[activeMonth]
|
||||
)
|
||||
const months = React.useMemo(() => desktopData.map((item) => item.month), [])
|
||||
|
||||
return (
|
||||
<Card data-chart={id}>
|
||||
<ChartStyle id={id} config={chartConfig} />
|
||||
<CardHeader>
|
||||
<CardDescription>January - June 2024</CardDescription>
|
||||
<CardTitle className="text-2xl font-bold">1,234 visitors</CardTitle>
|
||||
<CardAction>
|
||||
<Select value={activeMonth} onValueChange={setActiveMonth}>
|
||||
<SelectTrigger
|
||||
className="ml-auto h-8 w-[120px]"
|
||||
aria-label="Select a value"
|
||||
>
|
||||
<SelectValue placeholder="Select month" />
|
||||
</SelectTrigger>
|
||||
<SelectContent align="end">
|
||||
{months.map((key) => {
|
||||
const config = chartConfig[key as keyof typeof chartConfig]
|
||||
|
||||
if (!config) {
|
||||
return null
|
||||
}
|
||||
|
||||
const color = "color" in config ? config.color : undefined
|
||||
|
||||
return (
|
||||
<SelectItem key={key} value={key}>
|
||||
<div className="flex items-center gap-2 text-xs">
|
||||
<span
|
||||
className="flex h-3 w-3 shrink-0 rounded-sm"
|
||||
style={{
|
||||
backgroundColor: color,
|
||||
}}
|
||||
/>
|
||||
{config?.label}
|
||||
</div>
|
||||
</SelectItem>
|
||||
)
|
||||
})}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</CardAction>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-1 justify-center pb-0">
|
||||
<ChartContainer
|
||||
id={id}
|
||||
config={chartConfig}
|
||||
className="mx-auto aspect-square w-full max-w-[300px]"
|
||||
>
|
||||
<PieChart>
|
||||
<ChartTooltip
|
||||
cursor={false}
|
||||
content={<ChartTooltipContent hideLabel />}
|
||||
/>
|
||||
<Pie
|
||||
data={desktopData}
|
||||
dataKey="desktop"
|
||||
nameKey="month"
|
||||
innerRadius={60}
|
||||
strokeWidth={5}
|
||||
activeIndex={activeIndex}
|
||||
activeShape={({
|
||||
outerRadius = 0,
|
||||
...props
|
||||
}: PieSectorDataItem) => (
|
||||
<g>
|
||||
<Sector {...props} outerRadius={outerRadius + 10} />
|
||||
<Sector
|
||||
{...props}
|
||||
outerRadius={outerRadius + 25}
|
||||
innerRadius={outerRadius + 12}
|
||||
/>
|
||||
</g>
|
||||
)}
|
||||
>
|
||||
<Label
|
||||
content={({ viewBox }) => {
|
||||
if (viewBox && "cx" in viewBox && "cy" in viewBox) {
|
||||
return (
|
||||
<text
|
||||
x={viewBox.cx}
|
||||
y={viewBox.cy}
|
||||
textAnchor="middle"
|
||||
dominantBaseline="middle"
|
||||
>
|
||||
<tspan
|
||||
x={viewBox.cx}
|
||||
y={viewBox.cy}
|
||||
className="fill-foreground text-3xl font-bold"
|
||||
>
|
||||
{desktopData[activeIndex].desktop.toLocaleString()}
|
||||
</tspan>
|
||||
<tspan
|
||||
x={viewBox.cx}
|
||||
y={(viewBox.cy || 0) + 24}
|
||||
className="fill-muted-foreground"
|
||||
>
|
||||
Visitors
|
||||
</tspan>
|
||||
</text>
|
||||
)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Pie>
|
||||
</PieChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { MoonIcon, SunIcon } from "lucide-react"
|
||||
import { useTheme } from "next-themes"
|
||||
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
|
||||
export function ModeToggle() {
|
||||
const { setTheme, resolvedTheme } = useTheme()
|
||||
|
||||
const toggleTheme = React.useCallback(() => {
|
||||
setTheme(resolvedTheme === "dark" ? "light" : "dark")
|
||||
}, [resolvedTheme, setTheme])
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="icon"
|
||||
className="group/toggle size-8"
|
||||
onClick={toggleTheme}
|
||||
>
|
||||
<SunIcon className="hidden [html.dark_&]:block" />
|
||||
<MoonIcon className="hidden [html.light_&]:block" />
|
||||
<span className="sr-only">Toggle theme</span>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
91
apps/v4/app/(examples)/dashboard-03/components/nav-main.tsx
Normal file
91
apps/v4/app/(examples)/dashboard-03/components/nav-main.tsx
Normal file
@@ -0,0 +1,91 @@
|
||||
"use client"
|
||||
|
||||
import { usePathname } from "next/navigation"
|
||||
import { ChevronRight, type LucideIcon } from "lucide-react"
|
||||
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
CollapsibleTrigger,
|
||||
} from "@/registry/new-york-v4/ui/collapsible"
|
||||
import {
|
||||
SidebarGroup,
|
||||
SidebarGroupLabel,
|
||||
SidebarMenu,
|
||||
SidebarMenuAction,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
SidebarMenuSub,
|
||||
SidebarMenuSubButton,
|
||||
SidebarMenuSubItem,
|
||||
} from "@/registry/new-york-v4/ui/sidebar"
|
||||
|
||||
export function NavMain({
|
||||
items,
|
||||
}: {
|
||||
items: {
|
||||
title: string
|
||||
url: string
|
||||
icon: LucideIcon
|
||||
isActive?: boolean
|
||||
items?: {
|
||||
title: string
|
||||
url: string
|
||||
}[]
|
||||
disabled?: boolean
|
||||
}[]
|
||||
}) {
|
||||
const pathname = usePathname()
|
||||
|
||||
return (
|
||||
<SidebarGroup>
|
||||
<SidebarGroupLabel>Dashboard</SidebarGroupLabel>
|
||||
<SidebarMenu>
|
||||
{items.map((item) => (
|
||||
<Collapsible key={item.title} asChild defaultOpen={item.isActive}>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton
|
||||
asChild
|
||||
tooltip={item.title}
|
||||
isActive={pathname === item.url}
|
||||
disabled={item.disabled}
|
||||
>
|
||||
<a
|
||||
href={item.disabled ? "#" : item.url}
|
||||
data-disabled={item.disabled}
|
||||
className="data-[disabled=true]:opacity-50"
|
||||
>
|
||||
<item.icon className="text-muted-foreground" />
|
||||
<span>{item.title}</span>
|
||||
</a>
|
||||
</SidebarMenuButton>
|
||||
{item.items?.length ? (
|
||||
<>
|
||||
<CollapsibleTrigger asChild>
|
||||
<SidebarMenuAction className="data-[state=open]:rotate-90">
|
||||
<ChevronRight />
|
||||
<span className="sr-only">Toggle</span>
|
||||
</SidebarMenuAction>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<SidebarMenuSub>
|
||||
{item.items?.map((subItem) => (
|
||||
<SidebarMenuSubItem key={subItem.title}>
|
||||
<SidebarMenuSubButton asChild>
|
||||
<a href={subItem.url}>
|
||||
<span>{subItem.title}</span>
|
||||
</a>
|
||||
</SidebarMenuSubButton>
|
||||
</SidebarMenuSubItem>
|
||||
))}
|
||||
</SidebarMenuSub>
|
||||
</CollapsibleContent>
|
||||
</>
|
||||
) : null}
|
||||
</SidebarMenuItem>
|
||||
</Collapsible>
|
||||
))}
|
||||
</SidebarMenu>
|
||||
</SidebarGroup>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import * as React from "react"
|
||||
import { type LucideIcon } from "lucide-react"
|
||||
|
||||
import {
|
||||
SidebarGroup,
|
||||
SidebarGroupContent,
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
} from "@/registry/new-york-v4/ui/sidebar"
|
||||
|
||||
export function NavSecondary({
|
||||
items,
|
||||
...props
|
||||
}: {
|
||||
items: {
|
||||
title: string
|
||||
url: string
|
||||
icon: LucideIcon
|
||||
}[]
|
||||
} & React.ComponentPropsWithoutRef<typeof SidebarGroup>) {
|
||||
return (
|
||||
<SidebarGroup {...props}>
|
||||
<SidebarGroupContent>
|
||||
<SidebarMenu>
|
||||
{items.map((item) => (
|
||||
<SidebarMenuItem key={item.title}>
|
||||
<SidebarMenuButton asChild size="sm">
|
||||
<a href={item.url}>
|
||||
<item.icon />
|
||||
<span>{item.title}</span>
|
||||
</a>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
))}
|
||||
</SidebarMenu>
|
||||
</SidebarGroupContent>
|
||||
</SidebarGroup>
|
||||
)
|
||||
}
|
||||
90
apps/v4/app/(examples)/dashboard-03/components/nav-user.tsx
Normal file
90
apps/v4/app/(examples)/dashboard-03/components/nav-user.tsx
Normal file
@@ -0,0 +1,90 @@
|
||||
"use client"
|
||||
|
||||
import { BadgeCheck, Bell, CreditCard, LogOut, Sparkles } from "lucide-react"
|
||||
|
||||
import {
|
||||
Avatar,
|
||||
AvatarFallback,
|
||||
AvatarImage,
|
||||
} from "@/registry/new-york-v4/ui/avatar"
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/registry/new-york-v4/ui/dropdown-menu"
|
||||
|
||||
export function NavUser({
|
||||
user,
|
||||
}: {
|
||||
user: {
|
||||
name: string
|
||||
email: string
|
||||
avatar: string
|
||||
}
|
||||
}) {
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="icon">
|
||||
<Avatar className="size-8 rounded-md">
|
||||
<AvatarImage src={user.avatar} alt={user.name} />
|
||||
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
|
||||
</Avatar>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
className="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg"
|
||||
side="bottom"
|
||||
align="end"
|
||||
sideOffset={4}
|
||||
>
|
||||
<DropdownMenuLabel className="p-0 font-normal">
|
||||
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
||||
<Avatar className="h-8 w-8 rounded-lg">
|
||||
<AvatarImage src={user.avatar} alt={user.name} />
|
||||
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||
<span className="truncate font-medium">{user.name}</span>
|
||||
<span className="text-muted-foreground truncate text-xs">
|
||||
{user.email}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem>
|
||||
<Sparkles />
|
||||
Upgrade to Pro
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem>
|
||||
<BadgeCheck />
|
||||
Account
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<CreditCard />
|
||||
Billing
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<Bell />
|
||||
Notifications
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>
|
||||
<LogOut />
|
||||
Log out
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
import {
|
||||
ArrowUpDownIcon,
|
||||
EllipsisVerticalIcon,
|
||||
ListFilterIcon,
|
||||
PlusIcon,
|
||||
} from "lucide-react"
|
||||
|
||||
import { Badge } from "@/registry/new-york-v4/ui/badge"
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
} from "@/registry/new-york-v4/ui/card"
|
||||
import { Checkbox } from "@/registry/new-york-v4/ui/checkbox"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/registry/new-york-v4/ui/dropdown-menu"
|
||||
import {
|
||||
Pagination,
|
||||
PaginationContent,
|
||||
PaginationEllipsis,
|
||||
PaginationItem,
|
||||
PaginationLink,
|
||||
PaginationNext,
|
||||
PaginationPrevious,
|
||||
} from "@/registry/new-york-v4/ui/pagination"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/registry/new-york-v4/ui/select"
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/registry/new-york-v4/ui/table"
|
||||
import { Tabs, TabsList, TabsTrigger } from "@/registry/new-york-v4/ui/tabs"
|
||||
|
||||
export function ProductsTable({
|
||||
products,
|
||||
}: {
|
||||
products: {
|
||||
id: string
|
||||
name: string
|
||||
price: number
|
||||
stock: number
|
||||
dateAdded: string
|
||||
status: string
|
||||
}[]
|
||||
}) {
|
||||
return (
|
||||
<Card className="flex w-full flex-col gap-4">
|
||||
<CardHeader className="flex flex-row items-center justify-between">
|
||||
<Tabs defaultValue="all">
|
||||
<TabsList className="w-full @3xl/page:w-fit">
|
||||
<TabsTrigger value="all">All Products</TabsTrigger>
|
||||
<TabsTrigger value="in-stock">In Stock</TabsTrigger>
|
||||
<TabsTrigger value="low-stock">Low Stock</TabsTrigger>
|
||||
<TabsTrigger value="add-product" asChild>
|
||||
<button>
|
||||
<PlusIcon />
|
||||
</button>
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
<div className="hidden items-center gap-2 **:data-[slot=button]:size-8 **:data-[slot=select-trigger]:h-8 @3xl/page:flex">
|
||||
<Select defaultValue="all">
|
||||
<SelectTrigger>
|
||||
<span className="text-muted-foreground text-sm">Category:</span>
|
||||
<SelectValue placeholder="Select a product" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">All</SelectItem>
|
||||
<SelectItem value="in-stock">In Stock</SelectItem>
|
||||
<SelectItem value="low-stock">Low Stock</SelectItem>
|
||||
<SelectItem value="archived">Archived</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Select defaultValue="all">
|
||||
<SelectTrigger>
|
||||
<span className="text-muted-foreground text-sm">Price:</span>
|
||||
<SelectValue placeholder="Select a product" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">$100-$200</SelectItem>
|
||||
<SelectItem value="in-stock">$200-$300</SelectItem>
|
||||
<SelectItem value="low-stock">$300-$400</SelectItem>
|
||||
<SelectItem value="archived">$400-$500</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Select defaultValue="all">
|
||||
<SelectTrigger>
|
||||
<span className="text-muted-foreground text-sm">Status:</span>
|
||||
<SelectValue placeholder="Select a product" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">In Stock</SelectItem>
|
||||
<SelectItem value="in-stock">Low Stock</SelectItem>
|
||||
<SelectItem value="low-stock">Archived</SelectItem>
|
||||
<SelectItem value="archived">Archived</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Button variant="outline" size="icon">
|
||||
<ListFilterIcon />
|
||||
</Button>
|
||||
<Button variant="outline" size="icon">
|
||||
<ArrowUpDownIcon />
|
||||
</Button>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-12 px-4">
|
||||
<Checkbox />
|
||||
</TableHead>
|
||||
<TableHead>Product</TableHead>
|
||||
<TableHead className="text-right">Price</TableHead>
|
||||
<TableHead className="text-right">Stock</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
<TableHead>Date Added</TableHead>
|
||||
<TableHead />
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody className="**:data-[slot=table-cell]:py-2.5">
|
||||
{products.map((product) => (
|
||||
<TableRow key={product.id}>
|
||||
<TableCell className="px-4">
|
||||
<Checkbox />
|
||||
</TableCell>
|
||||
<TableCell className="font-medium">{product.name}</TableCell>
|
||||
<TableCell className="text-right">
|
||||
${product.price.toFixed(2)}
|
||||
</TableCell>
|
||||
<TableCell className="text-right">{product.stock}</TableCell>
|
||||
<TableCell>
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className={
|
||||
product.status === "Low Stock"
|
||||
? "border-orange-700 bg-transparent text-orange-700 dark:border-orange-700 dark:bg-transparent dark:text-orange-700"
|
||||
: "bg-green-100 text-green-800 dark:bg-green-950 dark:text-green-100"
|
||||
}
|
||||
>
|
||||
{product.status}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{new Date(product.dateAdded).toLocaleDateString("en-US", {
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
year: "numeric",
|
||||
})}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="icon" className="size-6">
|
||||
<EllipsisVerticalIcon />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem>Edit</DropdownMenuItem>
|
||||
<DropdownMenuItem variant="destructive">
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
<CardFooter className="flex flex-col items-center justify-between border-t pt-6 @3xl/page:flex-row">
|
||||
<div className="text-muted-foreground hidden text-sm @3xl/page:block">
|
||||
Showing 1-10 of 100 products
|
||||
</div>
|
||||
<Pagination className="mx-0 w-fit">
|
||||
<PaginationContent>
|
||||
<PaginationItem>
|
||||
<PaginationPrevious href="#" />
|
||||
</PaginationItem>
|
||||
<PaginationItem>
|
||||
<PaginationLink href="#">1</PaginationLink>
|
||||
</PaginationItem>
|
||||
<PaginationItem>
|
||||
<PaginationLink href="#" isActive>
|
||||
2
|
||||
</PaginationLink>
|
||||
</PaginationItem>
|
||||
<PaginationItem>
|
||||
<PaginationLink href="#">3</PaginationLink>
|
||||
</PaginationItem>
|
||||
<PaginationItem>
|
||||
<PaginationEllipsis />
|
||||
</PaginationItem>
|
||||
<PaginationItem>
|
||||
<PaginationNext href="#" />
|
||||
</PaginationItem>
|
||||
</PaginationContent>
|
||||
</Pagination>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { Search } from "lucide-react"
|
||||
|
||||
import { Label } from "@/registry/new-york-v4/ui/label"
|
||||
import { SidebarInput } from "@/registry/new-york-v4/ui/sidebar"
|
||||
|
||||
export function SearchForm({ ...props }: React.ComponentProps<"form">) {
|
||||
return (
|
||||
<form {...props}>
|
||||
<div className="relative">
|
||||
<Label htmlFor="search" className="sr-only">
|
||||
Search
|
||||
</Label>
|
||||
<SidebarInput
|
||||
id="search"
|
||||
placeholder="Type to search..."
|
||||
className="h-8 pl-7"
|
||||
/>
|
||||
<Search className="pointer-events-none absolute top-1/2 left-2 size-4 -translate-y-1/2 opacity-50 select-none" />
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
103
apps/v4/app/(examples)/dashboard-03/components/site-header.tsx
Normal file
103
apps/v4/app/(examples)/dashboard-03/components/site-header.tsx
Normal file
@@ -0,0 +1,103 @@
|
||||
"use client"
|
||||
|
||||
import { Fragment, useMemo } from "react"
|
||||
import { usePathname } from "next/navigation"
|
||||
import { SidebarIcon } from "lucide-react"
|
||||
|
||||
import { ThemeSelector } from "@/components/theme-selector"
|
||||
import { SearchForm } from "@/registry/new-york-v4/blocks/sidebar-16/components/search-form"
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
BreadcrumbList,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
} from "@/registry/new-york-v4/ui/breadcrumb"
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
import { Separator } from "@/registry/new-york-v4/ui/separator"
|
||||
import { useSidebar } from "@/registry/new-york-v4/ui/sidebar"
|
||||
import { ModeToggle } from "@/app/(examples)/dashboard-03/components/mode-toggle"
|
||||
import { NavUser } from "@/app/(examples)/dashboard-03/components/nav-user"
|
||||
|
||||
export function SiteHeader() {
|
||||
const { toggleSidebar } = useSidebar()
|
||||
const pathname = usePathname()
|
||||
|
||||
// Faux breadcrumbs for demo.
|
||||
const breadcrumbs = useMemo(() => {
|
||||
return pathname
|
||||
.split("/")
|
||||
.filter((path) => path !== "")
|
||||
.map((path, index, array) => ({
|
||||
label: path,
|
||||
href: `/${array.slice(0, index + 1).join("/")}`,
|
||||
}))
|
||||
}, [pathname])
|
||||
|
||||
return (
|
||||
<header
|
||||
data-slot="site-header"
|
||||
className="bg-background sticky top-0 z-50 flex w-full items-center border-b"
|
||||
>
|
||||
<div className="flex h-(--header-height) w-full items-center gap-2 px-2 pr-4">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={toggleSidebar}
|
||||
className="gap-2.5 has-[>svg]:px-2"
|
||||
>
|
||||
<SidebarIcon />
|
||||
<span className="truncate font-medium">Acme Inc</span>
|
||||
</Button>
|
||||
<Separator
|
||||
orientation="vertical"
|
||||
className="mr-2 data-[orientation=vertical]:h-4"
|
||||
/>
|
||||
<Breadcrumb className="hidden sm:block">
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink href="/" className="capitalize">
|
||||
Home
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
{breadcrumbs.map((breadcrumb, index) =>
|
||||
index === breadcrumbs.length - 1 ? (
|
||||
<BreadcrumbItem key={index}>
|
||||
<BreadcrumbPage className="capitalize">
|
||||
{breadcrumb.label}
|
||||
</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
) : (
|
||||
<Fragment key={index}>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink
|
||||
href={breadcrumb.href}
|
||||
className="capitalize"
|
||||
>
|
||||
{breadcrumb.label}
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
</Fragment>
|
||||
)
|
||||
)}
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
<div className="ml-auto flex items-center gap-2">
|
||||
<SearchForm className="w-fullsm:w-auto" />
|
||||
<ThemeSelector />
|
||||
<ModeToggle />
|
||||
<NavUser
|
||||
user={{
|
||||
name: "shadcn",
|
||||
email: "m@example.com",
|
||||
avatar: "/avatars/shadcn.jpg",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
8
apps/v4/app/(examples)/dashboard-03/customers/page.tsx
Normal file
8
apps/v4/app/(examples)/dashboard-03/customers/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
export default function CustomersPage() {
|
||||
return (
|
||||
<div className="p-6">
|
||||
<div className="bg-input p-4">Input</div>
|
||||
<div className="bg-input/30 p-4">Input 50</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
31
apps/v4/app/(examples)/dashboard-03/layout.tsx
Normal file
31
apps/v4/app/(examples)/dashboard-03/layout.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import { cookies } from "next/headers"
|
||||
|
||||
import {
|
||||
SidebarInset,
|
||||
SidebarProvider,
|
||||
} from "@/registry/new-york-v4/ui/sidebar"
|
||||
import { AppSidebar } from "@/app/(examples)/dashboard-03/components/app-sidebar"
|
||||
import { SiteHeader } from "@/app/(examples)/dashboard-03/components/site-header"
|
||||
|
||||
import "../../themes.css"
|
||||
|
||||
export default async function DashboardLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
const cookieStore = await cookies()
|
||||
const defaultOpen = cookieStore.get("sidebar_state")?.value === "true"
|
||||
|
||||
return (
|
||||
<main className="[--header-height:calc(theme(spacing.14))]">
|
||||
<SidebarProvider defaultOpen={defaultOpen} className="flex flex-col">
|
||||
<SiteHeader />
|
||||
<div className="flex flex-1">
|
||||
<AppSidebar />
|
||||
<SidebarInset>{children}</SidebarInset>
|
||||
</div>
|
||||
</SidebarProvider>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
206
apps/v4/app/(examples)/dashboard-03/page.tsx
Normal file
206
apps/v4/app/(examples)/dashboard-03/page.tsx
Normal file
@@ -0,0 +1,206 @@
|
||||
import { Metadata } from "next"
|
||||
import {
|
||||
DownloadIcon,
|
||||
FilterIcon,
|
||||
TrendingDownIcon,
|
||||
TrendingUpIcon,
|
||||
} from "lucide-react"
|
||||
|
||||
import { Badge } from "@/registry/new-york-v4/ui/badge"
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
import {
|
||||
Card,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/registry/new-york-v4/ui/card"
|
||||
import {
|
||||
Tabs,
|
||||
TabsContent,
|
||||
TabsList,
|
||||
TabsTrigger,
|
||||
} from "@/registry/new-york-v4/ui/tabs"
|
||||
import { AnalyticsDatePicker } from "@/app/(examples)/dashboard-03/components/analytics-date-picker"
|
||||
import { ChartRevenue } from "@/app/(examples)/dashboard-03/components/chart-revenue"
|
||||
import { ChartVisitors } from "@/app/(examples)/dashboard-03/components/chart-visitors"
|
||||
import { ProductsTable } from "@/app/(examples)/dashboard-03/components/products-table"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Dashboard",
|
||||
description: "An example dashboard to test the new components.",
|
||||
}
|
||||
|
||||
// Load from database.
|
||||
const products = [
|
||||
{
|
||||
id: "1",
|
||||
name: "BJÖRKSNÄS Dining Table",
|
||||
price: 599.99,
|
||||
stock: 12,
|
||||
dateAdded: "2023-06-15",
|
||||
status: "In Stock",
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "POÄNG Armchair",
|
||||
price: 249.99,
|
||||
stock: 28,
|
||||
dateAdded: "2023-07-22",
|
||||
status: "In Stock",
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
name: "MALM Bed Frame",
|
||||
price: 399.99,
|
||||
stock: 15,
|
||||
dateAdded: "2023-08-05",
|
||||
status: "In Stock",
|
||||
},
|
||||
{
|
||||
id: "4",
|
||||
name: "KALLAX Shelf Unit",
|
||||
price: 179.99,
|
||||
stock: 32,
|
||||
dateAdded: "2023-09-12",
|
||||
status: "In Stock",
|
||||
},
|
||||
{
|
||||
id: "5",
|
||||
name: "STOCKHOLM Rug",
|
||||
price: 299.99,
|
||||
stock: 8,
|
||||
dateAdded: "2023-10-18",
|
||||
status: "Low Stock",
|
||||
},
|
||||
{
|
||||
id: "6",
|
||||
name: "KIVIK Sofa",
|
||||
price: 899.99,
|
||||
stock: 6,
|
||||
dateAdded: "2023-11-02",
|
||||
status: "Low Stock",
|
||||
},
|
||||
{
|
||||
id: "7",
|
||||
name: "LISABO Coffee Table",
|
||||
price: 149.99,
|
||||
stock: 22,
|
||||
dateAdded: "2023-11-29",
|
||||
status: "In Stock",
|
||||
},
|
||||
{
|
||||
id: "8",
|
||||
name: "HEMNES Bookcase",
|
||||
price: 249.99,
|
||||
stock: 17,
|
||||
dateAdded: "2023-12-10",
|
||||
status: "In Stock",
|
||||
},
|
||||
{
|
||||
id: "9",
|
||||
name: "EKEDALEN Dining Chairs (Set of 2)",
|
||||
price: 199.99,
|
||||
stock: 14,
|
||||
dateAdded: "2024-01-05",
|
||||
status: "In Stock",
|
||||
},
|
||||
{
|
||||
id: "10",
|
||||
name: "FRIHETEN Sleeper Sofa",
|
||||
price: 799.99,
|
||||
stock: 9,
|
||||
dateAdded: "2024-01-18",
|
||||
status: "Low Stock",
|
||||
},
|
||||
]
|
||||
|
||||
export default function DashboardPage() {
|
||||
return (
|
||||
<div className="@container/page flex flex-1 flex-col gap-8 p-6">
|
||||
<Tabs defaultValue="overview" className="gap-6">
|
||||
<div
|
||||
data-slot="dashboard-header"
|
||||
className="flex items-center justify-between"
|
||||
>
|
||||
<TabsList className="w-full @3xl/page:w-fit">
|
||||
<TabsTrigger value="overview">Overview</TabsTrigger>
|
||||
<TabsTrigger value="analytics">Analytics</TabsTrigger>
|
||||
<TabsTrigger value="reports">Reports</TabsTrigger>
|
||||
<TabsTrigger value="exports" disabled>
|
||||
Exports
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
<div className="hidden items-center gap-2 @3xl/page:flex">
|
||||
<AnalyticsDatePicker />
|
||||
<Button variant="outline">
|
||||
<FilterIcon />
|
||||
Filter
|
||||
</Button>
|
||||
<Button variant="outline">
|
||||
<DownloadIcon />
|
||||
Export
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<TabsContent value="overview" className="flex flex-col gap-4">
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-4">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Total Revenue</CardTitle>
|
||||
<CardDescription>$1,250.00 in the last 30 days</CardDescription>
|
||||
</CardHeader>
|
||||
<CardFooter>
|
||||
<Badge variant="outline">
|
||||
<TrendingUpIcon />
|
||||
+12.5%
|
||||
</Badge>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>New Customers</CardTitle>
|
||||
<CardDescription>-12 customers from last month</CardDescription>
|
||||
</CardHeader>
|
||||
<CardFooter>
|
||||
<Badge variant="outline">
|
||||
<TrendingDownIcon />
|
||||
-20%
|
||||
</Badge>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Active Accounts</CardTitle>
|
||||
<CardDescription>+2,345 users from last month</CardDescription>
|
||||
</CardHeader>
|
||||
<CardFooter>
|
||||
<Badge variant="outline">
|
||||
<TrendingUpIcon />
|
||||
+12.5%
|
||||
</Badge>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Growth Rate</CardTitle>
|
||||
<CardDescription>+12.5% increase per month</CardDescription>
|
||||
</CardHeader>
|
||||
<CardFooter>
|
||||
<Badge variant="outline">
|
||||
<TrendingUpIcon />
|
||||
+4.5%
|
||||
</Badge>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-4 @4xl/page:grid-cols-[2fr_1fr]">
|
||||
<ChartRevenue />
|
||||
<ChartVisitors />
|
||||
</div>
|
||||
<ProductsTable products={products} />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
500
apps/v4/app/(examples)/dashboard-03/settings/page.tsx
Normal file
500
apps/v4/app/(examples)/dashboard-03/settings/page.tsx
Normal file
@@ -0,0 +1,500 @@
|
||||
import { Metadata } from "next"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
import {
|
||||
Card,
|
||||
CardAction,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/registry/new-york-v4/ui/card"
|
||||
import { Checkbox } from "@/registry/new-york-v4/ui/checkbox"
|
||||
import { Input } from "@/registry/new-york-v4/ui/input"
|
||||
import { Label } from "@/registry/new-york-v4/ui/label"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectLabel,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/registry/new-york-v4/ui/select"
|
||||
import { Switch } from "@/registry/new-york-v4/ui/switch"
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/registry/new-york-v4/ui/table"
|
||||
import {
|
||||
Tabs,
|
||||
TabsContent,
|
||||
TabsList,
|
||||
TabsTrigger,
|
||||
} from "@/registry/new-york-v4/ui/tabs"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Settings",
|
||||
description: "Manage your account settings",
|
||||
}
|
||||
|
||||
const timezones = [
|
||||
{
|
||||
label: "Americas",
|
||||
timezones: [
|
||||
{ value: "America/New_York", label: "(GMT-5) New York" },
|
||||
{ value: "America/Los_Angeles", label: "(GMT-8) Los Angeles" },
|
||||
{ value: "America/Chicago", label: "(GMT-6) Chicago" },
|
||||
{ value: "America/Toronto", label: "(GMT-5) Toronto" },
|
||||
{ value: "America/Vancouver", label: "(GMT-8) Vancouver" },
|
||||
{ value: "America/Sao_Paulo", label: "(GMT-3) São Paulo" },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Europe",
|
||||
timezones: [
|
||||
{ value: "Europe/London", label: "(GMT+0) London" },
|
||||
{ value: "Europe/Paris", label: "(GMT+1) Paris" },
|
||||
{ value: "Europe/Berlin", label: "(GMT+1) Berlin" },
|
||||
{ value: "Europe/Rome", label: "(GMT+1) Rome" },
|
||||
{ value: "Europe/Madrid", label: "(GMT+1) Madrid" },
|
||||
{ value: "Europe/Amsterdam", label: "(GMT+1) Amsterdam" },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Asia/Pacific",
|
||||
timezones: [
|
||||
{ value: "Asia/Tokyo", label: "(GMT+9) Tokyo" },
|
||||
{ value: "Asia/Shanghai", label: "(GMT+8) Shanghai" },
|
||||
{ value: "Asia/Singapore", label: "(GMT+8) Singapore" },
|
||||
{ value: "Asia/Dubai", label: "(GMT+4) Dubai" },
|
||||
{ value: "Australia/Sydney", label: "(GMT+11) Sydney" },
|
||||
{ value: "Asia/Seoul", label: "(GMT+9) Seoul" },
|
||||
],
|
||||
},
|
||||
] as const
|
||||
|
||||
const loginHistory = [
|
||||
{
|
||||
date: "2024-01-01",
|
||||
ip: "192.168.1.1",
|
||||
location: "New York, USA",
|
||||
},
|
||||
{
|
||||
date: "2023-12-29",
|
||||
ip: "172.16.0.100",
|
||||
location: "London, UK",
|
||||
},
|
||||
{
|
||||
date: "2023-12-28",
|
||||
ip: "10.0.0.50",
|
||||
location: "Toronto, Canada",
|
||||
},
|
||||
{
|
||||
date: "2023-12-25",
|
||||
ip: "192.168.2.15",
|
||||
location: "Sydney, Australia",
|
||||
},
|
||||
] as const
|
||||
|
||||
const activeSessions = [
|
||||
{
|
||||
device: "MacBook Pro",
|
||||
browser: "Chrome",
|
||||
os: "macOS",
|
||||
},
|
||||
{
|
||||
device: "iPhone",
|
||||
browser: "Safari",
|
||||
os: "iOS",
|
||||
},
|
||||
{
|
||||
device: "iPad",
|
||||
browser: "Safari",
|
||||
os: "iOS",
|
||||
},
|
||||
{
|
||||
device: "Android Phone",
|
||||
browser: "Chrome",
|
||||
os: "Android",
|
||||
},
|
||||
] as const
|
||||
|
||||
export default function SettingsPage() {
|
||||
return (
|
||||
<div className="@container/page flex flex-1 flex-col gap-8 p-6">
|
||||
<Tabs defaultValue="account" className="gap-6">
|
||||
<div
|
||||
data-slot="dashboard-header"
|
||||
className="flex items-center justify-between"
|
||||
>
|
||||
<TabsList>
|
||||
<TabsTrigger value="account">Account</TabsTrigger>
|
||||
<TabsTrigger value="security">Security</TabsTrigger>
|
||||
<TabsTrigger value="notifications">Notifications</TabsTrigger>
|
||||
<TabsTrigger value="privacy">Privacy</TabsTrigger>
|
||||
</TabsList>
|
||||
</div>
|
||||
<TabsContent value="account" className="grid gap-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Account Settings</CardTitle>
|
||||
<CardDescription>
|
||||
Make changes to your account here.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form id="form-account" className="@container">
|
||||
<FieldGroup>
|
||||
<Field>
|
||||
<Label htmlFor="name">Name</Label>
|
||||
<FieldControl>
|
||||
<Input
|
||||
id="name"
|
||||
placeholder="First and last name"
|
||||
required
|
||||
/>
|
||||
</FieldControl>
|
||||
<FieldDescription>
|
||||
This is your public display name.
|
||||
</FieldDescription>
|
||||
</Field>
|
||||
<Field>
|
||||
<Label htmlFor="email">Email</Label>
|
||||
<FieldControl>
|
||||
<Input
|
||||
id="email"
|
||||
placeholder="you@example.com"
|
||||
required
|
||||
/>
|
||||
</FieldControl>
|
||||
</Field>
|
||||
<Field>
|
||||
<Label htmlFor="timezone">Timezone</Label>
|
||||
<FieldControl>
|
||||
<Select>
|
||||
<SelectTrigger id="timezone">
|
||||
<SelectValue placeholder="Select a timezone" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{timezones.map((timezone) => (
|
||||
<SelectGroup key={timezone.label}>
|
||||
<SelectLabel>{timezone.label}</SelectLabel>
|
||||
{timezone.timezones.map((time) => (
|
||||
<SelectItem key={time.value} value={time.value}>
|
||||
{time.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FieldControl>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</form>
|
||||
</CardContent>
|
||||
<CardFooter className="border-t">
|
||||
<Button type="submit" form="form-account" variant="secondary">
|
||||
Save changes
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Notifications</CardTitle>
|
||||
<CardDescription>
|
||||
Manage how you receive notifications.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form id="form-notifications" className="@container">
|
||||
<FieldGroup>
|
||||
<Field>
|
||||
<Label htmlFor="channels">Notification Channels</Label>
|
||||
<FieldControl className="flex flex-col gap-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox id="notification-email" />
|
||||
<Label htmlFor="notification-email">Email</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox id="notification-sms" />
|
||||
<Label htmlFor="notification-sms">SMS</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox id="notification-push" />
|
||||
<Label htmlFor="notification-push">Push</Label>
|
||||
</div>
|
||||
</FieldControl>
|
||||
<FieldDescription>
|
||||
Choose how you want to receive notifications.
|
||||
</FieldDescription>
|
||||
</Field>
|
||||
<Field>
|
||||
<Label htmlFor="types">Notification Types</Label>
|
||||
<FieldControl className="flex flex-col gap-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox id="notification-account" />
|
||||
<Label htmlFor="notification-account">
|
||||
Account Activity
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
id="notification-security"
|
||||
defaultChecked
|
||||
disabled
|
||||
/>
|
||||
<Label htmlFor="notification-security">
|
||||
Security Alerts
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox id="notification-marketing" />
|
||||
<Label htmlFor="notification-marketing">
|
||||
Marketing & Promotions
|
||||
</Label>
|
||||
</div>
|
||||
</FieldControl>
|
||||
<FieldDescription>
|
||||
Choose how you want to receive notifications.
|
||||
</FieldDescription>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</form>
|
||||
</CardContent>
|
||||
<CardFooter className="border-t">
|
||||
<Button
|
||||
type="submit"
|
||||
form="form-notifications"
|
||||
variant="secondary"
|
||||
>
|
||||
Save changes
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
<TabsContent
|
||||
value="security"
|
||||
className="grid gap-6 @3xl/page:grid-cols-2"
|
||||
>
|
||||
<Card className="@3xl/page:col-span-2">
|
||||
<CardHeader>
|
||||
<CardTitle>Security Settings</CardTitle>
|
||||
<CardDescription>
|
||||
Make changes to your security settings here.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="@container">
|
||||
<form id="form-security">
|
||||
<FieldGroup>
|
||||
<Field>
|
||||
<Label htmlFor="current-password">Current Password</Label>
|
||||
<FieldControl>
|
||||
<Input
|
||||
id="current-password"
|
||||
placeholder="Current password"
|
||||
required
|
||||
/>
|
||||
</FieldControl>
|
||||
<FieldDescription>
|
||||
This is your current password.
|
||||
</FieldDescription>
|
||||
</Field>
|
||||
<Field>
|
||||
<Label htmlFor="new-password">New Password</Label>
|
||||
<FieldControl>
|
||||
<Input
|
||||
id="new-password"
|
||||
placeholder="New password"
|
||||
required
|
||||
/>
|
||||
</FieldControl>
|
||||
</Field>
|
||||
<Field>
|
||||
<Label htmlFor="confirm-password">Confirm Password</Label>
|
||||
<FieldControl>
|
||||
<Input
|
||||
id="confirm-password"
|
||||
placeholder="Confirm password"
|
||||
/>
|
||||
</FieldControl>
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldControl>
|
||||
<Switch
|
||||
id="enable-two-factor-auth"
|
||||
className="self-start"
|
||||
/>
|
||||
</FieldControl>
|
||||
<Label htmlFor="enable-two-factor-auth">
|
||||
Enable two-factor authentication
|
||||
</Label>
|
||||
<FieldDescription>
|
||||
This will add an extra layer of security to your account.
|
||||
Make this an extra long description to test the layout.
|
||||
</FieldDescription>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</form>
|
||||
</CardContent>
|
||||
<CardFooter className="border-t">
|
||||
<Button type="submit" form="form-security" variant="secondary">
|
||||
Save changes
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Login History</CardTitle>
|
||||
<CardDescription>
|
||||
Recent login activities on your account.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Date</TableHead>
|
||||
<TableHead className="hidden @md/page:table-cell">
|
||||
IP
|
||||
</TableHead>
|
||||
<TableHead>Location</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{loginHistory.map((login) => (
|
||||
<TableRow key={login.date}>
|
||||
<TableCell>
|
||||
<div className="flex flex-col gap-1">
|
||||
{new Date(login.date).toLocaleDateString("en-US", {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
})}
|
||||
<span className="flex @md/page:hidden">
|
||||
{login.ip}
|
||||
</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="hidden @md/page:table-cell">
|
||||
{login.ip}
|
||||
</TableCell>
|
||||
<TableCell>{login.location}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Active Sessions</CardTitle>
|
||||
<CardDescription>
|
||||
Current active sessions on your account.
|
||||
</CardDescription>
|
||||
<CardAction>
|
||||
<Button variant="outline" size="sm">
|
||||
<span className="hidden @md/card-header:block">
|
||||
Manage Sessions
|
||||
</span>
|
||||
<span className="block @md/card-header:hidden">Manage</span>
|
||||
</Button>
|
||||
</CardAction>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Device</TableHead>
|
||||
<TableHead>Browser</TableHead>
|
||||
<TableHead>OS</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{activeSessions.map((session) => (
|
||||
<TableRow key={session.device}>
|
||||
<TableCell>{session.device}</TableCell>
|
||||
<TableCell>{session.browser}</TableCell>
|
||||
<TableCell>{session.os}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function FieldGroup({ children }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="field-group"
|
||||
className="@container/field-group flex max-w-4xl min-w-0 flex-col gap-8 @3xl:gap-6"
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function Field({ children, className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="field"
|
||||
className={cn(
|
||||
"grid auto-rows-min items-start gap-3 *:data-[slot=label]:col-start-1 *:data-[slot=label]:row-start-1 @3xl/field-group:grid-cols-2 @3xl/field-group:gap-6",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function FieldControl({
|
||||
children,
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="field-control"
|
||||
className={cn(
|
||||
"@3xl/field-group:col-start-2 @3xl/field-group:row-span-2 @3xl/field-group:row-start-1 @3xl/field-group:self-start",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function FieldDescription({
|
||||
children,
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<"p">) {
|
||||
return (
|
||||
<p
|
||||
data-slot="field-description"
|
||||
className={cn(
|
||||
"text-muted-foreground text-sm @3xl/field-group:col-start-1 @3xl/field-group:row-start-1 @3xl/field-group:translate-y-6",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</p>
|
||||
)
|
||||
}
|
||||
181
apps/v4/app/(examples)/dashboard/components/app-sidebar.tsx
Normal file
181
apps/v4/app/(examples)/dashboard/components/app-sidebar.tsx
Normal file
@@ -0,0 +1,181 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import {
|
||||
IconCamera,
|
||||
IconChartBar,
|
||||
IconDashboard,
|
||||
IconDatabase,
|
||||
IconFileAi,
|
||||
IconFileDescription,
|
||||
IconFileWord,
|
||||
IconFolder,
|
||||
IconHelp,
|
||||
IconInnerShadowTop,
|
||||
IconListDetails,
|
||||
IconReport,
|
||||
IconSearch,
|
||||
IconSettings,
|
||||
IconUsers,
|
||||
} from "@tabler/icons-react"
|
||||
|
||||
import {
|
||||
Sidebar,
|
||||
SidebarContent,
|
||||
SidebarFooter,
|
||||
SidebarHeader,
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
} from "@/registry/new-york-v4/ui/sidebar"
|
||||
import { NavDocuments } from "@/app/(examples)/dashboard/components/nav-documents"
|
||||
import { NavMain } from "@/app/(examples)/dashboard/components/nav-main"
|
||||
import { NavSecondary } from "@/app/(examples)/dashboard/components/nav-secondary"
|
||||
import { NavUser } from "@/app/(examples)/dashboard/components/nav-user"
|
||||
|
||||
const data = {
|
||||
user: {
|
||||
name: "shadcn",
|
||||
email: "m@example.com",
|
||||
avatar: "/avatars/shadcn.jpg",
|
||||
},
|
||||
navMain: [
|
||||
{
|
||||
title: "Dashboard",
|
||||
url: "#",
|
||||
icon: IconDashboard,
|
||||
},
|
||||
{
|
||||
title: "Lifecycle",
|
||||
url: "#",
|
||||
icon: IconListDetails,
|
||||
},
|
||||
{
|
||||
title: "Analytics",
|
||||
url: "#",
|
||||
icon: IconChartBar,
|
||||
},
|
||||
{
|
||||
title: "Projects",
|
||||
url: "#",
|
||||
icon: IconFolder,
|
||||
},
|
||||
{
|
||||
title: "Team",
|
||||
url: "#",
|
||||
icon: IconUsers,
|
||||
},
|
||||
],
|
||||
navClouds: [
|
||||
{
|
||||
title: "Capture",
|
||||
icon: IconCamera,
|
||||
isActive: true,
|
||||
url: "#",
|
||||
items: [
|
||||
{
|
||||
title: "Active Proposals",
|
||||
url: "#",
|
||||
},
|
||||
{
|
||||
title: "Archived",
|
||||
url: "#",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Proposal",
|
||||
icon: IconFileDescription,
|
||||
url: "#",
|
||||
items: [
|
||||
{
|
||||
title: "Active Proposals",
|
||||
url: "#",
|
||||
},
|
||||
{
|
||||
title: "Archived",
|
||||
url: "#",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Prompts",
|
||||
icon: IconFileAi,
|
||||
url: "#",
|
||||
items: [
|
||||
{
|
||||
title: "Active Proposals",
|
||||
url: "#",
|
||||
},
|
||||
{
|
||||
title: "Archived",
|
||||
url: "#",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
navSecondary: [
|
||||
{
|
||||
title: "Settings",
|
||||
url: "#",
|
||||
icon: IconSettings,
|
||||
},
|
||||
{
|
||||
title: "Get Help",
|
||||
url: "#",
|
||||
icon: IconHelp,
|
||||
},
|
||||
{
|
||||
title: "Search",
|
||||
url: "#",
|
||||
icon: IconSearch,
|
||||
},
|
||||
],
|
||||
documents: [
|
||||
{
|
||||
name: "Data Library",
|
||||
url: "#",
|
||||
icon: IconDatabase,
|
||||
},
|
||||
{
|
||||
name: "Reports",
|
||||
url: "#",
|
||||
icon: IconReport,
|
||||
},
|
||||
{
|
||||
name: "Word Assistant",
|
||||
url: "#",
|
||||
icon: IconFileWord,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
||||
return (
|
||||
<Sidebar collapsible="offcanvas" {...props}>
|
||||
<SidebarHeader>
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton
|
||||
asChild
|
||||
className="data-[slot=sidebar-menu-button]:!p-1.5"
|
||||
>
|
||||
<a href="#">
|
||||
<IconInnerShadowTop className="!size-5" />
|
||||
<span className="text-base font-semibold">Acme Inc.</span>
|
||||
</a>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
</SidebarHeader>
|
||||
<SidebarContent>
|
||||
<NavMain items={data.navMain} />
|
||||
<NavDocuments items={data.documents} />
|
||||
<NavSecondary items={data.navSecondary} className="mt-auto" />
|
||||
</SidebarContent>
|
||||
<SidebarFooter>
|
||||
<NavUser user={data.user} />
|
||||
</SidebarFooter>
|
||||
</Sidebar>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,292 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { Area, AreaChart, CartesianGrid, XAxis } from "recharts"
|
||||
|
||||
import { useIsMobile } from "@/registry/new-york-v4/hooks/use-mobile"
|
||||
import {
|
||||
Card,
|
||||
CardAction,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/registry/new-york-v4/ui/card"
|
||||
import {
|
||||
ChartConfig,
|
||||
ChartContainer,
|
||||
ChartTooltip,
|
||||
ChartTooltipContent,
|
||||
} from "@/registry/new-york-v4/ui/chart"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/registry/new-york-v4/ui/select"
|
||||
import {
|
||||
ToggleGroup,
|
||||
ToggleGroupItem,
|
||||
} from "@/registry/new-york-v4/ui/toggle-group"
|
||||
|
||||
export const description = "An interactive area chart"
|
||||
|
||||
const chartData = [
|
||||
{ date: "2024-04-01", desktop: 222, mobile: 150 },
|
||||
{ date: "2024-04-02", desktop: 97, mobile: 180 },
|
||||
{ date: "2024-04-03", desktop: 167, mobile: 120 },
|
||||
{ date: "2024-04-04", desktop: 242, mobile: 260 },
|
||||
{ date: "2024-04-05", desktop: 373, mobile: 290 },
|
||||
{ date: "2024-04-06", desktop: 301, mobile: 340 },
|
||||
{ date: "2024-04-07", desktop: 245, mobile: 180 },
|
||||
{ date: "2024-04-08", desktop: 409, mobile: 320 },
|
||||
{ date: "2024-04-09", desktop: 59, mobile: 110 },
|
||||
{ date: "2024-04-10", desktop: 261, mobile: 190 },
|
||||
{ date: "2024-04-11", desktop: 327, mobile: 350 },
|
||||
{ date: "2024-04-12", desktop: 292, mobile: 210 },
|
||||
{ date: "2024-04-13", desktop: 342, mobile: 380 },
|
||||
{ date: "2024-04-14", desktop: 137, mobile: 220 },
|
||||
{ date: "2024-04-15", desktop: 120, mobile: 170 },
|
||||
{ date: "2024-04-16", desktop: 138, mobile: 190 },
|
||||
{ date: "2024-04-17", desktop: 446, mobile: 360 },
|
||||
{ date: "2024-04-18", desktop: 364, mobile: 410 },
|
||||
{ date: "2024-04-19", desktop: 243, mobile: 180 },
|
||||
{ date: "2024-04-20", desktop: 89, mobile: 150 },
|
||||
{ date: "2024-04-21", desktop: 137, mobile: 200 },
|
||||
{ date: "2024-04-22", desktop: 224, mobile: 170 },
|
||||
{ date: "2024-04-23", desktop: 138, mobile: 230 },
|
||||
{ date: "2024-04-24", desktop: 387, mobile: 290 },
|
||||
{ date: "2024-04-25", desktop: 215, mobile: 250 },
|
||||
{ date: "2024-04-26", desktop: 75, mobile: 130 },
|
||||
{ date: "2024-04-27", desktop: 383, mobile: 420 },
|
||||
{ date: "2024-04-28", desktop: 122, mobile: 180 },
|
||||
{ date: "2024-04-29", desktop: 315, mobile: 240 },
|
||||
{ date: "2024-04-30", desktop: 454, mobile: 380 },
|
||||
{ date: "2024-05-01", desktop: 165, mobile: 220 },
|
||||
{ date: "2024-05-02", desktop: 293, mobile: 310 },
|
||||
{ date: "2024-05-03", desktop: 247, mobile: 190 },
|
||||
{ date: "2024-05-04", desktop: 385, mobile: 420 },
|
||||
{ date: "2024-05-05", desktop: 481, mobile: 390 },
|
||||
{ date: "2024-05-06", desktop: 498, mobile: 520 },
|
||||
{ date: "2024-05-07", desktop: 388, mobile: 300 },
|
||||
{ date: "2024-05-08", desktop: 149, mobile: 210 },
|
||||
{ date: "2024-05-09", desktop: 227, mobile: 180 },
|
||||
{ date: "2024-05-10", desktop: 293, mobile: 330 },
|
||||
{ date: "2024-05-11", desktop: 335, mobile: 270 },
|
||||
{ date: "2024-05-12", desktop: 197, mobile: 240 },
|
||||
{ date: "2024-05-13", desktop: 197, mobile: 160 },
|
||||
{ date: "2024-05-14", desktop: 448, mobile: 490 },
|
||||
{ date: "2024-05-15", desktop: 473, mobile: 380 },
|
||||
{ date: "2024-05-16", desktop: 338, mobile: 400 },
|
||||
{ date: "2024-05-17", desktop: 499, mobile: 420 },
|
||||
{ date: "2024-05-18", desktop: 315, mobile: 350 },
|
||||
{ date: "2024-05-19", desktop: 235, mobile: 180 },
|
||||
{ date: "2024-05-20", desktop: 177, mobile: 230 },
|
||||
{ date: "2024-05-21", desktop: 82, mobile: 140 },
|
||||
{ date: "2024-05-22", desktop: 81, mobile: 120 },
|
||||
{ date: "2024-05-23", desktop: 252, mobile: 290 },
|
||||
{ date: "2024-05-24", desktop: 294, mobile: 220 },
|
||||
{ date: "2024-05-25", desktop: 201, mobile: 250 },
|
||||
{ date: "2024-05-26", desktop: 213, mobile: 170 },
|
||||
{ date: "2024-05-27", desktop: 420, mobile: 460 },
|
||||
{ date: "2024-05-28", desktop: 233, mobile: 190 },
|
||||
{ date: "2024-05-29", desktop: 78, mobile: 130 },
|
||||
{ date: "2024-05-30", desktop: 340, mobile: 280 },
|
||||
{ date: "2024-05-31", desktop: 178, mobile: 230 },
|
||||
{ date: "2024-06-01", desktop: 178, mobile: 200 },
|
||||
{ date: "2024-06-02", desktop: 470, mobile: 410 },
|
||||
{ date: "2024-06-03", desktop: 103, mobile: 160 },
|
||||
{ date: "2024-06-04", desktop: 439, mobile: 380 },
|
||||
{ date: "2024-06-05", desktop: 88, mobile: 140 },
|
||||
{ date: "2024-06-06", desktop: 294, mobile: 250 },
|
||||
{ date: "2024-06-07", desktop: 323, mobile: 370 },
|
||||
{ date: "2024-06-08", desktop: 385, mobile: 320 },
|
||||
{ date: "2024-06-09", desktop: 438, mobile: 480 },
|
||||
{ date: "2024-06-10", desktop: 155, mobile: 200 },
|
||||
{ date: "2024-06-11", desktop: 92, mobile: 150 },
|
||||
{ date: "2024-06-12", desktop: 492, mobile: 420 },
|
||||
{ date: "2024-06-13", desktop: 81, mobile: 130 },
|
||||
{ date: "2024-06-14", desktop: 426, mobile: 380 },
|
||||
{ date: "2024-06-15", desktop: 307, mobile: 350 },
|
||||
{ date: "2024-06-16", desktop: 371, mobile: 310 },
|
||||
{ date: "2024-06-17", desktop: 475, mobile: 520 },
|
||||
{ date: "2024-06-18", desktop: 107, mobile: 170 },
|
||||
{ date: "2024-06-19", desktop: 341, mobile: 290 },
|
||||
{ date: "2024-06-20", desktop: 408, mobile: 450 },
|
||||
{ date: "2024-06-21", desktop: 169, mobile: 210 },
|
||||
{ date: "2024-06-22", desktop: 317, mobile: 270 },
|
||||
{ date: "2024-06-23", desktop: 480, mobile: 530 },
|
||||
{ date: "2024-06-24", desktop: 132, mobile: 180 },
|
||||
{ date: "2024-06-25", desktop: 141, mobile: 190 },
|
||||
{ date: "2024-06-26", desktop: 434, mobile: 380 },
|
||||
{ date: "2024-06-27", desktop: 448, mobile: 490 },
|
||||
{ date: "2024-06-28", desktop: 149, mobile: 200 },
|
||||
{ date: "2024-06-29", desktop: 103, mobile: 160 },
|
||||
{ date: "2024-06-30", desktop: 446, mobile: 400 },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
visitors: {
|
||||
label: "Visitors",
|
||||
},
|
||||
desktop: {
|
||||
label: "Desktop",
|
||||
color: "var(--primary)",
|
||||
},
|
||||
mobile: {
|
||||
label: "Mobile",
|
||||
color: "var(--primary)",
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export function ChartAreaInteractive() {
|
||||
const isMobile = useIsMobile()
|
||||
const [timeRange, setTimeRange] = React.useState("90d")
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isMobile) {
|
||||
setTimeRange("7d")
|
||||
}
|
||||
}, [isMobile])
|
||||
|
||||
const filteredData = chartData.filter((item) => {
|
||||
const date = new Date(item.date)
|
||||
const referenceDate = new Date("2024-06-30")
|
||||
let daysToSubtract = 90
|
||||
if (timeRange === "30d") {
|
||||
daysToSubtract = 30
|
||||
} else if (timeRange === "7d") {
|
||||
daysToSubtract = 7
|
||||
}
|
||||
const startDate = new Date(referenceDate)
|
||||
startDate.setDate(startDate.getDate() - daysToSubtract)
|
||||
return date >= startDate
|
||||
})
|
||||
|
||||
return (
|
||||
<Card className="@container/card">
|
||||
<CardHeader>
|
||||
<CardTitle>Total Visitors</CardTitle>
|
||||
<CardDescription>
|
||||
<span className="hidden @[540px]/card:block">
|
||||
Total for the last 3 months
|
||||
</span>
|
||||
<span className="@[540px]/card:hidden">Last 3 months</span>
|
||||
</CardDescription>
|
||||
<CardAction>
|
||||
<ToggleGroup
|
||||
type="single"
|
||||
value={timeRange}
|
||||
onValueChange={setTimeRange}
|
||||
variant="outline"
|
||||
className="hidden *:data-[slot=toggle-group-item]:!px-4 @[767px]/card:flex"
|
||||
>
|
||||
<ToggleGroupItem value="90d">Last 3 months</ToggleGroupItem>
|
||||
<ToggleGroupItem value="30d">Last 30 days</ToggleGroupItem>
|
||||
<ToggleGroupItem value="7d">Last 7 days</ToggleGroupItem>
|
||||
</ToggleGroup>
|
||||
<Select value={timeRange} onValueChange={setTimeRange}>
|
||||
<SelectTrigger
|
||||
className="flex w-40 **:data-[slot=select-value]:block **:data-[slot=select-value]:truncate @[767px]/card:hidden"
|
||||
size="sm"
|
||||
aria-label="Select a value"
|
||||
>
|
||||
<SelectValue placeholder="Last 3 months" />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="rounded-xl">
|
||||
<SelectItem value="90d" className="rounded-lg">
|
||||
Last 3 months
|
||||
</SelectItem>
|
||||
<SelectItem value="30d" className="rounded-lg">
|
||||
Last 30 days
|
||||
</SelectItem>
|
||||
<SelectItem value="7d" className="rounded-lg">
|
||||
Last 7 days
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</CardAction>
|
||||
</CardHeader>
|
||||
<CardContent className="px-2 pt-4 sm:px-6 sm:pt-6">
|
||||
<ChartContainer
|
||||
config={chartConfig}
|
||||
className="aspect-auto h-[250px] w-full"
|
||||
>
|
||||
<AreaChart data={filteredData}>
|
||||
<defs>
|
||||
<linearGradient id="fillDesktop" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop
|
||||
offset="5%"
|
||||
stopColor="var(--color-desktop)"
|
||||
stopOpacity={1.0}
|
||||
/>
|
||||
<stop
|
||||
offset="95%"
|
||||
stopColor="var(--color-desktop)"
|
||||
stopOpacity={0.1}
|
||||
/>
|
||||
</linearGradient>
|
||||
<linearGradient id="fillMobile" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop
|
||||
offset="5%"
|
||||
stopColor="var(--color-mobile)"
|
||||
stopOpacity={0.8}
|
||||
/>
|
||||
<stop
|
||||
offset="95%"
|
||||
stopColor="var(--color-mobile)"
|
||||
stopOpacity={0.1}
|
||||
/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<CartesianGrid vertical={false} />
|
||||
<XAxis
|
||||
dataKey="date"
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
tickMargin={8}
|
||||
minTickGap={32}
|
||||
tickFormatter={(value) => {
|
||||
const date = new Date(value)
|
||||
return date.toLocaleDateString("en-US", {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
})
|
||||
}}
|
||||
/>
|
||||
<ChartTooltip
|
||||
cursor={false}
|
||||
defaultIndex={isMobile ? -1 : 10}
|
||||
content={
|
||||
<ChartTooltipContent
|
||||
labelFormatter={(value) => {
|
||||
return new Date(value).toLocaleDateString("en-US", {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
})
|
||||
}}
|
||||
indicator="dot"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Area
|
||||
dataKey="mobile"
|
||||
type="natural"
|
||||
fill="url(#fillMobile)"
|
||||
stroke="var(--color-mobile)"
|
||||
stackId="a"
|
||||
/>
|
||||
<Area
|
||||
dataKey="desktop"
|
||||
type="natural"
|
||||
fill="url(#fillDesktop)"
|
||||
stroke="var(--color-desktop)"
|
||||
stackId="a"
|
||||
/>
|
||||
</AreaChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
807
apps/v4/app/(examples)/dashboard/components/data-table.tsx
Normal file
807
apps/v4/app/(examples)/dashboard/components/data-table.tsx
Normal file
@@ -0,0 +1,807 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import {
|
||||
DndContext,
|
||||
KeyboardSensor,
|
||||
MouseSensor,
|
||||
TouchSensor,
|
||||
closestCenter,
|
||||
useSensor,
|
||||
useSensors,
|
||||
type DragEndEvent,
|
||||
type UniqueIdentifier,
|
||||
} from "@dnd-kit/core"
|
||||
import { restrictToVerticalAxis } from "@dnd-kit/modifiers"
|
||||
import {
|
||||
SortableContext,
|
||||
arrayMove,
|
||||
useSortable,
|
||||
verticalListSortingStrategy,
|
||||
} from "@dnd-kit/sortable"
|
||||
import { CSS } from "@dnd-kit/utilities"
|
||||
import {
|
||||
IconChevronDown,
|
||||
IconChevronLeft,
|
||||
IconChevronRight,
|
||||
IconChevronsLeft,
|
||||
IconChevronsRight,
|
||||
IconCircleCheckFilled,
|
||||
IconDotsVertical,
|
||||
IconGripVertical,
|
||||
IconLayoutColumns,
|
||||
IconLoader,
|
||||
IconPlus,
|
||||
IconTrendingUp,
|
||||
} from "@tabler/icons-react"
|
||||
import {
|
||||
ColumnDef,
|
||||
ColumnFiltersState,
|
||||
Row,
|
||||
SortingState,
|
||||
VisibilityState,
|
||||
flexRender,
|
||||
getCoreRowModel,
|
||||
getFacetedRowModel,
|
||||
getFacetedUniqueValues,
|
||||
getFilteredRowModel,
|
||||
getPaginationRowModel,
|
||||
getSortedRowModel,
|
||||
useReactTable,
|
||||
} from "@tanstack/react-table"
|
||||
import { Area, AreaChart, CartesianGrid, XAxis } from "recharts"
|
||||
import { toast } from "sonner"
|
||||
import { z } from "zod"
|
||||
|
||||
import { useIsMobile } from "@/registry/new-york-v4/hooks/use-mobile"
|
||||
import { Badge } from "@/registry/new-york-v4/ui/badge"
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
import {
|
||||
ChartConfig,
|
||||
ChartContainer,
|
||||
ChartTooltip,
|
||||
ChartTooltipContent,
|
||||
} from "@/registry/new-york-v4/ui/chart"
|
||||
import { Checkbox } from "@/registry/new-york-v4/ui/checkbox"
|
||||
import {
|
||||
Drawer,
|
||||
DrawerClose,
|
||||
DrawerContent,
|
||||
DrawerDescription,
|
||||
DrawerFooter,
|
||||
DrawerHeader,
|
||||
DrawerTitle,
|
||||
DrawerTrigger,
|
||||
} from "@/registry/new-york-v4/ui/drawer"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/registry/new-york-v4/ui/dropdown-menu"
|
||||
import { Input } from "@/registry/new-york-v4/ui/input"
|
||||
import { Label } from "@/registry/new-york-v4/ui/label"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/registry/new-york-v4/ui/select"
|
||||
import { Separator } from "@/registry/new-york-v4/ui/separator"
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/registry/new-york-v4/ui/table"
|
||||
import {
|
||||
Tabs,
|
||||
TabsContent,
|
||||
TabsList,
|
||||
TabsTrigger,
|
||||
} from "@/registry/new-york-v4/ui/tabs"
|
||||
|
||||
export const schema = z.object({
|
||||
id: z.number(),
|
||||
header: z.string(),
|
||||
type: z.string(),
|
||||
status: z.string(),
|
||||
target: z.string(),
|
||||
limit: z.string(),
|
||||
reviewer: z.string(),
|
||||
})
|
||||
|
||||
// Create a separate component for the drag handle
|
||||
function DragHandle({ id }: { id: number }) {
|
||||
const { attributes, listeners } = useSortable({
|
||||
id,
|
||||
})
|
||||
|
||||
return (
|
||||
<Button
|
||||
{...attributes}
|
||||
{...listeners}
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="text-muted-foreground size-7 hover:bg-transparent"
|
||||
>
|
||||
<IconGripVertical className="text-muted-foreground size-3" />
|
||||
<span className="sr-only">Drag to reorder</span>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
const columns: ColumnDef<z.infer<typeof schema>>[] = [
|
||||
{
|
||||
id: "drag",
|
||||
header: () => null,
|
||||
cell: ({ row }) => <DragHandle id={row.original.id} />,
|
||||
},
|
||||
{
|
||||
id: "select",
|
||||
header: ({ table }) => (
|
||||
<div className="flex items-center justify-center">
|
||||
<Checkbox
|
||||
checked={
|
||||
table.getIsAllPageRowsSelected() ||
|
||||
(table.getIsSomePageRowsSelected() && "indeterminate")
|
||||
}
|
||||
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
|
||||
aria-label="Select all"
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
cell: ({ row }) => (
|
||||
<div className="flex items-center justify-center">
|
||||
<Checkbox
|
||||
checked={row.getIsSelected()}
|
||||
onCheckedChange={(value) => row.toggleSelected(!!value)}
|
||||
aria-label="Select row"
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
enableSorting: false,
|
||||
enableHiding: false,
|
||||
},
|
||||
{
|
||||
accessorKey: "header",
|
||||
header: "Header",
|
||||
cell: ({ row }) => {
|
||||
return <TableCellViewer item={row.original} />
|
||||
},
|
||||
enableHiding: false,
|
||||
},
|
||||
{
|
||||
accessorKey: "type",
|
||||
header: "Section Type",
|
||||
cell: ({ row }) => (
|
||||
<div className="w-32">
|
||||
<Badge variant="outline" className="text-muted-foreground px-1.5">
|
||||
{row.original.type}
|
||||
</Badge>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "status",
|
||||
header: "Status",
|
||||
cell: ({ row }) => (
|
||||
<Badge variant="outline" className="text-muted-foreground px-1.5">
|
||||
{row.original.status === "Done" ? (
|
||||
<IconCircleCheckFilled className="fill-green-500 dark:fill-green-400" />
|
||||
) : (
|
||||
<IconLoader />
|
||||
)}
|
||||
{row.original.status}
|
||||
</Badge>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "target",
|
||||
header: () => <div className="w-full text-right">Target</div>,
|
||||
cell: ({ row }) => (
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault()
|
||||
toast.promise(new Promise((resolve) => setTimeout(resolve, 1000)), {
|
||||
loading: `Saving ${row.original.header}`,
|
||||
success: "Done",
|
||||
error: "Error",
|
||||
})
|
||||
}}
|
||||
>
|
||||
<Label htmlFor={`${row.original.id}-target`} className="sr-only">
|
||||
Target
|
||||
</Label>
|
||||
<Input
|
||||
className="hover:bg-input/30 focus-visible:bg-background dark:hover:bg-input/30 dark:focus-visible:bg-input/30 h-8 w-16 border-transparent bg-transparent text-right shadow-none focus-visible:border dark:bg-transparent"
|
||||
defaultValue={row.original.target}
|
||||
id={`${row.original.id}-target`}
|
||||
/>
|
||||
</form>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "limit",
|
||||
header: () => <div className="w-full text-right">Limit</div>,
|
||||
cell: ({ row }) => (
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault()
|
||||
toast.promise(new Promise((resolve) => setTimeout(resolve, 1000)), {
|
||||
loading: `Saving ${row.original.header}`,
|
||||
success: "Done",
|
||||
error: "Error",
|
||||
})
|
||||
}}
|
||||
>
|
||||
<Label htmlFor={`${row.original.id}-limit`} className="sr-only">
|
||||
Limit
|
||||
</Label>
|
||||
<Input
|
||||
className="hover:bg-input/30 focus-visible:bg-background dark:hover:bg-input/30 dark:focus-visible:bg-input/30 h-8 w-16 border-transparent bg-transparent text-right shadow-none focus-visible:border dark:bg-transparent"
|
||||
defaultValue={row.original.limit}
|
||||
id={`${row.original.id}-limit`}
|
||||
/>
|
||||
</form>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "reviewer",
|
||||
header: "Reviewer",
|
||||
cell: ({ row }) => {
|
||||
const isAssigned = row.original.reviewer !== "Assign reviewer"
|
||||
|
||||
if (isAssigned) {
|
||||
return row.original.reviewer
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Label htmlFor={`${row.original.id}-reviewer`} className="sr-only">
|
||||
Reviewer
|
||||
</Label>
|
||||
<Select>
|
||||
<SelectTrigger
|
||||
className="w-38 **:data-[slot=select-value]:block **:data-[slot=select-value]:truncate"
|
||||
size="sm"
|
||||
id={`${row.original.id}-reviewer`}
|
||||
>
|
||||
<SelectValue placeholder="Assign reviewer" />
|
||||
</SelectTrigger>
|
||||
<SelectContent align="end">
|
||||
<SelectItem value="Eddie Lake">Eddie Lake</SelectItem>
|
||||
<SelectItem value="Jamik Tashpulatov">
|
||||
Jamik Tashpulatov
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
cell: () => (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="data-[state=open]:bg-muted text-muted-foreground flex size-8"
|
||||
size="icon"
|
||||
>
|
||||
<IconDotsVertical />
|
||||
<span className="sr-only">Open menu</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-32">
|
||||
<DropdownMenuItem>Edit</DropdownMenuItem>
|
||||
<DropdownMenuItem>Make a copy</DropdownMenuItem>
|
||||
<DropdownMenuItem>Favorite</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem variant="destructive">Delete</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
),
|
||||
},
|
||||
]
|
||||
|
||||
function DraggableRow({ row }: { row: Row<z.infer<typeof schema>> }) {
|
||||
const { transform, transition, setNodeRef, isDragging } = useSortable({
|
||||
id: row.original.id,
|
||||
})
|
||||
|
||||
return (
|
||||
<TableRow
|
||||
data-state={row.getIsSelected() && "selected"}
|
||||
data-dragging={isDragging}
|
||||
ref={setNodeRef}
|
||||
className="relative z-0 data-[dragging=true]:z-10 data-[dragging=true]:opacity-80"
|
||||
style={{
|
||||
transform: CSS.Transform.toString(transform),
|
||||
transition: transition,
|
||||
}}
|
||||
>
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<TableCell key={cell.id}>
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
)
|
||||
}
|
||||
|
||||
export function DataTable({
|
||||
data: initialData,
|
||||
}: {
|
||||
data: z.infer<typeof schema>[]
|
||||
}) {
|
||||
const [data, setData] = React.useState(() => initialData)
|
||||
const [rowSelection, setRowSelection] = React.useState({})
|
||||
const [columnVisibility, setColumnVisibility] =
|
||||
React.useState<VisibilityState>({})
|
||||
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
||||
[]
|
||||
)
|
||||
const [sorting, setSorting] = React.useState<SortingState>([])
|
||||
const [pagination, setPagination] = React.useState({
|
||||
pageIndex: 0,
|
||||
pageSize: 10,
|
||||
})
|
||||
const sortableId = React.useId()
|
||||
const sensors = useSensors(
|
||||
useSensor(MouseSensor, {}),
|
||||
useSensor(TouchSensor, {}),
|
||||
useSensor(KeyboardSensor, {})
|
||||
)
|
||||
|
||||
const dataIds = React.useMemo<UniqueIdentifier[]>(
|
||||
() => data?.map(({ id }) => id) || [],
|
||||
[data]
|
||||
)
|
||||
|
||||
const table = useReactTable({
|
||||
data,
|
||||
columns,
|
||||
state: {
|
||||
sorting,
|
||||
columnVisibility,
|
||||
rowSelection,
|
||||
columnFilters,
|
||||
pagination,
|
||||
},
|
||||
getRowId: (row) => row.id.toString(),
|
||||
enableRowSelection: true,
|
||||
onRowSelectionChange: setRowSelection,
|
||||
onSortingChange: setSorting,
|
||||
onColumnFiltersChange: setColumnFilters,
|
||||
onColumnVisibilityChange: setColumnVisibility,
|
||||
onPaginationChange: setPagination,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getFilteredRowModel: getFilteredRowModel(),
|
||||
getPaginationRowModel: getPaginationRowModel(),
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
getFacetedRowModel: getFacetedRowModel(),
|
||||
getFacetedUniqueValues: getFacetedUniqueValues(),
|
||||
})
|
||||
|
||||
function handleDragEnd(event: DragEndEvent) {
|
||||
const { active, over } = event
|
||||
if (active && over && active.id !== over.id) {
|
||||
setData((data) => {
|
||||
const oldIndex = dataIds.indexOf(active.id)
|
||||
const newIndex = dataIds.indexOf(over.id)
|
||||
return arrayMove(data, oldIndex, newIndex)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Tabs
|
||||
defaultValue="outline"
|
||||
className="w-full flex-col justify-start gap-6"
|
||||
>
|
||||
<div className="flex items-center justify-between px-4 lg:px-6">
|
||||
<Label htmlFor="view-selector" className="sr-only">
|
||||
View
|
||||
</Label>
|
||||
<Select defaultValue="outline">
|
||||
<SelectTrigger
|
||||
className="flex w-fit @4xl/main:hidden"
|
||||
size="sm"
|
||||
id="view-selector"
|
||||
>
|
||||
<SelectValue placeholder="Select a view" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="outline">Outline</SelectItem>
|
||||
<SelectItem value="past-performance">Past Performance</SelectItem>
|
||||
<SelectItem value="key-personnel">Key Personnel</SelectItem>
|
||||
<SelectItem value="focus-documents">Focus Documents</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<TabsList className="**:data-[slot=badge]:bg-muted-foreground/30 hidden **:data-[slot=badge]:size-5 **:data-[slot=badge]:rounded-full **:data-[slot=badge]:px-1 @4xl/main:flex">
|
||||
<TabsTrigger value="outline">Outline</TabsTrigger>
|
||||
<TabsTrigger value="past-performance">
|
||||
Past Performance <Badge variant="secondary">3</Badge>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="key-personnel">
|
||||
Key Personnel <Badge variant="secondary">2</Badge>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="focus-documents">Focus Documents</TabsTrigger>
|
||||
</TabsList>
|
||||
<div className="flex items-center gap-2">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" size="sm">
|
||||
<IconLayoutColumns />
|
||||
<span className="hidden lg:inline">Customize Columns</span>
|
||||
<span className="lg:hidden">Columns</span>
|
||||
<IconChevronDown />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-56">
|
||||
{table
|
||||
.getAllColumns()
|
||||
.filter(
|
||||
(column) =>
|
||||
typeof column.accessorFn !== "undefined" &&
|
||||
column.getCanHide()
|
||||
)
|
||||
.map((column) => {
|
||||
return (
|
||||
<DropdownMenuCheckboxItem
|
||||
key={column.id}
|
||||
className="capitalize"
|
||||
checked={column.getIsVisible()}
|
||||
onCheckedChange={(value) =>
|
||||
column.toggleVisibility(!!value)
|
||||
}
|
||||
>
|
||||
{column.id}
|
||||
</DropdownMenuCheckboxItem>
|
||||
)
|
||||
})}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<Button variant="outline" size="sm">
|
||||
<IconPlus />
|
||||
<span className="hidden lg:inline">Add Section</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<TabsContent
|
||||
value="outline"
|
||||
className="relative flex flex-col gap-4 overflow-auto px-4 lg:px-6"
|
||||
>
|
||||
<div className="overflow-hidden rounded-lg border">
|
||||
<DndContext
|
||||
collisionDetection={closestCenter}
|
||||
modifiers={[restrictToVerticalAxis]}
|
||||
onDragEnd={handleDragEnd}
|
||||
sensors={sensors}
|
||||
id={sortableId}
|
||||
>
|
||||
<Table>
|
||||
<TableHeader className="bg-muted sticky top-0 z-10">
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<TableRow key={headerGroup.id}>
|
||||
{headerGroup.headers.map((header) => {
|
||||
return (
|
||||
<TableHead key={header.id} colSpan={header.colSpan}>
|
||||
{header.isPlaceholder
|
||||
? null
|
||||
: flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext()
|
||||
)}
|
||||
</TableHead>
|
||||
)
|
||||
})}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableHeader>
|
||||
<TableBody className="**:data-[slot=table-cell]:first:w-8">
|
||||
{table.getRowModel().rows?.length ? (
|
||||
<SortableContext
|
||||
items={dataIds}
|
||||
strategy={verticalListSortingStrategy}
|
||||
>
|
||||
{table.getRowModel().rows.map((row) => (
|
||||
<DraggableRow key={row.id} row={row} />
|
||||
))}
|
||||
</SortableContext>
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell
|
||||
colSpan={columns.length}
|
||||
className="h-24 text-center"
|
||||
>
|
||||
No results.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</DndContext>
|
||||
</div>
|
||||
<div className="flex items-center justify-between px-4">
|
||||
<div className="text-muted-foreground hidden flex-1 text-sm lg:flex">
|
||||
{table.getFilteredSelectedRowModel().rows.length} of{" "}
|
||||
{table.getFilteredRowModel().rows.length} row(s) selected.
|
||||
</div>
|
||||
<div className="flex w-full items-center gap-8 lg:w-fit">
|
||||
<div className="hidden items-center gap-2 lg:flex">
|
||||
<Label htmlFor="rows-per-page" className="text-sm font-medium">
|
||||
Rows per page
|
||||
</Label>
|
||||
<Select
|
||||
value={`${table.getState().pagination.pageSize}`}
|
||||
onValueChange={(value) => {
|
||||
table.setPageSize(Number(value))
|
||||
}}
|
||||
>
|
||||
<SelectTrigger size="sm" className="w-20" id="rows-per-page">
|
||||
<SelectValue
|
||||
placeholder={table.getState().pagination.pageSize}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
<SelectContent side="top">
|
||||
{[10, 20, 30, 40, 50].map((pageSize) => (
|
||||
<SelectItem key={pageSize} value={`${pageSize}`}>
|
||||
{pageSize}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="flex w-fit items-center justify-center text-sm font-medium">
|
||||
Page {table.getState().pagination.pageIndex + 1} of{" "}
|
||||
{table.getPageCount()}
|
||||
</div>
|
||||
<div className="ml-auto flex items-center gap-2 lg:ml-0">
|
||||
<Button
|
||||
variant="outline"
|
||||
className="hidden h-8 w-8 p-0 lg:flex"
|
||||
onClick={() => table.setPageIndex(0)}
|
||||
disabled={!table.getCanPreviousPage()}
|
||||
>
|
||||
<span className="sr-only">Go to first page</span>
|
||||
<IconChevronsLeft />
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="size-8"
|
||||
size="icon"
|
||||
onClick={() => table.previousPage()}
|
||||
disabled={!table.getCanPreviousPage()}
|
||||
>
|
||||
<span className="sr-only">Go to previous page</span>
|
||||
<IconChevronLeft />
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="size-8"
|
||||
size="icon"
|
||||
onClick={() => table.nextPage()}
|
||||
disabled={!table.getCanNextPage()}
|
||||
>
|
||||
<span className="sr-only">Go to next page</span>
|
||||
<IconChevronRight />
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="hidden size-8 lg:flex"
|
||||
size="icon"
|
||||
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
|
||||
disabled={!table.getCanNextPage()}
|
||||
>
|
||||
<span className="sr-only">Go to last page</span>
|
||||
<IconChevronsRight />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
<TabsContent
|
||||
value="past-performance"
|
||||
className="flex flex-col px-4 lg:px-6"
|
||||
>
|
||||
<div className="aspect-video w-full flex-1 rounded-lg border border-dashed"></div>
|
||||
</TabsContent>
|
||||
<TabsContent value="key-personnel" className="flex flex-col px-4 lg:px-6">
|
||||
<div className="aspect-video w-full flex-1 rounded-lg border border-dashed"></div>
|
||||
</TabsContent>
|
||||
<TabsContent
|
||||
value="focus-documents"
|
||||
className="flex flex-col px-4 lg:px-6"
|
||||
>
|
||||
<div className="aspect-video w-full flex-1 rounded-lg border border-dashed"></div>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
)
|
||||
}
|
||||
|
||||
const chartData = [
|
||||
{ month: "January", desktop: 186, mobile: 80 },
|
||||
{ month: "February", desktop: 305, mobile: 200 },
|
||||
{ month: "March", desktop: 237, mobile: 120 },
|
||||
{ month: "April", desktop: 73, mobile: 190 },
|
||||
{ month: "May", desktop: 209, mobile: 130 },
|
||||
{ month: "June", desktop: 214, mobile: 140 },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
desktop: {
|
||||
label: "Desktop",
|
||||
color: "var(--primary)",
|
||||
},
|
||||
mobile: {
|
||||
label: "Mobile",
|
||||
color: "var(--primary)",
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
function TableCellViewer({ item }: { item: z.infer<typeof schema> }) {
|
||||
const isMobile = useIsMobile()
|
||||
|
||||
return (
|
||||
<Drawer direction={isMobile ? "bottom" : "right"}>
|
||||
<DrawerTrigger asChild>
|
||||
<Button variant="link" className="text-foreground w-fit px-0 text-left">
|
||||
{item.header}
|
||||
</Button>
|
||||
</DrawerTrigger>
|
||||
<DrawerContent>
|
||||
<DrawerHeader className="gap-1">
|
||||
<DrawerTitle>{item.header}</DrawerTitle>
|
||||
<DrawerDescription>
|
||||
Showing total visitors for the last 6 months
|
||||
</DrawerDescription>
|
||||
</DrawerHeader>
|
||||
<div className="flex flex-col gap-4 overflow-y-auto px-4 text-sm">
|
||||
{!isMobile && (
|
||||
<>
|
||||
<ChartContainer config={chartConfig}>
|
||||
<AreaChart
|
||||
accessibilityLayer
|
||||
data={chartData}
|
||||
margin={{
|
||||
left: 0,
|
||||
right: 10,
|
||||
}}
|
||||
>
|
||||
<CartesianGrid vertical={false} />
|
||||
<XAxis
|
||||
dataKey="month"
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
tickMargin={8}
|
||||
tickFormatter={(value) => value.slice(0, 3)}
|
||||
hide
|
||||
/>
|
||||
<ChartTooltip
|
||||
cursor={false}
|
||||
content={<ChartTooltipContent indicator="dot" />}
|
||||
/>
|
||||
<Area
|
||||
dataKey="mobile"
|
||||
type="natural"
|
||||
fill="var(--color-mobile)"
|
||||
fillOpacity={0.6}
|
||||
stroke="var(--color-mobile)"
|
||||
stackId="a"
|
||||
/>
|
||||
<Area
|
||||
dataKey="desktop"
|
||||
type="natural"
|
||||
fill="var(--color-desktop)"
|
||||
fillOpacity={0.4}
|
||||
stroke="var(--color-desktop)"
|
||||
stackId="a"
|
||||
/>
|
||||
</AreaChart>
|
||||
</ChartContainer>
|
||||
<Separator />
|
||||
<div className="grid gap-2">
|
||||
<div className="flex gap-2 leading-none font-medium">
|
||||
Trending up by 5.2% this month{" "}
|
||||
<IconTrendingUp className="size-4" />
|
||||
</div>
|
||||
<div className="text-muted-foreground">
|
||||
Showing total visitors for the last 6 months. This is just
|
||||
some random text to test the layout. It spans multiple lines
|
||||
and should wrap around.
|
||||
</div>
|
||||
</div>
|
||||
<Separator />
|
||||
</>
|
||||
)}
|
||||
<form className="flex flex-col gap-4">
|
||||
<div className="flex flex-col gap-3">
|
||||
<Label htmlFor="header">Header</Label>
|
||||
<Input id="header" defaultValue={item.header} />
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="flex flex-col gap-3">
|
||||
<Label htmlFor="type">Type</Label>
|
||||
<Select defaultValue={item.type}>
|
||||
<SelectTrigger id="type" className="w-full">
|
||||
<SelectValue placeholder="Select a type" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="Table of Contents">
|
||||
Table of Contents
|
||||
</SelectItem>
|
||||
<SelectItem value="Executive Summary">
|
||||
Executive Summary
|
||||
</SelectItem>
|
||||
<SelectItem value="Technical Approach">
|
||||
Technical Approach
|
||||
</SelectItem>
|
||||
<SelectItem value="Design">Design</SelectItem>
|
||||
<SelectItem value="Capabilities">Capabilities</SelectItem>
|
||||
<SelectItem value="Focus Documents">
|
||||
Focus Documents
|
||||
</SelectItem>
|
||||
<SelectItem value="Narrative">Narrative</SelectItem>
|
||||
<SelectItem value="Cover Page">Cover Page</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="flex flex-col gap-3">
|
||||
<Label htmlFor="status">Status</Label>
|
||||
<Select defaultValue={item.status}>
|
||||
<SelectTrigger id="status" className="w-full">
|
||||
<SelectValue placeholder="Select a status" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="Done">Done</SelectItem>
|
||||
<SelectItem value="In Progress">In Progress</SelectItem>
|
||||
<SelectItem value="Not Started">Not Started</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="flex flex-col gap-3">
|
||||
<Label htmlFor="target">Target</Label>
|
||||
<Input id="target" defaultValue={item.target} />
|
||||
</div>
|
||||
<div className="flex flex-col gap-3">
|
||||
<Label htmlFor="limit">Limit</Label>
|
||||
<Input id="limit" defaultValue={item.limit} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-3">
|
||||
<Label htmlFor="reviewer">Reviewer</Label>
|
||||
<Select defaultValue={item.reviewer}>
|
||||
<SelectTrigger id="reviewer" className="w-full">
|
||||
<SelectValue placeholder="Select a reviewer" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="Eddie Lake">Eddie Lake</SelectItem>
|
||||
<SelectItem value="Jamik Tashpulatov">
|
||||
Jamik Tashpulatov
|
||||
</SelectItem>
|
||||
<SelectItem value="Emily Whalen">Emily Whalen</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<DrawerFooter>
|
||||
<Button>Submit</Button>
|
||||
<DrawerClose asChild>
|
||||
<Button variant="outline">Done</Button>
|
||||
</DrawerClose>
|
||||
</DrawerFooter>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
)
|
||||
}
|
||||
27
apps/v4/app/(examples)/dashboard/components/mode-toggle.tsx
Normal file
27
apps/v4/app/(examples)/dashboard/components/mode-toggle.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { IconBrightness } from "@tabler/icons-react"
|
||||
import { useTheme } from "next-themes"
|
||||
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
|
||||
export function ModeToggle() {
|
||||
const { setTheme, resolvedTheme } = useTheme()
|
||||
|
||||
const toggleTheme = React.useCallback(() => {
|
||||
setTheme(resolvedTheme === "dark" ? "light" : "dark")
|
||||
}, [resolvedTheme, setTheme])
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="icon"
|
||||
className="group/toggle size-8"
|
||||
onClick={toggleTheme}
|
||||
>
|
||||
<IconBrightness />
|
||||
<span className="sr-only">Toggle theme</span>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
"use client"
|
||||
|
||||
import {
|
||||
IconDots,
|
||||
IconFolder,
|
||||
IconShare3,
|
||||
IconTrash,
|
||||
type Icon,
|
||||
} from "@tabler/icons-react"
|
||||
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/registry/new-york-v4/ui/dropdown-menu"
|
||||
import {
|
||||
SidebarGroup,
|
||||
SidebarGroupLabel,
|
||||
SidebarMenu,
|
||||
SidebarMenuAction,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
useSidebar,
|
||||
} from "@/registry/new-york-v4/ui/sidebar"
|
||||
|
||||
export function NavDocuments({
|
||||
items,
|
||||
}: {
|
||||
items: {
|
||||
name: string
|
||||
url: string
|
||||
icon: Icon
|
||||
}[]
|
||||
}) {
|
||||
const { isMobile } = useSidebar()
|
||||
|
||||
return (
|
||||
<SidebarGroup className="group-data-[collapsible=icon]:hidden">
|
||||
<SidebarGroupLabel>Documents</SidebarGroupLabel>
|
||||
<SidebarMenu>
|
||||
{items.map((item) => (
|
||||
<SidebarMenuItem key={item.name}>
|
||||
<SidebarMenuButton asChild>
|
||||
<a href={item.url}>
|
||||
<item.icon />
|
||||
<span>{item.name}</span>
|
||||
</a>
|
||||
</SidebarMenuButton>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<SidebarMenuAction
|
||||
showOnHover
|
||||
className="data-[state=open]:bg-accent rounded-sm"
|
||||
>
|
||||
<IconDots />
|
||||
<span className="sr-only">More</span>
|
||||
</SidebarMenuAction>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
className="w-24 rounded-lg"
|
||||
side={isMobile ? "bottom" : "right"}
|
||||
align={isMobile ? "end" : "start"}
|
||||
>
|
||||
<DropdownMenuItem>
|
||||
<IconFolder />
|
||||
<span>Open</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<IconShare3 />
|
||||
<span>Share</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem variant="destructive">
|
||||
<IconTrash />
|
||||
<span>Delete</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</SidebarMenuItem>
|
||||
))}
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton className="text-sidebar-foreground/70">
|
||||
<IconDots className="text-sidebar-foreground/70" />
|
||||
<span>More</span>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
</SidebarGroup>
|
||||
)
|
||||
}
|
||||
58
apps/v4/app/(examples)/dashboard/components/nav-main.tsx
Normal file
58
apps/v4/app/(examples)/dashboard/components/nav-main.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
"use client"
|
||||
|
||||
import { IconCirclePlusFilled, IconMail, type Icon } from "@tabler/icons-react"
|
||||
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
import {
|
||||
SidebarGroup,
|
||||
SidebarGroupContent,
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
} from "@/registry/new-york-v4/ui/sidebar"
|
||||
|
||||
export function NavMain({
|
||||
items,
|
||||
}: {
|
||||
items: {
|
||||
title: string
|
||||
url: string
|
||||
icon?: Icon
|
||||
}[]
|
||||
}) {
|
||||
return (
|
||||
<SidebarGroup>
|
||||
<SidebarGroupContent className="flex flex-col gap-2">
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem className="flex items-center gap-2">
|
||||
<SidebarMenuButton
|
||||
tooltip="Quick Create"
|
||||
className="bg-primary text-primary-foreground hover:bg-primary/90 hover:text-primary-foreground active:bg-primary/90 active:text-primary-foreground min-w-8 duration-200 ease-linear"
|
||||
>
|
||||
<IconCirclePlusFilled />
|
||||
<span>Quick Create</span>
|
||||
</SidebarMenuButton>
|
||||
<Button
|
||||
size="icon"
|
||||
className="size-8 group-data-[collapsible=icon]:opacity-0"
|
||||
variant="outline"
|
||||
>
|
||||
<IconMail />
|
||||
<span className="sr-only">Inbox</span>
|
||||
</Button>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
<SidebarMenu>
|
||||
{items.map((item) => (
|
||||
<SidebarMenuItem key={item.title}>
|
||||
<SidebarMenuButton tooltip={item.title}>
|
||||
{item.icon && <item.icon />}
|
||||
<span>{item.title}</span>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
))}
|
||||
</SidebarMenu>
|
||||
</SidebarGroupContent>
|
||||
</SidebarGroup>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { IconBrightness, type Icon } from "@tabler/icons-react"
|
||||
import { useTheme } from "next-themes"
|
||||
|
||||
import {
|
||||
SidebarGroup,
|
||||
SidebarGroupContent,
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
} from "@/registry/new-york-v4/ui/sidebar"
|
||||
import { Skeleton } from "@/registry/new-york-v4/ui/skeleton"
|
||||
import { Switch } from "@/registry/new-york-v4/ui/switch"
|
||||
|
||||
export function NavSecondary({
|
||||
items,
|
||||
...props
|
||||
}: {
|
||||
items: {
|
||||
title: string
|
||||
url: string
|
||||
icon: Icon
|
||||
}[]
|
||||
} & React.ComponentPropsWithoutRef<typeof SidebarGroup>) {
|
||||
const { resolvedTheme, setTheme } = useTheme()
|
||||
const [mounted, setMounted] = React.useState(false)
|
||||
|
||||
React.useEffect(() => {
|
||||
setMounted(true)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<SidebarGroup {...props}>
|
||||
<SidebarGroupContent>
|
||||
<SidebarMenu>
|
||||
{items.map((item) => (
|
||||
<SidebarMenuItem key={item.title}>
|
||||
<SidebarMenuButton asChild>
|
||||
<a href={item.url}>
|
||||
<item.icon />
|
||||
<span>{item.title}</span>
|
||||
</a>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
))}
|
||||
<SidebarMenuItem className="group-data-[collapsible=icon]:hidden">
|
||||
<SidebarMenuButton asChild>
|
||||
<label>
|
||||
<IconBrightness />
|
||||
<span>Dark Mode</span>
|
||||
{mounted ? (
|
||||
<Switch
|
||||
className="ml-auto"
|
||||
checked={resolvedTheme !== "light"}
|
||||
onCheckedChange={() =>
|
||||
setTheme(resolvedTheme === "dark" ? "light" : "dark")
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<Skeleton className="ml-auto h-4 w-8 rounded-full" />
|
||||
)}
|
||||
</label>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
</SidebarGroupContent>
|
||||
</SidebarGroup>
|
||||
)
|
||||
}
|
||||
110
apps/v4/app/(examples)/dashboard/components/nav-user.tsx
Normal file
110
apps/v4/app/(examples)/dashboard/components/nav-user.tsx
Normal file
@@ -0,0 +1,110 @@
|
||||
"use client"
|
||||
|
||||
import {
|
||||
IconCreditCard,
|
||||
IconDotsVertical,
|
||||
IconLogout,
|
||||
IconNotification,
|
||||
IconUserCircle,
|
||||
} from "@tabler/icons-react"
|
||||
|
||||
import {
|
||||
Avatar,
|
||||
AvatarFallback,
|
||||
AvatarImage,
|
||||
} from "@/registry/new-york-v4/ui/avatar"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/registry/new-york-v4/ui/dropdown-menu"
|
||||
import {
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
useSidebar,
|
||||
} from "@/registry/new-york-v4/ui/sidebar"
|
||||
|
||||
export function NavUser({
|
||||
user,
|
||||
}: {
|
||||
user: {
|
||||
name: string
|
||||
email: string
|
||||
avatar: string
|
||||
}
|
||||
}) {
|
||||
const { isMobile } = useSidebar()
|
||||
|
||||
return (
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<SidebarMenuButton
|
||||
size="lg"
|
||||
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
||||
>
|
||||
<Avatar className="h-8 w-8 rounded-lg grayscale">
|
||||
<AvatarImage src={user.avatar} alt={user.name} />
|
||||
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||
<span className="truncate font-medium">{user.name}</span>
|
||||
<span className="text-muted-foreground truncate text-xs">
|
||||
{user.email}
|
||||
</span>
|
||||
</div>
|
||||
<IconDotsVertical className="ml-auto size-4" />
|
||||
</SidebarMenuButton>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
className="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg"
|
||||
side={isMobile ? "bottom" : "right"}
|
||||
align="end"
|
||||
sideOffset={4}
|
||||
>
|
||||
<DropdownMenuLabel className="p-0 font-normal">
|
||||
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
||||
<Avatar className="h-8 w-8 rounded-lg">
|
||||
<AvatarImage src={user.avatar} alt={user.name} />
|
||||
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||
<span className="truncate font-medium">{user.name}</span>
|
||||
<span className="text-muted-foreground truncate text-xs">
|
||||
{user.email}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem>
|
||||
<IconUserCircle />
|
||||
Account
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<IconCreditCard />
|
||||
Billing
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<IconNotification />
|
||||
Notifications
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>
|
||||
<IconLogout />
|
||||
Log out
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
)
|
||||
}
|
||||
102
apps/v4/app/(examples)/dashboard/components/section-cards.tsx
Normal file
102
apps/v4/app/(examples)/dashboard/components/section-cards.tsx
Normal file
@@ -0,0 +1,102 @@
|
||||
import { IconTrendingDown, IconTrendingUp } from "@tabler/icons-react"
|
||||
|
||||
import { Badge } from "@/registry/new-york-v4/ui/badge"
|
||||
import {
|
||||
Card,
|
||||
CardAction,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/registry/new-york-v4/ui/card"
|
||||
|
||||
export function SectionCards() {
|
||||
return (
|
||||
<div className="*:data-[slot=card]:from-primary/5 *:data-[slot=card]:to-card dark:*:data-[slot=card]:bg-card grid grid-cols-1 gap-4 px-4 *:data-[slot=card]:bg-gradient-to-t *:data-[slot=card]:shadow-xs lg:px-6 @xl/main:grid-cols-2 @5xl/main:grid-cols-4">
|
||||
<Card className="@container/card">
|
||||
<CardHeader>
|
||||
<CardDescription>Total Revenue</CardDescription>
|
||||
<CardTitle className="text-2xl font-semibold tabular-nums @[250px]/card:text-3xl">
|
||||
$1,250.00
|
||||
</CardTitle>
|
||||
<CardAction>
|
||||
<Badge variant="outline">
|
||||
<IconTrendingUp />
|
||||
+12.5%
|
||||
</Badge>
|
||||
</CardAction>
|
||||
</CardHeader>
|
||||
<CardFooter className="flex-col items-start gap-1.5 text-sm">
|
||||
<div className="line-clamp-1 flex gap-2 font-medium">
|
||||
Trending up this month <IconTrendingUp className="size-4" />
|
||||
</div>
|
||||
<div className="text-muted-foreground">
|
||||
Visitors for the last 6 months
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
<Card className="@container/card">
|
||||
<CardHeader>
|
||||
<CardDescription>New Customers</CardDescription>
|
||||
<CardTitle className="text-2xl font-semibold tabular-nums @[250px]/card:text-3xl">
|
||||
1,234
|
||||
</CardTitle>
|
||||
<CardAction>
|
||||
<Badge variant="outline">
|
||||
<IconTrendingDown />
|
||||
-20%
|
||||
</Badge>
|
||||
</CardAction>
|
||||
</CardHeader>
|
||||
<CardFooter className="flex-col items-start gap-1.5 text-sm">
|
||||
<div className="line-clamp-1 flex gap-2 font-medium">
|
||||
Down 20% this period <IconTrendingDown className="size-4" />
|
||||
</div>
|
||||
<div className="text-muted-foreground">
|
||||
Acquisition needs attention
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
<Card className="@container/card">
|
||||
<CardHeader>
|
||||
<CardDescription>Active Accounts</CardDescription>
|
||||
<CardTitle className="text-2xl font-semibold tabular-nums @[250px]/card:text-3xl">
|
||||
45,678
|
||||
</CardTitle>
|
||||
<CardAction>
|
||||
<Badge variant="outline">
|
||||
<IconTrendingUp />
|
||||
+12.5%
|
||||
</Badge>
|
||||
</CardAction>
|
||||
</CardHeader>
|
||||
<CardFooter className="flex-col items-start gap-1.5 text-sm">
|
||||
<div className="line-clamp-1 flex gap-2 font-medium">
|
||||
Strong user retention <IconTrendingUp className="size-4" />
|
||||
</div>
|
||||
<div className="text-muted-foreground">Engagement exceed targets</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
<Card className="@container/card">
|
||||
<CardHeader>
|
||||
<CardDescription>Growth Rate</CardDescription>
|
||||
<CardTitle className="text-2xl font-semibold tabular-nums @[250px]/card:text-3xl">
|
||||
4.5%
|
||||
</CardTitle>
|
||||
<CardAction>
|
||||
<Badge variant="outline">
|
||||
<IconTrendingUp />
|
||||
+4.5%
|
||||
</Badge>
|
||||
</CardAction>
|
||||
</CardHeader>
|
||||
<CardFooter className="flex-col items-start gap-1.5 text-sm">
|
||||
<div className="line-clamp-1 flex gap-2 font-medium">
|
||||
Steady performance increase <IconTrendingUp className="size-4" />
|
||||
</div>
|
||||
<div className="text-muted-foreground">Meets growth projections</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
34
apps/v4/app/(examples)/dashboard/components/site-header.tsx
Normal file
34
apps/v4/app/(examples)/dashboard/components/site-header.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
import { Separator } from "@/registry/new-york-v4/ui/separator"
|
||||
import { SidebarTrigger } from "@/registry/new-york-v4/ui/sidebar"
|
||||
import { ModeToggle } from "@/app/(examples)/dashboard/components/mode-toggle"
|
||||
import { ThemeSelector } from "@/app/(examples)/dashboard/components/theme-selector"
|
||||
|
||||
export function SiteHeader() {
|
||||
return (
|
||||
<header className="flex h-(--header-height) shrink-0 items-center gap-2 border-b transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-(--header-height)">
|
||||
<div className="flex w-full items-center gap-1 px-4 lg:gap-2 lg:px-6">
|
||||
<SidebarTrigger className="-ml-1" />
|
||||
<Separator
|
||||
orientation="vertical"
|
||||
className="mx-2 data-[orientation=vertical]:h-4"
|
||||
/>
|
||||
<h1 className="text-base font-medium">Documents</h1>
|
||||
<div className="ml-auto flex items-center gap-2">
|
||||
<Button variant="ghost" asChild size="sm" className="hidden sm:flex">
|
||||
<a
|
||||
href="https://github.com/shadcn-ui/ui/tree/main/apps/v4/app/(examples)/dashboard"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
className="dark:text-foreground"
|
||||
>
|
||||
GitHub
|
||||
</a>
|
||||
</Button>
|
||||
<ThemeSelector />
|
||||
<ModeToggle />
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
103
apps/v4/app/(examples)/dashboard/components/theme-selector.tsx
Normal file
103
apps/v4/app/(examples)/dashboard/components/theme-selector.tsx
Normal file
@@ -0,0 +1,103 @@
|
||||
"use client"
|
||||
|
||||
import { useThemeConfig } from "@/components/active-theme"
|
||||
import { Label } from "@/registry/new-york-v4/ui/label"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectLabel,
|
||||
SelectSeparator,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/registry/new-york-v4/ui/select"
|
||||
|
||||
const DEFAULT_THEMES = [
|
||||
{
|
||||
name: "Default",
|
||||
value: "default",
|
||||
},
|
||||
{
|
||||
name: "Blue",
|
||||
value: "blue",
|
||||
},
|
||||
{
|
||||
name: "Green",
|
||||
value: "green",
|
||||
},
|
||||
{
|
||||
name: "Amber",
|
||||
value: "amber",
|
||||
},
|
||||
]
|
||||
|
||||
const SCALED_THEMES = [
|
||||
{
|
||||
name: "Default",
|
||||
value: "default-scaled",
|
||||
},
|
||||
{
|
||||
name: "Blue",
|
||||
value: "blue-scaled",
|
||||
},
|
||||
]
|
||||
|
||||
const MONO_THEMES = [
|
||||
{
|
||||
name: "Mono",
|
||||
value: "mono-scaled",
|
||||
},
|
||||
]
|
||||
|
||||
export function ThemeSelector() {
|
||||
const { activeTheme, setActiveTheme } = useThemeConfig()
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<Label htmlFor="theme-selector" className="sr-only">
|
||||
Theme
|
||||
</Label>
|
||||
<Select value={activeTheme} onValueChange={setActiveTheme}>
|
||||
<SelectTrigger
|
||||
id="theme-selector"
|
||||
size="sm"
|
||||
className="justify-start *:data-[slot=select-value]:w-12"
|
||||
>
|
||||
<span className="text-muted-foreground hidden sm:block">
|
||||
Select a theme:
|
||||
</span>
|
||||
<span className="text-muted-foreground block sm:hidden">Theme</span>
|
||||
<SelectValue placeholder="Select a theme" />
|
||||
</SelectTrigger>
|
||||
<SelectContent align="end">
|
||||
<SelectGroup>
|
||||
<SelectLabel>Default</SelectLabel>
|
||||
{DEFAULT_THEMES.map((theme) => (
|
||||
<SelectItem key={theme.name} value={theme.value}>
|
||||
{theme.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
<SelectSeparator />
|
||||
<SelectGroup>
|
||||
<SelectLabel>Scaled</SelectLabel>
|
||||
{SCALED_THEMES.map((theme) => (
|
||||
<SelectItem key={theme.name} value={theme.value}>
|
||||
{theme.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
<SelectGroup>
|
||||
<SelectLabel>Monospaced</SelectLabel>
|
||||
{MONO_THEMES.map((theme) => (
|
||||
<SelectItem key={theme.name} value={theme.value}>
|
||||
{theme.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
614
apps/v4/app/(examples)/dashboard/data.json
Normal file
614
apps/v4/app/(examples)/dashboard/data.json
Normal file
@@ -0,0 +1,614 @@
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"header": "Cover page",
|
||||
"type": "Cover page",
|
||||
"status": "In Process",
|
||||
"target": "18",
|
||||
"limit": "5",
|
||||
"reviewer": "Eddie Lake"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"header": "Table of contents",
|
||||
"type": "Table of contents",
|
||||
"status": "Done",
|
||||
"target": "29",
|
||||
"limit": "24",
|
||||
"reviewer": "Eddie Lake"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"header": "Executive summary",
|
||||
"type": "Narrative",
|
||||
"status": "Done",
|
||||
"target": "10",
|
||||
"limit": "13",
|
||||
"reviewer": "Eddie Lake"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"header": "Technical approach",
|
||||
"type": "Narrative",
|
||||
"status": "Done",
|
||||
"target": "27",
|
||||
"limit": "23",
|
||||
"reviewer": "Jamik Tashpulatov"
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"header": "Design",
|
||||
"type": "Narrative",
|
||||
"status": "In Process",
|
||||
"target": "2",
|
||||
"limit": "16",
|
||||
"reviewer": "Jamik Tashpulatov"
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"header": "Capabilities",
|
||||
"type": "Narrative",
|
||||
"status": "In Process",
|
||||
"target": "20",
|
||||
"limit": "8",
|
||||
"reviewer": "Jamik Tashpulatov"
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"header": "Integration with existing systems",
|
||||
"type": "Narrative",
|
||||
"status": "In Process",
|
||||
"target": "19",
|
||||
"limit": "21",
|
||||
"reviewer": "Jamik Tashpulatov"
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"header": "Innovation and Advantages",
|
||||
"type": "Narrative",
|
||||
"status": "Done",
|
||||
"target": "25",
|
||||
"limit": "26",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"header": "Overview of EMR's Innovative Solutions",
|
||||
"type": "Technical content",
|
||||
"status": "Done",
|
||||
"target": "7",
|
||||
"limit": "23",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"header": "Advanced Algorithms and Machine Learning",
|
||||
"type": "Narrative",
|
||||
"status": "Done",
|
||||
"target": "30",
|
||||
"limit": "28",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"header": "Adaptive Communication Protocols",
|
||||
"type": "Narrative",
|
||||
"status": "Done",
|
||||
"target": "9",
|
||||
"limit": "31",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"header": "Advantages Over Current Technologies",
|
||||
"type": "Narrative",
|
||||
"status": "Done",
|
||||
"target": "12",
|
||||
"limit": "0",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 13,
|
||||
"header": "Past Performance",
|
||||
"type": "Narrative",
|
||||
"status": "Done",
|
||||
"target": "22",
|
||||
"limit": "33",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 14,
|
||||
"header": "Customer Feedback and Satisfaction Levels",
|
||||
"type": "Narrative",
|
||||
"status": "Done",
|
||||
"target": "15",
|
||||
"limit": "34",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
"header": "Implementation Challenges and Solutions",
|
||||
"type": "Narrative",
|
||||
"status": "Done",
|
||||
"target": "3",
|
||||
"limit": "35",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 16,
|
||||
"header": "Security Measures and Data Protection Policies",
|
||||
"type": "Narrative",
|
||||
"status": "In Process",
|
||||
"target": "6",
|
||||
"limit": "36",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 17,
|
||||
"header": "Scalability and Future Proofing",
|
||||
"type": "Narrative",
|
||||
"status": "Done",
|
||||
"target": "4",
|
||||
"limit": "37",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 18,
|
||||
"header": "Cost-Benefit Analysis",
|
||||
"type": "Plain language",
|
||||
"status": "Done",
|
||||
"target": "14",
|
||||
"limit": "38",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 19,
|
||||
"header": "User Training and Onboarding Experience",
|
||||
"type": "Narrative",
|
||||
"status": "Done",
|
||||
"target": "17",
|
||||
"limit": "39",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 20,
|
||||
"header": "Future Development Roadmap",
|
||||
"type": "Narrative",
|
||||
"status": "Done",
|
||||
"target": "11",
|
||||
"limit": "40",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 21,
|
||||
"header": "System Architecture Overview",
|
||||
"type": "Technical content",
|
||||
"status": "In Process",
|
||||
"target": "24",
|
||||
"limit": "18",
|
||||
"reviewer": "Maya Johnson"
|
||||
},
|
||||
{
|
||||
"id": 22,
|
||||
"header": "Risk Management Plan",
|
||||
"type": "Narrative",
|
||||
"status": "Done",
|
||||
"target": "15",
|
||||
"limit": "22",
|
||||
"reviewer": "Carlos Rodriguez"
|
||||
},
|
||||
{
|
||||
"id": 23,
|
||||
"header": "Compliance Documentation",
|
||||
"type": "Legal",
|
||||
"status": "In Process",
|
||||
"target": "31",
|
||||
"limit": "27",
|
||||
"reviewer": "Sarah Chen"
|
||||
},
|
||||
{
|
||||
"id": 24,
|
||||
"header": "API Documentation",
|
||||
"type": "Technical content",
|
||||
"status": "Done",
|
||||
"target": "8",
|
||||
"limit": "12",
|
||||
"reviewer": "Raj Patel"
|
||||
},
|
||||
{
|
||||
"id": 25,
|
||||
"header": "User Interface Mockups",
|
||||
"type": "Visual",
|
||||
"status": "In Process",
|
||||
"target": "19",
|
||||
"limit": "25",
|
||||
"reviewer": "Leila Ahmadi"
|
||||
},
|
||||
{
|
||||
"id": 26,
|
||||
"header": "Database Schema",
|
||||
"type": "Technical content",
|
||||
"status": "Done",
|
||||
"target": "22",
|
||||
"limit": "20",
|
||||
"reviewer": "Thomas Wilson"
|
||||
},
|
||||
{
|
||||
"id": 27,
|
||||
"header": "Testing Methodology",
|
||||
"type": "Technical content",
|
||||
"status": "In Process",
|
||||
"target": "17",
|
||||
"limit": "14",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 28,
|
||||
"header": "Deployment Strategy",
|
||||
"type": "Narrative",
|
||||
"status": "Done",
|
||||
"target": "26",
|
||||
"limit": "30",
|
||||
"reviewer": "Eddie Lake"
|
||||
},
|
||||
{
|
||||
"id": 29,
|
||||
"header": "Budget Breakdown",
|
||||
"type": "Financial",
|
||||
"status": "In Process",
|
||||
"target": "13",
|
||||
"limit": "16",
|
||||
"reviewer": "Jamik Tashpulatov"
|
||||
},
|
||||
{
|
||||
"id": 30,
|
||||
"header": "Market Analysis",
|
||||
"type": "Research",
|
||||
"status": "Done",
|
||||
"target": "29",
|
||||
"limit": "32",
|
||||
"reviewer": "Sophia Martinez"
|
||||
},
|
||||
{
|
||||
"id": 31,
|
||||
"header": "Competitor Comparison",
|
||||
"type": "Research",
|
||||
"status": "In Process",
|
||||
"target": "21",
|
||||
"limit": "19",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 32,
|
||||
"header": "Maintenance Plan",
|
||||
"type": "Technical content",
|
||||
"status": "Done",
|
||||
"target": "16",
|
||||
"limit": "23",
|
||||
"reviewer": "Alex Thompson"
|
||||
},
|
||||
{
|
||||
"id": 33,
|
||||
"header": "User Personas",
|
||||
"type": "Research",
|
||||
"status": "In Process",
|
||||
"target": "27",
|
||||
"limit": "24",
|
||||
"reviewer": "Nina Patel"
|
||||
},
|
||||
{
|
||||
"id": 34,
|
||||
"header": "Accessibility Compliance",
|
||||
"type": "Legal",
|
||||
"status": "Done",
|
||||
"target": "18",
|
||||
"limit": "21",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 35,
|
||||
"header": "Performance Metrics",
|
||||
"type": "Technical content",
|
||||
"status": "In Process",
|
||||
"target": "23",
|
||||
"limit": "26",
|
||||
"reviewer": "David Kim"
|
||||
},
|
||||
{
|
||||
"id": 36,
|
||||
"header": "Disaster Recovery Plan",
|
||||
"type": "Technical content",
|
||||
"status": "Done",
|
||||
"target": "14",
|
||||
"limit": "17",
|
||||
"reviewer": "Jamik Tashpulatov"
|
||||
},
|
||||
{
|
||||
"id": 37,
|
||||
"header": "Third-party Integrations",
|
||||
"type": "Technical content",
|
||||
"status": "In Process",
|
||||
"target": "25",
|
||||
"limit": "28",
|
||||
"reviewer": "Eddie Lake"
|
||||
},
|
||||
{
|
||||
"id": 38,
|
||||
"header": "User Feedback Summary",
|
||||
"type": "Research",
|
||||
"status": "Done",
|
||||
"target": "20",
|
||||
"limit": "15",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 39,
|
||||
"header": "Localization Strategy",
|
||||
"type": "Narrative",
|
||||
"status": "In Process",
|
||||
"target": "12",
|
||||
"limit": "19",
|
||||
"reviewer": "Maria Garcia"
|
||||
},
|
||||
{
|
||||
"id": 40,
|
||||
"header": "Mobile Compatibility",
|
||||
"type": "Technical content",
|
||||
"status": "Done",
|
||||
"target": "28",
|
||||
"limit": "31",
|
||||
"reviewer": "James Wilson"
|
||||
},
|
||||
{
|
||||
"id": 41,
|
||||
"header": "Data Migration Plan",
|
||||
"type": "Technical content",
|
||||
"status": "In Process",
|
||||
"target": "19",
|
||||
"limit": "22",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 42,
|
||||
"header": "Quality Assurance Protocols",
|
||||
"type": "Technical content",
|
||||
"status": "Done",
|
||||
"target": "30",
|
||||
"limit": "33",
|
||||
"reviewer": "Priya Singh"
|
||||
},
|
||||
{
|
||||
"id": 43,
|
||||
"header": "Stakeholder Analysis",
|
||||
"type": "Research",
|
||||
"status": "In Process",
|
||||
"target": "11",
|
||||
"limit": "14",
|
||||
"reviewer": "Eddie Lake"
|
||||
},
|
||||
{
|
||||
"id": 44,
|
||||
"header": "Environmental Impact Assessment",
|
||||
"type": "Research",
|
||||
"status": "Done",
|
||||
"target": "24",
|
||||
"limit": "27",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 45,
|
||||
"header": "Intellectual Property Rights",
|
||||
"type": "Legal",
|
||||
"status": "In Process",
|
||||
"target": "17",
|
||||
"limit": "20",
|
||||
"reviewer": "Sarah Johnson"
|
||||
},
|
||||
{
|
||||
"id": 46,
|
||||
"header": "Customer Support Framework",
|
||||
"type": "Narrative",
|
||||
"status": "Done",
|
||||
"target": "22",
|
||||
"limit": "25",
|
||||
"reviewer": "Jamik Tashpulatov"
|
||||
},
|
||||
{
|
||||
"id": 47,
|
||||
"header": "Version Control Strategy",
|
||||
"type": "Technical content",
|
||||
"status": "In Process",
|
||||
"target": "15",
|
||||
"limit": "18",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 48,
|
||||
"header": "Continuous Integration Pipeline",
|
||||
"type": "Technical content",
|
||||
"status": "Done",
|
||||
"target": "26",
|
||||
"limit": "29",
|
||||
"reviewer": "Michael Chen"
|
||||
},
|
||||
{
|
||||
"id": 49,
|
||||
"header": "Regulatory Compliance",
|
||||
"type": "Legal",
|
||||
"status": "In Process",
|
||||
"target": "13",
|
||||
"limit": "16",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 50,
|
||||
"header": "User Authentication System",
|
||||
"type": "Technical content",
|
||||
"status": "Done",
|
||||
"target": "28",
|
||||
"limit": "31",
|
||||
"reviewer": "Eddie Lake"
|
||||
},
|
||||
{
|
||||
"id": 51,
|
||||
"header": "Data Analytics Framework",
|
||||
"type": "Technical content",
|
||||
"status": "In Process",
|
||||
"target": "21",
|
||||
"limit": "24",
|
||||
"reviewer": "Jamik Tashpulatov"
|
||||
},
|
||||
{
|
||||
"id": 52,
|
||||
"header": "Cloud Infrastructure",
|
||||
"type": "Technical content",
|
||||
"status": "Done",
|
||||
"target": "16",
|
||||
"limit": "19",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 53,
|
||||
"header": "Network Security Measures",
|
||||
"type": "Technical content",
|
||||
"status": "In Process",
|
||||
"target": "29",
|
||||
"limit": "32",
|
||||
"reviewer": "Lisa Wong"
|
||||
},
|
||||
{
|
||||
"id": 54,
|
||||
"header": "Project Timeline",
|
||||
"type": "Planning",
|
||||
"status": "Done",
|
||||
"target": "14",
|
||||
"limit": "17",
|
||||
"reviewer": "Eddie Lake"
|
||||
},
|
||||
{
|
||||
"id": 55,
|
||||
"header": "Resource Allocation",
|
||||
"type": "Planning",
|
||||
"status": "In Process",
|
||||
"target": "27",
|
||||
"limit": "30",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 56,
|
||||
"header": "Team Structure and Roles",
|
||||
"type": "Planning",
|
||||
"status": "Done",
|
||||
"target": "20",
|
||||
"limit": "23",
|
||||
"reviewer": "Jamik Tashpulatov"
|
||||
},
|
||||
{
|
||||
"id": 57,
|
||||
"header": "Communication Protocols",
|
||||
"type": "Planning",
|
||||
"status": "In Process",
|
||||
"target": "15",
|
||||
"limit": "18",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 58,
|
||||
"header": "Success Metrics",
|
||||
"type": "Planning",
|
||||
"status": "Done",
|
||||
"target": "30",
|
||||
"limit": "33",
|
||||
"reviewer": "Eddie Lake"
|
||||
},
|
||||
{
|
||||
"id": 59,
|
||||
"header": "Internationalization Support",
|
||||
"type": "Technical content",
|
||||
"status": "In Process",
|
||||
"target": "23",
|
||||
"limit": "26",
|
||||
"reviewer": "Jamik Tashpulatov"
|
||||
},
|
||||
{
|
||||
"id": 60,
|
||||
"header": "Backup and Recovery Procedures",
|
||||
"type": "Technical content",
|
||||
"status": "Done",
|
||||
"target": "18",
|
||||
"limit": "21",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 61,
|
||||
"header": "Monitoring and Alerting System",
|
||||
"type": "Technical content",
|
||||
"status": "In Process",
|
||||
"target": "25",
|
||||
"limit": "28",
|
||||
"reviewer": "Daniel Park"
|
||||
},
|
||||
{
|
||||
"id": 62,
|
||||
"header": "Code Review Guidelines",
|
||||
"type": "Technical content",
|
||||
"status": "Done",
|
||||
"target": "12",
|
||||
"limit": "15",
|
||||
"reviewer": "Eddie Lake"
|
||||
},
|
||||
{
|
||||
"id": 63,
|
||||
"header": "Documentation Standards",
|
||||
"type": "Technical content",
|
||||
"status": "In Process",
|
||||
"target": "27",
|
||||
"limit": "30",
|
||||
"reviewer": "Jamik Tashpulatov"
|
||||
},
|
||||
{
|
||||
"id": 64,
|
||||
"header": "Release Management Process",
|
||||
"type": "Planning",
|
||||
"status": "Done",
|
||||
"target": "22",
|
||||
"limit": "25",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 65,
|
||||
"header": "Feature Prioritization Matrix",
|
||||
"type": "Planning",
|
||||
"status": "In Process",
|
||||
"target": "19",
|
||||
"limit": "22",
|
||||
"reviewer": "Emma Davis"
|
||||
},
|
||||
{
|
||||
"id": 66,
|
||||
"header": "Technical Debt Assessment",
|
||||
"type": "Technical content",
|
||||
"status": "Done",
|
||||
"target": "24",
|
||||
"limit": "27",
|
||||
"reviewer": "Eddie Lake"
|
||||
},
|
||||
{
|
||||
"id": 67,
|
||||
"header": "Capacity Planning",
|
||||
"type": "Planning",
|
||||
"status": "In Process",
|
||||
"target": "21",
|
||||
"limit": "24",
|
||||
"reviewer": "Jamik Tashpulatov"
|
||||
},
|
||||
{
|
||||
"id": 68,
|
||||
"header": "Service Level Agreements",
|
||||
"type": "Legal",
|
||||
"status": "Done",
|
||||
"target": "26",
|
||||
"limit": "29",
|
||||
"reviewer": "Assign reviewer"
|
||||
}
|
||||
]
|
||||
36
apps/v4/app/(examples)/dashboard/layout.tsx
Normal file
36
apps/v4/app/(examples)/dashboard/layout.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import { cookies } from "next/headers"
|
||||
|
||||
import {
|
||||
SidebarInset,
|
||||
SidebarProvider,
|
||||
} from "@/registry/new-york-v4/ui/sidebar"
|
||||
import { AppSidebar } from "@/app/(examples)/dashboard/components/app-sidebar"
|
||||
import { SiteHeader } from "@/app/(examples)/dashboard/components/site-header"
|
||||
|
||||
import "@/app/(examples)/dashboard/theme.css"
|
||||
|
||||
export default async function DashboardLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
const cookieStore = await cookies()
|
||||
const defaultOpen = cookieStore.get("sidebar_state")?.value === "true"
|
||||
|
||||
return (
|
||||
<SidebarProvider
|
||||
defaultOpen={defaultOpen}
|
||||
style={
|
||||
{
|
||||
"--sidebar-width": "calc(var(--spacing) * 72)",
|
||||
} as React.CSSProperties
|
||||
}
|
||||
>
|
||||
<AppSidebar variant="inset" />
|
||||
<SidebarInset>
|
||||
<SiteHeader />
|
||||
<div className="flex flex-1 flex-col">{children}</div>
|
||||
</SidebarInset>
|
||||
</SidebarProvider>
|
||||
)
|
||||
}
|
||||
18
apps/v4/app/(examples)/dashboard/page.tsx
Normal file
18
apps/v4/app/(examples)/dashboard/page.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { ChartAreaInteractive } from "@/app/(examples)/dashboard/components/chart-area-interactive"
|
||||
import { DataTable } from "@/app/(examples)/dashboard/components/data-table"
|
||||
import { SectionCards } from "@/app/(examples)/dashboard/components/section-cards"
|
||||
import data from "@/app/(examples)/dashboard/data.json"
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<div className="@container/main flex flex-1 flex-col gap-2">
|
||||
<div className="flex flex-col gap-4 py-4 md:gap-6 md:py-6">
|
||||
<SectionCards />
|
||||
<div className="px-4 lg:px-6">
|
||||
<ChartAreaInteractive />
|
||||
</div>
|
||||
<DataTable data={data} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
105
apps/v4/app/(examples)/dashboard/theme.css
Normal file
105
apps/v4/app/(examples)/dashboard/theme.css
Normal file
@@ -0,0 +1,105 @@
|
||||
body {
|
||||
@apply overscroll-none bg-transparent;
|
||||
}
|
||||
|
||||
:root {
|
||||
--font-sans: var(--font-inter);
|
||||
--header-height: calc(var(--spacing) * 12 + 1px);
|
||||
}
|
||||
|
||||
.theme-scaled {
|
||||
@media (min-width: 1024px) {
|
||||
--radius: 0.6rem;
|
||||
--text-lg: 1.05rem;
|
||||
--text-base: 0.85rem;
|
||||
--text-sm: 0.8rem;
|
||||
--spacing: 0.222222rem;
|
||||
}
|
||||
|
||||
[data-slot="card"] {
|
||||
--spacing: 0.16rem;
|
||||
}
|
||||
|
||||
[data-slot="select-trigger"],
|
||||
[data-slot="toggle-group-item"] {
|
||||
--spacing: 0.222222rem;
|
||||
}
|
||||
}
|
||||
|
||||
.theme-default,
|
||||
.theme-default-scaled {
|
||||
--primary: var(--color-neutral-600);
|
||||
--primary-foreground: var(--color-neutral-50);
|
||||
|
||||
@variant dark {
|
||||
--primary: var(--color-neutral-500);
|
||||
--primary-foreground: var(--color-neutral-50);
|
||||
}
|
||||
}
|
||||
|
||||
.theme-blue,
|
||||
.theme-blue-scaled {
|
||||
--primary: var(--color-blue-600);
|
||||
--primary-foreground: var(--color-blue-50);
|
||||
|
||||
@variant dark {
|
||||
--primary: var(--color-blue-500);
|
||||
--primary-foreground: var(--color-blue-50);
|
||||
}
|
||||
}
|
||||
|
||||
.theme-green,
|
||||
.theme-green-scaled {
|
||||
--primary: var(--color-lime-600);
|
||||
--primary-foreground: var(--color-lime-50);
|
||||
|
||||
@variant dark {
|
||||
--primary: var(--color-lime-600);
|
||||
--primary-foreground: var(--color-lime-50);
|
||||
}
|
||||
}
|
||||
|
||||
.theme-amber,
|
||||
.theme-amber-scaled {
|
||||
--primary: var(--color-amber-600);
|
||||
--primary-foreground: var(--color-amber-50);
|
||||
|
||||
@variant dark {
|
||||
--primary: var(--color-amber-500);
|
||||
--primary-foreground: var(--color-amber-50);
|
||||
}
|
||||
}
|
||||
|
||||
.theme-mono,
|
||||
.theme-mono-scaled {
|
||||
--font-sans: var(--font-mono);
|
||||
--primary: var(--color-neutral-600);
|
||||
--primary-foreground: var(--color-neutral-50);
|
||||
|
||||
@variant dark {
|
||||
--primary: var(--color-neutral-500);
|
||||
--primary-foreground: var(--color-neutral-50);
|
||||
}
|
||||
|
||||
.rounded-xs,
|
||||
.rounded-sm,
|
||||
.rounded-md,
|
||||
.rounded-lg,
|
||||
.rounded-xl {
|
||||
@apply !rounded-none;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.shadow-xs,
|
||||
.shadow-sm,
|
||||
.shadow-md,
|
||||
.shadow-lg,
|
||||
.shadow-xl {
|
||||
@apply !shadow-none;
|
||||
}
|
||||
|
||||
[data-slot="toggle-group"],
|
||||
[data-slot="toggle-group-item"] {
|
||||
@apply !rounded-none !shadow-none;
|
||||
}
|
||||
}
|
||||
96
apps/v4/app/(view)/view/[name]/page.tsx
Normal file
96
apps/v4/app/(view)/view/[name]/page.tsx
Normal file
@@ -0,0 +1,96 @@
|
||||
import * as React from "react"
|
||||
import { Metadata } from "next"
|
||||
import { notFound } from "next/navigation"
|
||||
import { registryItemSchema } from "shadcn/registry"
|
||||
import { z } from "zod"
|
||||
|
||||
import { getRegistryComponent, getRegistryItem } from "@/lib/registry"
|
||||
import { absoluteUrl, cn } from "@/lib/utils"
|
||||
import { siteConfig } from "@/www/config/site"
|
||||
|
||||
const getCachedRegistryItem = React.cache(async (name: string) => {
|
||||
return await getRegistryItem(name)
|
||||
})
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{
|
||||
name: string
|
||||
}>
|
||||
}): Promise<Metadata> {
|
||||
const { name } = await params
|
||||
const item = await getCachedRegistryItem(name)
|
||||
|
||||
if (!item) {
|
||||
return {}
|
||||
}
|
||||
|
||||
const title = item.name
|
||||
const description = item.description
|
||||
|
||||
return {
|
||||
title: `${item.name}${item.description ? ` - ${item.description}` : ""}`,
|
||||
description,
|
||||
openGraph: {
|
||||
title,
|
||||
description,
|
||||
type: "article",
|
||||
url: absoluteUrl(`/blocks/${item.name}`),
|
||||
images: [
|
||||
{
|
||||
url: siteConfig.ogImage,
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: siteConfig.name,
|
||||
},
|
||||
],
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title,
|
||||
description,
|
||||
images: [siteConfig.ogImage],
|
||||
creator: "@shadcn",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export const dynamicParams = false
|
||||
|
||||
export async function generateStaticParams() {
|
||||
const { Index } = await import("@/__registry__")
|
||||
const index = z.record(registryItemSchema).parse(Index)
|
||||
|
||||
return Object.values(index)
|
||||
.filter((block) =>
|
||||
["registry:block", "registry:component"].includes(block.type)
|
||||
)
|
||||
.map((block) => ({
|
||||
name: block.name,
|
||||
}))
|
||||
}
|
||||
|
||||
export default async function BlockPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{
|
||||
name: string
|
||||
}>
|
||||
}) {
|
||||
const { name } = await params
|
||||
const item = await getCachedRegistryItem(name)
|
||||
const Component = getRegistryComponent(name)
|
||||
|
||||
if (!item || !Component) {
|
||||
return notFound()
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={cn("themes-wrapper bg-background", item.meta?.container)}>
|
||||
<Component />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
123
apps/v4/app/globals.css
Normal file
123
apps/v4/app/globals.css
Normal file
@@ -0,0 +1,123 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
@import "tw-animate-css";
|
||||
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
@import "./themes.css";
|
||||
|
||||
:root {
|
||||
--radius: 0.625rem;
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.145 0 0);
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.145 0 0);
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.145 0 0);
|
||||
--primary: oklch(0.205 0 0);
|
||||
--primary-foreground: oklch(0.985 0 0);
|
||||
--secondary: oklch(0.97 0 0);
|
||||
--secondary-foreground: oklch(0.205 0 0);
|
||||
--muted: oklch(0.97 0 0);
|
||||
--muted-foreground: oklch(0.556 0 0);
|
||||
--accent: oklch(0.97 0 0);
|
||||
--accent-foreground: oklch(0.205 0 0);
|
||||
--destructive: oklch(0.577 0.245 27.325);
|
||||
--border: oklch(0.922 0 0);
|
||||
--input: oklch(0.922 0 0);
|
||||
--ring: oklch(0.708 0 0);
|
||||
--chart-1: oklch(0.646 0.222 41.116);
|
||||
--chart-2: oklch(0.6 0.118 184.704);
|
||||
--chart-3: oklch(0.398 0.07 227.392);
|
||||
--chart-4: oklch(0.828 0.189 84.429);
|
||||
--chart-5: oklch(0.769 0.188 70.08);
|
||||
--sidebar: oklch(0.985 0 0);
|
||||
--sidebar-foreground: oklch(0.145 0 0);
|
||||
--sidebar-primary: oklch(0.205 0 0);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.97 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.205 0 0);
|
||||
--sidebar-border: oklch(0.922 0 0);
|
||||
--sidebar-ring: oklch(0.708 0 0);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: oklch(0.145 0 0);
|
||||
--foreground: oklch(0.985 0 0);
|
||||
--card: oklch(0.205 0 0);
|
||||
--card-foreground: oklch(0.985 0 0);
|
||||
--popover: oklch(0.269 0 0);
|
||||
--popover-foreground: oklch(0.985 0 0);
|
||||
--primary: oklch(0.922 0 0);
|
||||
--primary-foreground: oklch(0.205 0 0);
|
||||
--secondary: oklch(0.269 0 0);
|
||||
--secondary-foreground: oklch(0.985 0 0);
|
||||
--muted: oklch(0.269 0 0);
|
||||
--muted-foreground: oklch(0.708 0 0);
|
||||
--accent: oklch(0.371 0 0);
|
||||
--accent-foreground: oklch(0.985 0 0);
|
||||
--destructive: oklch(0.704 0.191 22.216);
|
||||
--border: oklch(1 0 0 / 10%);
|
||||
--input: oklch(1 0 0 / 15%);
|
||||
--ring: oklch(0.556 0 0);
|
||||
--chart-1: oklch(0.488 0.243 264.376);
|
||||
--chart-2: oklch(0.696 0.17 162.48);
|
||||
--chart-3: oklch(0.769 0.188 70.08);
|
||||
--chart-4: oklch(0.627 0.265 303.9);
|
||||
--chart-5: oklch(0.645 0.246 16.439);
|
||||
--sidebar: oklch(0.205 0 0);
|
||||
--sidebar-foreground: oklch(0.985 0 0);
|
||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.269 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||
--sidebar-border: oklch(1 0 0 / 10%);
|
||||
--sidebar-ring: oklch(0.439 0 0);
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-card: var(--card);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
--color-popover: var(--popover);
|
||||
--color-popover-foreground: var(--popover-foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-accent: var(--accent);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-border: var(--border);
|
||||
--color-input: var(--input);
|
||||
--color-ring: var(--ring);
|
||||
--color-chart-1: var(--chart-1);
|
||||
--color-chart-2: var(--chart-2);
|
||||
--color-chart-3: var(--chart-3);
|
||||
--color-chart-4: var(--chart-4);
|
||||
--color-chart-5: var(--chart-5);
|
||||
--color-sidebar: var(--sidebar);
|
||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||
--color-sidebar-primary: var(--sidebar-primary);
|
||||
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||
--color-sidebar-accent: var(--sidebar-accent);
|
||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||
--color-sidebar-border: var(--sidebar-border);
|
||||
--color-sidebar-ring: var(--sidebar-ring);
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border outline-ring/50;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
123
apps/v4/app/layout.tsx
Normal file
123
apps/v4/app/layout.tsx
Normal file
@@ -0,0 +1,123 @@
|
||||
import type { Metadata, Viewport } from "next"
|
||||
import { cookies } from "next/headers"
|
||||
|
||||
import { fontVariables } from "@/lib/fonts"
|
||||
import { Analytics } from "@/components/analytics"
|
||||
import { ThemeProvider } from "@/components/theme-provider"
|
||||
import { Toaster } from "@/registry/new-york-v4/ui/sonner"
|
||||
import { siteConfig } from "@/www/config/site"
|
||||
|
||||
import "./globals.css"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { ActiveThemeProvider } from "@/components/active-theme"
|
||||
|
||||
const META_THEME_COLORS = {
|
||||
light: "#ffffff",
|
||||
dark: "#09090b",
|
||||
}
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
default: siteConfig.name,
|
||||
template: `%s - ${siteConfig.name}`,
|
||||
},
|
||||
metadataBase: new URL("https://v4.shadcn.com"),
|
||||
description: siteConfig.description,
|
||||
keywords: [
|
||||
"Next.js",
|
||||
"React",
|
||||
"Tailwind CSS",
|
||||
"Server Components",
|
||||
"Radix UI",
|
||||
],
|
||||
authors: [
|
||||
{
|
||||
name: "shadcn",
|
||||
url: "https://shadcn.com",
|
||||
},
|
||||
],
|
||||
creator: "shadcn",
|
||||
openGraph: {
|
||||
type: "website",
|
||||
locale: "en_US",
|
||||
url: "https://v4.shadcn.com",
|
||||
title: siteConfig.name,
|
||||
description: siteConfig.description,
|
||||
siteName: siteConfig.name,
|
||||
images: [
|
||||
{
|
||||
url: "https://v4.shadcn.com/opengraph-image.png",
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: siteConfig.name,
|
||||
},
|
||||
],
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title: siteConfig.name,
|
||||
description: siteConfig.description,
|
||||
images: ["https://v4.shadcn.com/opengraph-image.png"],
|
||||
creator: "@shadcn",
|
||||
},
|
||||
icons: {
|
||||
icon: "/favicon.ico",
|
||||
shortcut: "/favicon-16x16.png",
|
||||
apple: "/apple-touch-icon.png",
|
||||
},
|
||||
manifest: `${siteConfig.url}/site.webmanifest`,
|
||||
}
|
||||
|
||||
export const viewport: Viewport = {
|
||||
themeColor: META_THEME_COLORS.light,
|
||||
}
|
||||
|
||||
export default async function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode
|
||||
}>) {
|
||||
const cookieStore = await cookies()
|
||||
const activeThemeValue = cookieStore.get("active_theme")?.value
|
||||
const isScaled = activeThemeValue?.endsWith("-scaled")
|
||||
|
||||
return (
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<head>
|
||||
<script
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
try {
|
||||
if (localStorage.theme === 'dark' || ((!('theme' in localStorage) || localStorage.theme === 'system') && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
||||
document.querySelector('meta[name="theme-color"]').setAttribute('content', '${META_THEME_COLORS.dark}')
|
||||
}
|
||||
} catch (_) {}
|
||||
`,
|
||||
}}
|
||||
/>
|
||||
</head>
|
||||
<body
|
||||
className={cn(
|
||||
"bg-background overscroll-none font-sans antialiased",
|
||||
activeThemeValue ? `theme-${activeThemeValue}` : "",
|
||||
isScaled ? "theme-scaled" : "",
|
||||
fontVariables
|
||||
)}
|
||||
>
|
||||
<ThemeProvider
|
||||
attribute="class"
|
||||
defaultTheme="system"
|
||||
enableSystem
|
||||
disableTransitionOnChange
|
||||
enableColorScheme
|
||||
>
|
||||
<ActiveThemeProvider initialTheme={activeThemeValue}>
|
||||
{children}
|
||||
<Toaster />
|
||||
<Analytics />
|
||||
</ActiveThemeProvider>
|
||||
</ThemeProvider>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
364
apps/v4/app/themes.css
Normal file
364
apps/v4/app/themes.css
Normal file
@@ -0,0 +1,364 @@
|
||||
.theme-stone {
|
||||
--radius: 0.625rem;
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.147 0.004 49.25);
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.147 0.004 49.25);
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.147 0.004 49.25);
|
||||
--primary: oklch(0.216 0.006 56.043);
|
||||
--primary-foreground: oklch(0.985 0.001 106.423);
|
||||
--secondary: oklch(0.97 0.001 106.424);
|
||||
--secondary-foreground: oklch(0.216 0.006 56.043);
|
||||
--muted: oklch(0.97 0.001 106.424);
|
||||
--muted-foreground: oklch(0.553 0.013 58.071);
|
||||
--accent: oklch(0.97 0.001 106.424);
|
||||
--accent-foreground: oklch(0.216 0.006 56.043);
|
||||
--destructive: oklch(0.577 0.245 27.325);
|
||||
--border: oklch(0.923 0.003 48.717);
|
||||
--input: oklch(0.923 0.003 48.717);
|
||||
--ring: oklch(0.709 0.01 56.259);
|
||||
--chart-1: oklch(0.646 0.222 41.116);
|
||||
--chart-2: oklch(0.6 0.118 184.704);
|
||||
--chart-3: oklch(0.398 0.07 227.392);
|
||||
--chart-4: oklch(0.828 0.189 84.429);
|
||||
--chart-5: oklch(0.769 0.188 70.08);
|
||||
--sidebar: oklch(0.985 0.001 106.423);
|
||||
--sidebar-foreground: oklch(0.147 0.004 49.25);
|
||||
--sidebar-primary: oklch(0.216 0.006 56.043);
|
||||
--sidebar-primary-foreground: oklch(0.985 0.001 106.423);
|
||||
--sidebar-accent: oklch(0.97 0.001 106.424);
|
||||
--sidebar-accent-foreground: oklch(0.216 0.006 56.043);
|
||||
--sidebar-border: oklch(0.923 0.003 48.717);
|
||||
--sidebar-ring: oklch(0.709 0.01 56.259);
|
||||
|
||||
@variant dark {
|
||||
--background: oklch(0.147 0.004 49.25);
|
||||
--foreground: oklch(0.985 0.001 106.423);
|
||||
--card: oklch(0.216 0.006 56.043);
|
||||
--card-foreground: oklch(0.985 0.001 106.423);
|
||||
--popover: oklch(0.216 0.006 56.043);
|
||||
--popover-foreground: oklch(0.985 0.001 106.423);
|
||||
--primary: oklch(0.923 0.003 48.717);
|
||||
--primary-foreground: oklch(0.216 0.006 56.043);
|
||||
--secondary: oklch(0.268 0.007 34.298);
|
||||
--secondary-foreground: oklch(0.985 0.001 106.423);
|
||||
--muted: oklch(0.268 0.007 34.298);
|
||||
--muted-foreground: oklch(0.709 0.01 56.259);
|
||||
--accent: oklch(0.268 0.007 34.298);
|
||||
--accent-foreground: oklch(0.985 0.001 106.423);
|
||||
--destructive: oklch(0.704 0.191 22.216);
|
||||
--border: oklch(1 0 0 / 10%);
|
||||
--input: oklch(1 0 0 / 15%);
|
||||
--ring: oklch(0.553 0.013 58.071);
|
||||
--chart-1: oklch(0.488 0.243 264.376);
|
||||
--chart-2: oklch(0.696 0.17 162.48);
|
||||
--chart-3: oklch(0.769 0.188 70.08);
|
||||
--chart-4: oklch(0.627 0.265 303.9);
|
||||
--chart-5: oklch(0.645 0.246 16.439);
|
||||
--sidebar: oklch(0.216 0.006 56.043);
|
||||
--sidebar-foreground: oklch(0.985 0.001 106.423);
|
||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||
--sidebar-primary-foreground: oklch(0.985 0.001 106.423);
|
||||
--sidebar-accent: oklch(0.268 0.007 34.298);
|
||||
--sidebar-accent-foreground: oklch(0.985 0.001 106.423);
|
||||
--sidebar-border: oklch(1 0 0 / 10%);
|
||||
--sidebar-ring: oklch(0.553 0.013 58.071);
|
||||
}
|
||||
}
|
||||
|
||||
.theme-zinc {
|
||||
--radius: 0.625rem;
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.141 0.005 285.823);
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.141 0.005 285.823);
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.141 0.005 285.823);
|
||||
--primary: oklch(0.21 0.006 285.885);
|
||||
--primary-foreground: oklch(0.985 0 0);
|
||||
--secondary: oklch(0.967 0.001 286.375);
|
||||
--secondary-foreground: oklch(0.21 0.006 285.885);
|
||||
--muted: oklch(0.967 0.001 286.375);
|
||||
--muted-foreground: oklch(0.552 0.016 285.938);
|
||||
--accent: oklch(0.967 0.001 286.375);
|
||||
--accent-foreground: oklch(0.21 0.006 285.885);
|
||||
--destructive: oklch(0.577 0.245 27.325);
|
||||
--border: oklch(0.92 0.004 286.32);
|
||||
--input: oklch(0.92 0.004 286.32);
|
||||
--ring: oklch(0.705 0.015 286.067);
|
||||
--chart-1: oklch(0.646 0.222 41.116);
|
||||
--chart-2: oklch(0.6 0.118 184.704);
|
||||
--chart-3: oklch(0.398 0.07 227.392);
|
||||
--chart-4: oklch(0.828 0.189 84.429);
|
||||
--chart-5: oklch(0.769 0.188 70.08);
|
||||
--sidebar: oklch(0.985 0 0);
|
||||
--sidebar-foreground: oklch(0.141 0.005 285.823);
|
||||
--sidebar-primary: oklch(0.21 0.006 285.885);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.967 0.001 286.375);
|
||||
--sidebar-accent-foreground: oklch(0.21 0.006 285.885);
|
||||
--sidebar-border: oklch(0.92 0.004 286.32);
|
||||
--sidebar-ring: oklch(0.705 0.015 286.067);
|
||||
|
||||
@variant dark {
|
||||
--background: oklch(0.141 0.005 285.823);
|
||||
--foreground: oklch(0.985 0 0);
|
||||
--card: oklch(0.21 0.006 285.885);
|
||||
--card-foreground: oklch(0.985 0 0);
|
||||
--popover: oklch(0.21 0.006 285.885);
|
||||
--popover-foreground: oklch(0.985 0 0);
|
||||
--primary: oklch(0.92 0.004 286.32);
|
||||
--primary-foreground: oklch(0.21 0.006 285.885);
|
||||
--secondary: oklch(0.274 0.006 286.033);
|
||||
--secondary-foreground: oklch(0.985 0 0);
|
||||
--muted: oklch(0.274 0.006 286.033);
|
||||
--muted-foreground: oklch(0.705 0.015 286.067);
|
||||
--accent: oklch(0.274 0.006 286.033);
|
||||
--accent-foreground: oklch(0.985 0 0);
|
||||
--destructive: oklch(0.704 0.191 22.216);
|
||||
--border: oklch(1 0 0 / 10%);
|
||||
--input: oklch(1 0 0 / 15%);
|
||||
--ring: oklch(0.552 0.016 285.938);
|
||||
--chart-1: oklch(0.488 0.243 264.376);
|
||||
--chart-2: oklch(0.696 0.17 162.48);
|
||||
--chart-3: oklch(0.769 0.188 70.08);
|
||||
--chart-4: oklch(0.627 0.265 303.9);
|
||||
--chart-5: oklch(0.645 0.246 16.439);
|
||||
--sidebar: oklch(0.21 0.006 285.885);
|
||||
--sidebar-foreground: oklch(0.985 0 0);
|
||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.274 0.006 286.033);
|
||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||
--sidebar-border: oklch(1 0 0 / 10%);
|
||||
--sidebar-ring: oklch(0.552 0.016 285.938);
|
||||
}
|
||||
}
|
||||
|
||||
.theme-neutral {
|
||||
--radius: 0.625rem;
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.145 0 0);
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.145 0 0);
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.145 0 0);
|
||||
--primary: oklch(0.205 0 0);
|
||||
--primary-foreground: oklch(0.985 0 0);
|
||||
--secondary: oklch(0.97 0 0);
|
||||
--secondary-foreground: oklch(0.205 0 0);
|
||||
--muted: oklch(0.97 0 0);
|
||||
--muted-foreground: oklch(0.556 0 0);
|
||||
--accent: oklch(0.97 0 0);
|
||||
--accent-foreground: oklch(0.205 0 0);
|
||||
--destructive: oklch(0.577 0.245 27.325);
|
||||
--border: oklch(0.922 0 0);
|
||||
--input: oklch(0.922 0 0);
|
||||
--ring: oklch(0.708 0 0);
|
||||
--chart-1: oklch(0.646 0.222 41.116);
|
||||
--chart-2: oklch(0.6 0.118 184.704);
|
||||
--chart-3: oklch(0.398 0.07 227.392);
|
||||
--chart-4: oklch(0.828 0.189 84.429);
|
||||
--chart-5: oklch(0.769 0.188 70.08);
|
||||
--sidebar: oklch(0.985 0 0);
|
||||
--sidebar-foreground: oklch(0.145 0 0);
|
||||
--sidebar-primary: oklch(0.205 0 0);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.97 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.205 0 0);
|
||||
--sidebar-border: oklch(0.922 0 0);
|
||||
--sidebar-ring: oklch(0.708 0 0);
|
||||
|
||||
@variant dark {
|
||||
--background: oklch(0.145 0 0);
|
||||
--foreground: oklch(0.985 0 0);
|
||||
--card: oklch(0.205 0 0);
|
||||
--card-foreground: oklch(0.985 0 0);
|
||||
--popover: oklch(0.205 0 0);
|
||||
--popover-foreground: oklch(0.985 0 0);
|
||||
--primary: oklch(0.922 0 0);
|
||||
--primary-foreground: oklch(0.205 0 0);
|
||||
--secondary: oklch(0.269 0 0);
|
||||
--secondary-foreground: oklch(0.985 0 0);
|
||||
--muted: oklch(0.269 0 0);
|
||||
--muted-foreground: oklch(0.708 0 0);
|
||||
--accent: oklch(0.269 0 0);
|
||||
--accent-foreground: oklch(0.985 0 0);
|
||||
--destructive: oklch(0.704 0.191 22.216);
|
||||
--border: oklch(1 0 0 / 10%);
|
||||
--input: oklch(1 0 0 / 15%);
|
||||
--ring: oklch(0.556 0 0);
|
||||
--chart-1: oklch(0.488 0.243 264.376);
|
||||
--chart-2: oklch(0.696 0.17 162.48);
|
||||
--chart-3: oklch(0.769 0.188 70.08);
|
||||
--chart-4: oklch(0.627 0.265 303.9);
|
||||
--chart-5: oklch(0.645 0.246 16.439);
|
||||
--sidebar: oklch(0.205 0 0);
|
||||
--sidebar-foreground: oklch(0.985 0 0);
|
||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.269 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||
--sidebar-border: oklch(1 0 0 / 10%);
|
||||
--sidebar-ring: oklch(0.556 0 0);
|
||||
}
|
||||
}
|
||||
|
||||
.theme-gray {
|
||||
--radius: 0.625rem;
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.13 0.028 261.692);
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.13 0.028 261.692);
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.13 0.028 261.692);
|
||||
--primary: oklch(0.21 0.034 264.665);
|
||||
--primary-foreground: oklch(0.985 0.002 247.839);
|
||||
--secondary: oklch(0.967 0.003 264.542);
|
||||
--secondary-foreground: oklch(0.21 0.034 264.665);
|
||||
--muted: oklch(0.967 0.003 264.542);
|
||||
--muted-foreground: oklch(0.551 0.027 264.364);
|
||||
--accent: oklch(0.967 0.003 264.542);
|
||||
--accent-foreground: oklch(0.21 0.034 264.665);
|
||||
--destructive: oklch(0.577 0.245 27.325);
|
||||
--border: oklch(0.928 0.006 264.531);
|
||||
--input: oklch(0.928 0.006 264.531);
|
||||
--ring: oklch(0.707 0.022 261.325);
|
||||
--chart-1: oklch(0.646 0.222 41.116);
|
||||
--chart-2: oklch(0.6 0.118 184.704);
|
||||
--chart-3: oklch(0.398 0.07 227.392);
|
||||
--chart-4: oklch(0.828 0.189 84.429);
|
||||
--chart-5: oklch(0.769 0.188 70.08);
|
||||
--sidebar: oklch(0.985 0.002 247.839);
|
||||
--sidebar-foreground: oklch(0.13 0.028 261.692);
|
||||
--sidebar-primary: oklch(0.21 0.034 264.665);
|
||||
--sidebar-primary-foreground: oklch(0.985 0.002 247.839);
|
||||
--sidebar-accent: oklch(0.967 0.003 264.542);
|
||||
--sidebar-accent-foreground: oklch(0.21 0.034 264.665);
|
||||
--sidebar-border: oklch(0.928 0.006 264.531);
|
||||
--sidebar-ring: oklch(0.707 0.022 261.325);
|
||||
|
||||
@variant dark {
|
||||
--background: oklch(0.13 0.028 261.692);
|
||||
--foreground: oklch(0.985 0.002 247.839);
|
||||
--card: oklch(0.21 0.034 264.665);
|
||||
--card-foreground: oklch(0.985 0.002 247.839);
|
||||
--popover: oklch(0.21 0.034 264.665);
|
||||
--popover-foreground: oklch(0.985 0.002 247.839);
|
||||
--primary: oklch(0.928 0.006 264.531);
|
||||
--primary-foreground: oklch(0.21 0.034 264.665);
|
||||
--secondary: oklch(0.278 0.033 256.848);
|
||||
--secondary-foreground: oklch(0.985 0.002 247.839);
|
||||
--muted: oklch(0.278 0.033 256.848);
|
||||
--muted-foreground: oklch(0.707 0.022 261.325);
|
||||
--accent: oklch(0.278 0.033 256.848);
|
||||
--accent-foreground: oklch(0.985 0.002 247.839);
|
||||
--destructive: oklch(0.704 0.191 22.216);
|
||||
--border: oklch(1 0 0 / 10%);
|
||||
--input: oklch(1 0 0 / 15%);
|
||||
--ring: oklch(0.551 0.027 264.364);
|
||||
--chart-1: oklch(0.488 0.243 264.376);
|
||||
--chart-2: oklch(0.696 0.17 162.48);
|
||||
--chart-3: oklch(0.769 0.188 70.08);
|
||||
--chart-4: oklch(0.627 0.265 303.9);
|
||||
--chart-5: oklch(0.645 0.246 16.439);
|
||||
--sidebar: oklch(0.21 0.034 264.665);
|
||||
--sidebar-foreground: oklch(0.985 0.002 247.839);
|
||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||
--sidebar-primary-foreground: oklch(0.985 0.002 247.839);
|
||||
--sidebar-accent: oklch(0.278 0.033 256.848);
|
||||
--sidebar-accent-foreground: oklch(0.985 0.002 247.839);
|
||||
--sidebar-border: oklch(1 0 0 / 10%);
|
||||
--sidebar-ring: oklch(0.551 0.027 264.364);
|
||||
}
|
||||
}
|
||||
|
||||
.theme-slate {
|
||||
--radius: 0.625rem;
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.129 0.042 264.695);
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.129 0.042 264.695);
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.129 0.042 264.695);
|
||||
--primary: oklch(0.208 0.042 265.755);
|
||||
--primary-foreground: oklch(0.984 0.003 247.858);
|
||||
--secondary: oklch(0.968 0.007 247.896);
|
||||
--secondary-foreground: oklch(0.208 0.042 265.755);
|
||||
--muted: oklch(0.968 0.007 247.896);
|
||||
--muted-foreground: oklch(0.554 0.046 257.417);
|
||||
--accent: oklch(0.968 0.007 247.896);
|
||||
--accent-foreground: oklch(0.208 0.042 265.755);
|
||||
--destructive: oklch(0.577 0.245 27.325);
|
||||
--border: oklch(0.929 0.013 255.508);
|
||||
--input: oklch(0.929 0.013 255.508);
|
||||
--ring: oklch(0.704 0.04 256.788);
|
||||
--chart-1: oklch(0.646 0.222 41.116);
|
||||
--chart-2: oklch(0.6 0.118 184.704);
|
||||
--chart-3: oklch(0.398 0.07 227.392);
|
||||
--chart-4: oklch(0.828 0.189 84.429);
|
||||
--chart-5: oklch(0.769 0.188 70.08);
|
||||
--sidebar: oklch(0.984 0.003 247.858);
|
||||
--sidebar-foreground: oklch(0.129 0.042 264.695);
|
||||
--sidebar-primary: oklch(0.208 0.042 265.755);
|
||||
--sidebar-primary-foreground: oklch(0.984 0.003 247.858);
|
||||
--sidebar-accent: oklch(0.968 0.007 247.896);
|
||||
--sidebar-accent-foreground: oklch(0.208 0.042 265.755);
|
||||
--sidebar-border: oklch(0.929 0.013 255.508);
|
||||
--sidebar-ring: oklch(0.704 0.04 256.788);
|
||||
|
||||
@variant dark {
|
||||
--background: oklch(0.129 0.042 264.695);
|
||||
--foreground: oklch(0.984 0.003 247.858);
|
||||
--card: oklch(0.208 0.042 265.755);
|
||||
--card-foreground: oklch(0.984 0.003 247.858);
|
||||
--popover: oklch(0.208 0.042 265.755);
|
||||
--popover-foreground: oklch(0.984 0.003 247.858);
|
||||
--primary: oklch(0.929 0.013 255.508);
|
||||
--primary-foreground: oklch(0.208 0.042 265.755);
|
||||
--secondary: oklch(0.279 0.041 260.031);
|
||||
--secondary-foreground: oklch(0.984 0.003 247.858);
|
||||
--muted: oklch(0.279 0.041 260.031);
|
||||
--muted-foreground: oklch(0.704 0.04 256.788);
|
||||
--accent: oklch(0.279 0.041 260.031);
|
||||
--accent-foreground: oklch(0.984 0.003 247.858);
|
||||
--destructive: oklch(0.704 0.191 22.216);
|
||||
--border: oklch(1 0 0 / 10%);
|
||||
--input: oklch(1 0 0 / 15%);
|
||||
--ring: oklch(0.551 0.027 264.364);
|
||||
--chart-1: oklch(0.488 0.243 264.376);
|
||||
--chart-2: oklch(0.696 0.17 162.48);
|
||||
--chart-3: oklch(0.769 0.188 70.08);
|
||||
--chart-4: oklch(0.627 0.265 303.9);
|
||||
--chart-5: oklch(0.645 0.246 16.439);
|
||||
--sidebar: oklch(0.208 0.042 265.755);
|
||||
--sidebar-foreground: oklch(0.984 0.003 247.858);
|
||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||
--sidebar-primary-foreground: oklch(0.984 0.003 247.858);
|
||||
--sidebar-accent: oklch(0.279 0.041 260.031);
|
||||
--sidebar-accent-foreground: oklch(0.984 0.003 247.858);
|
||||
--sidebar-border: oklch(1 0 0 / 10%);
|
||||
--sidebar-ring: oklch(0.551 0.027 264.364);
|
||||
}
|
||||
}
|
||||
|
||||
.theme-scaled {
|
||||
@media (min-width: 1024px) {
|
||||
--radius: 0.6rem;
|
||||
--text-lg: 1.05rem;
|
||||
--text-base: 0.85rem;
|
||||
--text-sm: 0.8rem;
|
||||
--spacing: 0.222222rem;
|
||||
}
|
||||
|
||||
[data-slot="card"] {
|
||||
--spacing: 0.16rem;
|
||||
}
|
||||
|
||||
[data-slot="card-header"] *,
|
||||
[data-slot="card-content"] *,
|
||||
[data-slot="card-footer"] * {
|
||||
--spacing: 0.222222rem;
|
||||
}
|
||||
}
|
||||
21
apps/v4/components.json
Normal file
21
apps/v4/components.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "new-york",
|
||||
"rsc": true,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "",
|
||||
"css": "app/globals.css",
|
||||
"baseColor": "neutral",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils",
|
||||
"ui": "@/registry/new-york-v4/ui",
|
||||
"lib": "@/lib",
|
||||
"hooks": "@/hooks"
|
||||
},
|
||||
"iconLibrary": "lucide"
|
||||
}
|
||||
72
apps/v4/components/accordion-demo.tsx
Normal file
72
apps/v4/components/accordion-demo.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from "@/registry/new-york-v4/ui/accordion"
|
||||
|
||||
export function AccordionDemo() {
|
||||
return (
|
||||
<div className="grid w-full max-w-xl gap-4">
|
||||
<Accordion type="single" collapsible className="w-full">
|
||||
<AccordionItem value="item-1">
|
||||
<AccordionTrigger>Is it accessible?</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Yes. It adheres to the WAI-ARIA design pattern.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="item-2">
|
||||
<AccordionTrigger>Is it styled?</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Yes. It comes with default styles that matches the other
|
||||
components' aesthetic.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="item-3">
|
||||
<AccordionTrigger>Is it animated?</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Yes. It's animated by default, but you can disable it if you
|
||||
prefer.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
<Accordion type="single" collapsible className="w-full">
|
||||
<AccordionItem value="item-1">
|
||||
<AccordionTrigger>
|
||||
What are the key considerations when implementing a comprehensive
|
||||
enterprise-level authentication system?
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Implementing a robust enterprise authentication system requires
|
||||
careful consideration of multiple factors. This includes secure
|
||||
password hashing and storage, multi-factor authentication (MFA)
|
||||
implementation, session management, OAuth2 and SSO integration,
|
||||
regular security audits, rate limiting to prevent brute force
|
||||
attacks, and maintaining detailed audit logs. Additionally,
|
||||
you'll need to consider scalability, performance impact, and
|
||||
compliance with relevant data protection regulations such as GDPR or
|
||||
HIPAA.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="item-2">
|
||||
<AccordionTrigger>
|
||||
How does modern distributed system architecture handle eventual
|
||||
consistency and data synchronization across multiple regions?
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Modern distributed systems employ various strategies to maintain
|
||||
data consistency across regions. This often involves using
|
||||
techniques like CRDT (Conflict-Free Replicated Data Types), vector
|
||||
clocks, and gossip protocols. Systems might implement event sourcing
|
||||
patterns, utilize message queues for asynchronous updates, and
|
||||
employ sophisticated conflict resolution strategies. Popular
|
||||
solutions like Amazon's DynamoDB and Google's Spanner
|
||||
demonstrate different approaches to solving these challenges,
|
||||
balancing between consistency, availability, and partition tolerance
|
||||
as described in the CAP theorem.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user