mirror of
https://github.com/shadcn-ui/ui.git
synced 2026-06-11 18:01:34 +00:00
Compare commits
1065 Commits
shadcn@2.6
...
shadcn/cod
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
69a88f9579 | ||
|
|
4e8263d7a3 | ||
|
|
e000e17856 | ||
|
|
1be8f98c46 | ||
|
|
6e476e4756 | ||
|
|
e2d36a3a7d | ||
|
|
a97ebe54f1 | ||
|
|
b2cc0dfe59 | ||
|
|
af99d4ebd3 | ||
|
|
a0a072dcdd | ||
|
|
447c7aac06 | ||
|
|
752615f231 | ||
|
|
f9b365bc7f | ||
|
|
17a1a9093a | ||
|
|
8159e98075 | ||
|
|
6a527b3e75 | ||
|
|
ebe689e85c | ||
|
|
8b683b44e6 | ||
|
|
725ca574f6 | ||
|
|
1dc39e2484 | ||
|
|
090556691c | ||
|
|
8e9f781cdb | ||
|
|
9d7c205442 | ||
|
|
902379fa3e | ||
|
|
94dcf37add | ||
|
|
843a5e2334 | ||
|
|
cdaad392ae | ||
|
|
49abe0d594 | ||
|
|
eeb33ae9c9 | ||
|
|
55fa1bb7cc | ||
|
|
90bbbb7993 | ||
|
|
fc9705665c | ||
|
|
52c477e118 | ||
|
|
41a4573002 | ||
|
|
f813fb5884 | ||
|
|
429c001412 | ||
|
|
cd7743cbc1 | ||
|
|
cadc3f96de | ||
|
|
a74515d6e1 | ||
|
|
0df9af0d75 | ||
|
|
e653c1a833 | ||
|
|
8b819e1db4 | ||
|
|
5236bfdf07 | ||
|
|
7e93eb81ea | ||
|
|
abf1555a65 | ||
|
|
584db77fee | ||
|
|
3faa91d670 | ||
|
|
2bf8ef86b9 | ||
|
|
624a4fe320 | ||
|
|
5508b5e4ec | ||
|
|
3af2ba80e8 | ||
|
|
40a00278ab | ||
|
|
5ab89f3ae3 | ||
|
|
40dc195fad | ||
|
|
ca374ad0a0 | ||
|
|
bc9f556c38 | ||
|
|
d06e54d2bb | ||
|
|
65ddce2886 | ||
|
|
f413598ba3 | ||
|
|
34c04d5f01 | ||
|
|
0029b3b6f7 | ||
|
|
75cc35272a | ||
|
|
821ac7ee4d | ||
|
|
8df46c4ded | ||
|
|
2303ce2372 | ||
|
|
cf672a9575 | ||
|
|
5ee4567353 | ||
|
|
6f72dba9c4 | ||
|
|
cd717896fa | ||
|
|
d47562cc08 | ||
|
|
aff5d7f0c1 | ||
|
|
4c0be13dcc | ||
|
|
afa410e47f | ||
|
|
7ee55e8bd3 | ||
|
|
e3c9a3f9dc | ||
|
|
aa841e35cf | ||
|
|
598fb2f55a | ||
|
|
aa786280a3 | ||
|
|
07fd9d0ea4 | ||
|
|
6ad0590d87 | ||
|
|
ff51e9ca3c | ||
|
|
7958cc6a33 | ||
|
|
2871e15418 | ||
|
|
c7d57548e5 | ||
|
|
dac13c90f2 | ||
|
|
50d8b764a9 | ||
|
|
90fc0b2dff | ||
|
|
d9d43d5b3b | ||
|
|
ce2c3ca688 | ||
|
|
3bd1bbe858 | ||
|
|
448fb0bc06 | ||
|
|
09a84892d9 | ||
|
|
16da5f2a56 | ||
|
|
f5f2a02eda | ||
|
|
dad006aa1e | ||
|
|
20a94ddb77 | ||
|
|
ae733168cd | ||
|
|
49616d2e16 | ||
|
|
7bc47bb858 | ||
|
|
e149aac756 | ||
|
|
62abc6be99 | ||
|
|
0072c9801f | ||
|
|
729708ad2e | ||
|
|
a4c6504c96 | ||
|
|
1bd5f3d7c8 | ||
|
|
3d6ea09c50 | ||
|
|
f45b8f3066 | ||
|
|
ad99fc9a73 | ||
|
|
da05ee321c | ||
|
|
de497a36bb | ||
|
|
882a9cb145 | ||
|
|
65cb5b49ff | ||
|
|
ae6f2e67aa | ||
|
|
d13d42d434 | ||
|
|
67c99dd33c | ||
|
|
ce012faf1e | ||
|
|
554a1a69a7 | ||
|
|
e489552614 | ||
|
|
8386198073 | ||
|
|
9c570f1435 | ||
|
|
ed2d9a6728 | ||
|
|
f336513d18 | ||
|
|
5755d6aa1f | ||
|
|
e363e343b7 | ||
|
|
fe955258c3 | ||
|
|
f5ac4a0d2a | ||
|
|
97ed7eb35c | ||
|
|
6909385aea | ||
|
|
8dabe113fa | ||
|
|
f5556230f1 | ||
|
|
327551f8b6 | ||
|
|
cdb4a4547f | ||
|
|
52f72b9cf7 | ||
|
|
048dac9359 | ||
|
|
f93d44730e | ||
|
|
21c64cb561 | ||
|
|
7e405f1568 | ||
|
|
04248d752e | ||
|
|
15f6a0fe49 | ||
|
|
54d254100d | ||
|
|
6d7f3479d1 | ||
|
|
5e1fca8b4e | ||
|
|
30229bfd14 | ||
|
|
1ce9c2dd6a | ||
|
|
5edf9c95b7 | ||
|
|
35657b4d5f | ||
|
|
b7786c4b42 | ||
|
|
6ca3784b67 | ||
|
|
c1b92c3175 | ||
|
|
b7afa9ba73 | ||
|
|
119d534e85 | ||
|
|
4e416dea5e | ||
|
|
b600dd7091 | ||
|
|
d59e5be214 | ||
|
|
cddbc1f3ff | ||
|
|
7c0d413e3c | ||
|
|
00de8addfe | ||
|
|
869e7bb17f | ||
|
|
8491d4207a | ||
|
|
6f31c22f11 | ||
|
|
a4c806ec26 | ||
|
|
1445fb769d | ||
|
|
a6bdaa6776 | ||
|
|
b44ca370f1 | ||
|
|
d2776903c2 | ||
|
|
936ee754b1 | ||
|
|
3a431547bb | ||
|
|
066e1e9abd | ||
|
|
b19fa88dec | ||
|
|
3aa50ddc9d | ||
|
|
f26db39334 | ||
|
|
3003e9e67a | ||
|
|
ee1303198a | ||
|
|
acb92a8df9 | ||
|
|
78410f9738 | ||
|
|
edf571debd | ||
|
|
257448bead | ||
|
|
e9f4cfb010 | ||
|
|
3c5f594b94 | ||
|
|
cf3f9f134a | ||
|
|
a643dc6ab5 | ||
|
|
8c705f8af9 | ||
|
|
28104c684d | ||
|
|
eccf6a2522 | ||
|
|
8ba3d50d7d | ||
|
|
75031d4461 | ||
|
|
13e64ea341 | ||
|
|
6034ffcd3c | ||
|
|
a749633d51 | ||
|
|
dad8a74ab4 | ||
|
|
3f03d30ce5 | ||
|
|
3365f4ebb2 | ||
|
|
68b8932406 | ||
|
|
a24351838a | ||
|
|
67b1083f3a | ||
|
|
aa4a97730a | ||
|
|
02f34a3b31 | ||
|
|
7cebd74ce5 | ||
|
|
bd1d93bbbc | ||
|
|
37ff1a3d12 | ||
|
|
308ebdbd3b | ||
|
|
cb6e798b90 | ||
|
|
2224411358 | ||
|
|
586f09a0c0 | ||
|
|
475ae744e6 | ||
|
|
553b6454f1 | ||
|
|
5805be2a2a | ||
|
|
c44d89a742 | ||
|
|
ce3fc7625a | ||
|
|
2532aeaa1d | ||
|
|
a4dafd1b32 | ||
|
|
07c87ff431 | ||
|
|
4a4b379f21 | ||
|
|
837e2bcc93 | ||
|
|
33dc7ea273 | ||
|
|
b8da7ce8b8 | ||
|
|
da3c255575 | ||
|
|
5eaad6ea6c | ||
|
|
f68e240293 | ||
|
|
ddc68e480a | ||
|
|
c31ebfaf6b | ||
|
|
e79f6e74bb | ||
|
|
57f9d875be | ||
|
|
a59144d8e1 | ||
|
|
3d8837bddb | ||
|
|
4d89b13e6f | ||
|
|
7d9689ba01 | ||
|
|
81a1dde380 | ||
|
|
8448acdf90 | ||
|
|
51b867e5dc | ||
|
|
c97ab6ee18 | ||
|
|
9584703534 | ||
|
|
f31ed81983 | ||
|
|
e85a698821 | ||
|
|
2bb09a50a1 | ||
|
|
17ed9baedb | ||
|
|
b40685050d | ||
|
|
0dab4f92ac | ||
|
|
0ddc3503a5 | ||
|
|
29ea3a7d67 | ||
|
|
823a1a42b4 | ||
|
|
0b66b1c473 | ||
|
|
934afbcf15 | ||
|
|
e0c924d2f4 | ||
|
|
a92b56491e | ||
|
|
6dcd9f4fef | ||
|
|
f5c36e520e | ||
|
|
fb2a3433e2 | ||
|
|
87ddddf41e | ||
|
|
45c8c1b873 | ||
|
|
68c9ada079 | ||
|
|
16a0473b10 | ||
|
|
4210d1ab05 | ||
|
|
bb7cf2c425 | ||
|
|
1a67379f57 | ||
|
|
d99fcf4a1c | ||
|
|
9954e2b014 | ||
|
|
7d28dfdb15 | ||
|
|
fd9c64f416 | ||
|
|
7e766f4714 | ||
|
|
9dc307f7cc | ||
|
|
47c0330610 | ||
|
|
ded8a4086f | ||
|
|
f6dc35c9a1 | ||
|
|
408d15f73f | ||
|
|
a50f6795cc | ||
|
|
da10396f2b | ||
|
|
c2f28e3ef5 | ||
|
|
40ab22fded | ||
|
|
db0482ed1f | ||
|
|
9f8a877e8f | ||
|
|
331fe02c2a | ||
|
|
34ee2a17c2 | ||
|
|
8dbb61cdd4 | ||
|
|
cc86750dfb | ||
|
|
646f884e8f | ||
|
|
fbdf6c02c1 | ||
|
|
8ab757be8d | ||
|
|
b557df5840 | ||
|
|
8271bb7f40 | ||
|
|
0008c487e9 | ||
|
|
ae68204542 | ||
|
|
e68e081d7f | ||
|
|
006dc8f9d0 | ||
|
|
b9b30a23e6 | ||
|
|
8af3cfd031 | ||
|
|
fae5e78292 | ||
|
|
a13adf8f3a | ||
|
|
dc89adf190 | ||
|
|
3fc793287b | ||
|
|
7d4dd65acd | ||
|
|
d4a2a5fe80 | ||
|
|
d9a01999e8 | ||
|
|
6bb4060686 | ||
|
|
605246f93b | ||
|
|
5ef76dece1 | ||
|
|
d24d2e6fd0 | ||
|
|
9546f3ad1e | ||
|
|
6d2c00376e | ||
|
|
117136ada3 | ||
|
|
f130d4d8c7 | ||
|
|
a46eea77a6 | ||
|
|
0b42927d38 | ||
|
|
b979ca6e79 | ||
|
|
91ce4cc854 | ||
|
|
b58195e154 | ||
|
|
0d3f6a0812 | ||
|
|
22ce4605d8 | ||
|
|
474d461b1c | ||
|
|
339de90b8a | ||
|
|
048313aefa | ||
|
|
805f73582f | ||
|
|
a6ab998e5c | ||
|
|
92075c8426 | ||
|
|
751c520865 | ||
|
|
4fa2ef66ed | ||
|
|
aa735ef562 | ||
|
|
a927f9c458 | ||
|
|
82f03d0f1d | ||
|
|
40aca13fb0 | ||
|
|
e2832bac7c | ||
|
|
5f96916701 | ||
|
|
4a96d95bde | ||
|
|
dc3eb9081a | ||
|
|
2ddd920e4d | ||
|
|
e1e9940a04 | ||
|
|
bc8626c6f8 | ||
|
|
f2817b7c49 | ||
|
|
fc79e82108 | ||
|
|
58052634fa | ||
|
|
c1374c5592 | ||
|
|
3a5d636345 | ||
|
|
642d802eee | ||
|
|
76ba624dce | ||
|
|
01d5f034b9 | ||
|
|
b7ced9f289 | ||
|
|
1df2bf4d9b | ||
|
|
9c39e1ddc9 | ||
|
|
bbac1cb663 | ||
|
|
3bc23a60c7 | ||
|
|
c171ae4761 | ||
|
|
b530f4928e | ||
|
|
9fc6afd181 | ||
|
|
eb3d88afbf | ||
|
|
8ded0658d4 | ||
|
|
d032f81fd6 | ||
|
|
75becccf78 | ||
|
|
bfb84e2960 | ||
|
|
2f64c5a407 | ||
|
|
9e6765f4e2 | ||
|
|
d77c84b7c9 | ||
|
|
7172f787ac | ||
|
|
77f66d5357 | ||
|
|
4307815c0f | ||
|
|
b484f36a22 | ||
|
|
360a649d2a | ||
|
|
4bdd23291c | ||
|
|
1ee480122b | ||
|
|
382a5220e0 | ||
|
|
627155b13c | ||
|
|
0ca4dd1b32 | ||
|
|
383bcc4fc1 | ||
|
|
8028a0d75d | ||
|
|
31c1c5eb56 | ||
|
|
ca9295016a | ||
|
|
7b90fe9833 | ||
|
|
330786352c | ||
|
|
da309ae929 | ||
|
|
3877ae5328 | ||
|
|
9c99070d54 | ||
|
|
5751250a7f | ||
|
|
f97ff8124c | ||
|
|
7f37ed96d1 | ||
|
|
7ff7049018 | ||
|
|
ae895787c1 | ||
|
|
305f5c7d47 | ||
|
|
f0d3984376 | ||
|
|
b8f355ac4f | ||
|
|
29195a17a7 | ||
|
|
e90efd4fa9 | ||
|
|
70c158990d | ||
|
|
6e2efb4b55 | ||
|
|
18db1a78ab | ||
|
|
bd87d729fd | ||
|
|
a6f3ef591f | ||
|
|
aaed0a186c | ||
|
|
2b74bbca5c | ||
|
|
36758f61b4 | ||
|
|
f9de81f032 | ||
|
|
444aa53803 | ||
|
|
4e9f3e6e05 | ||
|
|
3fc4482d7c | ||
|
|
ad851375dd | ||
|
|
dd3e942057 | ||
|
|
dd4439c34a | ||
|
|
e81d850438 | ||
|
|
779453be26 | ||
|
|
867d341182 | ||
|
|
78b51f9a11 | ||
|
|
417772dd9c | ||
|
|
b86885512f | ||
|
|
65381cd614 | ||
|
|
e3f11d8fe1 | ||
|
|
093eb419a8 | ||
|
|
ad25490cf9 | ||
|
|
e94d3d80fa | ||
|
|
0e6b6d90bc | ||
|
|
ce1f9259bf | ||
|
|
8cec12b98b | ||
|
|
028b1b2d93 | ||
|
|
d8e5d0d4f1 | ||
|
|
0da9826821 | ||
|
|
b416e09e8b | ||
|
|
2ef58bd75d | ||
|
|
cac794208e | ||
|
|
a22aec8694 | ||
|
|
6f11e820b5 | ||
|
|
6a75b60b4f | ||
|
|
c494adbd87 | ||
|
|
3aa0f13869 | ||
|
|
e9af9efaf3 | ||
|
|
1ecc8066db | ||
|
|
525775fb36 | ||
|
|
3e4c608aca | ||
|
|
bd5028e331 | ||
|
|
4207614600 | ||
|
|
e1af950724 | ||
|
|
e91388a010 | ||
|
|
8648ddb528 | ||
|
|
feff5b6a57 | ||
|
|
32198910ce | ||
|
|
07f7147ff3 | ||
|
|
0e8a006adc | ||
|
|
4193e3c78f | ||
|
|
d2f91d6f1e | ||
|
|
9ed5093474 | ||
|
|
a12dd019d3 | ||
|
|
e53bc92f41 | ||
|
|
597a8db2d9 | ||
|
|
0b0f639cd0 | ||
|
|
6b4ba6bca1 | ||
|
|
3cdd67b5b4 | ||
|
|
2b03bc7a53 | ||
|
|
f6447b8936 | ||
|
|
4069c33671 | ||
|
|
4dbc5581a5 | ||
|
|
3fc5c1c995 | ||
|
|
f123057ae5 | ||
|
|
5025ec818f | ||
|
|
65c6c8146d | ||
|
|
b93745f24a | ||
|
|
bbb59c9fe1 | ||
|
|
fb56f6571a | ||
|
|
082af1f82c | ||
|
|
f5b3a0cbad | ||
|
|
d602ccc224 | ||
|
|
ab54e7b7bd | ||
|
|
0137b07f66 | ||
|
|
ae95fbd1be | ||
|
|
625bd97d8b | ||
|
|
603fce7cd3 | ||
|
|
c759f460d5 | ||
|
|
e1c00667f7 | ||
|
|
46631fc4d4 | ||
|
|
f235a5d951 | ||
|
|
b0b711f181 | ||
|
|
f1b7102583 | ||
|
|
f076420e68 | ||
|
|
4ce0a7eaa1 | ||
|
|
270b730c21 | ||
|
|
14a6cc5999 | ||
|
|
0067873f60 | ||
|
|
fc16e1461f | ||
|
|
8f01916bb2 | ||
|
|
87d522f249 | ||
|
|
ead138b4cd | ||
|
|
ef39979548 | ||
|
|
ab6c8caf2f | ||
|
|
ba9206bded | ||
|
|
c5838cf955 | ||
|
|
0c41fc30e4 | ||
|
|
8270cfa39e | ||
|
|
06e356cab9 | ||
|
|
f24631dc48 | ||
|
|
ec936bcd06 | ||
|
|
6c7975e400 | ||
|
|
8acef7ab66 | ||
|
|
4ddfd39b0d | ||
|
|
3ba37cc24c | ||
|
|
da080118b0 | ||
|
|
e8897ea80a | ||
|
|
9d26f582fa | ||
|
|
0a2ad2176c | ||
|
|
7c36439836 | ||
|
|
a1e3afed06 | ||
|
|
be5b1bbae3 | ||
|
|
52de23bf95 | ||
|
|
1d16fe46cd | ||
|
|
cbecda13f9 | ||
|
|
24649ec103 | ||
|
|
b9f62a8399 | ||
|
|
689d45e095 | ||
|
|
33f7b3f2bb | ||
|
|
2cce072393 | ||
|
|
d64bdec2f9 | ||
|
|
5adacdecad | ||
|
|
f2552d3f3b | ||
|
|
b435e01199 | ||
|
|
cd576df6e4 | ||
|
|
9fbd3b1a72 | ||
|
|
c6dd35a092 | ||
|
|
470c6f42b0 | ||
|
|
e6956e45ac | ||
|
|
a2b9dedbb7 | ||
|
|
384129609f | ||
|
|
5be0811f01 | ||
|
|
1a10b4671a | ||
|
|
e7d36b7e21 | ||
|
|
290fac9115 | ||
|
|
0633333db4 | ||
|
|
630323ad47 | ||
|
|
51fc7f5457 | ||
|
|
44a9b3bd12 | ||
|
|
2b879a5ec8 | ||
|
|
381f2ef165 | ||
|
|
825ebca3f0 | ||
|
|
e0063070a6 | ||
|
|
013ae51d10 | ||
|
|
08e54510ed | ||
|
|
a95606cee9 | ||
|
|
c990476d99 | ||
|
|
44c8f02d06 | ||
|
|
a012542015 | ||
|
|
926df433a7 | ||
|
|
5c09e0d8fa | ||
|
|
dba86053f5 | ||
|
|
cd188b267d | ||
|
|
8a09fbaac9 | ||
|
|
9676c8f4ee | ||
|
|
9b5aeab889 | ||
|
|
28ebf1b88a | ||
|
|
f922e82f53 | ||
|
|
beec1e060e | ||
|
|
26a24d3d5c | ||
|
|
c3c7f03f04 | ||
|
|
4af29d6c20 | ||
|
|
b28f77f893 | ||
|
|
b8c7ae8088 | ||
|
|
d21c74fb3a | ||
|
|
d6548b4ae8 | ||
|
|
110a4ec10b | ||
|
|
851562f4f2 | ||
|
|
b7b839ebc2 | ||
|
|
8d9be074a3 | ||
|
|
a0c077da9e | ||
|
|
540cd031c3 | ||
|
|
4d9720449f | ||
|
|
f1e10f3da8 | ||
|
|
e2225d4a93 | ||
|
|
444f6889c8 | ||
|
|
03a7804c42 | ||
|
|
acc847bed3 | ||
|
|
abfa2ddb74 | ||
|
|
5e92c160dd | ||
|
|
d41e857ba3 | ||
|
|
99651191cc | ||
|
|
712285f60e | ||
|
|
aed95086e0 | ||
|
|
1990280d66 | ||
|
|
2bf55c9133 | ||
|
|
3192a3db55 | ||
|
|
afa2a7adf2 | ||
|
|
728d8af275 | ||
|
|
38de7fddc2 | ||
|
|
c719d24f3a | ||
|
|
4479965555 | ||
|
|
7ea124b25d | ||
|
|
f746368369 | ||
|
|
164b6ff6c1 | ||
|
|
7ae522e610 | ||
|
|
e1a0ec3061 | ||
|
|
f8222528eb | ||
|
|
759003c781 | ||
|
|
6d467d2e1d | ||
|
|
893cddd2dc | ||
|
|
1781186def | ||
|
|
89b9a76368 | ||
|
|
6529256e98 | ||
|
|
b142bd2fd5 | ||
|
|
0266253841 | ||
|
|
4a39de5c56 | ||
|
|
e5fda2c139 | ||
|
|
d53f7489ce | ||
|
|
dfe784b44a | ||
|
|
40b9de46e9 | ||
|
|
6d97ab0b9b | ||
|
|
d06e84a007 | ||
|
|
a29185c9cf | ||
|
|
84c801ac67 | ||
|
|
3dbe9e6a3e | ||
|
|
4fa8f9b4c2 | ||
|
|
24205601e1 | ||
|
|
267d45ac7a | ||
|
|
caadc3d7e8 | ||
|
|
a4ee54836e | ||
|
|
7b5c919eae | ||
|
|
f1cacdc051 | ||
|
|
8cb8fb66b3 | ||
|
|
ef01cd4315 | ||
|
|
6cb2a1fd65 | ||
|
|
ee88d296f4 | ||
|
|
598f17812d | ||
|
|
0ae734bdb2 | ||
|
|
18bd8f07cb | ||
|
|
5fc9ced0fd | ||
|
|
b5dff005f6 | ||
|
|
c5c08bb773 | ||
|
|
5998e59839 | ||
|
|
4b7e38ab42 | ||
|
|
e2ba2d241e | ||
|
|
13e2a6c598 | ||
|
|
47c47eaed2 | ||
|
|
25e88fe4e9 | ||
|
|
d3590ceff9 | ||
|
|
d04bc84a51 | ||
|
|
f68465e815 | ||
|
|
094edfcfe6 | ||
|
|
5a42652c41 | ||
|
|
3409681949 | ||
|
|
1c989f9155 | ||
|
|
0aea23013c | ||
|
|
bfce3031a3 | ||
|
|
cfb81c61de | ||
|
|
7860ab83d1 | ||
|
|
2acaf954d7 | ||
|
|
1e9e337923 | ||
|
|
66d2400784 | ||
|
|
682c98989d | ||
|
|
77d7b39ef7 | ||
|
|
5b3ba49aec | ||
|
|
54edfd228d | ||
|
|
fd3e5515f3 | ||
|
|
65ad910bca | ||
|
|
d4a1c89e8e | ||
|
|
78023693c6 | ||
|
|
0fc52a7f4d | ||
|
|
8fcfc563a9 | ||
|
|
f393c251fe | ||
|
|
f2583391ea | ||
|
|
c2fd847d65 | ||
|
|
f6f2dfa5b2 | ||
|
|
d07a7af82b | ||
|
|
b6d845f8a6 | ||
|
|
bd29630e4e | ||
|
|
93ad19e4da | ||
|
|
31f8af8409 | ||
|
|
9317a93152 | ||
|
|
ccafdaf7c6 | ||
|
|
f0d147d581 | ||
|
|
df67e49aac | ||
|
|
c0de90e1a1 | ||
|
|
0447708efa | ||
|
|
4a470fc617 | ||
|
|
137b1c12b7 | ||
|
|
73296e79c0 | ||
|
|
78e5fa2a39 | ||
|
|
9cf47dd4a3 | ||
|
|
f53400f934 | ||
|
|
b3d6f872db | ||
|
|
2aa5e11f6f | ||
|
|
058ebc5acd | ||
|
|
a60683dea5 | ||
|
|
1dc1b8dbfb | ||
|
|
c6273cca03 | ||
|
|
b15d7e8221 | ||
|
|
46e3c26a6e | ||
|
|
f36e25f703 | ||
|
|
55f5d1c7cc | ||
|
|
db19605996 | ||
|
|
40012adb14 | ||
|
|
ad8104e473 | ||
|
|
5fb0c4d19a | ||
|
|
31c86f9fd5 | ||
|
|
aad175ff87 | ||
|
|
081c91c461 | ||
|
|
7dbf3688fb | ||
|
|
99ad18b389 | ||
|
|
fabb886de9 | ||
|
|
4b561cf050 | ||
|
|
0c2373f592 | ||
|
|
ff42c27d41 | ||
|
|
075b6aef97 | ||
|
|
f4e0f671de | ||
|
|
d3156c09ae | ||
|
|
46bf4a0f06 | ||
|
|
b61b718727 | ||
|
|
ee9b6b36ec | ||
|
|
33de348d41 | ||
|
|
edcc96fc73 | ||
|
|
ef90a97e72 | ||
|
|
86d9b00084 | ||
|
|
672f845322 | ||
|
|
d01074deed | ||
|
|
321ceaf1c4 | ||
|
|
32a972f4ce | ||
|
|
d28e02be1b | ||
|
|
6699158a22 | ||
|
|
142cd8ef13 | ||
|
|
bdedce2750 | ||
|
|
4cb283d68e | ||
|
|
480a6cdb37 | ||
|
|
8ba883738e | ||
|
|
b022c24825 | ||
|
|
3587477865 | ||
|
|
05143a80e6 | ||
|
|
728d2003b7 | ||
|
|
12c9e6b0b5 | ||
|
|
56cd757c45 | ||
|
|
9eb784054f | ||
|
|
824577692b | ||
|
|
6be68df08c | ||
|
|
cc48808a0d | ||
|
|
a56b3720d1 | ||
|
|
334db11234 | ||
|
|
8a5027a0cd | ||
|
|
803206305d | ||
|
|
d0fb73ac0e | ||
|
|
62218c1c0c | ||
|
|
dd1563d57d | ||
|
|
0538384860 | ||
|
|
d43b437abc | ||
|
|
8fbfacd243 | ||
|
|
778cee31ee | ||
|
|
73d8b8a817 | ||
|
|
55ab069aca | ||
|
|
c39925a9be | ||
|
|
51179ccd64 | ||
|
|
dcfa05e392 | ||
|
|
541f55df04 | ||
|
|
69010e0230 | ||
|
|
a8025c866e | ||
|
|
6e34ec7280 | ||
|
|
10ccb244a1 | ||
|
|
16fdb07ccc | ||
|
|
49da1fae79 | ||
|
|
a2244d42f7 | ||
|
|
c2075e2a8b | ||
|
|
dd2d8d7ead | ||
|
|
b6a93b7ec6 | ||
|
|
4899d3f0da | ||
|
|
3d04cb099a | ||
|
|
cde343916c | ||
|
|
c877df07b8 | ||
|
|
65e5c1c3cf | ||
|
|
8a7f05f670 | ||
|
|
db004ce4c0 | ||
|
|
e23698a897 | ||
|
|
5813ef20a3 | ||
|
|
515024b69e | ||
|
|
f7284c5cc3 | ||
|
|
c02d00aafc | ||
|
|
df497ad236 | ||
|
|
1e468e33ac | ||
|
|
ff91c31a71 | ||
|
|
25d6a18f6f | ||
|
|
c0309510b6 | ||
|
|
a3a1574668 | ||
|
|
65d581ea5a | ||
|
|
fdf80a1d49 | ||
|
|
86c494c452 | ||
|
|
eb158686b9 | ||
|
|
134cd46edb | ||
|
|
47b0efb20c | ||
|
|
bd4d09d33e | ||
|
|
14d6265580 | ||
|
|
68805d29a1 | ||
|
|
c100d5841a | ||
|
|
7a71da5218 | ||
|
|
e18902039a | ||
|
|
559af6c245 | ||
|
|
8971be484f | ||
|
|
ad6a3c6367 | ||
|
|
befa56b5be | ||
|
|
5d1770e36d | ||
|
|
653521725a | ||
|
|
7c0618bf43 | ||
|
|
854641cea1 | ||
|
|
3a72007f61 | ||
|
|
6b53b238fb | ||
|
|
b398fea304 | ||
|
|
f22174a77f | ||
|
|
c9a39f1007 | ||
|
|
a8ad21f81f | ||
|
|
504503c638 | ||
|
|
f8df5c95cb | ||
|
|
2bfc1c82ba | ||
|
|
84bd724d97 | ||
|
|
39fdf94550 | ||
|
|
08479cc3db | ||
|
|
02d5ce85ec | ||
|
|
c0329c86b9 | ||
|
|
3b1491f908 | ||
|
|
ca4c1c43ec | ||
|
|
1e840eb53c | ||
|
|
96ac92e63f | ||
|
|
e11546e692 | ||
|
|
0b4d62f95c | ||
|
|
dae80dad65 | ||
|
|
abc09809e8 | ||
|
|
8a40fe0ead | ||
|
|
b3ab304a00 | ||
|
|
bb45fd83c3 | ||
|
|
84678ee1c0 | ||
|
|
33ffb0419c | ||
|
|
a2f6c031e2 | ||
|
|
ac098d8cf0 | ||
|
|
8160610410 | ||
|
|
c7901e3a41 | ||
|
|
d73ac361b3 | ||
|
|
ebad2901ce | ||
|
|
4f617d59b8 | ||
|
|
ed0e103bd6 | ||
|
|
9cab0c9b18 | ||
|
|
d80e084814 | ||
|
|
efcf9728c2 | ||
|
|
8835bacc8b | ||
|
|
f2556d2386 | ||
|
|
75a0000075 | ||
|
|
ac306c60f5 | ||
|
|
5e2ef1f8bd | ||
|
|
7d9b8aefff | ||
|
|
58208e3802 | ||
|
|
a16a77446a | ||
|
|
39032bb390 | ||
|
|
d7e0dc3ec8 | ||
|
|
6bddba986d | ||
|
|
b70059b25b | ||
|
|
37bc2eec1f | ||
|
|
bb048fb532 | ||
|
|
9c373dbd27 | ||
|
|
d75b092c61 | ||
|
|
be49662bf5 | ||
|
|
b2b2e3fc98 | ||
|
|
188b746074 | ||
|
|
6f093a0f3f | ||
|
|
f18f1eaff7 | ||
|
|
9ac1b5c0a5 | ||
|
|
f63b70b413 | ||
|
|
54e725d986 | ||
|
|
62dbad36bb | ||
|
|
a707424fa2 | ||
|
|
e2bfa6bd85 | ||
|
|
6292464d90 | ||
|
|
6617167d6f | ||
|
|
ca28857d40 | ||
|
|
343bc941b1 | ||
|
|
c9311f26fa | ||
|
|
4e0871f426 | ||
|
|
cb769b7059 | ||
|
|
93037dca94 | ||
|
|
ed9d5939e6 | ||
|
|
b52ec12f1e | ||
|
|
2ab9bff4bb | ||
|
|
2f6b51fa0a | ||
|
|
8a4764ed91 | ||
|
|
e934d4645b | ||
|
|
08b8e499d8 | ||
|
|
69402b3579 | ||
|
|
679c852254 | ||
|
|
d478412e44 | ||
|
|
d5c8a25150 | ||
|
|
26433a651c | ||
|
|
c3da716e94 | ||
|
|
b2572d0287 | ||
|
|
b83f042416 | ||
|
|
6567897393 | ||
|
|
2675fa3941 | ||
|
|
fbda67c88c | ||
|
|
e8674ee848 | ||
|
|
adb66f4d43 | ||
|
|
3afb46eaf6 | ||
|
|
7cd019ad36 | ||
|
|
bea7d30536 | ||
|
|
40c3ff513a | ||
|
|
89ebfdce47 | ||
|
|
b83023034a | ||
|
|
6a534d7954 | ||
|
|
ef1987ded9 | ||
|
|
77bf7d28b4 | ||
|
|
41f4f7357d | ||
|
|
bc99818e04 | ||
|
|
162ba7b13c | ||
|
|
f12db1e3a2 | ||
|
|
ce3e2b1df8 | ||
|
|
dcfe911b33 | ||
|
|
7210a4919a | ||
|
|
d198908510 | ||
|
|
b0b1cd1f0d | ||
|
|
f3d70724b6 | ||
|
|
407e9c6802 | ||
|
|
c67e630521 | ||
|
|
f494411953 | ||
|
|
a43c1d1342 | ||
|
|
607a6fd127 | ||
|
|
fbcc665b49 | ||
|
|
7ddcf31e43 | ||
|
|
3e39163b08 | ||
|
|
e311fdae04 | ||
|
|
26640d9d88 | ||
|
|
3e20c228da | ||
|
|
0810c0e1a2 | ||
|
|
1205ea5445 | ||
|
|
4430ab8bab | ||
|
|
d6716db9cc | ||
|
|
da8fa6aacd | ||
|
|
e96f9edf02 | ||
|
|
b19e9cadb2 | ||
|
|
3bb47bf914 | ||
|
|
a72fac6fde | ||
|
|
4b3186c46b | ||
|
|
e67e955f2a | ||
|
|
bf047b9824 | ||
|
|
04432835f9 | ||
|
|
77e6f28e81 | ||
|
|
f1e51ec8a1 | ||
|
|
3c525b8305 | ||
|
|
e7e844ff63 | ||
|
|
e14c55ac65 | ||
|
|
043be944ab | ||
|
|
4eb257bc14 | ||
|
|
1289192d4f | ||
|
|
75dde2e646 | ||
|
|
b9f3ce1988 | ||
|
|
cdf58be7e1 | ||
|
|
fae1a81add | ||
|
|
fc6d909ba2 | ||
|
|
590b9be610 | ||
|
|
41eb9d5c46 | ||
|
|
b7c28199be | ||
|
|
7869defd42 | ||
|
|
6daa5215cc | ||
|
|
722fb81b95 | ||
|
|
543be31722 | ||
|
|
09b90cd5c2 | ||
|
|
c95959a9b3 | ||
|
|
08820ce5ee | ||
|
|
cb96e58992 | ||
|
|
fce5926265 | ||
|
|
f7c0f81258 | ||
|
|
960b22b301 | ||
|
|
6f057c9cc3 | ||
|
|
615a32d97a | ||
|
|
bfe6e1946c | ||
|
|
baaa82e4e7 | ||
|
|
caeed7bd65 | ||
|
|
61254f0c3f | ||
|
|
3dcd797f2c | ||
|
|
b76f5cdbf7 | ||
|
|
fcb1e2ca50 | ||
|
|
df94537e0f | ||
|
|
275e3a2d59 | ||
|
|
e5402f9a20 | ||
|
|
04668da018 | ||
|
|
0805751703 | ||
|
|
9ecb19cf2e | ||
|
|
9c5eb0d20f | ||
|
|
2752ce11d8 | ||
|
|
d972caa853 | ||
|
|
00b2f0796e | ||
|
|
3ed9af5757 | ||
|
|
a4237e38f7 | ||
|
|
1178d40352 | ||
|
|
cc612359ee | ||
|
|
4d0272a659 | ||
|
|
a15534bdb7 | ||
|
|
62c41c3271 | ||
|
|
851c0fa0d1 | ||
|
|
e84c819977 | ||
|
|
64f8baf9aa | ||
|
|
4b44c6489a | ||
|
|
f9021e9388 | ||
|
|
b1e3d4b740 | ||
|
|
084fb927a1 | ||
|
|
7304ef2105 | ||
|
|
b34f3fdc4f | ||
|
|
2ecf876fa1 | ||
|
|
dcd2c3ef14 | ||
|
|
17422714f6 | ||
|
|
fc27ba2692 | ||
|
|
f854190b53 | ||
|
|
396275e46a | ||
|
|
296feb28a2 | ||
|
|
a941287411 | ||
|
|
2e34c95c4e | ||
|
|
fed7e3bfdc | ||
|
|
4f5333ea7a | ||
|
|
b5b8deedde | ||
|
|
7d71b02fb1 | ||
|
|
b3639227d0 | ||
|
|
a4a3600757 | ||
|
|
a426fea941 | ||
|
|
6e870c3993 | ||
|
|
68aa3389de | ||
|
|
2e9ccede8f | ||
|
|
fc8927a1f9 | ||
|
|
ccfd14946b | ||
|
|
01c02b289a | ||
|
|
a80ab37483 | ||
|
|
469250115f | ||
|
|
2c164b0f22 | ||
|
|
578f83cbef | ||
|
|
07eda36b13 | ||
|
|
0eccdc9c5f | ||
|
|
0940c6aec7 | ||
|
|
e244952500 | ||
|
|
0e3d6b24d3 | ||
|
|
cef5af9ed3 | ||
|
|
6deb0fdbb6 | ||
|
|
e9ae79f874 | ||
|
|
d891132f2a | ||
|
|
873f7f2773 | ||
|
|
e6778dee87 | ||
|
|
97a8de1c1b | ||
|
|
19d7fbb731 | ||
|
|
a9ab05ad83 | ||
|
|
6ac114ae68 | ||
|
|
d5770e4350 | ||
|
|
4730276256 | ||
|
|
4e04567b07 | ||
|
|
6f63b04d28 | ||
|
|
e38228b574 | ||
|
|
8807103586 | ||
|
|
3424ab709e | ||
|
|
4a86a55cac | ||
|
|
2926574d0e | ||
|
|
20e913d8e1 | ||
|
|
3433aaffaa | ||
|
|
d9cdc3f7ae | ||
|
|
e75e7b3866 | ||
|
|
ed5237c231 | ||
|
|
f85ca066dc | ||
|
|
54e66d4450 | ||
|
|
6c341c16ae | ||
|
|
06d03d64f4 | ||
|
|
6407a3b330 | ||
|
|
96b15f6090 | ||
|
|
2fe9cf6d26 | ||
|
|
728cb4cfa5 | ||
|
|
db93787712 | ||
|
|
1cdd6c1645 | ||
|
|
4983c6e1f4 | ||
|
|
7443edcfb0 | ||
|
|
9d9a33be52 | ||
|
|
d544a7f7a5 | ||
|
|
48fe0d709f | ||
|
|
ed244ea0b5 | ||
|
|
b8fede1742 | ||
|
|
84d6c83bad | ||
|
|
5b8ee41511 | ||
|
|
7c3d34cdc9 | ||
|
|
56c4c83511 | ||
|
|
2821cb0e39 | ||
|
|
3c87402de2 | ||
|
|
20a88e1f15 | ||
|
|
cb19ab8464 |
@@ -7,5 +7,5 @@
|
||||
"access": "public",
|
||||
"baseBranch": "main",
|
||||
"updateInternalDependencies": "patch",
|
||||
"ignore": ["www", "v4"]
|
||||
"ignore": ["v4", "tests"]
|
||||
}
|
||||
|
||||
13
.claude/settings.local.json
Normal file
13
.claude/settings.local.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(npm test:*)",
|
||||
"Bash(npm run typecheck:*)",
|
||||
"Bash(ls:*)",
|
||||
"Bash(cat:*)",
|
||||
"WebSearch",
|
||||
"WebFetch(domain:github.com)"
|
||||
],
|
||||
"deny": []
|
||||
}
|
||||
}
|
||||
10
.github/changeset-version.js
vendored
10
.github/changeset-version.js
vendored
@@ -1,12 +1,12 @@
|
||||
// ORIGINALLY FROM CLOUDFLARE WRANGLER:
|
||||
// https://github.com/cloudflare/wrangler2/blob/main/.github/changeset-version.js
|
||||
|
||||
import { exec } from "child_process"
|
||||
import { execSync } from "child_process"
|
||||
|
||||
// This script is used by the `release.yml` workflow to update the version of the packages being released.
|
||||
// The standard step is only to run `changeset version` but this does not update the package-lock.json file.
|
||||
// So we also run `npm install`, which does this update.
|
||||
// The standard step is only to run `changeset version` but this does not update the pnpm-lock.yaml file.
|
||||
// So we also run `pnpm install`, which does this update.
|
||||
// This is a workaround until this is handled automatically by `changeset version`.
|
||||
// See https://github.com/changesets/changesets/issues/421.
|
||||
exec("npx changeset version")
|
||||
exec("npm install")
|
||||
execSync("npx changeset version", { stdio: "inherit" })
|
||||
execSync("pnpm install --lockfile-only", { stdio: "inherit" })
|
||||
|
||||
40
.github/dependabot.yml
vendored
40
.github/dependabot.yml
vendored
@@ -4,3 +4,43 @@ updates:
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/templates/astro-app"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/templates/astro-monorepo"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/templates/next-app"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/templates/next-monorepo"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/templates/react-router-app"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/templates/react-router-monorepo"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/templates/start-app"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/templates/start-monorepo"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/templates/vite-app"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/templates/vite-monorepo"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
3
.github/workflows/code-check.yml
vendored
3
.github/workflows/code-check.yml
vendored
@@ -77,6 +77,9 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Build packages
|
||||
run: pnpm --filter=shadcn build
|
||||
|
||||
- run: pnpm format:check
|
||||
|
||||
tsc:
|
||||
|
||||
125
.github/workflows/code-format.yml
vendored
Normal file
125
.github/workflows/code-format.yml
vendored
Normal file
@@ -0,0 +1,125 @@
|
||||
name: Code format
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["Code check"]
|
||||
types:
|
||||
- completed
|
||||
|
||||
permissions:
|
||||
actions: read
|
||||
contents: write
|
||||
pull-requests: read
|
||||
|
||||
jobs:
|
||||
format:
|
||||
if: |
|
||||
github.event.workflow_run.conclusion == 'failure' &&
|
||||
github.event.workflow_run.event == 'pull_request'
|
||||
runs-on: ubuntu-latest
|
||||
name: Run pnpm format:write
|
||||
steps:
|
||||
- name: Inspect failed workflow run
|
||||
id: meta
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const pr = context.payload.workflow_run.pull_requests[0]
|
||||
|
||||
if (!pr) {
|
||||
core.notice("Skipping because the failed run is not attached to a pull request.")
|
||||
core.setOutput("should_run", "false")
|
||||
return
|
||||
}
|
||||
|
||||
const { data: pullRequest } = await github.rest.pulls.get({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: pr.number,
|
||||
})
|
||||
|
||||
const { data: jobs } = await github.rest.actions.listJobsForWorkflowRun({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
run_id: context.payload.workflow_run.id,
|
||||
per_page: 100,
|
||||
})
|
||||
|
||||
const formatJob = jobs.jobs.find((job) => job.name === "pnpm format:check")
|
||||
const sameRepo =
|
||||
pullRequest.head.repo.full_name === `${context.repo.owner}/${context.repo.repo}`
|
||||
const shouldRun = formatJob?.conclusion === "failure" && sameRepo
|
||||
|
||||
if (!formatJob) {
|
||||
core.notice("Skipping because the format job could not be found in the failed run.")
|
||||
} else if (formatJob.conclusion !== "failure") {
|
||||
core.notice(
|
||||
`Skipping because the format job concluded with "${formatJob.conclusion}".`
|
||||
)
|
||||
}
|
||||
|
||||
if (!sameRepo) {
|
||||
core.notice(
|
||||
`Skipping PR #${pullRequest.number} because its head branch lives in ${pullRequest.head.repo.full_name}.`
|
||||
)
|
||||
}
|
||||
|
||||
core.setOutput("head_ref", pullRequest.head.ref)
|
||||
core.setOutput("should_run", shouldRun ? "true" : "false")
|
||||
|
||||
- name: Checkout pull request branch
|
||||
if: steps.meta.outputs.should_run == 'true'
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ steps.meta.outputs.head_ref }}
|
||||
|
||||
- name: Install Node.js
|
||||
if: steps.meta.outputs.should_run == 'true'
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Install pnpm
|
||||
if: steps.meta.outputs.should_run == 'true'
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9.0.6
|
||||
run_install: false
|
||||
|
||||
- name: Get pnpm store directory
|
||||
if: steps.meta.outputs.should_run == 'true'
|
||||
id: pnpm-cache
|
||||
run: |
|
||||
echo "pnpm_cache_dir=$(pnpm store path)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Setup pnpm cache
|
||||
if: steps.meta.outputs.should_run == 'true'
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
|
||||
- name: Install dependencies
|
||||
if: steps.meta.outputs.should_run == 'true'
|
||||
run: pnpm install
|
||||
|
||||
- name: Apply formatting
|
||||
if: steps.meta.outputs.should_run == 'true'
|
||||
run: pnpm format:write
|
||||
|
||||
- name: Commit formatting changes
|
||||
if: steps.meta.outputs.should_run == 'true'
|
||||
run: |
|
||||
if git diff --quiet; then
|
||||
echo "No formatting changes to commit."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git add -A
|
||||
git commit -m "style: apply automated formatting"
|
||||
git push origin HEAD:${{ steps.meta.outputs.head_ref }}
|
||||
22
.github/workflows/prerelease.yml
vendored
22
.github/workflows/prerelease.yml
vendored
@@ -7,6 +7,11 @@ on:
|
||||
types: [labeled]
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
prerelease:
|
||||
if: |
|
||||
@@ -18,7 +23,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout Repo
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -27,23 +32,22 @@ jobs:
|
||||
with:
|
||||
version: 9.0.6
|
||||
|
||||
- name: Use Node.js 18
|
||||
uses: actions/setup-node@v3
|
||||
- name: Use Node.js 20
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 20
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Update npm for OIDC support
|
||||
run: npm install -g npm@latest
|
||||
|
||||
- name: Install NPM Dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Modify package.json version
|
||||
run: node .github/version-script-beta.js
|
||||
|
||||
- name: Authenticate to NPM
|
||||
run: echo "//registry.npmjs.org/:_authToken=$NPM_ACCESS_TOKEN" >> packages/shadcn/.npmrc
|
||||
env:
|
||||
NPM_ACCESS_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }}
|
||||
|
||||
- name: Publish Beta to NPM
|
||||
run: pnpm pub:beta
|
||||
|
||||
|
||||
19
.github/workflows/release.yml
vendored
19
.github/workflows/release.yml
vendored
@@ -7,6 +7,11 @@ on:
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
release:
|
||||
if: ${{ github.repository_owner == 'shadcn-ui' }}
|
||||
@@ -23,13 +28,16 @@ jobs:
|
||||
with:
|
||||
version: 9.0.6
|
||||
|
||||
- name: Use Node.js 18
|
||||
uses: actions/setup-node@v3
|
||||
- name: Use Node.js 20
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
version: 9.0.6
|
||||
node-version: 18
|
||||
node-version: 20
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Update npm for OIDC support
|
||||
run: npm install -g npm@latest
|
||||
|
||||
- name: Install NPM Dependencies
|
||||
run: pnpm install
|
||||
|
||||
@@ -41,7 +49,7 @@ jobs:
|
||||
|
||||
- name: Create Version PR or Publish to NPM
|
||||
id: changesets
|
||||
uses: changesets/action@v1.4.1
|
||||
uses: changesets/action@v1
|
||||
with:
|
||||
commit: "chore(release): version packages"
|
||||
title: "chore(release): version packages"
|
||||
@@ -49,5 +57,4 @@ jobs:
|
||||
publish: npx changeset publish
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
NPM_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }}
|
||||
NODE_ENV: "production"
|
||||
|
||||
5
.github/workflows/test.yml
vendored
5
.github/workflows/test.yml
vendored
@@ -8,6 +8,9 @@ jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
name: pnpm test
|
||||
env:
|
||||
NEXT_PUBLIC_APP_URL: http://localhost:4000
|
||||
NEXT_PUBLIC_V0_URL: https://v0.dev
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
@@ -16,7 +19,7 @@ jobs:
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 22
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
|
||||
129
.github/workflows/validate-registries.yml
vendored
Normal file
129
.github/workflows/validate-registries.yml
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
name: Validate Registries
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "apps/v4/public/r/registries.json"
|
||||
- "apps/v4/registry/directory.json"
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "apps/v4/public/r/registries.json"
|
||||
- "apps/v4/registry/directory.json"
|
||||
|
||||
jobs:
|
||||
check-registry-sync:
|
||||
if: github.event_name == 'pull_request'
|
||||
runs-on: ubuntu-latest
|
||||
name: check-registry-sync
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Check changed files
|
||||
id: changed
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
CHANGED_FILES=$(gh pr diff ${{ github.event.pull_request.number }} --repo ${{ github.repository }} --name-only)
|
||||
|
||||
DIRECTORY_CHANGED=false
|
||||
REGISTRIES_CHANGED=false
|
||||
|
||||
if echo "$CHANGED_FILES" | grep -q "^apps/v4/registry/directory.json$"; then
|
||||
DIRECTORY_CHANGED=true
|
||||
fi
|
||||
|
||||
if echo "$CHANGED_FILES" | grep -q "^apps/v4/public/r/registries.json$"; then
|
||||
REGISTRIES_CHANGED=true
|
||||
fi
|
||||
|
||||
echo "directory_changed=$DIRECTORY_CHANGED" >> $GITHUB_OUTPUT
|
||||
echo "registries_changed=$REGISTRIES_CHANGED" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Flag missing registries.json update
|
||||
if: steps.changed.outputs.directory_changed == 'true' && steps.changed.outputs.registries_changed == 'false'
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
gh pr edit ${{ github.event.pull_request.number }} --repo ${{ github.repository }} --add-label "registries: invalid"
|
||||
gh pr comment ${{ github.event.pull_request.number }} --repo ${{ github.repository }} --body "can you run \`pnpm registry:build\` and commit the json files please?"
|
||||
exit 1
|
||||
|
||||
validate:
|
||||
runs-on: ubuntu-latest
|
||||
name: pnpm validate:registries
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Block reserved registry namespaces
|
||||
env:
|
||||
RESERVED_NAMESPACES: "@shadcn,@ui,@blocks,@components,@block,@component,@util,@utils,@registry,@lib,@hook,@hooks,@theme,@themes,@chart,@charts"
|
||||
run: |
|
||||
node <<'EOF'
|
||||
const fs = require("node:fs")
|
||||
|
||||
const files = [
|
||||
"apps/v4/public/r/registries.json",
|
||||
"apps/v4/registry/directory.json",
|
||||
]
|
||||
const reservedNamespaces = new Set(
|
||||
process.env.RESERVED_NAMESPACES.split(",").filter(Boolean)
|
||||
)
|
||||
|
||||
function readNames(filePath) {
|
||||
return JSON.parse(fs.readFileSync(filePath, "utf8")).map(
|
||||
(entry) => entry.name
|
||||
)
|
||||
}
|
||||
|
||||
const violations = files.flatMap((filePath) => {
|
||||
return readNames(filePath)
|
||||
.filter((name) => reservedNamespaces.has(name))
|
||||
.map((name) => `${filePath}: ${name}`)
|
||||
})
|
||||
|
||||
if (violations.length > 0) {
|
||||
console.error("Reserved registry namespaces are not allowed:")
|
||||
|
||||
for (const violation of violations) {
|
||||
console.error(`- ${violation}`)
|
||||
}
|
||||
|
||||
process.exit(1)
|
||||
}
|
||||
EOF
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
id: pnpm-install
|
||||
with:
|
||||
version: 9.0.6
|
||||
run_install: false
|
||||
|
||||
- name: Get pnpm store directory
|
||||
id: pnpm-cache
|
||||
run: |
|
||||
echo "pnpm_cache_dir=$(pnpm store path)" >> $GITHUB_OUTPUT
|
||||
- uses: actions/cache@v3
|
||||
name: Setup pnpm cache
|
||||
with:
|
||||
path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Validate registries
|
||||
run: pnpm --filter=v4 validate:registries
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -39,3 +39,7 @@ tsconfig.tsbuildinfo
|
||||
.idea
|
||||
.fleet
|
||||
.vscode
|
||||
|
||||
.notes
|
||||
.playwright-mcp
|
||||
shadcn-workspace
|
||||
|
||||
@@ -3,5 +3,6 @@ node_modules
|
||||
.next
|
||||
build
|
||||
.contentlayer
|
||||
apps/www/pages/api/registry.json
|
||||
**/fixtures
|
||||
deprecated
|
||||
apps/v4/registry/styles/**/*.css
|
||||
|
||||
17
.vscode/settings.json
vendored
17
.vscode/settings.json
vendored
@@ -3,15 +3,18 @@
|
||||
{ "pattern": "apps/*/" },
|
||||
{ "pattern": "packages/*/" }
|
||||
],
|
||||
"tailwindCSS.experimental.classRegex": [
|
||||
["cva\\(((?:[^()]|\\([^()]*\\))*)\\)", "[\"'`]?([^\"'`]+)[\"'`]?"],
|
||||
["cn\\(((?:[^()]|\\([^()]*\\))*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"]
|
||||
// "cva\\(([^)]*)\\)",
|
||||
// "[\"'`]([^\"'`]*).*?[\"'`]"
|
||||
],
|
||||
"tailwindCSS.classFunctions": ["cva", "cn"],
|
||||
"vitest.debugExclude": [
|
||||
"<node_internals>/**",
|
||||
"**/node_modules/**",
|
||||
"**/fixtures/**"
|
||||
]
|
||||
],
|
||||
"files.exclude": {
|
||||
"deprecated": true
|
||||
},
|
||||
"search.exclude": {
|
||||
"apps/v4/registry/radix-*": true,
|
||||
"apps/v4/public/r/*": true,
|
||||
"packages/shadcn/test/fixtures/*": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,28 +20,25 @@ This repository is structured as follows:
|
||||
|
||||
```
|
||||
apps
|
||||
└── www
|
||||
└── v4
|
||||
├── app
|
||||
├── components
|
||||
├── content
|
||||
└── registry
|
||||
├── default
|
||||
│ ├── example
|
||||
│ └── ui
|
||||
└── new-york
|
||||
└── new-york-v4
|
||||
├── example
|
||||
└── ui
|
||||
packages
|
||||
└── cli
|
||||
└── shadcn
|
||||
```
|
||||
|
||||
| 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. |
|
||||
| Path | Description |
|
||||
| -------------------- | ---------------------------------------- |
|
||||
| `apps/v4/app` | The Next.js application for the website. |
|
||||
| `apps/v4/components` | The React components for the website. |
|
||||
| `apps/v4/content` | The content for the website. |
|
||||
| `apps/v4/registry` | The registry for the components. |
|
||||
| `packages/shadcn` | The `shadcn` package. |
|
||||
|
||||
## Development
|
||||
|
||||
@@ -82,32 +79,26 @@ You can use the `pnpm --filter=[WORKSPACE]` command to start the development pro
|
||||
1. To run the `ui.shadcn.com` website:
|
||||
|
||||
```bash
|
||||
pnpm --filter=www dev
|
||||
pnpm --filter=v4 dev
|
||||
```
|
||||
|
||||
2. To run the `shadcn-ui` package:
|
||||
2. To run the `shadcn` package:
|
||||
|
||||
```bash
|
||||
pnpm --filter=shadcn-ui dev
|
||||
pnpm --filter=shadcn 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:
|
||||
1. Start by running the dev server:
|
||||
|
||||
```bash
|
||||
pnpm www:dev
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
2. Run the development script for the CLI:
|
||||
|
||||
```bash
|
||||
pnpm shadcn:dev
|
||||
```
|
||||
|
||||
3. In another terminal tab, test the CLI by running:
|
||||
2. In another terminal tab, test the CLI by running:
|
||||
|
||||
```bash
|
||||
pnpm shadcn
|
||||
@@ -119,36 +110,27 @@ To run the CLI locally, you can follow the workflow:
|
||||
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:
|
||||
The documentation for this project is located in the `v4` workspace. You can run the documentation locally by running the following command:
|
||||
|
||||
```bash
|
||||
pnpm --filter=www dev
|
||||
pnpm --filter=v4 dev
|
||||
```
|
||||
|
||||
Documentation is written using [MDX](https://mdxjs.com). You can find the documentation files in the `apps/www/content/docs` directory.
|
||||
Documentation is written using [MDX](https://mdxjs.com). You can find the documentation files in the `apps/v4/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.
|
||||
We use a registry system for developing components. You can find the source code for the components under `apps/v4/registry`. The components are organized by styles.
|
||||
|
||||
```bash
|
||||
apps
|
||||
└── www
|
||||
└── v4
|
||||
└── registry
|
||||
├── default
|
||||
│ ├── example
|
||||
│ └── ui
|
||||
└── new-york
|
||||
└── new-york-v4
|
||||
├── example
|
||||
└── ui
|
||||
```
|
||||
@@ -157,7 +139,7 @@ When adding or modifying components, please ensure that:
|
||||
|
||||
1. You make the changes for every style.
|
||||
2. You update the documentation.
|
||||
3. You run `pnpm build:registry` to update the registry.
|
||||
3. You run `pnpm registry:build` to update the registry.
|
||||
|
||||
## Commit Convention
|
||||
|
||||
@@ -196,9 +178,9 @@ If you have a request for a new component, please open a discussion on GitHub. W
|
||||
|
||||
## 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).
|
||||
The `shadcn` package is a CLI for adding components to your project. You can find the documentation for the CLI [here](https://ui.shadcn.com/docs/cli).
|
||||
|
||||
Any changes to the CLI should be made in the `packages/cli` directory. If you can, it would be great if you could add tests for your changes.
|
||||
Any changes to the CLI should be made in the `packages/shadcn` directory. If you can, it would be great if you could add tests for your changes.
|
||||
|
||||
## Testing
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# shadcn/ui
|
||||
|
||||
Accessible and customizable components that you can copy and paste into your apps. Free. Open Source. **Use this to build your own component library**.
|
||||
A set of beautifully designed components that you can customize, extend, and build on. Start here then make it your own. Open Source. Open Code. **Use this to build your own component library**.
|
||||
|
||||

|
||||

|
||||
|
||||
## Documentation
|
||||
|
||||
Visit http://ui.shadcn.com/docs to view the documentation.
|
||||
Visit https://ui.shadcn.com/docs to view the documentation.
|
||||
|
||||
## Contributing
|
||||
|
||||
@@ -14,4 +14,4 @@ Please read the [contributing guide](/CONTRIBUTING.md).
|
||||
|
||||
## License
|
||||
|
||||
Licensed under the [MIT license](https://github.com/shadcn/ui/blob/main/LICENSE.md).
|
||||
Licensed under the [MIT license](./LICENSE.md).
|
||||
|
||||
@@ -6,4 +6,4 @@ We will investigate all legitimate reports and do our best to quickly fix the pr
|
||||
|
||||
Our preference is that you make use of GitHub's private vulnerability reporting feature to disclose potential security vulnerabilities in our Open Source Software.
|
||||
|
||||
To do this, please visit the security tab of the repository and click the "Report a vulnerability" button.
|
||||
To do this, please visit the security tab of the repository and click the [Report a vulnerability](https://github.com/shadcn-ui/ui/security/advisories/new) button.
|
||||
|
||||
2
apps/v4/.env.example
Normal file
2
apps/v4/.env.example
Normal file
@@ -0,0 +1,2 @@
|
||||
NEXT_PUBLIC_V0_URL=https://v0.dev
|
||||
NEXT_PUBLIC_APP_URL=http://localhost:4000
|
||||
1
apps/v4/.gitignore
vendored
1
apps/v4/.gitignore
vendored
@@ -32,6 +32,7 @@ yarn-error.log*
|
||||
|
||||
# env files (can opt-in for committing if needed)
|
||||
.env*
|
||||
!.env.example
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
@@ -5,3 +5,4 @@ build
|
||||
.contentlayer
|
||||
registry/__index__.tsx
|
||||
content/docs/components/calendar.mdx
|
||||
registry/styles/**/*.css
|
||||
|
||||
134
apps/v4/app/(app)/(root)/components/appearance-settings.tsx
Normal file
134
apps/v4/app/(app)/(root)/components/appearance-settings.tsx
Normal file
@@ -0,0 +1,134 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { Button } from "@/examples/radix/ui/button"
|
||||
import { ButtonGroup } from "@/examples/radix/ui/button-group"
|
||||
import {
|
||||
Field,
|
||||
FieldContent,
|
||||
FieldDescription,
|
||||
FieldGroup,
|
||||
FieldLabel,
|
||||
FieldLegend,
|
||||
FieldSeparator,
|
||||
FieldSet,
|
||||
FieldTitle,
|
||||
} from "@/examples/radix/ui/field"
|
||||
import { Input } from "@/examples/radix/ui/input"
|
||||
import { RadioGroup, RadioGroupItem } from "@/examples/radix/ui/radio-group"
|
||||
import { Switch } from "@/examples/radix/ui/switch"
|
||||
import { IconMinus, IconPlus } from "@tabler/icons-react"
|
||||
|
||||
export function AppearanceSettings() {
|
||||
const [gpuCount, setGpuCount] = React.useState(8)
|
||||
|
||||
const handleGpuAdjustment = React.useCallback((adjustment: number) => {
|
||||
setGpuCount((prevCount) =>
|
||||
Math.max(1, Math.min(99, prevCount + adjustment))
|
||||
)
|
||||
}, [])
|
||||
|
||||
const handleGpuInputChange = React.useCallback(
|
||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = parseInt(e.target.value, 10)
|
||||
if (!isNaN(value) && value >= 1 && value <= 99) {
|
||||
setGpuCount(value)
|
||||
}
|
||||
},
|
||||
[]
|
||||
)
|
||||
|
||||
return (
|
||||
<FieldSet>
|
||||
<FieldGroup>
|
||||
<FieldSet>
|
||||
<FieldLegend>Compute Environment</FieldLegend>
|
||||
<FieldDescription>
|
||||
Select the compute environment for your cluster.
|
||||
</FieldDescription>
|
||||
<RadioGroup defaultValue="kubernetes">
|
||||
<FieldLabel htmlFor="kubernetes-r2h">
|
||||
<Field orientation="horizontal">
|
||||
<FieldContent>
|
||||
<FieldTitle>Kubernetes</FieldTitle>
|
||||
<FieldDescription>
|
||||
Run GPU workloads on a K8s configured cluster. This is the
|
||||
default.
|
||||
</FieldDescription>
|
||||
</FieldContent>
|
||||
<RadioGroupItem
|
||||
value="kubernetes"
|
||||
id="kubernetes-r2h"
|
||||
aria-label="Kubernetes"
|
||||
/>
|
||||
</Field>
|
||||
</FieldLabel>
|
||||
<FieldLabel htmlFor="vm-z4k">
|
||||
<Field orientation="horizontal">
|
||||
<FieldContent>
|
||||
<FieldTitle>Virtual Machine</FieldTitle>
|
||||
<FieldDescription>
|
||||
Access a VM configured cluster to run workloads. (Coming
|
||||
soon)
|
||||
</FieldDescription>
|
||||
</FieldContent>
|
||||
<RadioGroupItem
|
||||
value="vm"
|
||||
id="vm-z4k"
|
||||
aria-label="Virtual Machine"
|
||||
/>
|
||||
</Field>
|
||||
</FieldLabel>
|
||||
</RadioGroup>
|
||||
</FieldSet>
|
||||
<FieldSeparator />
|
||||
<Field orientation="horizontal">
|
||||
<FieldContent>
|
||||
<FieldLabel htmlFor="number-of-gpus-f6l">Number of GPUs</FieldLabel>
|
||||
<FieldDescription>You can add more later.</FieldDescription>
|
||||
</FieldContent>
|
||||
<ButtonGroup>
|
||||
<Input
|
||||
id="number-of-gpus-f6l"
|
||||
value={gpuCount}
|
||||
onChange={handleGpuInputChange}
|
||||
size={3}
|
||||
className="h-7 w-14! font-mono"
|
||||
maxLength={3}
|
||||
/>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon-sm"
|
||||
type="button"
|
||||
aria-label="Decrement"
|
||||
onClick={() => handleGpuAdjustment(-1)}
|
||||
disabled={gpuCount <= 1}
|
||||
>
|
||||
<IconMinus />
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon-sm"
|
||||
type="button"
|
||||
aria-label="Increment"
|
||||
onClick={() => handleGpuAdjustment(1)}
|
||||
disabled={gpuCount >= 99}
|
||||
>
|
||||
<IconPlus />
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</Field>
|
||||
<FieldSeparator />
|
||||
<Field orientation="horizontal">
|
||||
<FieldContent>
|
||||
<FieldLabel htmlFor="tinting">Wallpaper Tinting</FieldLabel>
|
||||
<FieldDescription>
|
||||
Allow the wallpaper to be tinted.
|
||||
</FieldDescription>
|
||||
</FieldContent>
|
||||
<Switch id="tinting" defaultChecked />
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</FieldSet>
|
||||
)
|
||||
}
|
||||
119
apps/v4/app/(app)/(root)/components/button-group-demo.tsx
Normal file
119
apps/v4/app/(app)/(root)/components/button-group-demo.tsx
Normal file
@@ -0,0 +1,119 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { Button } from "@/examples/radix/ui/button"
|
||||
import { ButtonGroup } from "@/examples/radix/ui/button-group"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuSub,
|
||||
DropdownMenuSubContent,
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/examples/radix/ui/dropdown-menu"
|
||||
import {
|
||||
ArchiveIcon,
|
||||
ArrowLeftIcon,
|
||||
CalendarPlusIcon,
|
||||
ClockIcon,
|
||||
ListFilterIcon,
|
||||
MailCheckIcon,
|
||||
MoreHorizontalIcon,
|
||||
TagIcon,
|
||||
Trash2Icon,
|
||||
} from "lucide-react"
|
||||
|
||||
export function ButtonGroupDemo() {
|
||||
const [label, setLabel] = React.useState("personal")
|
||||
|
||||
return (
|
||||
<ButtonGroup>
|
||||
<ButtonGroup className="hidden sm:flex">
|
||||
<Button variant="outline" size="icon-sm" aria-label="Go Back">
|
||||
<ArrowLeftIcon />
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup>
|
||||
<Button variant="outline" size="sm">
|
||||
Archive
|
||||
</Button>
|
||||
<Button variant="outline" size="sm">
|
||||
Report
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup>
|
||||
<Button variant="outline" size="sm">
|
||||
Snooze
|
||||
</Button>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" size="icon-sm" aria-label="More Options">
|
||||
<MoreHorizontalIcon />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-48">
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem>
|
||||
<MailCheckIcon />
|
||||
Mark as Read
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<ArchiveIcon />
|
||||
Archive
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem>
|
||||
<ClockIcon />
|
||||
Snooze
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<CalendarPlusIcon />
|
||||
Add to Calendar
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<ListFilterIcon />
|
||||
Add to List
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger>
|
||||
<TagIcon />
|
||||
Label As...
|
||||
</DropdownMenuSubTrigger>
|
||||
<DropdownMenuSubContent>
|
||||
<DropdownMenuRadioGroup
|
||||
value={label}
|
||||
onValueChange={setLabel}
|
||||
>
|
||||
<DropdownMenuRadioItem value="personal">
|
||||
Personal
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="work">
|
||||
Work
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="other">
|
||||
Other
|
||||
</DropdownMenuRadioItem>
|
||||
</DropdownMenuRadioGroup>
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuSub>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem variant="destructive">
|
||||
<Trash2Icon />
|
||||
Trash
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</ButtonGroup>
|
||||
</ButtonGroup>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { Button } from "@/examples/radix/ui/button"
|
||||
import { ButtonGroup } from "@/examples/radix/ui/button-group"
|
||||
import {
|
||||
InputGroup,
|
||||
InputGroupAddon,
|
||||
InputGroupButton,
|
||||
InputGroupInput,
|
||||
} from "@/examples/radix/ui/input-group"
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/examples/radix/ui/tooltip"
|
||||
import { AudioLinesIcon, PlusIcon } from "lucide-react"
|
||||
|
||||
export function ButtonGroupInputGroup() {
|
||||
const [voiceEnabled, setVoiceEnabled] = React.useState(false)
|
||||
return (
|
||||
<ButtonGroup className="[--radius:9999rem]">
|
||||
<ButtonGroup>
|
||||
<Button variant="outline" size="icon" aria-label="Add">
|
||||
<PlusIcon />
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup className="flex-1">
|
||||
<InputGroup>
|
||||
<InputGroupInput
|
||||
placeholder={
|
||||
voiceEnabled ? "Record and send audio..." : "Send a message..."
|
||||
}
|
||||
disabled={voiceEnabled}
|
||||
/>
|
||||
<InputGroupAddon align="inline-end">
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<InputGroupButton
|
||||
onClick={() => setVoiceEnabled(!voiceEnabled)}
|
||||
data-active={voiceEnabled}
|
||||
className="data-[active=true]:bg-primary data-[active=true]:text-primary-foreground"
|
||||
aria-pressed={voiceEnabled}
|
||||
size="icon-xs"
|
||||
aria-label="Voice Mode"
|
||||
>
|
||||
<AudioLinesIcon />
|
||||
</InputGroupButton>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Voice Mode</TooltipContent>
|
||||
</Tooltip>
|
||||
</InputGroupAddon>
|
||||
</InputGroup>
|
||||
</ButtonGroup>
|
||||
</ButtonGroup>
|
||||
)
|
||||
}
|
||||
31
apps/v4/app/(app)/(root)/components/button-group-nested.tsx
Normal file
31
apps/v4/app/(app)/(root)/components/button-group-nested.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
"use client"
|
||||
|
||||
import { Button } from "@/examples/radix/ui/button"
|
||||
import { ButtonGroup } from "@/examples/radix/ui/button-group"
|
||||
import { ArrowLeftIcon, ArrowRightIcon } from "lucide-react"
|
||||
|
||||
export function ButtonGroupNested() {
|
||||
return (
|
||||
<ButtonGroup>
|
||||
<ButtonGroup>
|
||||
<Button variant="outline" size="sm">
|
||||
1
|
||||
</Button>
|
||||
<Button variant="outline" size="sm">
|
||||
2
|
||||
</Button>
|
||||
<Button variant="outline" size="sm">
|
||||
3
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup>
|
||||
<Button variant="outline" size="icon-sm" aria-label="Previous">
|
||||
<ArrowLeftIcon />
|
||||
</Button>
|
||||
<Button variant="outline" size="icon-sm" aria-label="Next">
|
||||
<ArrowRightIcon />
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</ButtonGroup>
|
||||
)
|
||||
}
|
||||
44
apps/v4/app/(app)/(root)/components/button-group-popover.tsx
Normal file
44
apps/v4/app/(app)/(root)/components/button-group-popover.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import { Button } from "@/examples/radix/ui/button"
|
||||
import { ButtonGroup } from "@/examples/radix/ui/button-group"
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/examples/radix/ui/popover"
|
||||
import { Separator } from "@/examples/radix/ui/separator"
|
||||
import { Textarea } from "@/examples/radix/ui/textarea"
|
||||
import { BotIcon, ChevronDownIcon } from "lucide-react"
|
||||
|
||||
export function ButtonGroupPopover() {
|
||||
return (
|
||||
<ButtonGroup>
|
||||
<Button variant="outline" size="sm">
|
||||
<BotIcon /> Copilot
|
||||
</Button>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant="outline" size="icon-sm" aria-label="Open Popover">
|
||||
<ChevronDownIcon />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent align="end" className="gap-0 rounded-xl p-0 text-sm">
|
||||
<div className="px-4 py-3">
|
||||
<div className="text-sm font-medium">Agent Tasks</div>
|
||||
</div>
|
||||
<Separator />
|
||||
<div className="p-4 text-sm *:[p:not(:last-child)]:mb-2">
|
||||
<Textarea
|
||||
placeholder="Describe your task in natural language."
|
||||
className="mb-4 resize-none"
|
||||
/>
|
||||
<p className="font-medium">Start a new task with Copilot</p>
|
||||
<p className="text-muted-foreground">
|
||||
Describe your task in natural language. Copilot will work in the
|
||||
background and open a pull request for your review.
|
||||
</p>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</ButtonGroup>
|
||||
)
|
||||
}
|
||||
57
apps/v4/app/(app)/(root)/components/empty-avatar-group.tsx
Normal file
57
apps/v4/app/(app)/(root)/components/empty-avatar-group.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
import {
|
||||
Avatar,
|
||||
AvatarFallback,
|
||||
AvatarGroup,
|
||||
AvatarImage,
|
||||
} from "@/examples/radix/ui/avatar"
|
||||
import { Button } from "@/examples/radix/ui/button"
|
||||
import {
|
||||
Empty,
|
||||
EmptyContent,
|
||||
EmptyDescription,
|
||||
EmptyHeader,
|
||||
EmptyMedia,
|
||||
EmptyTitle,
|
||||
} from "@/examples/radix/ui/empty"
|
||||
import { PlusIcon } from "lucide-react"
|
||||
|
||||
export function EmptyAvatarGroup() {
|
||||
return (
|
||||
<Empty className="flex-none border py-10">
|
||||
<EmptyHeader>
|
||||
<EmptyMedia>
|
||||
<AvatarGroup className="grayscale">
|
||||
<Avatar>
|
||||
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||
<AvatarFallback>CN</AvatarFallback>
|
||||
</Avatar>
|
||||
<Avatar>
|
||||
<AvatarImage
|
||||
src="https://github.com/maxleiter.png"
|
||||
alt="@maxleiter"
|
||||
/>
|
||||
<AvatarFallback>LR</AvatarFallback>
|
||||
</Avatar>
|
||||
<Avatar>
|
||||
<AvatarImage
|
||||
src="https://github.com/evilrabbit.png"
|
||||
alt="@evilrabbit"
|
||||
/>
|
||||
<AvatarFallback>ER</AvatarFallback>
|
||||
</Avatar>
|
||||
</AvatarGroup>
|
||||
</EmptyMedia>
|
||||
<EmptyTitle>No Team Members</EmptyTitle>
|
||||
<EmptyDescription>
|
||||
Invite your team to collaborate on this project.
|
||||
</EmptyDescription>
|
||||
</EmptyHeader>
|
||||
<EmptyContent>
|
||||
<Button size="sm">
|
||||
<PlusIcon />
|
||||
Invite Members
|
||||
</Button>
|
||||
</EmptyContent>
|
||||
</Empty>
|
||||
)
|
||||
}
|
||||
43
apps/v4/app/(app)/(root)/components/empty-input-group.tsx
Normal file
43
apps/v4/app/(app)/(root)/components/empty-input-group.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import { SearchIcon } from "lucide-react"
|
||||
|
||||
import {
|
||||
Empty,
|
||||
EmptyContent,
|
||||
EmptyDescription,
|
||||
EmptyHeader,
|
||||
EmptyTitle,
|
||||
} from "@/registry/new-york-v4/ui/empty"
|
||||
import {
|
||||
InputGroup,
|
||||
InputGroupAddon,
|
||||
InputGroupInput,
|
||||
} from "@/registry/new-york-v4/ui/input-group"
|
||||
import { Kbd } from "@/registry/new-york-v4/ui/kbd"
|
||||
|
||||
export function EmptyInputGroup() {
|
||||
return (
|
||||
<Empty>
|
||||
<EmptyHeader>
|
||||
<EmptyTitle>404 - Not Found</EmptyTitle>
|
||||
<EmptyDescription>
|
||||
The page you're looking for doesn't exist. Try searching for
|
||||
what you need below.
|
||||
</EmptyDescription>
|
||||
</EmptyHeader>
|
||||
<EmptyContent>
|
||||
<InputGroup className="w-3/4">
|
||||
<InputGroupInput placeholder="Try searching for pages..." />
|
||||
<InputGroupAddon>
|
||||
<SearchIcon />
|
||||
</InputGroupAddon>
|
||||
<InputGroupAddon align="inline-end">
|
||||
<Kbd>/</Kbd>
|
||||
</InputGroupAddon>
|
||||
</InputGroup>
|
||||
<EmptyDescription>
|
||||
Need help? <a href="#">Contact support</a>
|
||||
</EmptyDescription>
|
||||
</EmptyContent>
|
||||
</Empty>
|
||||
)
|
||||
}
|
||||
15
apps/v4/app/(app)/(root)/components/field-checkbox.tsx
Normal file
15
apps/v4/app/(app)/(root)/components/field-checkbox.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Checkbox } from "@/examples/radix/ui/checkbox"
|
||||
import { Field, FieldLabel } from "@/examples/radix/ui/field"
|
||||
|
||||
export function FieldCheckbox() {
|
||||
return (
|
||||
<FieldLabel htmlFor="checkbox-demo">
|
||||
<Field orientation="horizontal">
|
||||
<Checkbox id="checkbox-demo" defaultChecked />
|
||||
<FieldLabel htmlFor="checkbox-demo" className="line-clamp-1">
|
||||
I agree to the terms and conditions
|
||||
</FieldLabel>
|
||||
</Field>
|
||||
</FieldLabel>
|
||||
)
|
||||
}
|
||||
62
apps/v4/app/(app)/(root)/components/field-choice-card.tsx
Normal file
62
apps/v4/app/(app)/(root)/components/field-choice-card.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
import {
|
||||
Field,
|
||||
FieldContent,
|
||||
FieldDescription,
|
||||
FieldGroup,
|
||||
FieldLabel,
|
||||
FieldSet,
|
||||
FieldTitle,
|
||||
} from "@/registry/new-york-v4/ui/field"
|
||||
import {
|
||||
RadioGroup,
|
||||
RadioGroupItem,
|
||||
} from "@/registry/new-york-v4/ui/radio-group"
|
||||
|
||||
export function FieldChoiceCard() {
|
||||
return (
|
||||
<div className="w-full max-w-md">
|
||||
<FieldGroup>
|
||||
<FieldSet>
|
||||
<FieldLabel htmlFor="compute-environment-p8w">
|
||||
Compute Environment
|
||||
</FieldLabel>
|
||||
<FieldDescription>
|
||||
Select the compute environment for your cluster.
|
||||
</FieldDescription>
|
||||
<RadioGroup defaultValue="kubernetes">
|
||||
<FieldLabel htmlFor="kubernetes-r2h">
|
||||
<Field orientation="horizontal">
|
||||
<RadioGroupItem
|
||||
value="kubernetes"
|
||||
id="kubernetes-r2h"
|
||||
aria-label="Kubernetes"
|
||||
/>
|
||||
<FieldContent>
|
||||
<FieldTitle>Kubernetes</FieldTitle>
|
||||
<FieldDescription>
|
||||
Run GPU workloads on a K8s configured cluster.
|
||||
</FieldDescription>
|
||||
</FieldContent>
|
||||
</Field>
|
||||
</FieldLabel>
|
||||
<FieldLabel htmlFor="vm-z4k">
|
||||
<Field orientation="horizontal">
|
||||
<RadioGroupItem
|
||||
value="vm"
|
||||
id="vm-z4k"
|
||||
aria-label="Virtual Machine"
|
||||
/>
|
||||
<FieldContent>
|
||||
<FieldTitle>Virtual Machine</FieldTitle>
|
||||
<FieldDescription>
|
||||
Access a VM configured cluster to run workloads.
|
||||
</FieldDescription>
|
||||
</FieldContent>
|
||||
</Field>
|
||||
</FieldLabel>
|
||||
</RadioGroup>
|
||||
</FieldSet>
|
||||
</FieldGroup>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
158
apps/v4/app/(app)/(root)/components/field-demo.tsx
Normal file
158
apps/v4/app/(app)/(root)/components/field-demo.tsx
Normal file
@@ -0,0 +1,158 @@
|
||||
import { Button } from "@/examples/radix/ui/button"
|
||||
import { Checkbox } from "@/examples/radix/ui/checkbox"
|
||||
import {
|
||||
Field,
|
||||
FieldDescription,
|
||||
FieldGroup,
|
||||
FieldLabel,
|
||||
FieldLegend,
|
||||
FieldSeparator,
|
||||
FieldSet,
|
||||
} from "@/examples/radix/ui/field"
|
||||
import { Input } from "@/examples/radix/ui/input"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/examples/radix/ui/select"
|
||||
import { Textarea } from "@/examples/radix/ui/textarea"
|
||||
|
||||
export function FieldDemo() {
|
||||
return (
|
||||
<div className="w-full max-w-md rounded-xl border p-6">
|
||||
<form>
|
||||
<FieldGroup>
|
||||
<FieldSet>
|
||||
<FieldLegend>Payment Method</FieldLegend>
|
||||
<FieldDescription>
|
||||
All transactions are secure and encrypted
|
||||
</FieldDescription>
|
||||
<FieldGroup>
|
||||
<Field>
|
||||
<FieldLabel htmlFor="checkout-7j9-card-name-43j">
|
||||
Name on Card
|
||||
</FieldLabel>
|
||||
<Input
|
||||
id="checkout-7j9-card-name-43j"
|
||||
placeholder="John Doe"
|
||||
required
|
||||
/>
|
||||
</Field>
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<Field className="col-span-2">
|
||||
<FieldLabel htmlFor="checkout-7j9-card-number-uw1">
|
||||
Card Number
|
||||
</FieldLabel>
|
||||
<Input
|
||||
id="checkout-7j9-card-number-uw1"
|
||||
placeholder="1234 5678 9012 3456"
|
||||
required
|
||||
/>
|
||||
<FieldDescription>
|
||||
Enter your 16-digit number.
|
||||
</FieldDescription>
|
||||
</Field>
|
||||
<Field className="col-span-1">
|
||||
<FieldLabel htmlFor="checkout-7j9-cvv">CVV</FieldLabel>
|
||||
<Input id="checkout-7j9-cvv" placeholder="123" required />
|
||||
</Field>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<Field>
|
||||
<FieldLabel htmlFor="checkout-7j9-exp-month-ts6">
|
||||
Month
|
||||
</FieldLabel>
|
||||
<Select defaultValue="">
|
||||
<SelectTrigger id="checkout-7j9-exp-month-ts6">
|
||||
<SelectValue placeholder="MM" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectItem value="01">01</SelectItem>
|
||||
<SelectItem value="02">02</SelectItem>
|
||||
<SelectItem value="03">03</SelectItem>
|
||||
<SelectItem value="04">04</SelectItem>
|
||||
<SelectItem value="05">05</SelectItem>
|
||||
<SelectItem value="06">06</SelectItem>
|
||||
<SelectItem value="07">07</SelectItem>
|
||||
<SelectItem value="08">08</SelectItem>
|
||||
<SelectItem value="09">09</SelectItem>
|
||||
<SelectItem value="10">10</SelectItem>
|
||||
<SelectItem value="11">11</SelectItem>
|
||||
<SelectItem value="12">12</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldLabel htmlFor="checkout-7j9-exp-year-f59">
|
||||
Year
|
||||
</FieldLabel>
|
||||
<Select defaultValue="">
|
||||
<SelectTrigger id="checkout-7j9-exp-year-f59">
|
||||
<SelectValue placeholder="YYYY" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectItem value="2024">2024</SelectItem>
|
||||
<SelectItem value="2025">2025</SelectItem>
|
||||
<SelectItem value="2026">2026</SelectItem>
|
||||
<SelectItem value="2027">2027</SelectItem>
|
||||
<SelectItem value="2028">2028</SelectItem>
|
||||
<SelectItem value="2029">2029</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</Field>
|
||||
</div>
|
||||
</FieldGroup>
|
||||
</FieldSet>
|
||||
<FieldSeparator />
|
||||
<FieldSet>
|
||||
<FieldLegend>Billing Address</FieldLegend>
|
||||
<FieldDescription>
|
||||
The billing address associated with your payment method
|
||||
</FieldDescription>
|
||||
<FieldGroup>
|
||||
<Field orientation="horizontal">
|
||||
<Checkbox
|
||||
id="checkout-7j9-same-as-shipping-wgm"
|
||||
defaultChecked
|
||||
/>
|
||||
<FieldLabel
|
||||
htmlFor="checkout-7j9-same-as-shipping-wgm"
|
||||
className="font-normal"
|
||||
>
|
||||
Same as shipping address
|
||||
</FieldLabel>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</FieldSet>
|
||||
<FieldSeparator />
|
||||
<FieldSet>
|
||||
<FieldGroup>
|
||||
<Field>
|
||||
<FieldLabel htmlFor="checkout-7j9-optional-comments">
|
||||
Comments
|
||||
</FieldLabel>
|
||||
<Textarea
|
||||
id="checkout-7j9-optional-comments"
|
||||
placeholder="Add any additional comments"
|
||||
/>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</FieldSet>
|
||||
<Field orientation="horizontal">
|
||||
<Button type="submit">Submit</Button>
|
||||
<Button variant="outline" type="button">
|
||||
Cancel
|
||||
</Button>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
72
apps/v4/app/(app)/(root)/components/field-hear.tsx
Normal file
72
apps/v4/app/(app)/(root)/components/field-hear.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
import { Card, CardContent } from "@/examples/radix/ui/card"
|
||||
import { Checkbox } from "@/examples/radix/ui/checkbox"
|
||||
import {
|
||||
Field,
|
||||
FieldDescription,
|
||||
FieldGroup,
|
||||
FieldLabel,
|
||||
FieldLegend,
|
||||
FieldSet,
|
||||
FieldTitle,
|
||||
} from "@/examples/radix/ui/field"
|
||||
|
||||
const options = [
|
||||
{
|
||||
label: "Social Media",
|
||||
value: "social-media",
|
||||
},
|
||||
|
||||
{
|
||||
label: "Search Engine",
|
||||
value: "search-engine",
|
||||
},
|
||||
{
|
||||
label: "Referral",
|
||||
value: "referral",
|
||||
},
|
||||
{
|
||||
label: "Other",
|
||||
value: "other",
|
||||
},
|
||||
]
|
||||
|
||||
export function FieldHear() {
|
||||
return (
|
||||
<Card className="py-4 shadow-none">
|
||||
<CardContent className="px-4">
|
||||
<form>
|
||||
<FieldGroup>
|
||||
<FieldSet className="gap-4">
|
||||
<FieldLegend>How did you hear about us?</FieldLegend>
|
||||
<FieldDescription className="line-clamp-1">
|
||||
Select the option that best describes how you heard about us.
|
||||
</FieldDescription>
|
||||
<FieldGroup className="flex flex-row flex-wrap gap-2 [--radius:9999rem]">
|
||||
{options.map((option) => (
|
||||
<FieldLabel
|
||||
htmlFor={option.value}
|
||||
key={option.value}
|
||||
className="w-fit!"
|
||||
>
|
||||
<Field
|
||||
orientation="horizontal"
|
||||
className="gap-1.5 overflow-hidden px-3! py-1.5! transition-all duration-100 ease-linear group-has-data-[state=checked]/field-label:px-2!"
|
||||
>
|
||||
<Checkbox
|
||||
value={option.value}
|
||||
id={option.value}
|
||||
defaultChecked={option.value === "social-media"}
|
||||
className="-ml-6 -translate-x-1 rounded-full transition-all duration-100 ease-linear data-[state=checked]:ml-0 data-[state=checked]:translate-x-0"
|
||||
/>
|
||||
<FieldTitle>{option.label}</FieldTitle>
|
||||
</Field>
|
||||
</FieldLabel>
|
||||
))}
|
||||
</FieldGroup>
|
||||
</FieldSet>
|
||||
</FieldGroup>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
30
apps/v4/app/(app)/(root)/components/field-slider.tsx
Normal file
30
apps/v4/app/(app)/(root)/components/field-slider.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import { Field, FieldDescription, FieldTitle } from "@/examples/radix/ui/field"
|
||||
import { Slider } from "@/examples/radix/ui/slider"
|
||||
|
||||
export function FieldSlider() {
|
||||
const [value, setValue] = useState([200, 800])
|
||||
return (
|
||||
<div className="w-full max-w-md">
|
||||
<Field>
|
||||
<FieldTitle>Price Range</FieldTitle>
|
||||
<FieldDescription>
|
||||
Set your budget range ($
|
||||
<span className="font-medium tabular-nums">{value[0]}</span> -{" "}
|
||||
<span className="font-medium tabular-nums">{value[1]}</span>).
|
||||
</FieldDescription>
|
||||
<Slider
|
||||
value={value}
|
||||
onValueChange={setValue}
|
||||
max={1000}
|
||||
min={0}
|
||||
step={10}
|
||||
className="mt-2 w-full"
|
||||
aria-label="Price Range"
|
||||
/>
|
||||
</Field>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
52
apps/v4/app/(app)/(root)/components/index.tsx
Normal file
52
apps/v4/app/(app)/(root)/components/index.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
import { FieldSeparator } from "@/examples/radix/ui/field"
|
||||
|
||||
import { AppearanceSettings } from "./appearance-settings"
|
||||
import { ButtonGroupDemo } from "./button-group-demo"
|
||||
import { ButtonGroupInputGroup } from "./button-group-input-group"
|
||||
import { ButtonGroupNested } from "./button-group-nested"
|
||||
import { ButtonGroupPopover } from "./button-group-popover"
|
||||
import { EmptyAvatarGroup } from "./empty-avatar-group"
|
||||
import { FieldCheckbox } from "./field-checkbox"
|
||||
import { FieldDemo } from "./field-demo"
|
||||
import { FieldHear } from "./field-hear"
|
||||
import { FieldSlider } from "./field-slider"
|
||||
import { InputGroupButtonExample } from "./input-group-button"
|
||||
import { InputGroupDemo } from "./input-group-demo"
|
||||
import { ItemDemo } from "./item-demo"
|
||||
import { NotionPromptForm } from "./notion-prompt-form"
|
||||
import { SpinnerBadge } from "./spinner-badge"
|
||||
import { SpinnerEmpty } from "./spinner-empty"
|
||||
|
||||
export function RootComponents() {
|
||||
return (
|
||||
<div className="mx-auto grid gap-8 py-1 theme-container md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 xl:gap-6 2xl:gap-8">
|
||||
<div className="flex flex-col gap-6 *:[div]:w-full *:[div]:max-w-full">
|
||||
<FieldDemo />
|
||||
</div>
|
||||
<div className="flex flex-col gap-6 *:[div]:w-full *:[div]:max-w-full">
|
||||
<EmptyAvatarGroup />
|
||||
<SpinnerBadge />
|
||||
<ButtonGroupInputGroup />
|
||||
<FieldSlider />
|
||||
<InputGroupDemo />
|
||||
</div>
|
||||
<div className="flex flex-col gap-6 *:[div]:w-full *:[div]:max-w-full">
|
||||
<InputGroupButtonExample />
|
||||
<ItemDemo />
|
||||
<FieldSeparator className="my-4">Appearance Settings</FieldSeparator>
|
||||
<AppearanceSettings />
|
||||
</div>
|
||||
<div className="order-first flex flex-col gap-6 lg:hidden xl:order-last xl:flex *:[div]:w-full *:[div]:max-w-full">
|
||||
<NotionPromptForm />
|
||||
<ButtonGroupDemo />
|
||||
<FieldCheckbox />
|
||||
<div className="flex justify-between gap-4">
|
||||
<ButtonGroupNested />
|
||||
<ButtonGroupPopover />
|
||||
</div>
|
||||
<FieldHear />
|
||||
<SpinnerEmpty />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
67
apps/v4/app/(app)/(root)/components/input-group-button.tsx
Normal file
67
apps/v4/app/(app)/(root)/components/input-group-button.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import {
|
||||
InputGroup,
|
||||
InputGroupAddon,
|
||||
InputGroupButton,
|
||||
InputGroupInput,
|
||||
} from "@/examples/radix/ui/input-group"
|
||||
import { Label } from "@/examples/radix/ui/label"
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/examples/radix/ui/popover"
|
||||
import { IconInfoCircle, IconStar } from "@tabler/icons-react"
|
||||
|
||||
export function InputGroupButtonExample() {
|
||||
const [isFavorite, setIsFavorite] = React.useState(false)
|
||||
|
||||
return (
|
||||
<div className="grid w-full max-w-sm gap-6">
|
||||
<Label htmlFor="input-secure-19" className="sr-only">
|
||||
Input Secure
|
||||
</Label>
|
||||
<InputGroup className="[--radius:9999px]">
|
||||
<InputGroupInput id="input-secure-19" className="pl-0.5!" />
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<InputGroupAddon>
|
||||
<InputGroupButton
|
||||
variant="secondary"
|
||||
size="icon-xs"
|
||||
aria-label="Info"
|
||||
>
|
||||
<IconInfoCircle />
|
||||
</InputGroupButton>
|
||||
</InputGroupAddon>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
align="start"
|
||||
alignOffset={10}
|
||||
className="flex flex-col gap-1 rounded-xl text-sm"
|
||||
>
|
||||
<p className="font-medium">Your connection is not secure.</p>
|
||||
<p>You should not enter any sensitive information on this site.</p>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<InputGroupAddon className="pl-1! text-muted-foreground">
|
||||
https://
|
||||
</InputGroupAddon>
|
||||
<InputGroupAddon align="inline-end">
|
||||
<InputGroupButton
|
||||
onClick={() => setIsFavorite(!isFavorite)}
|
||||
size="icon-xs"
|
||||
aria-label="Favorite"
|
||||
>
|
||||
<IconStar
|
||||
data-favorite={isFavorite}
|
||||
className="data-[favorite=true]:fill-primary data-[favorite=true]:stroke-primary"
|
||||
/>
|
||||
</InputGroupButton>
|
||||
</InputGroupAddon>
|
||||
</InputGroup>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
97
apps/v4/app/(app)/(root)/components/input-group-demo.tsx
Normal file
97
apps/v4/app/(app)/(root)/components/input-group-demo.tsx
Normal file
@@ -0,0 +1,97 @@
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/examples/radix/ui/dropdown-menu"
|
||||
import {
|
||||
InputGroup,
|
||||
InputGroupAddon,
|
||||
InputGroupButton,
|
||||
InputGroupInput,
|
||||
InputGroupText,
|
||||
InputGroupTextarea,
|
||||
} from "@/examples/radix/ui/input-group"
|
||||
import { Separator } from "@/examples/radix/ui/separator"
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/examples/radix/ui/tooltip"
|
||||
import { IconCheck, IconInfoCircle, IconPlus } from "@tabler/icons-react"
|
||||
import { ArrowUpIcon, Search } from "lucide-react"
|
||||
|
||||
export function InputGroupDemo() {
|
||||
return (
|
||||
<div className="grid w-full max-w-sm gap-6">
|
||||
<InputGroup>
|
||||
<InputGroupInput placeholder="Search..." />
|
||||
<InputGroupAddon>
|
||||
<Search />
|
||||
</InputGroupAddon>
|
||||
<InputGroupAddon align="inline-end">12 results</InputGroupAddon>
|
||||
</InputGroup>
|
||||
<InputGroup>
|
||||
<InputGroupInput placeholder="example.com" className="pl-1!" />
|
||||
<InputGroupAddon>
|
||||
<InputGroupText>https://</InputGroupText>
|
||||
</InputGroupAddon>
|
||||
<InputGroupAddon align="inline-end">
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<InputGroupButton
|
||||
className="rounded-full"
|
||||
size="icon-xs"
|
||||
aria-label="Info"
|
||||
>
|
||||
<IconInfoCircle />
|
||||
</InputGroupButton>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>This is content in a tooltip.</TooltipContent>
|
||||
</Tooltip>
|
||||
</InputGroupAddon>
|
||||
</InputGroup>
|
||||
<InputGroup>
|
||||
<InputGroupTextarea placeholder="Ask, Search or Chat..." />
|
||||
<InputGroupAddon align="block-end">
|
||||
<InputGroupButton
|
||||
variant="outline"
|
||||
className="rounded-full"
|
||||
size="icon-xs"
|
||||
aria-label="Add"
|
||||
>
|
||||
<IconPlus />
|
||||
</InputGroupButton>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<InputGroupButton variant="ghost">Auto</InputGroupButton>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent side="top" align="start">
|
||||
<DropdownMenuItem>Auto</DropdownMenuItem>
|
||||
<DropdownMenuItem>Agent</DropdownMenuItem>
|
||||
<DropdownMenuItem>Manual</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<InputGroupText className="ml-auto">52% used</InputGroupText>
|
||||
<Separator orientation="vertical" className="h-4!" />
|
||||
<InputGroupButton
|
||||
variant="default"
|
||||
className="rounded-full"
|
||||
size="icon-xs"
|
||||
>
|
||||
<ArrowUpIcon />
|
||||
<span className="sr-only">Send</span>
|
||||
</InputGroupButton>
|
||||
</InputGroupAddon>
|
||||
</InputGroup>
|
||||
<InputGroup>
|
||||
<InputGroupInput placeholder="@shadcn" />
|
||||
<InputGroupAddon align="inline-end">
|
||||
<div className="flex size-4 items-center justify-center rounded-full bg-primary text-foreground">
|
||||
<IconCheck className="size-3 text-white" />
|
||||
</div>
|
||||
</InputGroupAddon>
|
||||
</InputGroup>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
46
apps/v4/app/(app)/(root)/components/input-group-textarea.tsx
Normal file
46
apps/v4/app/(app)/(root)/components/input-group-textarea.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import {
|
||||
IconBrandJavascript,
|
||||
IconCopy,
|
||||
IconCornerDownLeft,
|
||||
IconRefresh,
|
||||
} from "@tabler/icons-react"
|
||||
|
||||
import {
|
||||
InputGroup,
|
||||
InputGroupAddon,
|
||||
InputGroupButton,
|
||||
InputGroupText,
|
||||
InputGroupTextarea,
|
||||
} from "@/registry/new-york-v4/ui/input-group"
|
||||
|
||||
export function InputGroupTextareaExample() {
|
||||
return (
|
||||
<div className="grid w-full max-w-md gap-4">
|
||||
<InputGroup>
|
||||
<InputGroupTextarea
|
||||
id="textarea-code-32"
|
||||
placeholder="console.log('Hello, world!');"
|
||||
className="min-h-[180px]"
|
||||
/>
|
||||
<InputGroupAddon align="block-end" className="border-t">
|
||||
<InputGroupText>Line 1, Column 1</InputGroupText>
|
||||
<InputGroupButton size="sm" className="ml-auto" variant="default">
|
||||
Run <IconCornerDownLeft />
|
||||
</InputGroupButton>
|
||||
</InputGroupAddon>
|
||||
<InputGroupAddon align="block-start" className="border-b">
|
||||
<InputGroupText className="font-mono font-medium">
|
||||
<IconBrandJavascript />
|
||||
script.js
|
||||
</InputGroupText>
|
||||
<InputGroupButton className="ml-auto">
|
||||
<IconRefresh />
|
||||
</InputGroupButton>
|
||||
<InputGroupButton variant="ghost">
|
||||
<IconCopy />
|
||||
</InputGroupButton>
|
||||
</InputGroupAddon>
|
||||
</InputGroup>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
78
apps/v4/app/(app)/(root)/components/item-avatar.tsx
Normal file
78
apps/v4/app/(app)/(root)/components/item-avatar.tsx
Normal file
@@ -0,0 +1,78 @@
|
||||
import { Plus } from "lucide-react"
|
||||
|
||||
import {
|
||||
Avatar,
|
||||
AvatarFallback,
|
||||
AvatarImage,
|
||||
} from "@/registry/new-york-v4/ui/avatar"
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
import {
|
||||
Item,
|
||||
ItemActions,
|
||||
ItemContent,
|
||||
ItemDescription,
|
||||
ItemMedia,
|
||||
ItemTitle,
|
||||
} from "@/registry/new-york-v4/ui/item"
|
||||
|
||||
export function ItemAvatar() {
|
||||
return (
|
||||
<div className="flex w-full max-w-lg flex-col gap-6">
|
||||
<Item variant="outline" className="hidden">
|
||||
<ItemMedia>
|
||||
<Avatar className="size-10">
|
||||
<AvatarImage src="https://github.com/maxleiter.png" />
|
||||
<AvatarFallback>LR</AvatarFallback>
|
||||
</Avatar>
|
||||
</ItemMedia>
|
||||
<ItemContent>
|
||||
<ItemTitle>Max Leiter</ItemTitle>
|
||||
<ItemDescription>Last seen 5 months ago</ItemDescription>
|
||||
</ItemContent>
|
||||
<ItemActions>
|
||||
<Button
|
||||
size="icon-sm"
|
||||
variant="outline"
|
||||
className="rounded-full"
|
||||
aria-label="Invite"
|
||||
>
|
||||
<Plus />
|
||||
</Button>
|
||||
</ItemActions>
|
||||
</Item>
|
||||
<Item variant="outline">
|
||||
<ItemMedia>
|
||||
<div className="flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background *:data-[slot=avatar]:grayscale">
|
||||
<Avatar className="hidden sm:flex">
|
||||
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||
<AvatarFallback>CN</AvatarFallback>
|
||||
</Avatar>
|
||||
<Avatar className="hidden sm:flex">
|
||||
<AvatarImage
|
||||
src="https://github.com/maxleiter.png"
|
||||
alt="@maxleiter"
|
||||
/>
|
||||
<AvatarFallback>LR</AvatarFallback>
|
||||
</Avatar>
|
||||
<Avatar>
|
||||
<AvatarImage
|
||||
src="https://github.com/evilrabbit.png"
|
||||
alt="@evilrabbit"
|
||||
/>
|
||||
<AvatarFallback>ER</AvatarFallback>
|
||||
</Avatar>
|
||||
</div>
|
||||
</ItemMedia>
|
||||
<ItemContent>
|
||||
<ItemTitle>No Team Members</ItemTitle>
|
||||
<ItemDescription>Invite your team to collaborate.</ItemDescription>
|
||||
</ItemContent>
|
||||
<ItemActions>
|
||||
<Button size="sm" variant="outline">
|
||||
Invite
|
||||
</Button>
|
||||
</ItemActions>
|
||||
</Item>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
41
apps/v4/app/(app)/(root)/components/item-demo.tsx
Normal file
41
apps/v4/app/(app)/(root)/components/item-demo.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import { Button } from "@/examples/radix/ui/button"
|
||||
import {
|
||||
Item,
|
||||
ItemActions,
|
||||
ItemContent,
|
||||
ItemDescription,
|
||||
ItemMedia,
|
||||
ItemTitle,
|
||||
} from "@/examples/radix/ui/item"
|
||||
import { BadgeCheckIcon, ChevronRightIcon } from "lucide-react"
|
||||
|
||||
export function ItemDemo() {
|
||||
return (
|
||||
<div className="flex w-full max-w-md flex-col gap-6">
|
||||
<Item variant="outline">
|
||||
<ItemContent>
|
||||
<ItemTitle>Two-factor authentication</ItemTitle>
|
||||
<ItemDescription className="text-pretty xl:hidden 2xl:block">
|
||||
Verify via email or phone number.
|
||||
</ItemDescription>
|
||||
</ItemContent>
|
||||
<ItemActions>
|
||||
<Button size="sm">Enable</Button>
|
||||
</ItemActions>
|
||||
</Item>
|
||||
<Item variant="outline" size="sm" asChild>
|
||||
<a href="#">
|
||||
<ItemMedia>
|
||||
<BadgeCheckIcon className="size-5" />
|
||||
</ItemMedia>
|
||||
<ItemContent>
|
||||
<ItemTitle>Your profile has been verified.</ItemTitle>
|
||||
</ItemContent>
|
||||
<ItemActions>
|
||||
<ChevronRightIcon className="size-4" />
|
||||
</ItemActions>
|
||||
</a>
|
||||
</Item>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
448
apps/v4/app/(app)/(root)/components/notion-prompt-form.tsx
Normal file
448
apps/v4/app/(app)/(root)/components/notion-prompt-form.tsx
Normal file
@@ -0,0 +1,448 @@
|
||||
"use client"
|
||||
|
||||
import { useMemo, useState } from "react"
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/examples/radix/ui/avatar"
|
||||
import { Badge } from "@/examples/radix/ui/badge"
|
||||
import {
|
||||
Command,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
CommandList,
|
||||
} from "@/examples/radix/ui/command"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuSub,
|
||||
DropdownMenuSubContent,
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/examples/radix/ui/dropdown-menu"
|
||||
import { Field, FieldLabel } from "@/examples/radix/ui/field"
|
||||
import {
|
||||
InputGroup,
|
||||
InputGroupAddon,
|
||||
InputGroupButton,
|
||||
InputGroupTextarea,
|
||||
} from "@/examples/radix/ui/input-group"
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/examples/radix/ui/popover"
|
||||
import { Switch } from "@/examples/radix/ui/switch"
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/examples/radix/ui/tooltip"
|
||||
import {
|
||||
IconApps,
|
||||
IconArrowUp,
|
||||
IconAt,
|
||||
IconBook,
|
||||
IconCircleDashedPlus,
|
||||
IconPaperclip,
|
||||
IconPlus,
|
||||
IconWorld,
|
||||
IconX,
|
||||
} from "@tabler/icons-react"
|
||||
|
||||
const SAMPLE_DATA = {
|
||||
mentionable: [
|
||||
{
|
||||
type: "page",
|
||||
title: "Meeting Notes",
|
||||
image: "📝",
|
||||
},
|
||||
{
|
||||
type: "page",
|
||||
title: "Project Dashboard",
|
||||
image: "📊",
|
||||
},
|
||||
{
|
||||
type: "page",
|
||||
title: "Ideas & Brainstorming",
|
||||
image: "💡",
|
||||
},
|
||||
{
|
||||
type: "page",
|
||||
title: "Calendar & Events",
|
||||
image: "📅",
|
||||
},
|
||||
{
|
||||
type: "page",
|
||||
title: "Documentation",
|
||||
image: "📚",
|
||||
},
|
||||
{
|
||||
type: "page",
|
||||
title: "Goals & Objectives",
|
||||
image: "🎯",
|
||||
},
|
||||
{
|
||||
type: "page",
|
||||
title: "Budget Planning",
|
||||
image: "💰",
|
||||
},
|
||||
{
|
||||
type: "page",
|
||||
title: "Team Directory",
|
||||
image: "👥",
|
||||
},
|
||||
{
|
||||
type: "page",
|
||||
title: "Technical Specs",
|
||||
image: "🔧",
|
||||
},
|
||||
{
|
||||
type: "page",
|
||||
title: "Analytics Report",
|
||||
image: "📈",
|
||||
},
|
||||
{
|
||||
type: "user",
|
||||
title: "shadcn",
|
||||
image: "https://github.com/shadcn.png",
|
||||
workspace: "Workspace",
|
||||
},
|
||||
{
|
||||
type: "user",
|
||||
title: "maxleiter",
|
||||
image: "https://github.com/maxleiter.png",
|
||||
workspace: "Workspace",
|
||||
},
|
||||
{
|
||||
type: "user",
|
||||
title: "evilrabbit",
|
||||
image: "https://github.com/evilrabbit.png",
|
||||
workspace: "Workspace",
|
||||
},
|
||||
],
|
||||
models: [
|
||||
{
|
||||
name: "Auto",
|
||||
},
|
||||
{
|
||||
name: "Agent Mode",
|
||||
badge: "Beta",
|
||||
},
|
||||
{
|
||||
name: "Plan Mode",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
function MentionableIcon({
|
||||
item,
|
||||
}: {
|
||||
item: (typeof SAMPLE_DATA.mentionable)[0]
|
||||
}) {
|
||||
return item.type === "page" ? (
|
||||
<span className="flex size-4 items-center justify-center">
|
||||
{item.image}
|
||||
</span>
|
||||
) : (
|
||||
<Avatar className="size-4">
|
||||
<AvatarImage src={item.image} />
|
||||
<AvatarFallback>{item.title[0]}</AvatarFallback>
|
||||
</Avatar>
|
||||
)
|
||||
}
|
||||
|
||||
export function NotionPromptForm() {
|
||||
const [mentions, setMentions] = useState<string[]>([])
|
||||
const [mentionPopoverOpen, setMentionPopoverOpen] = useState(false)
|
||||
const [modelPopoverOpen, setModelPopoverOpen] = useState(false)
|
||||
const [selectedModel, setSelectedModel] = useState<
|
||||
(typeof SAMPLE_DATA.models)[0]
|
||||
>(SAMPLE_DATA.models[0])
|
||||
const [scopeMenuOpen, setScopeMenuOpen] = useState(false)
|
||||
|
||||
const grouped = useMemo(() => {
|
||||
return SAMPLE_DATA.mentionable.reduce(
|
||||
(acc, item) => {
|
||||
const isAvailable = !mentions.includes(item.title)
|
||||
|
||||
if (isAvailable) {
|
||||
if (!acc[item.type]) {
|
||||
acc[item.type] = []
|
||||
}
|
||||
acc[item.type].push(item)
|
||||
}
|
||||
return acc
|
||||
},
|
||||
{} as Record<string, typeof SAMPLE_DATA.mentionable>
|
||||
)
|
||||
}, [mentions])
|
||||
|
||||
const hasMentions = mentions.length > 0
|
||||
|
||||
return (
|
||||
<form>
|
||||
<Field>
|
||||
<FieldLabel htmlFor="notion-prompt" className="sr-only">
|
||||
Prompt
|
||||
</FieldLabel>
|
||||
<InputGroup className="rounded-xl">
|
||||
<InputGroupTextarea
|
||||
id="notion-prompt"
|
||||
placeholder="Ask, search, or make anything..."
|
||||
/>
|
||||
<InputGroupAddon align="block-start" className="pt-3">
|
||||
<Popover
|
||||
open={mentionPopoverOpen}
|
||||
onOpenChange={setMentionPopoverOpen}
|
||||
>
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
asChild
|
||||
onFocusCapture={(e) => e.stopPropagation()}
|
||||
>
|
||||
<PopoverTrigger asChild>
|
||||
<InputGroupButton
|
||||
variant="outline"
|
||||
size={!hasMentions ? "sm" : "icon-sm"}
|
||||
className="transition-transform"
|
||||
>
|
||||
<IconAt /> {!hasMentions && "Add context"}
|
||||
</InputGroupButton>
|
||||
</PopoverTrigger>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Mention a person, page, or date</TooltipContent>
|
||||
</Tooltip>
|
||||
<PopoverContent className="p-0" align="start">
|
||||
<Command>
|
||||
<CommandInput placeholder="Search pages..." />
|
||||
<CommandList>
|
||||
<CommandEmpty>No pages found</CommandEmpty>
|
||||
{Object.entries(grouped).map(([type, items]) => (
|
||||
<CommandGroup
|
||||
key={type}
|
||||
heading={type === "page" ? "Pages" : "Users"}
|
||||
>
|
||||
{items.map((item) => (
|
||||
<CommandItem
|
||||
key={item.title}
|
||||
value={item.title}
|
||||
onSelect={(currentValue) => {
|
||||
setMentions((prev) => [...prev, currentValue])
|
||||
setMentionPopoverOpen(false)
|
||||
}}
|
||||
className="rounded-lg"
|
||||
>
|
||||
<MentionableIcon item={item} />
|
||||
{item.title}
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
))}
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<div className="-m-1.5 no-scrollbar flex gap-1 overflow-y-auto p-1.5">
|
||||
{mentions.map((mention) => {
|
||||
const item = SAMPLE_DATA.mentionable.find(
|
||||
(item) => item.title === mention
|
||||
)
|
||||
|
||||
if (!item) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<InputGroupButton
|
||||
key={mention}
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
className="rounded-full pl-2!"
|
||||
onClick={() => {
|
||||
setMentions((prev) => prev.filter((m) => m !== mention))
|
||||
}}
|
||||
>
|
||||
<MentionableIcon item={item} />
|
||||
{item.title}
|
||||
<IconX />
|
||||
</InputGroupButton>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</InputGroupAddon>
|
||||
<InputGroupAddon align="block-end" className="gap-1">
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<InputGroupButton
|
||||
size="icon-sm"
|
||||
className="rounded-full"
|
||||
aria-label="Attach file"
|
||||
>
|
||||
<IconPaperclip />
|
||||
</InputGroupButton>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Attach file</TooltipContent>
|
||||
</Tooltip>
|
||||
<DropdownMenu
|
||||
open={modelPopoverOpen}
|
||||
onOpenChange={setModelPopoverOpen}
|
||||
>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<InputGroupButton size="sm" className="rounded-full">
|
||||
{selectedModel.name}
|
||||
</InputGroupButton>
|
||||
</DropdownMenuTrigger>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Select AI model</TooltipContent>
|
||||
</Tooltip>
|
||||
<DropdownMenuContent
|
||||
side="top"
|
||||
align="start"
|
||||
className="min-w-48"
|
||||
>
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuLabel className="text-xs text-muted-foreground">
|
||||
Select Agent Mode
|
||||
</DropdownMenuLabel>
|
||||
{SAMPLE_DATA.models.map((model) => (
|
||||
<DropdownMenuCheckboxItem
|
||||
key={model.name}
|
||||
checked={model.name === selectedModel.name}
|
||||
onCheckedChange={(checked) => {
|
||||
if (checked) {
|
||||
setSelectedModel(model)
|
||||
}
|
||||
}}
|
||||
className="pl-2 *:[span:first-child]:right-2 *:[span:first-child]:left-auto"
|
||||
>
|
||||
{model.name}
|
||||
{model.badge && (
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className="h-5 rounded-sm bg-blue-100 px-1 text-xs text-blue-800 dark:bg-blue-900 dark:text-blue-100"
|
||||
>
|
||||
{model.badge}
|
||||
</Badge>
|
||||
)}
|
||||
</DropdownMenuCheckboxItem>
|
||||
))}
|
||||
</DropdownMenuGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<DropdownMenu open={scopeMenuOpen} onOpenChange={setScopeMenuOpen}>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<InputGroupButton size="sm" className="rounded-full">
|
||||
<IconWorld /> All Sources
|
||||
</InputGroupButton>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent side="top" align="end" className="w-72">
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem
|
||||
asChild
|
||||
onSelect={(e) => e.preventDefault()}
|
||||
>
|
||||
<label htmlFor="web-search">
|
||||
<IconWorld /> Web Search{" "}
|
||||
<Switch
|
||||
id="web-search"
|
||||
className="ml-auto"
|
||||
defaultChecked
|
||||
/>
|
||||
</label>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem
|
||||
asChild
|
||||
onSelect={(e) => e.preventDefault()}
|
||||
>
|
||||
<label htmlFor="apps">
|
||||
<IconApps /> Apps and Integrations
|
||||
<Switch id="apps" className="ml-auto" defaultChecked />
|
||||
</label>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<IconCircleDashedPlus /> All Sources I can access
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger>
|
||||
<Avatar className="size-4">
|
||||
<AvatarImage src="https://github.com/shadcn.png" />
|
||||
<AvatarFallback>CN</AvatarFallback>
|
||||
</Avatar>
|
||||
shadcn
|
||||
</DropdownMenuSubTrigger>
|
||||
<DropdownMenuSubContent className="w-72 p-0 [--radius:1rem]">
|
||||
<Command>
|
||||
<CommandInput
|
||||
placeholder="Find or use knowledge in..."
|
||||
autoFocus
|
||||
/>
|
||||
<CommandList>
|
||||
<CommandEmpty>No knowledge found</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{SAMPLE_DATA.mentionable
|
||||
.filter((item) => item.type === "user")
|
||||
.map((user) => (
|
||||
<CommandItem
|
||||
key={user.title}
|
||||
value={user.title}
|
||||
onSelect={() => {
|
||||
// Handle user selection here
|
||||
console.log("Selected user:", user.title)
|
||||
}}
|
||||
>
|
||||
<Avatar className="size-4">
|
||||
<AvatarImage src={user.image} />
|
||||
<AvatarFallback>
|
||||
{user.title[0]}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
{user.title}{" "}
|
||||
<span className="text-muted-foreground">
|
||||
- {user.workspace}
|
||||
</span>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuSub>
|
||||
<DropdownMenuItem>
|
||||
<IconBook /> Help Center
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem>
|
||||
<IconPlus /> Connect Apps
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuLabel className="text-xs text-muted-foreground">
|
||||
We'll only search in the sources selected here.
|
||||
</DropdownMenuLabel>
|
||||
</DropdownMenuGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<InputGroupButton
|
||||
aria-label="Send"
|
||||
className="ml-auto rounded-full"
|
||||
variant="default"
|
||||
size="icon-sm"
|
||||
>
|
||||
<IconArrowUp />
|
||||
</InputGroupButton>
|
||||
</InputGroupAddon>
|
||||
</InputGroup>
|
||||
</Field>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
21
apps/v4/app/(app)/(root)/components/spinner-badge.tsx
Normal file
21
apps/v4/app/(app)/(root)/components/spinner-badge.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import { Badge } from "@/examples/radix/ui/badge"
|
||||
import { Spinner } from "@/examples/radix/ui/spinner"
|
||||
|
||||
export function SpinnerBadge() {
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge>
|
||||
<Spinner />
|
||||
Syncing
|
||||
</Badge>
|
||||
<Badge variant="secondary">
|
||||
<Spinner />
|
||||
Updating
|
||||
</Badge>
|
||||
<Badge variant="outline">
|
||||
<Spinner />
|
||||
Loading
|
||||
</Badge>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
31
apps/v4/app/(app)/(root)/components/spinner-empty.tsx
Normal file
31
apps/v4/app/(app)/(root)/components/spinner-empty.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import { Button } from "@/examples/radix/ui/button"
|
||||
import {
|
||||
Empty,
|
||||
EmptyContent,
|
||||
EmptyDescription,
|
||||
EmptyHeader,
|
||||
EmptyMedia,
|
||||
EmptyTitle,
|
||||
} from "@/examples/radix/ui/empty"
|
||||
import { Spinner } from "@/examples/radix/ui/spinner"
|
||||
|
||||
export function SpinnerEmpty() {
|
||||
return (
|
||||
<Empty className="w-full border md:p-6">
|
||||
<EmptyHeader>
|
||||
<EmptyMedia variant="icon">
|
||||
<Spinner />
|
||||
</EmptyMedia>
|
||||
<EmptyTitle>Processing your request</EmptyTitle>
|
||||
<EmptyDescription>
|
||||
Please wait while we process your request. Do not refresh the page.
|
||||
</EmptyDescription>
|
||||
</EmptyHeader>
|
||||
<EmptyContent>
|
||||
<Button variant="outline" size="sm">
|
||||
Cancel
|
||||
</Button>
|
||||
</EmptyContent>
|
||||
</Empty>
|
||||
)
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
import { Metadata } from "next"
|
||||
import { type Metadata } from "next"
|
||||
import Image from "next/image"
|
||||
import Link from "next/link"
|
||||
|
||||
import { Announcement } from "@/components/announcement"
|
||||
import { CardsDemo } from "@/components/cards"
|
||||
import { ExamplesNav } from "@/components/examples-nav"
|
||||
import {
|
||||
PageActions,
|
||||
@@ -15,9 +14,11 @@ import { PageNav } from "@/components/page-nav"
|
||||
import { ThemeSelector } from "@/components/theme-selector"
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
|
||||
const title = "Build your Component Library"
|
||||
import { RootComponents } from "./components"
|
||||
|
||||
const title = "The Foundation for your Design System"
|
||||
const description =
|
||||
"A set of beautifully-designed, accessible components and a code distribution platform. Works with your favorite frameworks. Open Source. Open Code."
|
||||
"A set of beautifully designed components that you can customize, extend, and build on. Start here then make it your own. Open Source. Open Code."
|
||||
|
||||
export const dynamic = "force-static"
|
||||
export const revalidate = false
|
||||
@@ -51,24 +52,24 @@ export default function IndexPage() {
|
||||
<div className="flex flex-1 flex-col">
|
||||
<PageHeader>
|
||||
<Announcement />
|
||||
<PageHeaderHeading>{title}</PageHeaderHeading>
|
||||
<PageHeaderHeading className="max-w-4xl">{title}</PageHeaderHeading>
|
||||
<PageHeaderDescription>{description}</PageHeaderDescription>
|
||||
<PageActions>
|
||||
<Button asChild size="sm">
|
||||
<Link href="/docs/installation">Get Started</Link>
|
||||
<Button asChild size="sm" className="h-[31px] rounded-lg">
|
||||
<Link href="/create">New Project</Link>
|
||||
</Button>
|
||||
<Button asChild size="sm" variant="ghost">
|
||||
<Link href="/blocks">Browse Blocks</Link>
|
||||
<Button asChild size="sm" variant="ghost" className="rounded-lg">
|
||||
<Link href="/docs/components">View Components</Link>
|
||||
</Button>
|
||||
</PageActions>
|
||||
</PageHeader>
|
||||
<PageNav className="hidden md:flex">
|
||||
<ExamplesNav className="[&>a:first-child]:text-primary flex-1 overflow-hidden" />
|
||||
<ExamplesNav className="flex-1 overflow-hidden [&>a:first-child]:text-primary" />
|
||||
<ThemeSelector className="mr-4 hidden md:flex" />
|
||||
</PageNav>
|
||||
<div className="container-wrapper section-soft flex-1 pb-6">
|
||||
<div className="container-wrapper flex-1 section-soft pb-6">
|
||||
<div className="container overflow-hidden">
|
||||
<section className="border-border/50 -mx-4 w-[160vw] overflow-hidden rounded-lg border md:hidden md:w-[150vw]">
|
||||
<section className="-mx-4 w-[160vw] overflow-hidden rounded-lg border border-border/50 md:hidden md:w-[150vw]">
|
||||
<Image
|
||||
src="/r/styles/new-york-v4/dashboard-01-light.png"
|
||||
width={1400}
|
||||
@@ -86,8 +87,8 @@ export default function IndexPage() {
|
||||
priority
|
||||
/>
|
||||
</section>
|
||||
<section className="theme-container hidden md:block">
|
||||
<CardsDemo />
|
||||
<section className="hidden theme-container md:block">
|
||||
<RootComponents />
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { getAllBlockIds } from "@/lib/blocks"
|
||||
import { registryCategories } from "@/lib/categories"
|
||||
import { BlockDisplay } from "@/components/block-display"
|
||||
import { registryCategories } from "@/registry/registry-categories"
|
||||
import { getActiveStyle } from "@/registry/_legacy-styles"
|
||||
|
||||
export const revalidate = false
|
||||
export const dynamic = "force-static"
|
||||
@@ -17,13 +18,16 @@ export default async function BlocksPage({
|
||||
}: {
|
||||
params: Promise<{ categories?: string[] }>
|
||||
}) {
|
||||
const { categories = [] } = await params
|
||||
const [{ categories = [] }, activeStyle] = await Promise.all([
|
||||
params,
|
||||
getActiveStyle(),
|
||||
])
|
||||
const blocks = await getAllBlockIds(["registry:block"], categories)
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-12 md:gap-24">
|
||||
{blocks.map((name) => (
|
||||
<BlockDisplay name={name} key={name} />
|
||||
<BlockDisplay name={name} key={name} styleName={activeStyle.name} />
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Metadata } from "next"
|
||||
import { type Metadata } from "next"
|
||||
import Link from "next/link"
|
||||
|
||||
import { Announcement } from "@/components/announcement"
|
||||
@@ -71,7 +71,7 @@ export default function BlocksLayout({
|
||||
<Link href="/blocks/sidebar">Browse all blocks</Link>
|
||||
</Button>
|
||||
</PageNav>
|
||||
<div className="container-wrapper section-soft flex-1 md:py-12">
|
||||
<div className="container-wrapper flex-1 section-soft md:py-12">
|
||||
<div className="container">{children}</div>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import Link from "next/link"
|
||||
|
||||
import { BlockDisplay } from "@/components/block-display"
|
||||
import { getActiveStyle } from "@/registry/_legacy-styles"
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
|
||||
export const dynamic = "force-static"
|
||||
@@ -15,10 +16,12 @@ const FEATURED_BLOCKS = [
|
||||
]
|
||||
|
||||
export default async function BlocksPage() {
|
||||
const activeStyle = await getActiveStyle()
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-12 md:gap-24">
|
||||
{FEATURED_BLOCKS.map((name) => (
|
||||
<BlockDisplay name={name} key={name} />
|
||||
<BlockDisplay name={name} key={name} styleName={activeStyle.name} />
|
||||
))}
|
||||
<div className="container-wrapper">
|
||||
<div className="container flex justify-center py-6">
|
||||
|
||||
@@ -2,7 +2,12 @@ import * as React from "react"
|
||||
import { notFound } from "next/navigation"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { ChartDisplay } from "@/components/chart-display"
|
||||
import {
|
||||
ChartDisplay,
|
||||
getCachedRegistryItem,
|
||||
getChartHighlightedCode,
|
||||
} from "@/components/chart-display"
|
||||
import { getActiveStyle } from "@/registry/_legacy-styles"
|
||||
import { charts } from "@/app/(app)/charts/charts"
|
||||
|
||||
export const revalidate = false
|
||||
@@ -41,6 +46,27 @@ export default async function ChartPage({ params }: ChartPageProps) {
|
||||
|
||||
const chartType = type as ChartType
|
||||
const chartList = charts[chartType]
|
||||
const activeStyle = await getActiveStyle()
|
||||
|
||||
// Prefetch all chart data in parallel for better performance.
|
||||
// Charts are rendered via iframes, so we only need the metadata and highlighted code.
|
||||
const chartDataPromises = chartList.map(async (chart) => {
|
||||
const registryItem = await getCachedRegistryItem(chart.id, activeStyle.name)
|
||||
if (!registryItem) return null
|
||||
|
||||
const highlightedCode = await getChartHighlightedCode(
|
||||
registryItem.files?.[0]?.content ?? ""
|
||||
)
|
||||
if (!highlightedCode) return null
|
||||
|
||||
return {
|
||||
...registryItem,
|
||||
highlightedCode,
|
||||
fullWidth: chart.fullWidth,
|
||||
}
|
||||
})
|
||||
|
||||
const prefetchedCharts = await Promise.all(chartDataPromises)
|
||||
|
||||
return (
|
||||
<div className="grid flex-1 gap-12 lg:gap-24">
|
||||
@@ -49,15 +75,14 @@ export default async function ChartPage({ params }: ChartPageProps) {
|
||||
</h2>
|
||||
<div className="grid flex-1 scroll-mt-20 items-stretch gap-10 md:grid-cols-2 md:gap-6 lg:grid-cols-3 xl:gap-10">
|
||||
{Array.from({ length: 12 }).map((_, index) => {
|
||||
const chart = chartList[index]
|
||||
const chart = prefetchedCharts[index]
|
||||
return chart ? (
|
||||
<ChartDisplay
|
||||
key={chart.id}
|
||||
name={chart.id}
|
||||
key={chart.name}
|
||||
chart={chart}
|
||||
style={activeStyle.name}
|
||||
className={cn(chart.fullWidth && "md:col-span-2 lg:col-span-3")}
|
||||
>
|
||||
<chart.component />
|
||||
</ChartDisplay>
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
key={`empty-${index}`}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import * as React from "react"
|
||||
import type * as React from "react"
|
||||
|
||||
import { ChartAreaAxes } from "@/registry/new-york-v4/charts/chart-area-axes"
|
||||
import { ChartAreaDefault } from "@/registry/new-york-v4/charts/chart-area-default"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Metadata } from "next"
|
||||
import { type Metadata } from "next"
|
||||
import Link from "next/link"
|
||||
|
||||
import { Announcement } from "@/components/announcement"
|
||||
@@ -63,9 +63,8 @@ export default function ChartsLayout({
|
||||
</PageHeader>
|
||||
<PageNav id="charts">
|
||||
<ChartsNav />
|
||||
<ThemeSelector className="mr-4 hidden md:flex" />
|
||||
</PageNav>
|
||||
<div className="container-wrapper section-soft flex-1">
|
||||
<div className="container-wrapper flex-1">
|
||||
<div className="container pb-6">
|
||||
<section className="theme-container">{children}</section>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Metadata } from "next"
|
||||
import { type Metadata } from "next"
|
||||
import Link from "next/link"
|
||||
|
||||
import { Announcement } from "@/components/announcement"
|
||||
@@ -62,7 +62,7 @@ export default function ColorsLayout({
|
||||
<div className="hidden">
|
||||
<div className="container-wrapper">
|
||||
<div className="container flex items-center justify-between gap-8 py-4">
|
||||
<ColorsNav className="[&>a:first-child]:text-primary flex-1 overflow-hidden" />
|
||||
<ColorsNav className="flex-1 overflow-hidden [&>a:first-child]:text-primary" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
import Link from "next/link"
|
||||
import { notFound } from "next/navigation"
|
||||
import { mdxComponents } from "@/mdx-components"
|
||||
import {
|
||||
IconArrowLeft,
|
||||
IconArrowRight,
|
||||
IconArrowUpRight,
|
||||
} from "@tabler/icons-react"
|
||||
import { findNeighbour } from "fumadocs-core/server"
|
||||
import { IconArrowLeft, IconArrowRight } from "@tabler/icons-react"
|
||||
import { findNeighbour } from "fumadocs-core/page-tree"
|
||||
|
||||
import { source } from "@/lib/source"
|
||||
import { absoluteUrl } from "@/lib/utils"
|
||||
import { DocsBaseSwitcher } from "@/components/docs-base-switcher"
|
||||
import { DocsCopyPage } from "@/components/docs-copy-page"
|
||||
import { DocsTableOfContents } from "@/components/docs-toc"
|
||||
import { OpenInV0Cta } from "@/components/open-in-v0-cta"
|
||||
import { Badge } from "@/registry/new-york-v4/ui/badge"
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
|
||||
export const revalidate = false
|
||||
@@ -24,7 +21,7 @@ export function generateStaticParams() {
|
||||
}
|
||||
|
||||
export async function generateMetadata(props: {
|
||||
params: Promise<{ slug?: string[] }>
|
||||
params: Promise<{ slug: string[] }>
|
||||
}) {
|
||||
const params = await props.params
|
||||
const page = source.getPage(params.slug)
|
||||
@@ -72,7 +69,7 @@ export async function generateMetadata(props: {
|
||||
}
|
||||
|
||||
export default async function Page(props: {
|
||||
params: Promise<{ slug?: string[] }>
|
||||
params: Promise<{ slug: string[] }>
|
||||
}) {
|
||||
const params = await props.params
|
||||
const page = source.getPage(params.slug)
|
||||
@@ -81,123 +78,117 @@ export default async function Page(props: {
|
||||
}
|
||||
|
||||
const doc = page.data
|
||||
// @ts-expect-error - revisit fumadocs types.
|
||||
const MDX = doc.body
|
||||
const neighbours = await findNeighbour(source.pageTree, page.url)
|
||||
|
||||
// @ts-expect-error - revisit fumadocs types.
|
||||
const links = doc.links
|
||||
const isChangelog = params.slug?.[0] === "changelog"
|
||||
const neighbours = isChangelog
|
||||
? { previous: null, next: null }
|
||||
: findNeighbour(source.pageTree, page.url)
|
||||
const raw = await page.data.getText("raw")
|
||||
|
||||
return (
|
||||
<div
|
||||
data-slot="docs"
|
||||
className="flex items-stretch text-[1.05rem] sm:text-[15px] xl:w-full"
|
||||
className="flex scroll-mt-24 items-stretch pb-8 text-[1.05rem] sm:text-[15px] xl:w-full"
|
||||
>
|
||||
<div className="flex min-w-0 flex-1 flex-col">
|
||||
<div className="h-(--top-spacing) shrink-0" />
|
||||
<div className="mx-auto flex w-full max-w-2xl min-w-0 flex-1 flex-col gap-8 px-4 py-6 text-neutral-800 md:px-0 lg:py-8 dark:text-neutral-300">
|
||||
<div className="mx-auto flex w-full max-w-[40rem] min-w-0 flex-1 flex-col gap-6 px-4 py-6 text-neutral-800 md:px-0 lg:py-8 dark:text-neutral-300">
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex items-start justify-between">
|
||||
<h1 className="scroll-m-20 text-4xl font-semibold tracking-tight sm:text-3xl xl:text-4xl">
|
||||
<div className="flex items-center justify-between md:items-start">
|
||||
<h1 className="scroll-m-24 text-3xl font-semibold tracking-tight sm:text-3xl">
|
||||
{doc.title}
|
||||
</h1>
|
||||
<div className="flex items-center gap-2 pt-1.5">
|
||||
{neighbours.previous && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="icon"
|
||||
className="extend-touch-target size-8 shadow-none md:size-7"
|
||||
asChild
|
||||
>
|
||||
<Link href={neighbours.previous.url}>
|
||||
<IconArrowLeft />
|
||||
<span className="sr-only">Previous</span>
|
||||
</Link>
|
||||
</Button>
|
||||
)}
|
||||
{neighbours.next && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="icon"
|
||||
className="extend-touch-target size-8 shadow-none md:size-7"
|
||||
asChild
|
||||
>
|
||||
<Link href={neighbours.next.url}>
|
||||
<span className="sr-only">Next</span>
|
||||
<IconArrowRight />
|
||||
</Link>
|
||||
</Button>
|
||||
)}
|
||||
<div className="docs-nav flex items-center gap-2">
|
||||
<div className="hidden sm:block">
|
||||
<DocsCopyPage page={raw} url={absoluteUrl(page.url)} />
|
||||
</div>
|
||||
<div className="ml-auto flex gap-2">
|
||||
{neighbours.previous && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="icon"
|
||||
className="extend-touch-target size-8 shadow-none md:size-7"
|
||||
asChild
|
||||
>
|
||||
<Link href={neighbours.previous.url}>
|
||||
<IconArrowLeft />
|
||||
<span className="sr-only">Previous</span>
|
||||
</Link>
|
||||
</Button>
|
||||
)}
|
||||
{neighbours.next && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="icon"
|
||||
className="extend-touch-target size-8 shadow-none md:size-7"
|
||||
asChild
|
||||
>
|
||||
<Link href={neighbours.next.url}>
|
||||
<span className="sr-only">Next</span>
|
||||
<IconArrowRight />
|
||||
</Link>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{doc.description && (
|
||||
<p className="text-muted-foreground text-[1.05rem] text-balance sm:text-base">
|
||||
<p className="text-[1.05rem] text-muted-foreground sm:text-base sm:text-balance md:max-w-[80%]">
|
||||
{doc.description}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{links ? (
|
||||
<div className="flex items-center space-x-2 pt-4">
|
||||
{links?.doc && (
|
||||
<Badge asChild variant="secondary">
|
||||
<Link href={links.doc} target="_blank" rel="noreferrer">
|
||||
Docs <IconArrowUpRight />
|
||||
</Link>
|
||||
</Badge>
|
||||
)}
|
||||
{links?.api && (
|
||||
<Badge asChild variant="secondary">
|
||||
<Link href={links.api} target="_blank" rel="noreferrer">
|
||||
API Reference <IconArrowUpRight />
|
||||
</Link>
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="w-full flex-1 *:data-[slot=alert]:first:mt-0">
|
||||
<div className="w-full flex-1 pb-16 *:data-[slot=alert]:first:mt-0 sm:pb-0">
|
||||
{params.slug &&
|
||||
params.slug[0] === "components" &&
|
||||
params.slug[1] &&
|
||||
params.slug[2] && (
|
||||
<DocsBaseSwitcher
|
||||
base={params.slug[1]}
|
||||
component={params.slug[2]}
|
||||
className="mb-4"
|
||||
/>
|
||||
)}
|
||||
<MDX components={mdxComponents} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="mx-auto flex h-16 w-full max-w-2xl items-center gap-2 px-4 md:px-0">
|
||||
{neighbours.previous && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
asChild
|
||||
className="shadow-none"
|
||||
>
|
||||
<Link href={neighbours.previous.url}>
|
||||
<IconArrowLeft /> {neighbours.previous.name}
|
||||
</Link>
|
||||
</Button>
|
||||
)}
|
||||
{neighbours.next && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
className="ml-auto shadow-none"
|
||||
asChild
|
||||
>
|
||||
<Link href={neighbours.next.url}>
|
||||
{neighbours.next.name} <IconArrowRight />
|
||||
</Link>
|
||||
</Button>
|
||||
)}
|
||||
<div className="hidden h-16 w-full items-center gap-2 px-4 sm:flex sm:px-0">
|
||||
{neighbours.previous && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
asChild
|
||||
className="shadow-none"
|
||||
>
|
||||
<Link href={neighbours.previous.url}>
|
||||
<IconArrowLeft /> {neighbours.previous.name}
|
||||
</Link>
|
||||
</Button>
|
||||
)}
|
||||
{neighbours.next && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
className="ml-auto shadow-none"
|
||||
asChild
|
||||
>
|
||||
<Link href={neighbours.next.url}>
|
||||
{neighbours.next.name} <IconArrowRight />
|
||||
</Link>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="sticky top-[calc(var(--header-height)+1px)] z-30 ml-auto hidden h-[calc(100svh-var(--header-height)-var(--footer-height))] w-72 flex-col gap-4 overflow-hidden overscroll-none pb-8 xl:flex">
|
||||
<div className="h-(--top-spacing) shrink-0" />
|
||||
{/* @ts-expect-error - revisit fumadocs types. */}
|
||||
<div className="sticky top-[calc(var(--header-height)+1px)] z-30 ml-auto hidden h-[90svh] w-(--sidebar-width) flex-col gap-4 overflow-hidden overscroll-none pb-8 xl:flex">
|
||||
<div className="h-(--top-spacing) shrink-0"></div>
|
||||
{doc.toc?.length ? (
|
||||
<div className="no-scrollbar overflow-y-auto px-8">
|
||||
{/* @ts-expect-error - revisit fumadocs types. */}
|
||||
<div className="no-scrollbar flex flex-col gap-8 overflow-y-auto px-8">
|
||||
<DocsTableOfContents toc={doc.toc} />
|
||||
<div className="h-12" />
|
||||
</div>
|
||||
) : null}
|
||||
<div className="flex flex-1 flex-col gap-12 px-6">
|
||||
<div className="hidden flex-1 flex-col gap-6 px-6 xl:flex">
|
||||
<OpenInV0Cta />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
144
apps/v4/app/(app)/docs/changelog/page.tsx
Normal file
144
apps/v4/app/(app)/docs/changelog/page.tsx
Normal file
@@ -0,0 +1,144 @@
|
||||
import Link from "next/link"
|
||||
import { Button } from "@/examples/radix/ui/button"
|
||||
import { mdxComponents } from "@/mdx-components"
|
||||
import { IconRss } from "@tabler/icons-react"
|
||||
|
||||
import { getChangelogPages, type ChangelogPageData } from "@/lib/changelog"
|
||||
import { absoluteUrl } from "@/lib/utils"
|
||||
import { OpenInV0Cta } from "@/components/open-in-v0-cta"
|
||||
|
||||
export const revalidate = false
|
||||
export const dynamic = "force-static"
|
||||
|
||||
export function generateMetadata() {
|
||||
return {
|
||||
title: "Changelog",
|
||||
description: "Latest updates and announcements.",
|
||||
openGraph: {
|
||||
title: "Changelog",
|
||||
description: "Latest updates and announcements.",
|
||||
type: "article",
|
||||
url: absoluteUrl("/docs/changelog"),
|
||||
images: [
|
||||
{
|
||||
url: `/og?title=${encodeURIComponent(
|
||||
"Changelog"
|
||||
)}&description=${encodeURIComponent(
|
||||
"Latest updates and announcements."
|
||||
)}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export default function ChangelogPage() {
|
||||
const pages = getChangelogPages()
|
||||
const latestPages = pages.slice(0, 5)
|
||||
const olderPages = pages.slice(5)
|
||||
|
||||
return (
|
||||
<div
|
||||
data-slot="docs"
|
||||
className="flex scroll-mt-24 items-stretch pb-8 text-[1.05rem] sm:text-[15px] xl:w-full"
|
||||
>
|
||||
<div className="flex min-w-0 flex-1 flex-col">
|
||||
<div className="h-(--top-spacing) shrink-0" />
|
||||
<div className="mx-auto flex w-full max-w-[40rem] min-w-0 flex-1 flex-col gap-6 px-4 py-6 text-neutral-800 md:px-0 lg:py-8 dark:text-neutral-300">
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<h1 className="scroll-m-24 text-4xl font-semibold tracking-tight sm:text-3xl">
|
||||
Changelog
|
||||
</h1>
|
||||
<Button variant="secondary" size="sm" asChild>
|
||||
<a href="/rss.xml" target="_blank" rel="noopener noreferrer">
|
||||
<IconRss />
|
||||
RSS
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-[1.05rem] text-muted-foreground sm:text-base sm:text-balance md:max-w-[80%]">
|
||||
Latest updates and announcements.
|
||||
</p>
|
||||
</div>
|
||||
<div className="w-full flex-1 pb-16 sm:pb-0">
|
||||
{latestPages.map((page) => {
|
||||
const data = page.data as ChangelogPageData
|
||||
const MDX = page.data.body
|
||||
|
||||
return (
|
||||
<article key={page.url} className="mb-12 border-b pb-12">
|
||||
<h2 className="font-heading text-xl font-semibold tracking-tight">
|
||||
{data.title}
|
||||
</h2>
|
||||
<div className="prose-changelog mt-6 *:first:mt-0">
|
||||
<MDX components={mdxComponents} />
|
||||
</div>
|
||||
</article>
|
||||
)
|
||||
})}
|
||||
{olderPages.length > 0 && (
|
||||
<div id="more-updates" className="mb-24 scroll-mt-24">
|
||||
<h2 className="font-heading mb-6 text-xl font-semibold tracking-tight">
|
||||
More Updates
|
||||
</h2>
|
||||
<div className="grid auto-rows-fr gap-3 sm:grid-cols-2">
|
||||
{olderPages.map((page) => {
|
||||
const data = page.data as ChangelogPageData
|
||||
const [date, ...titleParts] = data.title.split(" - ")
|
||||
const title = titleParts.join(" - ")
|
||||
return (
|
||||
<Link
|
||||
key={page.url}
|
||||
href={page.url}
|
||||
className="flex w-full flex-col rounded-xl bg-surface px-4 py-3 text-surface-foreground transition-colors hover:bg-surface/80"
|
||||
>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{date}
|
||||
</span>
|
||||
<span className="text-sm font-medium">{title}</span>
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="sticky top-[calc(var(--header-height)+1px)] z-30 ml-auto hidden h-[90svh] w-72 flex-col gap-4 overflow-hidden overscroll-none pb-8 lg:flex">
|
||||
<div className="h-(--top-spacing) shrink-0"></div>
|
||||
<div className="no-scrollbar flex flex-col gap-8 overflow-y-auto px-8">
|
||||
<div className="flex flex-col gap-2 p-4 pt-0 text-sm">
|
||||
<p className="sticky top-0 h-6 bg-background text-xs font-medium text-muted-foreground">
|
||||
On This Page
|
||||
</p>
|
||||
{latestPages.map((page) => {
|
||||
const data = page.data as ChangelogPageData
|
||||
return (
|
||||
<Link
|
||||
key={page.url}
|
||||
href={page.url}
|
||||
className="text-[0.8rem] text-muted-foreground no-underline transition-colors hover:text-foreground"
|
||||
>
|
||||
{data.title}
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
{olderPages.length > 0 && (
|
||||
<a
|
||||
href="#more-updates"
|
||||
className="text-[0.8rem] text-muted-foreground no-underline transition-colors hover:text-foreground"
|
||||
>
|
||||
More Updates
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="hidden flex-1 flex-col gap-6 px-6 xl:flex">
|
||||
<OpenInV0Cta />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -9,7 +9,14 @@ export default function DocsLayout({
|
||||
}) {
|
||||
return (
|
||||
<div className="container-wrapper flex flex-1 flex-col px-2">
|
||||
<SidebarProvider className="3xl:fixed:container 3xl:fixed:px-3 min-h-min flex-1 items-start px-0 [--sidebar-width:220px] [--top-spacing:0] lg:grid lg:grid-cols-[var(--sidebar-width)_minmax(0,1fr)] lg:[--sidebar-width:240px] lg:[--top-spacing:calc(var(--spacing)*4)]">
|
||||
<SidebarProvider
|
||||
className="min-h-min flex-1 items-start px-0 [--top-spacing:0] lg:grid lg:grid-cols-[var(--sidebar-width)_minmax(0,1fr)] lg:[--top-spacing:calc(var(--spacing)*4)] 3xl:fixed:container 3xl:fixed:px-3"
|
||||
style={
|
||||
{
|
||||
"--sidebar-width": "calc(var(--spacing) * 72)",
|
||||
} as React.CSSProperties
|
||||
}
|
||||
>
|
||||
<DocsSidebar tree={source.pageTree} />
|
||||
<div className="h-full w-full">{children}</div>
|
||||
</SidebarProvider>
|
||||
|
||||
@@ -5,8 +5,14 @@ import * as React from "react"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Icons } from "@/components/icons"
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
import {
|
||||
Field,
|
||||
FieldGroup,
|
||||
FieldLabel,
|
||||
FieldSeparator,
|
||||
} from "@/registry/new-york-v4/ui/field"
|
||||
import { Input } from "@/registry/new-york-v4/ui/input"
|
||||
import { Label } from "@/registry/new-york-v4/ui/label"
|
||||
import { Spinner } from "@/registry/new-york-v4/ui/spinner"
|
||||
|
||||
export function UserAuthForm({
|
||||
className,
|
||||
@@ -26,11 +32,11 @@ export function UserAuthForm({
|
||||
return (
|
||||
<div className={cn("grid gap-6", className)} {...props}>
|
||||
<form onSubmit={onSubmit}>
|
||||
<div className="grid gap-2">
|
||||
<div className="grid gap-1">
|
||||
<Label className="sr-only" htmlFor="email">
|
||||
<FieldGroup>
|
||||
<Field>
|
||||
<FieldLabel className="sr-only" htmlFor="email">
|
||||
Email
|
||||
</Label>
|
||||
</FieldLabel>
|
||||
<Input
|
||||
id="email"
|
||||
placeholder="name@example.com"
|
||||
@@ -40,31 +46,18 @@ export function UserAuthForm({
|
||||
autoCorrect="off"
|
||||
disabled={isLoading}
|
||||
/>
|
||||
</div>
|
||||
<Button disabled={isLoading}>
|
||||
{isLoading && (
|
||||
<Icons.spinner className="mr-2 h-4 w-4 animate-spin" />
|
||||
)}
|
||||
Sign In with Email
|
||||
</Button>
|
||||
</div>
|
||||
</Field>
|
||||
<Field>
|
||||
<Button disabled={isLoading}>
|
||||
{isLoading && <Spinner />}
|
||||
Sign In with Email
|
||||
</Button>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</form>
|
||||
<div className="relative">
|
||||
<div className="absolute inset-0 flex items-center">
|
||||
<span className="w-full border-t" />
|
||||
</div>
|
||||
<div className="relative flex justify-center text-xs uppercase">
|
||||
<span className="bg-background text-muted-foreground px-2">
|
||||
Or continue with
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<FieldSeparator>Or continue with</FieldSeparator>
|
||||
<Button variant="outline" type="button" disabled={isLoading}>
|
||||
{isLoading ? (
|
||||
<Icons.spinner className="mr-2 h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<Icons.gitHub className="mr-2 h-4 w-4" />
|
||||
)}{" "}
|
||||
{isLoading ? <Spinner /> : <Icons.gitHub className="mr-2 h-4 w-4" />}{" "}
|
||||
GitHub
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { Metadata } from "next"
|
||||
import { type Metadata } from "next"
|
||||
import Image from "next/image"
|
||||
import Link from "next/link"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { buttonVariants } from "@/registry/new-york-v4/ui/button"
|
||||
import { FieldDescription } from "@/registry/new-york-v4/ui/field"
|
||||
import { UserAuthForm } from "@/app/(app)/examples/authentication/components/user-auth-form"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
@@ -42,8 +43,8 @@ export default function AuthenticationPage() {
|
||||
>
|
||||
Login
|
||||
</Link>
|
||||
<div className="text-primary relative hidden h-full flex-col p-10 lg:flex dark:border-r">
|
||||
<div className="bg-primary/5 absolute inset-0" />
|
||||
<div className="relative hidden h-full flex-col p-10 text-primary lg:flex dark:border-r">
|
||||
<div className="absolute inset-0 bg-primary/5" />
|
||||
<div className="relative z-20 flex items-center text-lg font-medium">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@@ -73,28 +74,16 @@ export default function AuthenticationPage() {
|
||||
<h1 className="text-2xl font-semibold tracking-tight">
|
||||
Create an account
|
||||
</h1>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Enter your email below to create your account
|
||||
</p>
|
||||
</div>
|
||||
<UserAuthForm />
|
||||
<p className="text-muted-foreground px-8 text-center text-sm">
|
||||
<FieldDescription className="px-6 text-center">
|
||||
By clicking continue, you agree to our{" "}
|
||||
<Link
|
||||
href="/terms"
|
||||
className="hover:text-primary underline underline-offset-4"
|
||||
>
|
||||
Terms of Service
|
||||
</Link>{" "}
|
||||
and{" "}
|
||||
<Link
|
||||
href="/privacy"
|
||||
className="hover:text-primary underline underline-offset-4"
|
||||
>
|
||||
Privacy Policy
|
||||
</Link>
|
||||
.
|
||||
</p>
|
||||
<Link href="/terms">Terms of Service</Link> and{" "}
|
||||
<Link href="/privacy">Privacy Policy</Link>.
|
||||
</FieldDescription>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -159,10 +159,10 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton
|
||||
asChild
|
||||
className="data-[slot=sidebar-menu-button]:!p-1.5"
|
||||
className="data-[slot=sidebar-menu-button]:p-1.5!"
|
||||
>
|
||||
<Link href="#">
|
||||
<IconInnerShadowTop className="!size-5" />
|
||||
<IconInnerShadowTop className="size-5!" />
|
||||
<span className="text-base font-semibold">Acme Inc.</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
|
||||
@@ -13,10 +13,10 @@ import {
|
||||
CardTitle,
|
||||
} from "@/registry/new-york-v4/ui/card"
|
||||
import {
|
||||
ChartConfig,
|
||||
ChartContainer,
|
||||
ChartTooltip,
|
||||
ChartTooltipContent,
|
||||
type ChartConfig,
|
||||
} from "@/registry/new-york-v4/ui/chart"
|
||||
import {
|
||||
Select,
|
||||
@@ -142,13 +142,7 @@ const chartConfig = {
|
||||
|
||||
export function ChartAreaInteractive() {
|
||||
const isMobile = useIsMobile()
|
||||
const [timeRange, setTimeRange] = React.useState("90d")
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isMobile) {
|
||||
setTimeRange("7d")
|
||||
}
|
||||
}, [isMobile])
|
||||
const [timeRange, setTimeRange] = React.useState("7d")
|
||||
|
||||
const filteredData = chartData.filter((item) => {
|
||||
const date = new Date(item.date)
|
||||
@@ -180,7 +174,7 @@ export function ChartAreaInteractive() {
|
||||
value={timeRange}
|
||||
onValueChange={setTimeRange}
|
||||
variant="outline"
|
||||
className="hidden *:data-[slot=toggle-group-item]:!px-4 @[767px]/card:flex"
|
||||
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>
|
||||
|
||||
@@ -35,8 +35,6 @@ import {
|
||||
IconTrendingUp,
|
||||
} from "@tabler/icons-react"
|
||||
import {
|
||||
ColumnDef,
|
||||
ColumnFiltersState,
|
||||
flexRender,
|
||||
getCoreRowModel,
|
||||
getFacetedRowModel,
|
||||
@@ -44,10 +42,12 @@ import {
|
||||
getFilteredRowModel,
|
||||
getPaginationRowModel,
|
||||
getSortedRowModel,
|
||||
Row,
|
||||
SortingState,
|
||||
useReactTable,
|
||||
VisibilityState,
|
||||
type ColumnDef,
|
||||
type ColumnFiltersState,
|
||||
type Row,
|
||||
type SortingState,
|
||||
type VisibilityState,
|
||||
} from "@tanstack/react-table"
|
||||
import { Area, AreaChart, CartesianGrid, XAxis } from "recharts"
|
||||
import { toast } from "sonner"
|
||||
@@ -57,10 +57,10 @@ 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,
|
||||
type ChartConfig,
|
||||
} from "@/registry/new-york-v4/ui/chart"
|
||||
import { Checkbox } from "@/registry/new-york-v4/ui/checkbox"
|
||||
import {
|
||||
@@ -128,9 +128,9 @@ function DragHandle({ id }: { id: number }) {
|
||||
{...listeners}
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="text-muted-foreground size-7 hover:bg-transparent"
|
||||
className="size-7 text-muted-foreground hover:bg-transparent"
|
||||
>
|
||||
<IconGripVertical className="text-muted-foreground size-3" />
|
||||
<IconGripVertical className="size-3 text-muted-foreground" />
|
||||
<span className="sr-only">Drag to reorder</span>
|
||||
</Button>
|
||||
)
|
||||
@@ -181,7 +181,7 @@ const columns: ColumnDef<z.infer<typeof schema>>[] = [
|
||||
header: "Section Type",
|
||||
cell: ({ row }) => (
|
||||
<div className="w-32">
|
||||
<Badge variant="outline" className="text-muted-foreground px-1.5">
|
||||
<Badge variant="outline" className="px-1.5 text-muted-foreground">
|
||||
{row.original.type}
|
||||
</Badge>
|
||||
</div>
|
||||
@@ -191,7 +191,7 @@ const columns: ColumnDef<z.infer<typeof schema>>[] = [
|
||||
accessorKey: "status",
|
||||
header: "Status",
|
||||
cell: ({ row }) => (
|
||||
<Badge variant="outline" className="text-muted-foreground px-1.5">
|
||||
<Badge variant="outline" className="px-1.5 text-muted-foreground">
|
||||
{row.original.status === "Done" ? (
|
||||
<IconCircleCheckFilled className="fill-green-500 dark:fill-green-400" />
|
||||
) : (
|
||||
@@ -219,7 +219,7 @@ const columns: ColumnDef<z.infer<typeof schema>>[] = [
|
||||
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"
|
||||
className="h-8 w-16 border-transparent bg-transparent text-right shadow-none hover:bg-input/30 focus-visible:border focus-visible:bg-background dark:bg-transparent dark:hover:bg-input/30 dark:focus-visible:bg-input/30"
|
||||
defaultValue={row.original.target}
|
||||
id={`${row.original.id}-target`}
|
||||
/>
|
||||
@@ -244,7 +244,7 @@ const columns: ColumnDef<z.infer<typeof schema>>[] = [
|
||||
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"
|
||||
className="h-8 w-16 border-transparent bg-transparent text-right shadow-none hover:bg-input/30 focus-visible:border focus-visible:bg-background dark:bg-transparent dark:hover:bg-input/30 dark:focus-visible:bg-input/30"
|
||||
defaultValue={row.original.limit}
|
||||
id={`${row.original.id}-limit`}
|
||||
/>
|
||||
@@ -292,7 +292,7 @@ const columns: ColumnDef<z.infer<typeof schema>>[] = [
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="data-[state=open]:bg-muted text-muted-foreground flex size-8"
|
||||
className="flex size-8 text-muted-foreground data-[state=open]:bg-muted"
|
||||
size="icon"
|
||||
>
|
||||
<IconDotsVertical />
|
||||
@@ -425,7 +425,7 @@ export function DataTable({
|
||||
<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">
|
||||
<TabsList className="hidden **:data-[slot=badge]:size-5 **:data-[slot=badge]:rounded-full **:data-[slot=badge]:bg-muted-foreground/30 **:data-[slot=badge]:px-1 @4xl/main:flex">
|
||||
<TabsTrigger value="outline">Outline</TabsTrigger>
|
||||
<TabsTrigger value="past-performance">
|
||||
Past Performance <Badge variant="secondary">3</Badge>
|
||||
@@ -488,7 +488,7 @@ export function DataTable({
|
||||
id={sortableId}
|
||||
>
|
||||
<Table>
|
||||
<TableHeader className="bg-muted sticky top-0 z-10">
|
||||
<TableHeader className="sticky top-0 z-10 bg-muted">
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<TableRow key={headerGroup.id}>
|
||||
{headerGroup.headers.map((header) => {
|
||||
@@ -531,7 +531,7 @@ export function DataTable({
|
||||
</DndContext>
|
||||
</div>
|
||||
<div className="flex items-center justify-between px-4">
|
||||
<div className="text-muted-foreground hidden flex-1 text-sm lg:flex">
|
||||
<div className="hidden flex-1 text-sm text-muted-foreground lg:flex">
|
||||
{table.getFilteredSelectedRowModel().rows.length} of{" "}
|
||||
{table.getFilteredRowModel().rows.length} row(s) selected.
|
||||
</div>
|
||||
@@ -653,7 +653,7 @@ function TableCellViewer({ item }: { item: z.infer<typeof schema> }) {
|
||||
return (
|
||||
<Drawer direction={isMobile ? "bottom" : "right"}>
|
||||
<DrawerTrigger asChild>
|
||||
<Button variant="link" className="text-foreground w-fit px-0 text-left">
|
||||
<Button variant="link" className="w-fit px-0 text-left text-foreground">
|
||||
{item.header}
|
||||
</Button>
|
||||
</DrawerTrigger>
|
||||
|
||||
@@ -52,7 +52,7 @@ export function NavDocuments({
|
||||
<DropdownMenuTrigger asChild>
|
||||
<SidebarMenuAction
|
||||
showOnHover
|
||||
className="data-[state=open]:bg-accent rounded-sm"
|
||||
className="rounded-sm data-[state=open]:bg-accent"
|
||||
>
|
||||
<IconDots />
|
||||
<span className="sr-only">More</span>
|
||||
|
||||
@@ -55,7 +55,7 @@ export function NavUser({
|
||||
</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">
|
||||
<span className="truncate text-xs text-muted-foreground">
|
||||
{user.email}
|
||||
</span>
|
||||
</div>
|
||||
@@ -76,7 +76,7 @@ export function NavUser({
|
||||
</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">
|
||||
<span className="truncate text-xs text-muted-foreground">
|
||||
{user.email}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
|
||||
export function SiteHeader() {
|
||||
return (
|
||||
<header className="bg-background/90 sticky top-0 z-10 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)">
|
||||
<header className="sticky top-0 z-10 flex h-(--header-height) shrink-0 items-center gap-2 border-b bg-background/90 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">
|
||||
<h1 className="text-base font-medium">Documents</h1>
|
||||
<div className="ml-auto flex items-center gap-2">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Metadata } from "next"
|
||||
import { type Metadata } from "next"
|
||||
import Link from "next/link"
|
||||
|
||||
import { Announcement } from "@/components/announcement"
|
||||
@@ -16,8 +16,9 @@ import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
export const dynamic = "force-static"
|
||||
export const revalidate = false
|
||||
|
||||
const title = "Examples"
|
||||
const description = "Check out some examples app built using the components."
|
||||
const title = "The Foundation for your Design System"
|
||||
const description =
|
||||
"A set of beautifully designed components that you can customize, extend, and build on. Start here then make it your own. Open Source. Open Code."
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title,
|
||||
@@ -52,28 +53,24 @@ export default function ExamplesLayout({
|
||||
<>
|
||||
<PageHeader>
|
||||
<Announcement />
|
||||
<PageHeaderHeading>Build your Component Library</PageHeaderHeading>
|
||||
<PageHeaderDescription>
|
||||
A set of beautifully-designed, accessible components and a code
|
||||
distribution platform. Works with your favorite frameworks. Open
|
||||
Source. Open Code.
|
||||
</PageHeaderDescription>
|
||||
<PageHeaderHeading className="max-w-4xl">{title}</PageHeaderHeading>
|
||||
<PageHeaderDescription>{description}</PageHeaderDescription>
|
||||
<PageActions>
|
||||
<Button asChild size="sm">
|
||||
<Link href="/docs">Get Started</Link>
|
||||
<Link href="/docs/installation">Get Started</Link>
|
||||
</Button>
|
||||
<Button asChild size="sm" variant="ghost">
|
||||
<Link href="/blocks">Browse Blocks</Link>
|
||||
<Link href="/docs/components">View Components</Link>
|
||||
</Button>
|
||||
</PageActions>
|
||||
</PageHeader>
|
||||
<PageNav id="examples">
|
||||
<ExamplesNav className="[&>a:first-child]:text-primary flex-1 overflow-hidden" />
|
||||
<ThemeSelector className="mr-4 hidden md:block" />
|
||||
<PageNav id="examples" className="hidden md:flex">
|
||||
<ExamplesNav className="flex-1 overflow-hidden [&>a:first-child]:text-primary" />
|
||||
<ThemeSelector className="mr-4 hidden md:flex" />
|
||||
</PageNav>
|
||||
<div className="container-wrapper section-soft flex flex-1 flex-col pb-6">
|
||||
<div className="theme-container container flex flex-1 scroll-mt-20 flex-col">
|
||||
<div className="bg-background flex flex-col overflow-hidden rounded-lg border bg-clip-padding md:flex-1 xl:rounded-xl">
|
||||
<div className="container-wrapper flex flex-1 flex-col section-soft pb-6">
|
||||
<div className="container flex flex-1 scroll-mt-20 flex-col theme-container">
|
||||
<div className="flex flex-col overflow-hidden rounded-lg border bg-background bg-clip-padding has-[[data-slot=rtl-components]]:overflow-visible has-[[data-slot=rtl-components]]:border-0 has-[[data-slot=rtl-components]]:bg-transparent md:flex-1 xl:rounded-xl">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -76,7 +76,7 @@ export function CodeViewer() {
|
||||
</pre>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Your API Key can be found here. You should use environment
|
||||
variables or a secret management tool to expose your key to your
|
||||
applications.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { SliderProps } from "@radix-ui/react-slider"
|
||||
import type { Slider as SliderPrimitive } from "radix-ui"
|
||||
|
||||
import {
|
||||
HoverCard,
|
||||
@@ -12,7 +12,9 @@ import { Label } from "@/registry/new-york-v4/ui/label"
|
||||
import { Slider } from "@/registry/new-york-v4/ui/slider"
|
||||
|
||||
interface MaxLengthSelectorProps {
|
||||
defaultValue: SliderProps["defaultValue"]
|
||||
defaultValue: React.ComponentProps<
|
||||
typeof SliderPrimitive.Root
|
||||
>["defaultValue"]
|
||||
}
|
||||
|
||||
export function MaxLengthSelector({ defaultValue }: MaxLengthSelectorProps) {
|
||||
@@ -25,7 +27,7 @@ export function MaxLengthSelector({ defaultValue }: MaxLengthSelectorProps) {
|
||||
<div className="grid gap-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label htmlFor="maxlength">Maximum Length</Label>
|
||||
<span className="text-muted-foreground hover:border-border w-12 rounded-md border border-transparent px-2 py-0.5 text-right text-sm">
|
||||
<span className="w-12 rounded-md border border-transparent px-2 py-0.5 text-right text-sm text-muted-foreground hover:border-border">
|
||||
{value}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { PopoverProps } from "@radix-ui/react-popover"
|
||||
import { Check, ChevronsUpDown } from "lucide-react"
|
||||
import type { Popover as PopoverPrimitive } from "radix-ui"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { useMutationObserver } from "@/hooks/use-mutation-observer"
|
||||
@@ -27,9 +27,10 @@ import {
|
||||
PopoverTrigger,
|
||||
} from "@/registry/new-york-v4/ui/popover"
|
||||
|
||||
import { Model, ModelType } from "../data/models"
|
||||
import { type Model, type ModelType } from "../data/models"
|
||||
|
||||
interface ModelSelectorProps extends PopoverProps {
|
||||
interface ModelSelectorProps
|
||||
extends React.ComponentProps<typeof PopoverPrimitive.Root> {
|
||||
types: readonly ModelType[]
|
||||
models: Model[]
|
||||
}
|
||||
@@ -77,7 +78,7 @@ export function ModelSelector({ models, types, ...props }: ModelSelectorProps) {
|
||||
>
|
||||
<div className="grid gap-2">
|
||||
<h4 className="leading-none font-medium">{peekedModel.name}</h4>
|
||||
<div className="text-muted-foreground text-sm">
|
||||
<div className="text-sm text-muted-foreground">
|
||||
{peekedModel.description}
|
||||
</div>
|
||||
{peekedModel.strengths ? (
|
||||
@@ -85,7 +86,7 @@ export function ModelSelector({ models, types, ...props }: ModelSelectorProps) {
|
||||
<h5 className="text-sm leading-none font-medium">
|
||||
Strengths
|
||||
</h5>
|
||||
<ul className="text-muted-foreground text-sm">
|
||||
<ul className="text-sm text-muted-foreground">
|
||||
{peekedModel.strengths}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { Dialog } from "@radix-ui/react-dialog"
|
||||
import { MoreHorizontal } from "lucide-react"
|
||||
import { toast } from "sonner"
|
||||
|
||||
@@ -16,6 +15,7 @@ import {
|
||||
} from "@/registry/new-york-v4/ui/alert-dialog"
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
import {
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
@@ -70,7 +70,7 @@ export function PresetActions() {
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="py-6">
|
||||
<h4 className="text-muted-foreground text-sm">
|
||||
<h4 className="text-sm text-muted-foreground">
|
||||
Playground Warnings
|
||||
</h4>
|
||||
<div className="flex items-start justify-between gap-4 pt-3">
|
||||
@@ -79,7 +79,7 @@ export function PresetActions() {
|
||||
<span className="font-semibold">
|
||||
Show a warning when content is flagged
|
||||
</span>
|
||||
<span className="text-muted-foreground text-sm">
|
||||
<span className="text-sm text-muted-foreground">
|
||||
A warning will be shown when sexual, hateful, violent or
|
||||
self-harm content is detected.
|
||||
</span>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { PopoverProps } from "@radix-ui/react-popover"
|
||||
import { Check, ChevronsUpDown } from "lucide-react"
|
||||
import type { Popover as PopoverPrimitive } from "radix-ui"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
@@ -21,9 +21,10 @@ import {
|
||||
PopoverTrigger,
|
||||
} from "@/registry/new-york-v4/ui/popover"
|
||||
|
||||
import { Preset } from "../data/presets"
|
||||
import { type Preset } from "../data/presets"
|
||||
|
||||
interface PresetSelectorProps extends PopoverProps {
|
||||
interface PresetSelectorProps
|
||||
extends React.ComponentProps<typeof PopoverPrimitive.Root> {
|
||||
presets: Preset[]
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ export function PresetShare() {
|
||||
<PopoverContent align="end" className="flex w-[520px] flex-col gap-4">
|
||||
<div className="flex flex-col gap-1 text-center sm:text-left">
|
||||
<h3 className="text-lg font-semibold">Share preset</h3>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Anyone who has this link and an OpenAI account will be able to view
|
||||
this.
|
||||
</p>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { SliderProps } from "@radix-ui/react-slider"
|
||||
import type { Slider as SliderPrimitive } from "radix-ui"
|
||||
|
||||
import {
|
||||
HoverCard,
|
||||
@@ -12,7 +12,9 @@ import { Label } from "@/registry/new-york-v4/ui/label"
|
||||
import { Slider } from "@/registry/new-york-v4/ui/slider"
|
||||
|
||||
interface TemperatureSelectorProps {
|
||||
defaultValue: SliderProps["defaultValue"]
|
||||
defaultValue: React.ComponentProps<
|
||||
typeof SliderPrimitive.Root
|
||||
>["defaultValue"]
|
||||
}
|
||||
|
||||
export function TemperatureSelector({
|
||||
@@ -27,7 +29,7 @@ export function TemperatureSelector({
|
||||
<div className="grid gap-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label htmlFor="temperature">Temperature</Label>
|
||||
<span className="text-muted-foreground hover:border-border w-12 rounded-md border border-transparent px-2 py-0.5 text-right text-sm">
|
||||
<span className="w-12 rounded-md border border-transparent px-2 py-0.5 text-right text-sm text-muted-foreground hover:border-border">
|
||||
{value}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { SliderProps } from "@radix-ui/react-slider"
|
||||
import type { Slider as SliderPrimitive } from "radix-ui"
|
||||
|
||||
import {
|
||||
HoverCard,
|
||||
@@ -12,7 +12,9 @@ import { Label } from "@/registry/new-york-v4/ui/label"
|
||||
import { Slider } from "@/registry/new-york-v4/ui/slider"
|
||||
|
||||
interface TopPSelectorProps {
|
||||
defaultValue: SliderProps["defaultValue"]
|
||||
defaultValue: React.ComponentProps<
|
||||
typeof SliderPrimitive.Root
|
||||
>["defaultValue"]
|
||||
}
|
||||
|
||||
export function TopPSelector({ defaultValue }: TopPSelectorProps) {
|
||||
@@ -25,7 +27,7 @@ export function TopPSelector({ defaultValue }: TopPSelectorProps) {
|
||||
<div className="grid gap-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label htmlFor="top-p">Top P</Label>
|
||||
<span className="text-muted-foreground hover:border-border w-12 rounded-md border border-transparent px-2 py-0.5 text-right text-sm">
|
||||
<span className="w-12 rounded-md border border-transparent px-2 py-0.5 text-right text-sm text-muted-foreground hover:border-border">
|
||||
{value}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Metadata } from "next"
|
||||
import { type Metadata } from "next"
|
||||
import Image from "next/image"
|
||||
import { RotateCcw } from "lucide-react"
|
||||
|
||||
@@ -278,7 +278,7 @@ export default function PlaygroundPage() {
|
||||
placeholder="We're writing to [inset]. Congrats from OpenAI!"
|
||||
className="h-full min-h-[300px] p-4 lg:min-h-[700px] xl:min-h-[700px]"
|
||||
/>
|
||||
<div className="bg-muted rounded-md border"></div>
|
||||
<div className="rounded-md border bg-muted"></div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button>Submit</Button>
|
||||
@@ -312,7 +312,7 @@ export default function PlaygroundPage() {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-muted min-h-[400px] rounded-md border lg:min-h-[700px]" />
|
||||
<div className="min-h-[400px] rounded-md border bg-muted lg:min-h-[700px]" />
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button>Submit</Button>
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { Button } from "@/examples/base/ui-rtl/button"
|
||||
import { ButtonGroup } from "@/examples/base/ui-rtl/button-group"
|
||||
import {
|
||||
Field,
|
||||
FieldContent,
|
||||
FieldDescription,
|
||||
FieldGroup,
|
||||
FieldLabel,
|
||||
FieldLegend,
|
||||
FieldSeparator,
|
||||
FieldSet,
|
||||
FieldTitle,
|
||||
} from "@/examples/base/ui-rtl/field"
|
||||
import { Input } from "@/examples/base/ui-rtl/input"
|
||||
import { RadioGroup, RadioGroupItem } from "@/examples/base/ui-rtl/radio-group"
|
||||
import { Switch } from "@/examples/base/ui-rtl/switch"
|
||||
import { IconMinus, IconPlus } from "@tabler/icons-react"
|
||||
|
||||
import { useLanguageContext } from "@/components/language-selector"
|
||||
|
||||
const translations = {
|
||||
ar: {
|
||||
dir: "rtl" as const,
|
||||
computeEnvironment: "بيئة الحوسبة",
|
||||
computeDescription: "اختر بيئة الحوسبة لمجموعتك.",
|
||||
kubernetes: "كوبرنيتس",
|
||||
kubernetesDescription:
|
||||
"تشغيل أحمال عمل GPU على مجموعة مُهيأة بـ K8s. هذا هو الافتراضي.",
|
||||
virtualMachine: "جهاز افتراضي",
|
||||
vmDescription: "الوصول إلى مجموعة VM مُهيأة لتشغيل أحمال العمل. (قريبًا)",
|
||||
numberOfGpus: "عدد وحدات GPU",
|
||||
gpuDescription: "يمكنك إضافة المزيد لاحقًا.",
|
||||
decrement: "إنقاص",
|
||||
increment: "زيادة",
|
||||
wallpaperTinting: "تلوين الخلفية",
|
||||
wallpaperDescription: "السماح بتلوين الخلفية.",
|
||||
},
|
||||
he: {
|
||||
dir: "rtl" as const,
|
||||
computeEnvironment: "סביבת מחשוב",
|
||||
computeDescription: "בחר את סביבת המחשוב לאשכול שלך.",
|
||||
kubernetes: "קוברנטיס",
|
||||
kubernetesDescription:
|
||||
"הפעל עומסי עבודה של GPU באשכול מוגדר K8s. זו ברירת המחדל.",
|
||||
virtualMachine: "מכונה וירטואלית",
|
||||
vmDescription: "גש לאשכול VM מוגדר להפעלת עומסי עבודה. (בקרוב)",
|
||||
numberOfGpus: "מספר GPUs",
|
||||
gpuDescription: "תוכל להוסיף עוד מאוחר יותר.",
|
||||
decrement: "הפחת",
|
||||
increment: "הגדל",
|
||||
wallpaperTinting: "צביעת טפט",
|
||||
wallpaperDescription: "אפשר לטפט להיצבע.",
|
||||
},
|
||||
}
|
||||
|
||||
export function AppearanceSettings() {
|
||||
const context = useLanguageContext()
|
||||
const lang = context?.language === "he" ? "he" : "ar"
|
||||
const t = translations[lang]
|
||||
const [gpuCount, setGpuCount] = React.useState(8)
|
||||
|
||||
const handleGpuAdjustment = React.useCallback((adjustment: number) => {
|
||||
setGpuCount((prevCount) =>
|
||||
Math.max(1, Math.min(99, prevCount + adjustment))
|
||||
)
|
||||
}, [])
|
||||
|
||||
const handleGpuInputChange = React.useCallback(
|
||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = parseInt(e.target.value, 10)
|
||||
if (!isNaN(value) && value >= 1 && value <= 99) {
|
||||
setGpuCount(value)
|
||||
}
|
||||
},
|
||||
[]
|
||||
)
|
||||
|
||||
return (
|
||||
<div dir={t.dir}>
|
||||
<FieldSet>
|
||||
<FieldGroup>
|
||||
<FieldSet>
|
||||
<FieldLegend>{t.computeEnvironment}</FieldLegend>
|
||||
<FieldDescription>{t.computeDescription}</FieldDescription>
|
||||
<RadioGroup defaultValue="kubernetes">
|
||||
<FieldLabel htmlFor="rtl-kubernetes">
|
||||
<Field orientation="horizontal">
|
||||
<FieldContent>
|
||||
<FieldTitle>{t.kubernetes}</FieldTitle>
|
||||
<FieldDescription>
|
||||
{t.kubernetesDescription}
|
||||
</FieldDescription>
|
||||
</FieldContent>
|
||||
<RadioGroupItem
|
||||
value="kubernetes"
|
||||
id="rtl-kubernetes"
|
||||
aria-label={t.kubernetes}
|
||||
/>
|
||||
</Field>
|
||||
</FieldLabel>
|
||||
<FieldLabel htmlFor="rtl-vm">
|
||||
<Field orientation="horizontal">
|
||||
<FieldContent>
|
||||
<FieldTitle>{t.virtualMachine}</FieldTitle>
|
||||
<FieldDescription>{t.vmDescription}</FieldDescription>
|
||||
</FieldContent>
|
||||
<RadioGroupItem
|
||||
value="vm"
|
||||
id="rtl-vm"
|
||||
aria-label={t.virtualMachine}
|
||||
/>
|
||||
</Field>
|
||||
</FieldLabel>
|
||||
</RadioGroup>
|
||||
</FieldSet>
|
||||
<FieldSeparator />
|
||||
<Field orientation="horizontal">
|
||||
<FieldContent>
|
||||
<FieldLabel htmlFor="rtl-gpu-count">{t.numberOfGpus}</FieldLabel>
|
||||
<FieldDescription>{t.gpuDescription}</FieldDescription>
|
||||
</FieldContent>
|
||||
<ButtonGroup>
|
||||
<Input
|
||||
id="rtl-gpu-count"
|
||||
value={gpuCount}
|
||||
onChange={handleGpuInputChange}
|
||||
size={3}
|
||||
className="h-7 w-14! font-mono"
|
||||
maxLength={3}
|
||||
/>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon-sm"
|
||||
type="button"
|
||||
aria-label={t.decrement}
|
||||
onClick={() => handleGpuAdjustment(-1)}
|
||||
disabled={gpuCount <= 1}
|
||||
>
|
||||
<IconMinus />
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon-sm"
|
||||
type="button"
|
||||
aria-label={t.increment}
|
||||
onClick={() => handleGpuAdjustment(1)}
|
||||
disabled={gpuCount >= 99}
|
||||
>
|
||||
<IconPlus />
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</Field>
|
||||
<FieldSeparator />
|
||||
<Field orientation="horizontal">
|
||||
<FieldContent>
|
||||
<FieldLabel htmlFor="rtl-tinting">
|
||||
{t.wallpaperTinting}
|
||||
</FieldLabel>
|
||||
<FieldDescription>{t.wallpaperDescription}</FieldDescription>
|
||||
</FieldContent>
|
||||
<Switch id="rtl-tinting" defaultChecked />
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</FieldSet>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
179
apps/v4/app/(app)/examples/rtl/components/button-group-demo.tsx
Normal file
179
apps/v4/app/(app)/examples/rtl/components/button-group-demo.tsx
Normal file
@@ -0,0 +1,179 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { Button } from "@/examples/base/ui-rtl/button"
|
||||
import { ButtonGroup } from "@/examples/base/ui-rtl/button-group"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuPortal,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuSub,
|
||||
DropdownMenuSubContent,
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/examples/base/ui-rtl/dropdown-menu"
|
||||
import {
|
||||
ArchiveIcon,
|
||||
ArrowLeftIcon,
|
||||
CalendarPlusIcon,
|
||||
ClockIcon,
|
||||
ListFilterIcon,
|
||||
MailCheckIcon,
|
||||
MoreHorizontalIcon,
|
||||
TagIcon,
|
||||
Trash2Icon,
|
||||
} from "lucide-react"
|
||||
|
||||
import { useLanguageContext } from "@/components/language-selector"
|
||||
|
||||
const translations = {
|
||||
ar: {
|
||||
dir: "rtl" as const,
|
||||
goBack: "رجوع",
|
||||
archive: "أرشفة",
|
||||
report: "إبلاغ",
|
||||
snooze: "تأجيل",
|
||||
moreOptions: "خيارات أخرى",
|
||||
markAsRead: "تحديد كمقروء",
|
||||
addToCalendar: "إضافة إلى التقويم",
|
||||
addToList: "إضافة إلى القائمة",
|
||||
labelAs: "تصنيف كـ...",
|
||||
personal: "شخصي",
|
||||
work: "عمل",
|
||||
other: "أخرى",
|
||||
trash: "حذف",
|
||||
},
|
||||
he: {
|
||||
dir: "rtl" as const,
|
||||
goBack: "חזור",
|
||||
archive: "ארכיון",
|
||||
report: "דווח",
|
||||
snooze: "נודניק",
|
||||
moreOptions: "אפשרויות נוספות",
|
||||
markAsRead: "סמן כנקרא",
|
||||
addToCalendar: "הוסף ליומן",
|
||||
addToList: "הוסף לרשימה",
|
||||
labelAs: "תייג כ...",
|
||||
personal: "אישי",
|
||||
work: "עבודה",
|
||||
other: "אחר",
|
||||
trash: "מחק",
|
||||
},
|
||||
}
|
||||
|
||||
export function ButtonGroupDemo() {
|
||||
const context = useLanguageContext()
|
||||
const lang = context?.language === "he" ? "he" : "ar"
|
||||
const t = translations[lang]
|
||||
const [label, setLabel] = React.useState("personal")
|
||||
|
||||
return (
|
||||
<div dir={t.dir}>
|
||||
<ButtonGroup>
|
||||
<ButtonGroup className="hidden sm:flex">
|
||||
<Button variant="outline" size="icon-sm" aria-label={t.goBack}>
|
||||
<ArrowLeftIcon className="rtl:rotate-180" />
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup>
|
||||
<Button variant="outline" size="sm">
|
||||
{t.archive}
|
||||
</Button>
|
||||
<Button variant="outline" size="sm">
|
||||
{t.report}
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup>
|
||||
<Button variant="outline" size="sm">
|
||||
{t.snooze}
|
||||
</Button>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger
|
||||
render={
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon-sm"
|
||||
aria-label={t.moreOptions}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<MoreHorizontalIcon />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
align="start"
|
||||
dir={t.dir}
|
||||
data-lang={lang}
|
||||
className="w-44"
|
||||
>
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem>
|
||||
<MailCheckIcon />
|
||||
{t.markAsRead}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<ArchiveIcon />
|
||||
{t.archive}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem>
|
||||
<ClockIcon />
|
||||
{t.snooze}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<CalendarPlusIcon />
|
||||
{t.addToCalendar}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<ListFilterIcon />
|
||||
{t.addToList}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger>
|
||||
<TagIcon />
|
||||
{t.labelAs}
|
||||
</DropdownMenuSubTrigger>
|
||||
<DropdownMenuPortal>
|
||||
<DropdownMenuSubContent
|
||||
side="left"
|
||||
dir={t.dir}
|
||||
data-lang={lang}
|
||||
>
|
||||
<DropdownMenuRadioGroup
|
||||
value={label}
|
||||
onValueChange={setLabel}
|
||||
>
|
||||
<DropdownMenuRadioItem value="personal">
|
||||
{t.personal}
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="work">
|
||||
{t.work}
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="other">
|
||||
{t.other}
|
||||
</DropdownMenuRadioItem>
|
||||
</DropdownMenuRadioGroup>
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuPortal>
|
||||
</DropdownMenuSub>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem variant="destructive">
|
||||
<Trash2Icon />
|
||||
{t.trash}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</ButtonGroup>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { Button } from "@/examples/base/ui-rtl/button"
|
||||
import { ButtonGroup } from "@/examples/base/ui-rtl/button-group"
|
||||
import {
|
||||
InputGroup,
|
||||
InputGroupAddon,
|
||||
InputGroupButton,
|
||||
InputGroupInput,
|
||||
} from "@/examples/base/ui-rtl/input-group"
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/examples/base/ui-rtl/tooltip"
|
||||
import { AudioLinesIcon, PlusIcon } from "lucide-react"
|
||||
|
||||
import { useLanguageContext } from "@/components/language-selector"
|
||||
|
||||
const translations = {
|
||||
ar: {
|
||||
dir: "rtl" as const,
|
||||
add: "إضافة",
|
||||
voicePlaceholder: "سجل وأرسل صوتًا...",
|
||||
messagePlaceholder: "أرسل رسالة...",
|
||||
voiceMode: "الوضع الصوتي",
|
||||
},
|
||||
he: {
|
||||
dir: "rtl" as const,
|
||||
add: "הוסף",
|
||||
voicePlaceholder: "הקלט ושלח אודיו...",
|
||||
messagePlaceholder: "שלח הודעה...",
|
||||
voiceMode: "מצב קולי",
|
||||
},
|
||||
}
|
||||
|
||||
export function ButtonGroupInputGroup() {
|
||||
const context = useLanguageContext()
|
||||
const lang = context?.language === "he" ? "he" : "ar"
|
||||
const t = translations[lang]
|
||||
const [voiceEnabled, setVoiceEnabled] = React.useState(false)
|
||||
|
||||
return (
|
||||
<ButtonGroup dir={t.dir}>
|
||||
<ButtonGroup>
|
||||
<Button variant="outline" size="icon" aria-label={t.add}>
|
||||
<PlusIcon />
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup className="flex-1">
|
||||
<InputGroup>
|
||||
<InputGroupInput
|
||||
placeholder={
|
||||
voiceEnabled ? t.voicePlaceholder : t.messagePlaceholder
|
||||
}
|
||||
disabled={voiceEnabled}
|
||||
/>
|
||||
<InputGroupAddon align="inline-end">
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={
|
||||
<InputGroupButton
|
||||
onClick={() => setVoiceEnabled(!voiceEnabled)}
|
||||
data-active={voiceEnabled}
|
||||
className="data-[active=true]:bg-primary data-[active=true]:text-primary-foreground"
|
||||
aria-pressed={voiceEnabled}
|
||||
size="icon-xs"
|
||||
aria-label={t.voiceMode}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<AudioLinesIcon />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{t.voiceMode}</TooltipContent>
|
||||
</Tooltip>
|
||||
</InputGroupAddon>
|
||||
</InputGroup>
|
||||
</ButtonGroup>
|
||||
</ButtonGroup>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
"use client"
|
||||
|
||||
import { Button } from "@/examples/base/ui-rtl/button"
|
||||
import { ButtonGroup } from "@/examples/base/ui-rtl/button-group"
|
||||
import { ArrowLeftIcon, ArrowRightIcon } from "lucide-react"
|
||||
|
||||
import { useLanguageContext } from "@/components/language-selector"
|
||||
|
||||
const translations = {
|
||||
ar: {
|
||||
dir: "rtl" as const,
|
||||
locale: "ar-SA",
|
||||
previous: "السابق",
|
||||
next: "التالي",
|
||||
},
|
||||
he: {
|
||||
dir: "rtl" as const,
|
||||
locale: "he-IL",
|
||||
previous: "הקודם",
|
||||
next: "הבא",
|
||||
},
|
||||
}
|
||||
|
||||
function formatNumber(value: number, locale: string) {
|
||||
return new Intl.NumberFormat(locale).format(value)
|
||||
}
|
||||
|
||||
export function ButtonGroupNested() {
|
||||
const context = useLanguageContext()
|
||||
const lang = context?.language === "he" ? "he" : "ar"
|
||||
const t = translations[lang]
|
||||
|
||||
return (
|
||||
<ButtonGroup dir={t.dir}>
|
||||
<ButtonGroup>
|
||||
<Button variant="outline" size="sm">
|
||||
{formatNumber(1, t.locale)}
|
||||
</Button>
|
||||
<Button variant="outline" size="sm">
|
||||
{formatNumber(2, t.locale)}
|
||||
</Button>
|
||||
<Button variant="outline" size="sm">
|
||||
{formatNumber(3, t.locale)}
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup>
|
||||
<Button variant="outline" size="icon-sm" aria-label={t.previous}>
|
||||
<ArrowLeftIcon className="rtl:rotate-180" />
|
||||
</Button>
|
||||
<Button variant="outline" size="icon-sm" aria-label={t.next}>
|
||||
<ArrowRightIcon className="rtl:rotate-180" />
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</ButtonGroup>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
"use client"
|
||||
|
||||
import { Button } from "@/examples/base/ui-rtl/button"
|
||||
import { ButtonGroup } from "@/examples/base/ui-rtl/button-group"
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/examples/base/ui-rtl/popover"
|
||||
import { Separator } from "@/examples/base/ui-rtl/separator"
|
||||
import { Textarea } from "@/examples/base/ui-rtl/textarea"
|
||||
import { BotIcon, ChevronDownIcon } from "lucide-react"
|
||||
|
||||
import { useLanguageContext } from "@/components/language-selector"
|
||||
|
||||
const translations = {
|
||||
ar: {
|
||||
dir: "rtl" as const,
|
||||
copilot: "المساعد",
|
||||
openPopover: "فتح القائمة",
|
||||
agentTasks: "مهام الوكيل",
|
||||
placeholder: "صف مهمتك بلغة طبيعية.",
|
||||
startTask: "ابدأ مهمة جديدة مع المساعد",
|
||||
description:
|
||||
"صف مهمتك بلغة طبيعية. سيعمل المساعد في الخلفية ويفتح طلب سحب لمراجعتك.",
|
||||
},
|
||||
he: {
|
||||
dir: "rtl" as const,
|
||||
copilot: "עוזר",
|
||||
openPopover: "פתח תפריט",
|
||||
agentTasks: "משימות סוכן",
|
||||
placeholder: "תאר את המשימה שלך בשפה טבעית.",
|
||||
startTask: "התחל משימה חדשה עם העוזר",
|
||||
description:
|
||||
"תאר את המשימה שלך בשפה טבעית. העוזר יעבוד ברקע ויפתח בקשת משיכה לבדיקתך.",
|
||||
},
|
||||
}
|
||||
|
||||
export function ButtonGroupPopover() {
|
||||
const context = useLanguageContext()
|
||||
const lang = context?.language === "he" ? "he" : "ar"
|
||||
const t = translations[lang]
|
||||
|
||||
return (
|
||||
<ButtonGroup dir={t.dir}>
|
||||
<Button variant="outline" size="sm">
|
||||
<BotIcon /> {t.copilot}
|
||||
</Button>
|
||||
<Popover>
|
||||
<PopoverTrigger
|
||||
render={
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon-sm"
|
||||
aria-label={t.openPopover}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<ChevronDownIcon />
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
align="start"
|
||||
dir={t.dir}
|
||||
data-lang={lang}
|
||||
className="p-0"
|
||||
>
|
||||
<div className="px-4 py-3">
|
||||
<div className="text-sm font-medium">{t.agentTasks}</div>
|
||||
</div>
|
||||
<Separator />
|
||||
<div className="p-4 text-sm *:[p:not(:last-child)]:mb-2">
|
||||
<Textarea
|
||||
placeholder={t.placeholder}
|
||||
className="mb-4 resize-none"
|
||||
/>
|
||||
<p className="font-medium">{t.startTask}</p>
|
||||
<p className="text-muted-foreground">{t.description}</p>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</ButtonGroup>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
"use client"
|
||||
|
||||
import {
|
||||
Avatar,
|
||||
AvatarFallback,
|
||||
AvatarGroup,
|
||||
AvatarImage,
|
||||
} from "@/examples/base/ui-rtl/avatar"
|
||||
import { Button } from "@/examples/base/ui-rtl/button"
|
||||
import {
|
||||
Empty,
|
||||
EmptyContent,
|
||||
EmptyDescription,
|
||||
EmptyHeader,
|
||||
EmptyMedia,
|
||||
EmptyTitle,
|
||||
} from "@/examples/base/ui-rtl/empty"
|
||||
import { PlusIcon } from "lucide-react"
|
||||
|
||||
import { useLanguageContext } from "@/components/language-selector"
|
||||
|
||||
const translations = {
|
||||
ar: {
|
||||
dir: "rtl" as const,
|
||||
title: "لا يوجد أعضاء في الفريق",
|
||||
description: "قم بدعوة فريقك للتعاون في هذا المشروع.",
|
||||
invite: "دعوة أعضاء",
|
||||
},
|
||||
he: {
|
||||
dir: "rtl" as const,
|
||||
title: "אין חברי צוות",
|
||||
description: "הזמן את הצוות שלך לשתף פעולה בפרויקט זה.",
|
||||
invite: "הזמן חברים",
|
||||
},
|
||||
}
|
||||
|
||||
export function EmptyAvatarGroup() {
|
||||
const context = useLanguageContext()
|
||||
const lang = context?.language === "he" ? "he" : "ar"
|
||||
const t = translations[lang]
|
||||
|
||||
return (
|
||||
<Empty className="flex-none border py-10" dir={t.dir}>
|
||||
<EmptyHeader>
|
||||
<EmptyMedia>
|
||||
<AvatarGroup className="grayscale">
|
||||
<Avatar>
|
||||
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||
<AvatarFallback>CN</AvatarFallback>
|
||||
</Avatar>
|
||||
<Avatar>
|
||||
<AvatarImage
|
||||
src="https://github.com/maxleiter.png"
|
||||
alt="@maxleiter"
|
||||
/>
|
||||
<AvatarFallback>LR</AvatarFallback>
|
||||
</Avatar>
|
||||
<Avatar>
|
||||
<AvatarImage
|
||||
src="https://github.com/evilrabbit.png"
|
||||
alt="@evilrabbit"
|
||||
/>
|
||||
<AvatarFallback>ER</AvatarFallback>
|
||||
</Avatar>
|
||||
</AvatarGroup>
|
||||
</EmptyMedia>
|
||||
<EmptyTitle>{t.title}</EmptyTitle>
|
||||
<EmptyDescription>{t.description}</EmptyDescription>
|
||||
</EmptyHeader>
|
||||
<EmptyContent>
|
||||
<Button size="sm">
|
||||
<PlusIcon />
|
||||
{t.invite}
|
||||
</Button>
|
||||
</EmptyContent>
|
||||
</Empty>
|
||||
)
|
||||
}
|
||||
36
apps/v4/app/(app)/examples/rtl/components/field-checkbox.tsx
Normal file
36
apps/v4/app/(app)/examples/rtl/components/field-checkbox.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
"use client"
|
||||
|
||||
import { Checkbox } from "@/examples/base/ui-rtl/checkbox"
|
||||
import { Field, FieldLabel } from "@/examples/base/ui-rtl/field"
|
||||
|
||||
import { useLanguageContext } from "@/components/language-selector"
|
||||
|
||||
const translations = {
|
||||
ar: {
|
||||
dir: "rtl" as const,
|
||||
terms: "أوافق على الشروط والأحكام",
|
||||
},
|
||||
he: {
|
||||
dir: "rtl" as const,
|
||||
terms: "אני מסכים לתנאים וההגבלות",
|
||||
},
|
||||
}
|
||||
|
||||
export function FieldCheckbox() {
|
||||
const context = useLanguageContext()
|
||||
const lang = context?.language === "he" ? "he" : "ar"
|
||||
const { dir, terms } = translations[lang]
|
||||
|
||||
return (
|
||||
<div dir={dir}>
|
||||
<FieldLabel htmlFor="checkbox-demo-rtl">
|
||||
<Field orientation="horizontal">
|
||||
<Checkbox id="checkbox-demo-rtl" defaultChecked />
|
||||
<FieldLabel htmlFor="checkbox-demo-rtl" className="line-clamp-1">
|
||||
{terms}
|
||||
</FieldLabel>
|
||||
</Field>
|
||||
</FieldLabel>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
217
apps/v4/app/(app)/examples/rtl/components/field-demo.tsx
Normal file
217
apps/v4/app/(app)/examples/rtl/components/field-demo.tsx
Normal file
@@ -0,0 +1,217 @@
|
||||
"use client"
|
||||
|
||||
import { Button } from "@/examples/base/ui-rtl/button"
|
||||
import { Checkbox } from "@/examples/base/ui-rtl/checkbox"
|
||||
import {
|
||||
Field,
|
||||
FieldDescription,
|
||||
FieldGroup,
|
||||
FieldLabel,
|
||||
FieldLegend,
|
||||
FieldSeparator,
|
||||
FieldSet,
|
||||
} from "@/examples/base/ui-rtl/field"
|
||||
import { Input } from "@/examples/base/ui-rtl/input"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/examples/base/ui-rtl/select"
|
||||
import { Textarea } from "@/examples/base/ui-rtl/textarea"
|
||||
|
||||
import { useLanguageContext } from "@/components/language-selector"
|
||||
|
||||
const translations = {
|
||||
ar: {
|
||||
dir: "rtl" as const,
|
||||
locale: "ar-SA",
|
||||
paymentMethod: "طريقة الدفع",
|
||||
secureEncrypted: "جميع المعاملات آمنة ومشفرة",
|
||||
nameOnCard: "الاسم على البطاقة",
|
||||
namePlaceholder: "أحمد محمد",
|
||||
cardNumber: "رقم البطاقة",
|
||||
cardDescription: "أدخل رقمك المكون من 16 رقمًا.",
|
||||
cvv: "رمز الأمان",
|
||||
month: "الشهر",
|
||||
year: "السنة",
|
||||
billingAddress: "عنوان الفواتير",
|
||||
billingDescription: "عنوان الفواتير المرتبط بطريقة الدفع الخاصة بك",
|
||||
sameAsShipping: "نفس عنوان الشحن",
|
||||
comments: "تعليقات",
|
||||
commentsPlaceholder: "أضف أي تعليقات إضافية",
|
||||
submit: "إرسال",
|
||||
cancel: "إلغاء",
|
||||
},
|
||||
he: {
|
||||
dir: "rtl" as const,
|
||||
locale: "he-IL",
|
||||
paymentMethod: "אמצעי תשלום",
|
||||
secureEncrypted: "כל העסקאות מאובטחות ומוצפנות",
|
||||
nameOnCard: "שם על הכרטיס",
|
||||
namePlaceholder: "ישראל ישראלי",
|
||||
cardNumber: "מספר כרטיס",
|
||||
cardDescription: "הזן את המספר בן 16 הספרות שלך.",
|
||||
cvv: "קוד אבטחה",
|
||||
month: "חודש",
|
||||
year: "שנה",
|
||||
billingAddress: "כתובת לחיוב",
|
||||
billingDescription: "כתובת החיוב המשויכת לאמצעי התשלום שלך",
|
||||
sameAsShipping: "זהה לכתובת המשלוח",
|
||||
comments: "הערות",
|
||||
commentsPlaceholder: "הוסף הערות נוספות",
|
||||
submit: "שלח",
|
||||
cancel: "ביטול",
|
||||
},
|
||||
}
|
||||
|
||||
function formatCardNumber(locale: string) {
|
||||
const formatter = new Intl.NumberFormat(locale, { useGrouping: false })
|
||||
return `${formatter.format(1234)} ${formatter.format(5678)} ${formatter.format(9012)} ${formatter.format(3456)}`
|
||||
}
|
||||
|
||||
function formatCvv(locale: string) {
|
||||
return new Intl.NumberFormat(locale, { useGrouping: false }).format(123)
|
||||
}
|
||||
|
||||
function getMonths(locale: string) {
|
||||
const formatter = new Intl.NumberFormat(locale, {
|
||||
minimumIntegerDigits: 2,
|
||||
useGrouping: false,
|
||||
})
|
||||
return Array.from({ length: 12 }, (_, i) => {
|
||||
const value = String(i + 1).padStart(2, "0")
|
||||
return { label: formatter.format(i + 1), value }
|
||||
})
|
||||
}
|
||||
|
||||
function getYears(locale: string) {
|
||||
const formatter = new Intl.NumberFormat(locale, { useGrouping: false })
|
||||
return Array.from({ length: 6 }, (_, i) => {
|
||||
const year = 2024 + i
|
||||
return { label: formatter.format(year), value: String(year) }
|
||||
})
|
||||
}
|
||||
|
||||
export function FieldDemo() {
|
||||
const context = useLanguageContext()
|
||||
const lang = context?.language === "he" ? "he" : "ar"
|
||||
const t = translations[lang]
|
||||
const months = getMonths(t.locale)
|
||||
const years = getYears(t.locale)
|
||||
const cardPlaceholder = formatCardNumber(t.locale)
|
||||
const cvvPlaceholder = formatCvv(t.locale)
|
||||
|
||||
return (
|
||||
<div dir={t.dir} className="w-full max-w-md rounded-lg border p-6">
|
||||
<form>
|
||||
<FieldGroup>
|
||||
<FieldSet>
|
||||
<FieldLegend>{t.paymentMethod}</FieldLegend>
|
||||
<FieldDescription>{t.secureEncrypted}</FieldDescription>
|
||||
<FieldGroup>
|
||||
<Field>
|
||||
<FieldLabel htmlFor="rtl-card-name">{t.nameOnCard}</FieldLabel>
|
||||
<Input
|
||||
id="rtl-card-name"
|
||||
placeholder={t.namePlaceholder}
|
||||
required
|
||||
/>
|
||||
</Field>
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<Field className="col-span-2">
|
||||
<FieldLabel htmlFor="rtl-card-number">
|
||||
{t.cardNumber}
|
||||
</FieldLabel>
|
||||
<Input
|
||||
id="rtl-card-number"
|
||||
placeholder={cardPlaceholder}
|
||||
required
|
||||
/>
|
||||
<FieldDescription>{t.cardDescription}</FieldDescription>
|
||||
</Field>
|
||||
<Field className="col-span-1">
|
||||
<FieldLabel htmlFor="rtl-cvv">{t.cvv}</FieldLabel>
|
||||
<Input id="rtl-cvv" placeholder={cvvPlaceholder} required />
|
||||
</Field>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<Field>
|
||||
<FieldLabel htmlFor="rtl-exp-month">{t.month}</FieldLabel>
|
||||
<Select defaultValue="" items={months}>
|
||||
<SelectTrigger id="rtl-exp-month">
|
||||
<SelectValue placeholder="MM" />
|
||||
</SelectTrigger>
|
||||
<SelectContent data-lang={lang} dir={t.dir}>
|
||||
<SelectGroup>
|
||||
{months.map((item) => (
|
||||
<SelectItem key={item.value} value={item.value}>
|
||||
{item.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldLabel htmlFor="rtl-exp-year">{t.year}</FieldLabel>
|
||||
<Select defaultValue="" items={years}>
|
||||
<SelectTrigger id="rtl-exp-year">
|
||||
<SelectValue placeholder="YYYY" />
|
||||
</SelectTrigger>
|
||||
<SelectContent data-lang={lang} dir={t.dir}>
|
||||
<SelectGroup>
|
||||
{years.map((item) => (
|
||||
<SelectItem key={item.value} value={item.value}>
|
||||
{item.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</Field>
|
||||
</div>
|
||||
</FieldGroup>
|
||||
</FieldSet>
|
||||
<FieldSeparator />
|
||||
<FieldSet>
|
||||
<FieldLegend>{t.billingAddress}</FieldLegend>
|
||||
<FieldDescription>{t.billingDescription}</FieldDescription>
|
||||
<FieldGroup>
|
||||
<Field orientation="horizontal">
|
||||
<Checkbox id="rtl-same-as-shipping" defaultChecked />
|
||||
<FieldLabel
|
||||
htmlFor="rtl-same-as-shipping"
|
||||
className="font-normal"
|
||||
>
|
||||
{t.sameAsShipping}
|
||||
</FieldLabel>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</FieldSet>
|
||||
<FieldSeparator />
|
||||
<FieldSet>
|
||||
<FieldGroup>
|
||||
<Field>
|
||||
<FieldLabel htmlFor="rtl-comments">{t.comments}</FieldLabel>
|
||||
<Textarea
|
||||
id="rtl-comments"
|
||||
placeholder={t.commentsPlaceholder}
|
||||
className="resize-none"
|
||||
/>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</FieldSet>
|
||||
<Field orientation="horizontal">
|
||||
<Button type="submit">{t.submit}</Button>
|
||||
<Button variant="outline" type="button">
|
||||
{t.cancel}
|
||||
</Button>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
90
apps/v4/app/(app)/examples/rtl/components/field-hear.tsx
Normal file
90
apps/v4/app/(app)/examples/rtl/components/field-hear.tsx
Normal file
@@ -0,0 +1,90 @@
|
||||
"use client"
|
||||
|
||||
import { Card, CardContent } from "@/examples/base/ui-rtl/card"
|
||||
import { Checkbox } from "@/examples/base/ui-rtl/checkbox"
|
||||
import {
|
||||
Field,
|
||||
FieldDescription,
|
||||
FieldGroup,
|
||||
FieldLabel,
|
||||
FieldLegend,
|
||||
FieldSet,
|
||||
FieldTitle,
|
||||
} from "@/examples/base/ui-rtl/field"
|
||||
|
||||
import { useLanguageContext } from "@/components/language-selector"
|
||||
|
||||
const translations = {
|
||||
ar: {
|
||||
dir: "rtl" as const,
|
||||
legend: "كيف سمعت عنا؟",
|
||||
description: "اختر الخيار الذي يصف أفضل طريقة سمعت عنا من خلالها.",
|
||||
socialMedia: "التواصل الاجتماعي",
|
||||
searchEngine: "البحث",
|
||||
referral: "إحالة",
|
||||
other: "أخرى",
|
||||
},
|
||||
he: {
|
||||
dir: "rtl" as const,
|
||||
legend: "איך שמעת עלינו?",
|
||||
description: "בחר את האפשרות שמתארת בצורה הטובה ביותר כיצד שמעת עלינו.",
|
||||
socialMedia: "חברתיות",
|
||||
searchEngine: "חיפוש",
|
||||
referral: "הפניה",
|
||||
other: "אחר",
|
||||
},
|
||||
}
|
||||
|
||||
export function FieldHear() {
|
||||
const context = useLanguageContext()
|
||||
const lang = context?.language === "he" ? "he" : "ar"
|
||||
const t = translations[lang]
|
||||
|
||||
const options = [
|
||||
{ label: t.socialMedia, value: "social-media" },
|
||||
{ label: t.searchEngine, value: "search-engine" },
|
||||
{ label: t.referral, value: "referral" },
|
||||
{ label: t.other, value: "other" },
|
||||
]
|
||||
|
||||
return (
|
||||
<div dir={t.dir}>
|
||||
<Card className="border-0 py-4 shadow-none">
|
||||
<CardContent className="px-4">
|
||||
<form>
|
||||
<FieldGroup>
|
||||
<FieldSet className="gap-4">
|
||||
<FieldLegend>{t.legend}</FieldLegend>
|
||||
<FieldDescription className="line-clamp-1">
|
||||
{t.description}
|
||||
</FieldDescription>
|
||||
<FieldGroup className="flex flex-row flex-wrap gap-2 [--radius:9999rem]">
|
||||
{options.map((option) => (
|
||||
<FieldLabel
|
||||
htmlFor={`rtl-${option.value}`}
|
||||
key={option.value}
|
||||
className="w-fit!"
|
||||
>
|
||||
<Field
|
||||
orientation="horizontal"
|
||||
className="gap-1.5 overflow-hidden px-3! py-1.5! transition-all duration-100 ease-linear group-has-data-[state=checked]/field-label:px-2!"
|
||||
>
|
||||
<Checkbox
|
||||
value={option.value}
|
||||
id={`rtl-${option.value}`}
|
||||
defaultChecked={option.value === "social-media"}
|
||||
className="-ms-6 translate-x-1 rounded-full transition-all duration-100 ease-linear data-checked:ms-0 data-checked:translate-x-0"
|
||||
/>
|
||||
<FieldTitle>{option.label}</FieldTitle>
|
||||
</Field>
|
||||
</FieldLabel>
|
||||
))}
|
||||
</FieldGroup>
|
||||
</FieldSet>
|
||||
</FieldGroup>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
67
apps/v4/app/(app)/examples/rtl/components/field-slider.tsx
Normal file
67
apps/v4/app/(app)/examples/rtl/components/field-slider.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import {
|
||||
Field,
|
||||
FieldDescription,
|
||||
FieldTitle,
|
||||
} from "@/examples/base/ui-rtl/field"
|
||||
import { Slider } from "@/examples/base/ui-rtl/slider"
|
||||
|
||||
import { useLanguageContext } from "@/components/language-selector"
|
||||
|
||||
const translations = {
|
||||
ar: {
|
||||
dir: "rtl" as const,
|
||||
locale: "ar-SA",
|
||||
title: "نطاق السعر",
|
||||
description: "حدد نطاق ميزانيتك",
|
||||
ariaLabel: "نطاق السعر",
|
||||
currency: "﷼",
|
||||
},
|
||||
he: {
|
||||
dir: "rtl" as const,
|
||||
locale: "he-IL",
|
||||
title: "טווח מחירים",
|
||||
description: "הגדר את טווח התקציב שלך",
|
||||
ariaLabel: "טווח מחירים",
|
||||
currency: "₪",
|
||||
},
|
||||
}
|
||||
|
||||
function formatNumber(value: number, locale: string) {
|
||||
return new Intl.NumberFormat(locale).format(value)
|
||||
}
|
||||
|
||||
export function FieldSlider() {
|
||||
const context = useLanguageContext()
|
||||
const lang = context?.language === "he" ? "he" : "ar"
|
||||
const t = translations[lang]
|
||||
const [value, setValue] = useState([200, 800])
|
||||
|
||||
return (
|
||||
<Field dir={t.dir}>
|
||||
<FieldTitle>{t.title}</FieldTitle>
|
||||
<FieldDescription>
|
||||
{t.description} ({t.currency}
|
||||
<span className="font-medium tabular-nums">
|
||||
{formatNumber(value[0], t.locale)}
|
||||
</span>{" "}
|
||||
-{" "}
|
||||
<span className="font-medium tabular-nums">
|
||||
{formatNumber(value[1], t.locale)}
|
||||
</span>
|
||||
).
|
||||
</FieldDescription>
|
||||
<Slider
|
||||
value={value}
|
||||
onValueChange={(value) => setValue(value as [number, number])}
|
||||
max={1000}
|
||||
min={0}
|
||||
step={10}
|
||||
className="mt-2 w-full"
|
||||
aria-label={t.ariaLabel}
|
||||
/>
|
||||
</Field>
|
||||
)
|
||||
}
|
||||
92
apps/v4/app/(app)/examples/rtl/components/index.tsx
Normal file
92
apps/v4/app/(app)/examples/rtl/components/index.tsx
Normal file
@@ -0,0 +1,92 @@
|
||||
"use client"
|
||||
|
||||
import { DirectionProvider } from "@/examples/base/ui-rtl/direction"
|
||||
import { FieldSeparator } from "@/examples/base/ui-rtl/field"
|
||||
|
||||
import {
|
||||
LanguageProvider,
|
||||
LanguageSelector,
|
||||
useLanguageContext,
|
||||
} from "@/components/language-selector"
|
||||
|
||||
import { AppearanceSettings } from "./appearance-settings"
|
||||
import { ButtonGroupDemo } from "./button-group-demo"
|
||||
import { ButtonGroupInputGroup } from "./button-group-input-group"
|
||||
import { ButtonGroupNested } from "./button-group-nested"
|
||||
import { ButtonGroupPopover } from "./button-group-popover"
|
||||
import { EmptyAvatarGroup } from "./empty-avatar-group"
|
||||
import { FieldCheckbox } from "./field-checkbox"
|
||||
import { FieldDemo } from "./field-demo"
|
||||
import { FieldHear } from "./field-hear"
|
||||
import { FieldSlider } from "./field-slider"
|
||||
import { InputGroupButtonExample } from "./input-group-button"
|
||||
import { InputGroupDemo } from "./input-group-demo"
|
||||
import { ItemDemo } from "./item-demo"
|
||||
import { NotionPromptForm } from "./notion-prompt-form"
|
||||
import { SpinnerBadge } from "./spinner-badge"
|
||||
import { SpinnerEmpty } from "./spinner-empty"
|
||||
|
||||
function RtlComponentsContent() {
|
||||
const context = useLanguageContext()
|
||||
|
||||
if (!context) {
|
||||
return null
|
||||
}
|
||||
|
||||
const { language } = context
|
||||
|
||||
return (
|
||||
<div
|
||||
className="relative grid gap-8 p-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 xl:gap-6 2xl:gap-8"
|
||||
dir="rtl"
|
||||
data-lang={language}
|
||||
data-slot="rtl-components"
|
||||
>
|
||||
<LanguageSelector
|
||||
value={language}
|
||||
onValueChange={context.setLanguage}
|
||||
className="absolute -top-12 right-52 hidden h-8! data-[size=sm]:rounded-lg lg:flex"
|
||||
languages={["ar", "he"]}
|
||||
/>
|
||||
<div className="flex flex-col gap-6 *:[div]:w-full *:[div]:max-w-full">
|
||||
<FieldDemo />
|
||||
</div>
|
||||
<div className="flex flex-col gap-6 *:[div]:w-full *:[div]:max-w-full">
|
||||
<EmptyAvatarGroup />
|
||||
<SpinnerBadge />
|
||||
<ButtonGroupInputGroup />
|
||||
<FieldSlider />
|
||||
<InputGroupDemo />
|
||||
</div>
|
||||
<div className="flex flex-col gap-6 *:[div]:w-full *:[div]:max-w-full">
|
||||
<InputGroupButtonExample />
|
||||
<ItemDemo />
|
||||
<FieldSeparator className="my-4">
|
||||
{language === "he" ? "הגדרות מראה" : "إعدادات المظهر"}
|
||||
</FieldSeparator>
|
||||
<AppearanceSettings />
|
||||
</div>
|
||||
<div className="order-first flex flex-col gap-6 lg:hidden xl:order-last xl:flex *:[div]:w-full *:[div]:max-w-full">
|
||||
<NotionPromptForm />
|
||||
<ButtonGroupDemo />
|
||||
<FieldCheckbox />
|
||||
<div className="flex justify-between gap-4">
|
||||
<ButtonGroupNested />
|
||||
<ButtonGroupPopover />
|
||||
</div>
|
||||
<FieldHear />
|
||||
<SpinnerEmpty />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function RtlComponents() {
|
||||
return (
|
||||
<LanguageProvider defaultLanguage="ar">
|
||||
<DirectionProvider direction="rtl">
|
||||
<RtlComponentsContent />
|
||||
</DirectionProvider>
|
||||
</LanguageProvider>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import {
|
||||
InputGroup,
|
||||
InputGroupAddon,
|
||||
InputGroupButton,
|
||||
InputGroupInput,
|
||||
} from "@/examples/base/ui-rtl/input-group"
|
||||
import { Label } from "@/examples/base/ui-rtl/label"
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/examples/base/ui-rtl/popover"
|
||||
import { IconInfoCircle, IconStar } from "@tabler/icons-react"
|
||||
|
||||
import { useLanguageContext } from "@/components/language-selector"
|
||||
|
||||
const translations = {
|
||||
ar: {
|
||||
dir: "rtl" as const,
|
||||
inputLabel: "السعر",
|
||||
info: "معلومات",
|
||||
priceInfo: "أدخل السعر بالريال السعودي.",
|
||||
priceDescription: "سيتم تحويل السعر تلقائياً.",
|
||||
favorite: "مفضل",
|
||||
currency: "ر.س",
|
||||
},
|
||||
he: {
|
||||
dir: "rtl" as const,
|
||||
inputLabel: "מחיר",
|
||||
info: "מידע",
|
||||
priceInfo: "הזן את המחיר בשקלים.",
|
||||
priceDescription: "המחיר יומר אוטומטית.",
|
||||
favorite: "מועדף",
|
||||
currency: "₪",
|
||||
},
|
||||
}
|
||||
|
||||
export function InputGroupButtonExample() {
|
||||
const context = useLanguageContext()
|
||||
const lang = context?.language === "he" ? "he" : "ar"
|
||||
const t = translations[lang]
|
||||
const [isFavorite, setIsFavorite] = React.useState(false)
|
||||
|
||||
return (
|
||||
<div dir={t.dir} className="grid w-full max-w-sm gap-6">
|
||||
<Label htmlFor="input-secure-rtl" className="sr-only">
|
||||
{t.inputLabel}
|
||||
</Label>
|
||||
<InputGroup className="[--radius:9999px]">
|
||||
<InputGroupInput id="input-secure-rtl" className="pr-0.5!" />
|
||||
<InputGroupAddon>
|
||||
<Popover>
|
||||
<PopoverTrigger
|
||||
render={
|
||||
<InputGroupButton
|
||||
variant="secondary"
|
||||
size="icon-xs"
|
||||
aria-label={t.info}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<IconInfoCircle />
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
align="end"
|
||||
alignOffset={10}
|
||||
className="flex flex-col gap-1 rounded-xl text-sm"
|
||||
data-lang={lang}
|
||||
dir={t.dir}
|
||||
>
|
||||
<p className="font-medium">{t.priceInfo}</p>
|
||||
<p>{t.priceDescription}</p>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</InputGroupAddon>
|
||||
<InputGroupAddon className="text-muted-foreground">
|
||||
{t.currency}
|
||||
</InputGroupAddon>
|
||||
<InputGroupAddon align="inline-end">
|
||||
<InputGroupButton
|
||||
onClick={() => setIsFavorite(!isFavorite)}
|
||||
size="icon-xs"
|
||||
aria-label={t.favorite}
|
||||
>
|
||||
<IconStar
|
||||
data-favorite={isFavorite}
|
||||
className="data-[favorite=true]:fill-primary data-[favorite=true]:stroke-primary"
|
||||
/>
|
||||
</InputGroupButton>
|
||||
</InputGroupAddon>
|
||||
</InputGroup>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
140
apps/v4/app/(app)/examples/rtl/components/input-group-demo.tsx
Normal file
140
apps/v4/app/(app)/examples/rtl/components/input-group-demo.tsx
Normal file
@@ -0,0 +1,140 @@
|
||||
"use client"
|
||||
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/examples/base/ui-rtl/dropdown-menu"
|
||||
import {
|
||||
InputGroup,
|
||||
InputGroupAddon,
|
||||
InputGroupButton,
|
||||
InputGroupInput,
|
||||
InputGroupText,
|
||||
InputGroupTextarea,
|
||||
} from "@/examples/base/ui-rtl/input-group"
|
||||
import { Separator } from "@/examples/base/ui-rtl/separator"
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/examples/base/ui-rtl/tooltip"
|
||||
import {
|
||||
IconCheck,
|
||||
IconChevronDown,
|
||||
IconInfoCircle,
|
||||
IconPlus,
|
||||
} from "@tabler/icons-react"
|
||||
import { ArrowUpIcon, Search } from "lucide-react"
|
||||
|
||||
import { useLanguageContext } from "@/components/language-selector"
|
||||
|
||||
const translations = {
|
||||
ar: {
|
||||
dir: "rtl" as const,
|
||||
search: "بحث...",
|
||||
results: "12 نتيجة",
|
||||
example: "example.com",
|
||||
tooltipContent: "هذا محتوى في تلميح.",
|
||||
askSearchChat: "اسأل، ابحث أو تحدث...",
|
||||
add: "إضافة",
|
||||
auto: "تلقائي",
|
||||
agent: "وكيل",
|
||||
manual: "يدوي",
|
||||
used: "52% مستخدم",
|
||||
send: "إرسال",
|
||||
},
|
||||
he: {
|
||||
dir: "rtl" as const,
|
||||
search: "חיפוש...",
|
||||
results: "12 תוצאות",
|
||||
example: "example.com",
|
||||
tooltipContent: "זה תוכן בטולטיפ.",
|
||||
askSearchChat: "שאל, חפש או שוחח...",
|
||||
add: "הוסף",
|
||||
auto: "אוטומטי",
|
||||
agent: "סוכן",
|
||||
manual: "ידני",
|
||||
used: "52% בשימוש",
|
||||
send: "שלח",
|
||||
},
|
||||
}
|
||||
|
||||
export function InputGroupDemo() {
|
||||
const context = useLanguageContext()
|
||||
const lang = context?.language === "he" ? "he" : "ar"
|
||||
const t = translations[lang]
|
||||
|
||||
return (
|
||||
<div dir={t.dir} className="grid w-full max-w-sm gap-6">
|
||||
<InputGroup>
|
||||
<InputGroupInput placeholder={t.search} />
|
||||
<InputGroupAddon>
|
||||
<Search />
|
||||
</InputGroupAddon>
|
||||
<InputGroupAddon align="inline-end">{t.results}</InputGroupAddon>
|
||||
</InputGroup>
|
||||
<InputGroup>
|
||||
<InputGroupInput placeholder={t.example} />
|
||||
<InputGroupAddon align="inline-end">
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={
|
||||
<InputGroupButton
|
||||
className="rounded-full"
|
||||
size="icon-xs"
|
||||
aria-label={t.add}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<IconInfoCircle />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{t.tooltipContent}</TooltipContent>
|
||||
</Tooltip>
|
||||
</InputGroupAddon>
|
||||
</InputGroup>
|
||||
<InputGroup>
|
||||
<InputGroupTextarea placeholder={t.askSearchChat} />
|
||||
<InputGroupAddon align="block-end">
|
||||
<InputGroupButton
|
||||
variant="outline"
|
||||
className="rounded-full"
|
||||
size="icon-xs"
|
||||
aria-label={t.add}
|
||||
>
|
||||
<IconPlus />
|
||||
</InputGroupButton>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger render={<InputGroupButton variant="ghost" />}>
|
||||
<IconChevronDown />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent side="top" align="start">
|
||||
<DropdownMenuItem>{t.auto}</DropdownMenuItem>
|
||||
<DropdownMenuItem>{t.agent}</DropdownMenuItem>
|
||||
<DropdownMenuItem>{t.manual}</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<InputGroupText className="ms-auto">{t.used}</InputGroupText>
|
||||
<Separator orientation="vertical" className="h-4!" />
|
||||
<InputGroupButton
|
||||
variant="default"
|
||||
className="rounded-full"
|
||||
size="icon-xs"
|
||||
>
|
||||
<ArrowUpIcon />
|
||||
<span className="sr-only">{t.send}</span>
|
||||
</InputGroupButton>
|
||||
</InputGroupAddon>
|
||||
</InputGroup>
|
||||
<InputGroup>
|
||||
<InputGroupInput placeholder="shadcn" />
|
||||
<InputGroupAddon align="inline-end">
|
||||
<div className="flex size-4 items-center justify-center rounded-full bg-primary text-foreground">
|
||||
<IconCheck className="size-3 text-white" />
|
||||
</div>
|
||||
</InputGroupAddon>
|
||||
</InputGroup>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
64
apps/v4/app/(app)/examples/rtl/components/item-demo.tsx
Normal file
64
apps/v4/app/(app)/examples/rtl/components/item-demo.tsx
Normal file
@@ -0,0 +1,64 @@
|
||||
"use client"
|
||||
|
||||
import { Button } from "@/examples/base/ui-rtl/button"
|
||||
import {
|
||||
Item,
|
||||
ItemActions,
|
||||
ItemContent,
|
||||
ItemDescription,
|
||||
ItemMedia,
|
||||
ItemTitle,
|
||||
} from "@/examples/base/ui-rtl/item"
|
||||
import { BadgeCheckIcon, ChevronRightIcon } from "lucide-react"
|
||||
|
||||
import { useLanguageContext } from "@/components/language-selector"
|
||||
|
||||
const translations = {
|
||||
ar: {
|
||||
dir: "rtl" as const,
|
||||
twoFactor: "المصادقة الثنائية",
|
||||
twoFactorDescription: "التحقق عبر البريد الإلكتروني أو رقم الهاتف.",
|
||||
enable: "تفعيل",
|
||||
verified: "تم التحقق من ملفك الشخصي.",
|
||||
},
|
||||
he: {
|
||||
dir: "rtl" as const,
|
||||
twoFactor: "אימות דו-שלבי",
|
||||
twoFactorDescription: "אמת באמצעות אימייל או מספר טלפון.",
|
||||
enable: "הפעל",
|
||||
verified: "הפרופיל שלך אומת.",
|
||||
},
|
||||
}
|
||||
|
||||
export function ItemDemo() {
|
||||
const context = useLanguageContext()
|
||||
const lang = context?.language === "he" ? "he" : "ar"
|
||||
const t = translations[lang]
|
||||
|
||||
return (
|
||||
<div dir={t.dir} className="flex w-full max-w-md flex-col gap-6">
|
||||
<Item variant="outline">
|
||||
<ItemContent>
|
||||
<ItemTitle>{t.twoFactor}</ItemTitle>
|
||||
<ItemDescription className="text-pretty xl:hidden 2xl:block">
|
||||
{t.twoFactorDescription}
|
||||
</ItemDescription>
|
||||
</ItemContent>
|
||||
<ItemActions>
|
||||
<Button size="sm">{t.enable}</Button>
|
||||
</ItemActions>
|
||||
</Item>
|
||||
<Item variant="outline" size="sm">
|
||||
<ItemMedia>
|
||||
<BadgeCheckIcon className="size-5" />
|
||||
</ItemMedia>
|
||||
<ItemContent>
|
||||
<ItemTitle>{t.verified}</ItemTitle>
|
||||
</ItemContent>
|
||||
<ItemActions>
|
||||
<ChevronRightIcon className="size-4 rtl:rotate-180" />
|
||||
</ItemActions>
|
||||
</Item>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
516
apps/v4/app/(app)/examples/rtl/components/notion-prompt-form.tsx
Normal file
516
apps/v4/app/(app)/examples/rtl/components/notion-prompt-form.tsx
Normal file
@@ -0,0 +1,516 @@
|
||||
"use client"
|
||||
|
||||
import { useMemo, useState } from "react"
|
||||
import {
|
||||
Avatar,
|
||||
AvatarFallback,
|
||||
AvatarImage,
|
||||
} from "@/examples/base/ui-rtl/avatar"
|
||||
import { Badge } from "@/examples/base/ui-rtl/badge"
|
||||
import {
|
||||
Command,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
CommandList,
|
||||
} from "@/examples/base/ui-rtl/command"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuSub,
|
||||
DropdownMenuSubContent,
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/examples/base/ui-rtl/dropdown-menu"
|
||||
import { Field, FieldLabel } from "@/examples/base/ui-rtl/field"
|
||||
import {
|
||||
InputGroup,
|
||||
InputGroupAddon,
|
||||
InputGroupButton,
|
||||
InputGroupTextarea,
|
||||
} from "@/examples/base/ui-rtl/input-group"
|
||||
import { Popover, PopoverContent } from "@/examples/base/ui-rtl/popover"
|
||||
import { Switch } from "@/examples/base/ui-rtl/switch"
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/examples/base/ui-rtl/tooltip"
|
||||
import {
|
||||
IconApps,
|
||||
IconArrowUp,
|
||||
IconAt,
|
||||
IconBook,
|
||||
IconCircleDashedPlus,
|
||||
IconPaperclip,
|
||||
IconPlus,
|
||||
IconWorld,
|
||||
IconX,
|
||||
} from "@tabler/icons-react"
|
||||
|
||||
import { useLanguageContext } from "@/components/language-selector"
|
||||
|
||||
const translations = {
|
||||
ar: {
|
||||
dir: "rtl" as const,
|
||||
prompt: "الأمر",
|
||||
placeholder: "اسأل، ابحث، أو أنشئ أي شيء...",
|
||||
addContext: "أضف سياق",
|
||||
mentionTooltip: "اذكر شخصًا أو صفحة أو تاريخًا",
|
||||
searchPages: "البحث في الصفحات...",
|
||||
noPagesFound: "لم يتم العثور على صفحات",
|
||||
pages: "الصفحات",
|
||||
users: "المستخدمون",
|
||||
attachFile: "إرفاق ملف",
|
||||
selectModel: "اختر نموذج الذكاء الاصطناعي",
|
||||
selectAgentMode: "اختر وضع الوكيل",
|
||||
webSearch: "البحث على الويب",
|
||||
appsIntegrations: "التطبيقات والتكاملات",
|
||||
allSourcesAccess: "جميع المصادر التي يمكنني الوصول إليها",
|
||||
findKnowledge: "ابحث أو استخدم المعرفة في...",
|
||||
noKnowledgeFound: "لم يتم العثور على معرفة",
|
||||
helpCenter: "مركز المساعدة",
|
||||
connectApps: "ربط التطبيقات",
|
||||
searchSourcesNote: "سنبحث فقط في المصادر المحددة هنا.",
|
||||
send: "إرسال",
|
||||
allSources: "جميع المصادر",
|
||||
auto: "تلقائي",
|
||||
agentMode: "وضع الوكيل",
|
||||
planMode: "وضع التخطيط",
|
||||
beta: "تجريبي",
|
||||
workspace: "مساحة العمل",
|
||||
meetingNotes: "ملاحظات الاجتماع",
|
||||
projectDashboard: "لوحة المشروع",
|
||||
ideasBrainstorming: "أفكار وعصف ذهني",
|
||||
calendarEvents: "التقويم والأحداث",
|
||||
documentation: "التوثيق",
|
||||
goalsObjectives: "الأهداف والغايات",
|
||||
budgetPlanning: "تخطيط الميزانية",
|
||||
teamDirectory: "دليل الفريق",
|
||||
technicalSpecs: "المواصفات التقنية",
|
||||
analyticsReport: "تقرير التحليلات",
|
||||
},
|
||||
he: {
|
||||
dir: "rtl" as const,
|
||||
prompt: "פקודה",
|
||||
placeholder: "שאל, חפש, או צור משהו...",
|
||||
addContext: "הוסף הקשר",
|
||||
mentionTooltip: "הזכר אדם, עמוד או תאריך",
|
||||
searchPages: "חפש עמודים...",
|
||||
noPagesFound: "לא נמצאו עמודים",
|
||||
pages: "עמודים",
|
||||
users: "משתמשים",
|
||||
attachFile: "צרף קובץ",
|
||||
selectModel: "בחר מודל AI",
|
||||
selectAgentMode: "בחר מצב סוכן",
|
||||
webSearch: "חיפוש באינטרנט",
|
||||
appsIntegrations: "אפליקציות ואינטגרציות",
|
||||
allSourcesAccess: "כל המקורות שיש לי גישה אליהם",
|
||||
findKnowledge: "מצא או השתמש בידע ב...",
|
||||
noKnowledgeFound: "לא נמצא ידע",
|
||||
helpCenter: "מרכז עזרה",
|
||||
connectApps: "חבר אפליקציות",
|
||||
searchSourcesNote: "נחפש רק במקורות שנבחרו כאן.",
|
||||
send: "שלח",
|
||||
allSources: "כל המקורות",
|
||||
auto: "אוטומטי",
|
||||
agentMode: "מצב סוכן",
|
||||
planMode: "מצב תכנון",
|
||||
beta: "בטא",
|
||||
workspace: "סביבת עבודה",
|
||||
meetingNotes: "הערות פגישה",
|
||||
projectDashboard: "לוח מחוונים לפרויקט",
|
||||
ideasBrainstorming: "רעיונות וסיעור מוחות",
|
||||
calendarEvents: "יומן ואירועים",
|
||||
documentation: "תיעוד",
|
||||
goalsObjectives: "מטרות ויעדים",
|
||||
budgetPlanning: "תכנון תקציב",
|
||||
teamDirectory: "ספריית צוות",
|
||||
technicalSpecs: "מפרט טכני",
|
||||
analyticsReport: "דוח אנליטיקה",
|
||||
},
|
||||
}
|
||||
|
||||
function MentionableIcon({
|
||||
item,
|
||||
}: {
|
||||
item: { type: string; title: string; image: string }
|
||||
}) {
|
||||
return item.type === "page" ? (
|
||||
<span className="flex size-4 items-center justify-center">
|
||||
{item.image}
|
||||
</span>
|
||||
) : (
|
||||
<Avatar className="size-4">
|
||||
<AvatarImage src={item.image} />
|
||||
<AvatarFallback>{item.title[0]}</AvatarFallback>
|
||||
</Avatar>
|
||||
)
|
||||
}
|
||||
|
||||
export function NotionPromptForm() {
|
||||
const context = useLanguageContext()
|
||||
const lang = context?.language === "he" ? "he" : "ar"
|
||||
const t = translations[lang]
|
||||
|
||||
const SAMPLE_DATA = useMemo(
|
||||
() => ({
|
||||
mentionable: [
|
||||
{ type: "page", title: t.meetingNotes, image: "📝" },
|
||||
{ type: "page", title: t.projectDashboard, image: "📊" },
|
||||
{ type: "page", title: t.ideasBrainstorming, image: "💡" },
|
||||
{ type: "page", title: t.calendarEvents, image: "📅" },
|
||||
{ type: "page", title: t.documentation, image: "📚" },
|
||||
{ type: "page", title: t.goalsObjectives, image: "🎯" },
|
||||
{ type: "page", title: t.budgetPlanning, image: "💰" },
|
||||
{ type: "page", title: t.teamDirectory, image: "👥" },
|
||||
{ type: "page", title: t.technicalSpecs, image: "🔧" },
|
||||
{ type: "page", title: t.analyticsReport, image: "📈" },
|
||||
{
|
||||
type: "user",
|
||||
title: "shadcn",
|
||||
image: "https://github.com/shadcn.png",
|
||||
workspace: t.workspace,
|
||||
},
|
||||
{
|
||||
type: "user",
|
||||
title: "maxleiter",
|
||||
image: "https://github.com/maxleiter.png",
|
||||
workspace: t.workspace,
|
||||
},
|
||||
{
|
||||
type: "user",
|
||||
title: "evilrabbit",
|
||||
image: "https://github.com/evilrabbit.png",
|
||||
workspace: t.workspace,
|
||||
},
|
||||
],
|
||||
models: [
|
||||
{ name: t.auto },
|
||||
{ name: t.agentMode, badge: t.beta },
|
||||
{ name: t.planMode },
|
||||
],
|
||||
}),
|
||||
[t]
|
||||
)
|
||||
|
||||
const [mentions, setMentions] = useState<string[]>([])
|
||||
const [mentionPopoverOpen, setMentionPopoverOpen] = useState(false)
|
||||
const [modelPopoverOpen, setModelPopoverOpen] = useState(false)
|
||||
const [selectedModel, setSelectedModel] = useState<
|
||||
(typeof SAMPLE_DATA.models)[0]
|
||||
>(SAMPLE_DATA.models[0])
|
||||
const [scopeMenuOpen, setScopeMenuOpen] = useState(false)
|
||||
|
||||
const grouped = useMemo(() => {
|
||||
return SAMPLE_DATA.mentionable.reduce(
|
||||
(acc, item) => {
|
||||
const isAvailable = !mentions.includes(item.title)
|
||||
|
||||
if (isAvailable) {
|
||||
if (!acc[item.type]) {
|
||||
acc[item.type] = []
|
||||
}
|
||||
acc[item.type].push(item)
|
||||
}
|
||||
return acc
|
||||
},
|
||||
{} as Record<string, typeof SAMPLE_DATA.mentionable>
|
||||
)
|
||||
}, [mentions, SAMPLE_DATA])
|
||||
|
||||
const hasMentions = mentions.length > 0
|
||||
|
||||
return (
|
||||
<div dir={t.dir}>
|
||||
<form>
|
||||
<Field>
|
||||
<FieldLabel htmlFor="rtl-notion-prompt" className="sr-only">
|
||||
{t.prompt}
|
||||
</FieldLabel>
|
||||
<InputGroup>
|
||||
<InputGroupTextarea
|
||||
id="rtl-notion-prompt"
|
||||
placeholder={t.placeholder}
|
||||
/>
|
||||
<InputGroupAddon align="block-start">
|
||||
<Popover
|
||||
open={mentionPopoverOpen}
|
||||
onOpenChange={setMentionPopoverOpen}
|
||||
>
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={
|
||||
<InputGroupButton
|
||||
variant="outline"
|
||||
size={!hasMentions ? "sm" : "icon-sm"}
|
||||
className="rounded-full transition-transform"
|
||||
/>
|
||||
}
|
||||
onFocusCapture={(e) => e.stopPropagation()}
|
||||
>
|
||||
<IconAt /> {!hasMentions && t.addContext}
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{t.mentionTooltip}</TooltipContent>
|
||||
</Tooltip>
|
||||
<PopoverContent className="p-0" align="start" dir={t.dir}>
|
||||
<Command>
|
||||
<CommandInput placeholder={t.searchPages} />
|
||||
<CommandList>
|
||||
<CommandEmpty>{t.noPagesFound}</CommandEmpty>
|
||||
{Object.entries(grouped).map(([type, items]) => (
|
||||
<CommandGroup
|
||||
key={type}
|
||||
heading={type === "page" ? t.pages : t.users}
|
||||
>
|
||||
{items.map((item) => (
|
||||
<CommandItem
|
||||
key={item.title}
|
||||
value={item.title}
|
||||
onSelect={(currentValue) => {
|
||||
setMentions((prev) => [...prev, currentValue])
|
||||
setMentionPopoverOpen(false)
|
||||
}}
|
||||
>
|
||||
<MentionableIcon item={item} />
|
||||
{item.title}
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
))}
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<div className="-m-1.5 no-scrollbar flex gap-1 overflow-y-auto p-1.5">
|
||||
{mentions.map((mention) => {
|
||||
const item = SAMPLE_DATA.mentionable.find(
|
||||
(item) => item.title === mention
|
||||
)
|
||||
|
||||
if (!item) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<InputGroupButton
|
||||
key={mention}
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
className="rounded-full pr-2!"
|
||||
onClick={() => {
|
||||
setMentions((prev) => prev.filter((m) => m !== mention))
|
||||
}}
|
||||
>
|
||||
<MentionableIcon item={item} />
|
||||
{item.title}
|
||||
<IconX />
|
||||
</InputGroupButton>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</InputGroupAddon>
|
||||
<InputGroupAddon align="block-end" className="gap-1">
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={
|
||||
<InputGroupButton
|
||||
size="icon-sm"
|
||||
className="rounded-full"
|
||||
aria-label={t.attachFile}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<IconPaperclip />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{t.attachFile}</TooltipContent>
|
||||
</Tooltip>
|
||||
<DropdownMenu
|
||||
open={modelPopoverOpen}
|
||||
onOpenChange={setModelPopoverOpen}
|
||||
>
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={
|
||||
<InputGroupButton size="sm" className="rounded-full" />
|
||||
}
|
||||
>
|
||||
{selectedModel.name}
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{t.selectModel}</TooltipContent>
|
||||
</Tooltip>
|
||||
<DropdownMenuContent
|
||||
side="top"
|
||||
align="start"
|
||||
className="w-48"
|
||||
dir={t.dir}
|
||||
>
|
||||
<DropdownMenuGroup className="w-48">
|
||||
<DropdownMenuLabel className="text-xs text-muted-foreground">
|
||||
{t.selectAgentMode}
|
||||
</DropdownMenuLabel>
|
||||
{SAMPLE_DATA.models.map((model) => (
|
||||
<DropdownMenuCheckboxItem
|
||||
key={model.name}
|
||||
checked={model.name === selectedModel.name}
|
||||
onCheckedChange={(checked) => {
|
||||
if (checked) {
|
||||
setSelectedModel(model)
|
||||
}
|
||||
}}
|
||||
className="pr-2 *:[span:first-child]:right-auto *:[span:first-child]:left-2"
|
||||
>
|
||||
{model.name}
|
||||
{model.badge && (
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className="h-5 rounded-sm bg-blue-100 px-1 text-xs text-blue-800 dark:bg-blue-900 dark:text-blue-100"
|
||||
>
|
||||
{model.badge}
|
||||
</Badge>
|
||||
)}
|
||||
</DropdownMenuCheckboxItem>
|
||||
))}
|
||||
</DropdownMenuGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<DropdownMenu
|
||||
open={scopeMenuOpen}
|
||||
onOpenChange={setScopeMenuOpen}
|
||||
>
|
||||
<DropdownMenuTrigger
|
||||
render={
|
||||
<InputGroupButton size="sm" className="rounded-full" />
|
||||
}
|
||||
>
|
||||
<IconWorld /> {t.allSources}
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
side="top"
|
||||
align="end"
|
||||
className="w-72"
|
||||
dir={t.dir}
|
||||
>
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem
|
||||
render={
|
||||
<label htmlFor="rtl-web-search">
|
||||
<IconWorld /> {t.webSearch}{" "}
|
||||
<Switch
|
||||
id="rtl-web-search"
|
||||
className="ms-auto"
|
||||
defaultChecked
|
||||
size="sm"
|
||||
/>
|
||||
</label>
|
||||
}
|
||||
onSelect={(e) => e.preventDefault()}
|
||||
></DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem
|
||||
render={
|
||||
<label htmlFor="rtl-apps">
|
||||
<IconApps /> {t.appsIntegrations}
|
||||
<Switch
|
||||
id="rtl-apps"
|
||||
className="ms-auto"
|
||||
defaultChecked
|
||||
size="sm"
|
||||
/>
|
||||
</label>
|
||||
}
|
||||
onSelect={(e) => e.preventDefault()}
|
||||
></DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<IconCircleDashedPlus /> {t.allSourcesAccess}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger>
|
||||
<Avatar className="size-4">
|
||||
<AvatarImage src="https://github.com/shadcn.png" />
|
||||
<AvatarFallback>CN</AvatarFallback>
|
||||
</Avatar>
|
||||
shadcn
|
||||
</DropdownMenuSubTrigger>
|
||||
<DropdownMenuSubContent
|
||||
className="w-72 rounded-lg p-0"
|
||||
dir={t.dir}
|
||||
side="left"
|
||||
>
|
||||
<Command>
|
||||
<CommandInput
|
||||
placeholder={t.findKnowledge}
|
||||
autoFocus
|
||||
/>
|
||||
<CommandList>
|
||||
<CommandEmpty>{t.noKnowledgeFound}</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{SAMPLE_DATA.mentionable
|
||||
.filter((item) => item.type === "user")
|
||||
.map((user) => (
|
||||
<CommandItem
|
||||
key={user.title}
|
||||
value={user.title}
|
||||
onSelect={() => {
|
||||
console.log("Selected user:", user.title)
|
||||
}}
|
||||
>
|
||||
<Avatar className="size-4">
|
||||
<AvatarImage src={user.image} />
|
||||
<AvatarFallback>
|
||||
{user.title[0]}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
{user.title}{" "}
|
||||
<span className="text-muted-foreground">
|
||||
-{" "}
|
||||
{
|
||||
(user as { workspace?: string })
|
||||
.workspace
|
||||
}
|
||||
</span>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuSub>
|
||||
<DropdownMenuItem>
|
||||
<IconBook /> {t.helpCenter}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem>
|
||||
<IconPlus /> {t.connectApps}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuLabel className="text-xs text-muted-foreground">
|
||||
{t.searchSourcesNote}
|
||||
</DropdownMenuLabel>
|
||||
</DropdownMenuGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<InputGroupButton
|
||||
aria-label={t.send}
|
||||
className="ms-auto rounded-full"
|
||||
variant="default"
|
||||
size="icon-sm"
|
||||
>
|
||||
<IconArrowUp />
|
||||
</InputGroupButton>
|
||||
</InputGroupAddon>
|
||||
</InputGroup>
|
||||
</Field>
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
44
apps/v4/app/(app)/examples/rtl/components/spinner-badge.tsx
Normal file
44
apps/v4/app/(app)/examples/rtl/components/spinner-badge.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
"use client"
|
||||
|
||||
import { Badge } from "@/examples/base/ui-rtl/badge"
|
||||
import { Spinner } from "@/examples/base/ui-rtl/spinner"
|
||||
|
||||
import { useLanguageContext } from "@/components/language-selector"
|
||||
|
||||
const translations = {
|
||||
ar: {
|
||||
dir: "rtl" as const,
|
||||
syncing: "جارٍ المزامنة",
|
||||
updating: "جارٍ التحديث",
|
||||
loading: "جارٍ التحميل",
|
||||
},
|
||||
he: {
|
||||
dir: "rtl" as const,
|
||||
syncing: "מסנכרן",
|
||||
updating: "מעדכן",
|
||||
loading: "טוען",
|
||||
},
|
||||
}
|
||||
|
||||
export function SpinnerBadge() {
|
||||
const context = useLanguageContext()
|
||||
const lang = context?.language === "he" ? "he" : "ar"
|
||||
const t = translations[lang]
|
||||
|
||||
return (
|
||||
<div dir={t.dir} className="flex items-center gap-2">
|
||||
<Badge>
|
||||
<Spinner />
|
||||
{t.syncing}
|
||||
</Badge>
|
||||
<Badge variant="secondary">
|
||||
<Spinner />
|
||||
{t.updating}
|
||||
</Badge>
|
||||
<Badge variant="outline">
|
||||
<Spinner />
|
||||
{t.loading}
|
||||
</Badge>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
52
apps/v4/app/(app)/examples/rtl/components/spinner-empty.tsx
Normal file
52
apps/v4/app/(app)/examples/rtl/components/spinner-empty.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
"use client"
|
||||
|
||||
import { Button } from "@/examples/base/ui-rtl/button"
|
||||
import {
|
||||
Empty,
|
||||
EmptyContent,
|
||||
EmptyDescription,
|
||||
EmptyHeader,
|
||||
EmptyMedia,
|
||||
EmptyTitle,
|
||||
} from "@/examples/base/ui-rtl/empty"
|
||||
import { Spinner } from "@/examples/base/ui-rtl/spinner"
|
||||
|
||||
import { useLanguageContext } from "@/components/language-selector"
|
||||
|
||||
const translations = {
|
||||
ar: {
|
||||
dir: "rtl" as const,
|
||||
title: "جارٍ معالجة طلبك",
|
||||
description: "يرجى الانتظار بينما نعالج طلبك. لا تقم بتحديث الصفحة.",
|
||||
cancel: "إلغاء",
|
||||
},
|
||||
he: {
|
||||
dir: "rtl" as const,
|
||||
title: "מעבד את הבקשה שלך",
|
||||
description: "אנא המתן בזמן שאנו מעבדים את בקשתך. אל תרענן את הדף.",
|
||||
cancel: "ביטול",
|
||||
},
|
||||
}
|
||||
|
||||
export function SpinnerEmpty() {
|
||||
const context = useLanguageContext()
|
||||
const lang = context?.language === "he" ? "he" : "ar"
|
||||
const t = translations[lang]
|
||||
|
||||
return (
|
||||
<Empty className="w-full border md:p-6" dir={t.dir}>
|
||||
<EmptyHeader>
|
||||
<EmptyMedia variant="icon">
|
||||
<Spinner />
|
||||
</EmptyMedia>
|
||||
<EmptyTitle>{t.title}</EmptyTitle>
|
||||
<EmptyDescription>{t.description}</EmptyDescription>
|
||||
</EmptyHeader>
|
||||
<EmptyContent>
|
||||
<Button variant="outline" size="sm">
|
||||
{t.cancel}
|
||||
</Button>
|
||||
</EmptyContent>
|
||||
</Empty>
|
||||
)
|
||||
}
|
||||
14
apps/v4/app/(app)/examples/rtl/page.tsx
Normal file
14
apps/v4/app/(app)/examples/rtl/page.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import { type Metadata } from "next"
|
||||
|
||||
import { RtlComponents } from "./components"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "RTL",
|
||||
description: "RTL example page with right-to-left language support.",
|
||||
}
|
||||
|
||||
export function RtlPage() {
|
||||
return <RtlComponents />
|
||||
}
|
||||
|
||||
export default RtlPage
|
||||
@@ -1,12 +1,12 @@
|
||||
"use client"
|
||||
|
||||
import { ColumnDef } from "@tanstack/react-table"
|
||||
import { type ColumnDef } from "@tanstack/react-table"
|
||||
|
||||
import { Badge } from "@/registry/new-york-v4/ui/badge"
|
||||
import { Checkbox } from "@/registry/new-york-v4/ui/checkbox"
|
||||
|
||||
import { labels, priorities, statuses } from "../data/data"
|
||||
import { Task } from "../data/schema"
|
||||
import { type Task } from "../data/schema"
|
||||
import { DataTableColumnHeader } from "./data-table-column-header"
|
||||
import { DataTableRowActions } from "./data-table-row-actions"
|
||||
|
||||
@@ -79,7 +79,7 @@ export const columns: ColumnDef<Task>[] = [
|
||||
return (
|
||||
<div className="flex w-[100px] items-center gap-2">
|
||||
{status.icon && (
|
||||
<status.icon className="text-muted-foreground size-4" />
|
||||
<status.icon className="size-4 text-muted-foreground" />
|
||||
)}
|
||||
<span>{status.label}</span>
|
||||
</div>
|
||||
@@ -106,7 +106,7 @@ export const columns: ColumnDef<Task>[] = [
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
{priority.icon && (
|
||||
<priority.icon className="text-muted-foreground size-4" />
|
||||
<priority.icon className="size-4 text-muted-foreground" />
|
||||
)}
|
||||
<span>{priority.label}</span>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Column } from "@tanstack/react-table"
|
||||
import { type Column } from "@tanstack/react-table"
|
||||
import { ArrowDown, ArrowUp, ChevronsUpDown, EyeOff } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
@@ -33,7 +33,7 @@ export function DataTableColumnHeader<TData, TValue>({
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="data-[state=open]:bg-accent -ml-3 h-8"
|
||||
className="-ml-3 h-8 data-[state=open]:bg-accent"
|
||||
>
|
||||
<span>{title}</span>
|
||||
{column.getIsSorted() === "desc" ? (
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from "react"
|
||||
import { Column } from "@tanstack/react-table"
|
||||
import { type Column } from "@tanstack/react-table"
|
||||
import { Check, PlusCircle } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
@@ -107,18 +107,18 @@ export function DataTableFacetedFilter<TData, TValue>({
|
||||
className={cn(
|
||||
"flex size-4 items-center justify-center rounded-[4px] border",
|
||||
isSelected
|
||||
? "bg-primary border-primary text-primary-foreground"
|
||||
? "border-primary bg-primary text-primary-foreground"
|
||||
: "border-input [&_svg]:invisible"
|
||||
)}
|
||||
>
|
||||
<Check className="text-primary-foreground size-3.5" />
|
||||
<Check className="size-3.5 text-primary-foreground" />
|
||||
</div>
|
||||
{option.icon && (
|
||||
<option.icon className="text-muted-foreground size-4" />
|
||||
<option.icon className="size-4 text-muted-foreground" />
|
||||
)}
|
||||
<span>{option.label}</span>
|
||||
{facets?.get(option.value) && (
|
||||
<span className="text-muted-foreground ml-auto flex size-4 items-center justify-center font-mono text-xs">
|
||||
<span className="ml-auto flex size-4 items-center justify-center font-mono text-xs text-muted-foreground">
|
||||
{facets.get(option.value)}
|
||||
</span>
|
||||
)}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Table } from "@tanstack/react-table"
|
||||
import { type Table } from "@tanstack/react-table"
|
||||
import {
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
@@ -24,7 +24,7 @@ export function DataTablePagination<TData>({
|
||||
}: DataTablePaginationProps<TData>) {
|
||||
return (
|
||||
<div className="flex items-center justify-between px-2">
|
||||
<div className="text-muted-foreground flex-1 text-sm">
|
||||
<div className="flex-1 text-sm text-muted-foreground">
|
||||
{table.getFilteredSelectedRowModel().rows.length} of{" "}
|
||||
{table.getFilteredRowModel().rows.length} row(s) selected.
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client"
|
||||
|
||||
import { Row } from "@tanstack/react-table"
|
||||
import { type Row } from "@tanstack/react-table"
|
||||
import { MoreHorizontal } from "lucide-react"
|
||||
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
@@ -36,7 +36,7 @@ export function DataTableRowActions<TData>({
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="data-[state=open]:bg-muted size-8"
|
||||
className="size-8 data-[state=open]:bg-muted"
|
||||
>
|
||||
<MoreHorizontal />
|
||||
<span className="sr-only">Open menu</span>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client"
|
||||
|
||||
import { Table } from "@tanstack/react-table"
|
||||
import { type Table } from "@tanstack/react-table"
|
||||
import { X } from "lucide-react"
|
||||
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"use client"
|
||||
|
||||
import { DropdownMenuTrigger } from "@radix-ui/react-dropdown-menu"
|
||||
import { Table } from "@tanstack/react-table"
|
||||
import { type Table } from "@tanstack/react-table"
|
||||
import { Settings2 } from "lucide-react"
|
||||
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
@@ -11,6 +10,7 @@ import {
|
||||
DropdownMenuContent,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/registry/new-york-v4/ui/dropdown-menu"
|
||||
|
||||
export function DataTableViewOptions<TData>({
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
import * as React from "react"
|
||||
import {
|
||||
ColumnDef,
|
||||
ColumnFiltersState,
|
||||
flexRender,
|
||||
getCoreRowModel,
|
||||
getFacetedRowModel,
|
||||
@@ -11,9 +9,11 @@ import {
|
||||
getFilteredRowModel,
|
||||
getPaginationRowModel,
|
||||
getSortedRowModel,
|
||||
SortingState,
|
||||
useReactTable,
|
||||
VisibilityState,
|
||||
type ColumnDef,
|
||||
type ColumnFiltersState,
|
||||
type SortingState,
|
||||
type VisibilityState,
|
||||
} from "@tanstack/react-table"
|
||||
|
||||
import {
|
||||
@@ -75,7 +75,7 @@ export function DataTable<TData, TValue>({
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<DataTableToolbar table={table} />
|
||||
<div className="rounded-md border">
|
||||
<div className="overflow-hidden rounded-md border">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
|
||||
@@ -30,7 +30,7 @@ export function UserNav() {
|
||||
<DropdownMenuLabel className="font-normal">
|
||||
<div className="flex flex-col space-y-1">
|
||||
<p className="text-sm leading-none font-medium">shadcn</p>
|
||||
<p className="text-muted-foreground text-xs leading-none">
|
||||
<p className="text-xs leading-none text-muted-foreground">
|
||||
m@example.com
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { promises as fs } from "fs"
|
||||
import path from "path"
|
||||
import { Metadata } from "next"
|
||||
import { type Metadata } from "next"
|
||||
import Image from "next/image"
|
||||
import { z } from "zod"
|
||||
|
||||
|
||||
@@ -3,7 +3,10 @@ import { SiteHeader } from "@/components/site-header"
|
||||
|
||||
export default function AppLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<div className="bg-background relative z-10 flex min-h-svh flex-col">
|
||||
<div
|
||||
data-slot="layout"
|
||||
className="relative z-10 flex min-h-svh flex-col bg-background"
|
||||
>
|
||||
<SiteHeader />
|
||||
<main className="flex flex-1 flex-col">{children}</main>
|
||||
<SiteFooter />
|
||||
|
||||
51
apps/v4/app/(app)/llm/[[...slug]]/route.ts
Normal file
51
apps/v4/app/(app)/llm/[[...slug]]/route.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { notFound } from "next/navigation"
|
||||
import { NextResponse, type NextRequest } from "next/server"
|
||||
|
||||
import { processMdxForLLMs } from "@/lib/llm"
|
||||
import { source } from "@/lib/source"
|
||||
import { getActiveStyle, type Style } from "@/registry/_legacy-styles"
|
||||
|
||||
export const revalidate = false
|
||||
|
||||
function getStyleFromSlug(slug: string[] | undefined, fallbackStyle: string) {
|
||||
// Detect base from URL: /docs/components/base/... or /docs/components/radix/...
|
||||
if (slug && slug[0] === "components" && slug[1]) {
|
||||
if (slug[1] === "base") {
|
||||
return "base-nova"
|
||||
}
|
||||
if (slug[1] === "radix") {
|
||||
return "new-york-v4"
|
||||
}
|
||||
}
|
||||
return fallbackStyle
|
||||
}
|
||||
|
||||
export async function GET(
|
||||
_req: NextRequest,
|
||||
{ params }: { params: Promise<{ slug?: string[] }> }
|
||||
) {
|
||||
const [{ slug }, activeStyle] = await Promise.all([params, getActiveStyle()])
|
||||
|
||||
const page = source.getPage(slug)
|
||||
|
||||
if (!page) {
|
||||
notFound()
|
||||
}
|
||||
|
||||
const effectiveStyle = getStyleFromSlug(slug, activeStyle.name)
|
||||
|
||||
const processedContent = processMdxForLLMs(
|
||||
await page.data.getText("raw"),
|
||||
effectiveStyle as Style["name"]
|
||||
)
|
||||
|
||||
return new NextResponse(processedContent, {
|
||||
headers: {
|
||||
"Content-Type": "text/markdown; charset=utf-8",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function generateStaticParams() {
|
||||
return source.generateParams()
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Metadata } from "next"
|
||||
import { type Metadata } from "next"
|
||||
import Link from "next/link"
|
||||
|
||||
import { Announcement } from "@/components/announcement"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user