mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-15 12:39:35 +01:00
Compare commits
163 Commits
feat/updat
...
v3.0.0-alp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
83c2b70d98 | ||
|
|
3b9ca2263d | ||
|
|
6636543256 | ||
|
|
f98b91c22a | ||
|
|
857238ff14 | ||
|
|
8b5d412fd7 | ||
|
|
ed44d9d101 | ||
|
|
5a900e460b | ||
|
|
4b241ba3c3 | ||
|
|
80befc107c | ||
|
|
6c946dc0e7 | ||
|
|
2d81c02356 | ||
|
|
d3b3b9bef3 | ||
|
|
b7ba2c7759 | ||
|
|
198d04de51 | ||
|
|
16e0339e7a | ||
|
|
6f1f9f4d81 | ||
|
|
46f4e5a24d | ||
|
|
b983ea2aec | ||
|
|
bd4abfa880 | ||
|
|
c1ff978370 | ||
|
|
984bb0899c | ||
|
|
a938d24f90 | ||
|
|
0f8c398673 | ||
|
|
695224f91f | ||
|
|
4b653ef773 | ||
|
|
816bb69deb | ||
|
|
5a7c3b13d3 | ||
|
|
b741ef3313 | ||
|
|
bc80a0121f | ||
|
|
e475b6438d | ||
|
|
3e283117d2 | ||
|
|
6484d010a1 | ||
|
|
d539109357 | ||
|
|
01c3934403 | ||
|
|
e79103131f | ||
|
|
37f05e2ba8 | ||
|
|
81ac076219 | ||
|
|
c440c91a29 | ||
|
|
b7ff7d8aa6 | ||
|
|
68a10f09d5 | ||
|
|
004a577467 | ||
|
|
f712135576 | ||
|
|
e2aaf5ba21 | ||
|
|
d65c7376d9 | ||
|
|
f04c2082ad | ||
|
|
bda6098db3 | ||
|
|
8344d852a5 | ||
|
|
6bd8a06871 | ||
|
|
148b02464d | ||
|
|
1240a3b604 | ||
|
|
e3b8d0e60e | ||
|
|
0d41210c91 | ||
|
|
bc2bcb30d9 | ||
|
|
ed2722257a | ||
|
|
f06fbafc1e | ||
|
|
ecc4755a17 | ||
|
|
9b58ce05f0 | ||
|
|
a481410c5f | ||
|
|
cd7ab3b2b9 | ||
|
|
9a17f90985 | ||
|
|
3496b2d541 | ||
|
|
a6c22052e1 | ||
|
|
6eb0bbbe1b | ||
|
|
a2512f680d | ||
|
|
4eb79021ed | ||
|
|
8a45c25df3 | ||
|
|
004fa1ebcb | ||
|
|
0f648024e0 | ||
|
|
e7995e7a0b | ||
|
|
15ca2f5701 | ||
|
|
08b9e4bff0 | ||
|
|
f49b49fd2c | ||
|
|
a74e8c4444 | ||
|
|
781081132d | ||
|
|
b1550d58ad | ||
|
|
e7b69b7d6f | ||
|
|
9478fc0768 | ||
|
|
2e9aeb5f05 | ||
|
|
86f2b4856c | ||
|
|
ba874c9191 | ||
|
|
ffc81cc950 | ||
|
|
d783387ed3 | ||
|
|
37655377e9 | ||
|
|
7ab88d30b2 | ||
|
|
ccb79b7ee4 | ||
|
|
c9806da6d8 | ||
|
|
3bccb6782a | ||
|
|
5a01a81577 | ||
|
|
3baddfd121 | ||
|
|
a7a1227c93 | ||
|
|
b259ddf271 | ||
|
|
c47ffc1cd5 | ||
|
|
0baa3a06d4 | ||
|
|
b140dbfe63 | ||
|
|
6d917baac0 | ||
|
|
c511c95537 | ||
|
|
de8228e504 | ||
|
|
29d2acf564 | ||
|
|
f5452ba0c5 | ||
|
|
b6a841e975 | ||
|
|
7bf85e9a09 | ||
|
|
9e8d50b2b8 | ||
|
|
b13b9e3ec0 | ||
|
|
126c893635 | ||
|
|
7d8b721bdd | ||
|
|
b120e8d998 | ||
|
|
2c4634a58f | ||
|
|
2cbf83eb84 | ||
|
|
da1b0bac04 | ||
|
|
7cc51d2efa | ||
|
|
c163ed8187 | ||
|
|
a6ecef0f0d | ||
|
|
faec8260a4 | ||
|
|
7a02bfeba6 | ||
|
|
9dd525ca26 | ||
|
|
21d8c352a9 | ||
|
|
5deadc7096 | ||
|
|
fa9f0a7e2a | ||
|
|
143c015bbd | ||
|
|
d75f47419d | ||
|
|
7b148daf1f | ||
|
|
30e0c7fddd | ||
|
|
14fb21be00 | ||
|
|
25091bad48 | ||
|
|
b75ed29068 | ||
|
|
b2fa65734b | ||
|
|
d3a079a644 | ||
|
|
8c6a8c283f | ||
|
|
2fc36c878c | ||
|
|
992be91823 | ||
|
|
bd2f077fe8 | ||
|
|
7329900ae5 | ||
|
|
8d85498ee1 | ||
|
|
5c292cf620 | ||
|
|
c0837059a9 | ||
|
|
f5ea2411dc | ||
|
|
1fbbfe8df0 | ||
|
|
0daac5bafb | ||
|
|
756f791a1a | ||
|
|
8ed434c105 | ||
|
|
190a2c9799 | ||
|
|
e55c0e2594 | ||
|
|
4312ca4702 | ||
|
|
2289742656 | ||
|
|
601f4b2cd2 | ||
|
|
cd080541a0 | ||
|
|
5722e0802d | ||
|
|
8d0026558a | ||
|
|
e5119a9ca4 | ||
|
|
976dd2a386 | ||
|
|
1d95eb7246 | ||
|
|
7cc26d098d | ||
|
|
9241ba1230 | ||
|
|
3396d5cebe | ||
|
|
937585cb3f | ||
|
|
9c00f7c7b7 | ||
|
|
73e25ed235 | ||
|
|
75c5e87724 | ||
|
|
95aa6f68b3 | ||
|
|
f516d7b36d | ||
|
|
300ccc4885 | ||
|
|
e48b416e3b |
3
.github/workflows/ci-v3.yml
vendored
3
.github/workflows/ci-v3.yml
vendored
@@ -61,5 +61,8 @@ jobs:
|
||||
- name: Build
|
||||
run: pnpm run build
|
||||
|
||||
- name: Build vue fixture
|
||||
run: pnpm run test:vue:build
|
||||
|
||||
- name: Publish
|
||||
run: pnpx pkg-pr-new publish --compact --no-template --pnpm
|
||||
|
||||
116
CHANGELOG.md
116
CHANGELOG.md
@@ -1,5 +1,121 @@
|
||||
# Changelog
|
||||
|
||||
## [3.0.0-alpha.10](https://github.com/nuxt/ui/compare/v3.0.0-alpha.9...v3.0.0-alpha.10) (2024-12-09)
|
||||
|
||||
### ⚠ BREAKING CHANGES
|
||||
|
||||
* **module:** migrate to `reka-ui` (#2448)
|
||||
* **Form:** resolve async validation in yup & issue directly mutate state (#2702)
|
||||
|
||||
### Features
|
||||
|
||||
* **Avatar:** add `default` slot for fallback ([b741ef3](https://github.com/nuxt/ui/commit/b741ef3313bb894beaed0eaa7323ee3d951bf935))
|
||||
* **Calendar:** add `icon` props ([#2778](https://github.com/nuxt/ui/issues/2778)) ([0f64802](https://github.com/nuxt/ui/commit/0f648024e0468d34c1499bb5b5d2ed32e0e7de4f))
|
||||
* **Calendar:** implement component ([#2618](https://github.com/nuxt/ui/issues/2618)) ([2e9aeb5](https://github.com/nuxt/ui/commit/2e9aeb5f05e94d63ea453c4f07a3e84ee2a02773))
|
||||
* **ColorPicker:** implement component ([#2670](https://github.com/nuxt/ui/issues/2670)) ([e475b64](https://github.com/nuxt/ui/commit/e475b6438d405e4695ffb19155d456534d16b897))
|
||||
* **CommandPalette:** add `active` field on items for consistency ([3765537](https://github.com/nuxt/ui/commit/37655377e9675982e2fce422bdd79ea651424548))
|
||||
* **css:** use `color-scheme` utilities on body ([a2512f6](https://github.com/nuxt/ui/commit/a2512f680dc0df7add48bc17ef3be30d579be782))
|
||||
* **i18n:** add Dutch locale ([#2728](https://github.com/nuxt/ui/issues/2728)) ([3baddfd](https://github.com/nuxt/ui/commit/3baddfd12186a68cc302f31cf0934cb9cf48060d))
|
||||
* **i18n:** add Turkish locale ([#2716](https://github.com/nuxt/ui/issues/2716)) ([de8228e](https://github.com/nuxt/ui/commit/de8228e504affd1a57106101f5168a33702d4d53))
|
||||
* **locale:** add Brazilian Portuguese language ([#2825](https://github.com/nuxt/ui/issues/2825)) ([b7ff7d8](https://github.com/nuxt/ui/commit/b7ff7d8aa63c41cf7afbecaa31824e098ea291af))
|
||||
* **locale:** add Japanese language ([#2794](https://github.com/nuxt/ui/issues/2794)) ([ecc4755](https://github.com/nuxt/ui/commit/ecc4755a17874e59e06e70307a40dfd3fb3f20a0))
|
||||
* **locale:** add Portuguese language ([#2855](https://github.com/nuxt/ui/issues/2855)) ([8b5d412](https://github.com/nuxt/ui/commit/8b5d412fd70b14a53cffa9129f5edd8a40e0f2e8))
|
||||
* **locale:** add Slovak language ([#2821](https://github.com/nuxt/ui/issues/2821)) ([68a10f0](https://github.com/nuxt/ui/commit/68a10f09d5f164f2f5f07e65297e29fa2d939425))
|
||||
* **locale:** translate Korean ([#2703](https://github.com/nuxt/ui/issues/2703)) ([2cbf83e](https://github.com/nuxt/ui/commit/2cbf83eb8484ad9abebd6ca01ad344918570af5b))
|
||||
* **module:** migrate to `reka-ui` ([#2448](https://github.com/nuxt/ui/issues/2448)) ([81ac076](https://github.com/nuxt/ui/commit/81ac076219c3d7ef151f641414a0fbeca2da0bdd))
|
||||
* **NavigationMenu:** handle `item.trailingIcon` display ([4b653ef](https://github.com/nuxt/ui/commit/4b653ef7735d9d2dfea65260433ade05eb3d3bd7))
|
||||
* **Stepper:** new component ([#2733](https://github.com/nuxt/ui/issues/2733)) ([6484d01](https://github.com/nuxt/ui/commit/6484d010a1eee6f5d86968e4701b945953089b17))
|
||||
* **Table:** handle `meta.class` on `th` and `td` ([#2790](https://github.com/nuxt/ui/issues/2790)) ([004a577](https://github.com/nuxt/ui/commit/004a5774678da24ccc267e96697c6088c51d5eea))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Breadcrumb:** missing `aria-hidden` on presentation items ([a7a1227](https://github.com/nuxt/ui/commit/a7a1227c93110727e24f822fa50b547eb66bb16e)), closes [#2725](https://github.com/nuxt/ui/issues/2725)
|
||||
* **Calendar:** handle icons in RTL mode ([#2770](https://github.com/nuxt/ui/issues/2770)) ([e7b69b7](https://github.com/nuxt/ui/commit/e7b69b7d6f0ebb3c578b9f58bcddf8ad36e6c6ce))
|
||||
* **Calendar:** omit `as` / `asChild` props ([9478fc0](https://github.com/nuxt/ui/commit/9478fc076846d4a7fef13e63bdc274cd8d161063))
|
||||
* **ColorPicker:** handle RTL mode ([#2858](https://github.com/nuxt/ui/issues/2858)) ([f98b91c](https://github.com/nuxt/ui/commit/f98b91c22ae21071a25f69cc8682eb6197a54c5a))
|
||||
* **CommandPalette:** keep `ignoreFilter` groups at their place ([#2833](https://github.com/nuxt/ui/issues/2833)) ([3b9ca22](https://github.com/nuxt/ui/commit/3b9ca2263de1b936639b1b20ad0baf1cb059fda5))
|
||||
* **components:** apply class on `trigger` instead of `content` when present ([a6ecef0](https://github.com/nuxt/ui/commit/a6ecef0f0d87a8dff4e4cb9ec507058ec94ed82b)), closes [#2132](https://github.com/nuxt/ui/issues/2132)
|
||||
* **components:** specify collisionPadding to all menus ([148b024](https://github.com/nuxt/ui/commit/148b02464d47ada421313327585924b17f4e3f2d))
|
||||
* **ContextMenu:** remove close animation ([#2798](https://github.com/nuxt/ui/issues/2798)) ([ed27222](https://github.com/nuxt/ui/commit/ed2722257a22c770eda811fbad58980bcef9dad5))
|
||||
* **defineShortcuts:** return `useEventListener` to unregister the listener ([80befc1](https://github.com/nuxt/ui/commit/80befc107c6c6e7ab99dbe12376976babf315158)), closes [#2031](https://github.com/nuxt/ui/issues/2031)
|
||||
* **devtools:** error with renderer when `colorMode` is disabled ([#2792](https://github.com/nuxt/ui/issues/2792)) ([f06fbaf](https://github.com/nuxt/ui/commit/f06fbafc1e709c7b4e54e2ba40d44c5770685a5d))
|
||||
* **Form:** resolve async validation in yup & issue directly mutate state ([#2702](https://github.com/nuxt/ui/issues/2702)) ([c9806da](https://github.com/nuxt/ui/commit/c9806da6d850ea50ff8d2f11a1fbc5a43459b71f))
|
||||
* **icons:** make `loading` icon clockwise ([#2797](https://github.com/nuxt/ui/issues/2797)) ([bc2bcb3](https://github.com/nuxt/ui/commit/bc2bcb30d97e2e873c4c7d535f82a4980cd35b02))
|
||||
* **Link:** partial query match for Vue ([#2787](https://github.com/nuxt/ui/issues/2787)) ([a6c2205](https://github.com/nuxt/ui/commit/a6c22052e1c70e4ce6b2c7f783667a7f8c6cafa4))
|
||||
* **locale:** improve German translation ([#2826](https://github.com/nuxt/ui/issues/2826)) ([c440c91](https://github.com/nuxt/ui/commit/c440c91a29fc1acd281a7f9d9b0cf74f5341553d))
|
||||
* **Modal:** prevent from going out of screen ([b7ba2c7](https://github.com/nuxt/ui/commit/b7ba2c7759485ddb0a8bae589e4b6536ac9b1c96)), closes [#2711](https://github.com/nuxt/ui/issues/2711)
|
||||
* **NavigationMenu:** wrong badge class ([86f2b48](https://github.com/nuxt/ui/commit/86f2b4856cc6beaf0440795500a5c74f9af04f36)), closes [#2766](https://github.com/nuxt/ui/issues/2766)
|
||||
* **Progress:** handle `horizontal` animation in RTL mode ([#2723](https://github.com/nuxt/ui/issues/2723)) ([0baa3a0](https://github.com/nuxt/ui/commit/0baa3a06d449ab97093c451bd16215cf83c39447))
|
||||
* **Stepper:** handle RTL mode ([#2844](https://github.com/nuxt/ui/issues/2844)) ([198d04d](https://github.com/nuxt/ui/commit/198d04de51d16ec7fcaa546370e4f67aa73bfee0))
|
||||
* **Stepper:** missing import ([816bb69](https://github.com/nuxt/ui/commit/816bb69debdbf83f36c3ed3627985142e62b7dd1))
|
||||
* **Table:** handle `loading` animation in RTL mode ([#2771](https://github.com/nuxt/ui/issues/2771)) ([b1550d5](https://github.com/nuxt/ui/commit/b1550d58adfeb09977619ad3ff7e776782a89603))
|
||||
* **Tabs:** prevent hover on disabled ([a938d24](https://github.com/nuxt/ui/commit/a938d24f90431494c2da89411c301a228ab8d3f7))
|
||||
* **Tabs:** truncate not working ([c1ff978](https://github.com/nuxt/ui/commit/c1ff978370fb343950837b380ccf02a33db53ccb))
|
||||
* **types:** handle array of strings in AppConfig ([#2854](https://github.com/nuxt/ui/issues/2854)) ([4b241ba](https://github.com/nuxt/ui/commit/4b241ba3c32f4456252768b664488799a8278f0e))
|
||||
* **useLocale:** update locale import to enable tree shaking ([#2735](https://github.com/nuxt/ui/issues/2735)) ([3bccb67](https://github.com/nuxt/ui/commit/3bccb6782a601e686df5d0ee405d738572f182e1))
|
||||
|
||||
## [3.0.0-alpha.9](https://github.com/nuxt/ui/compare/v3.0.0-alpha.8...v3.0.0-alpha.9) (2024-11-19)
|
||||
|
||||
### Features
|
||||
|
||||
* **cli:** add locale command ([#2586](https://github.com/nuxt/ui/issues/2586)) ([824ba56](https://github.com/nuxt/ui/commit/824ba5629183bc4cd59321213558770db211f6e5))
|
||||
* **css:** add `--ui-bg-muted` / `--ui-border-muted` variables ([7f6db45](https://github.com/nuxt/ui/commit/7f6db45f1e15ef39cda9b732cb601c552f29570a))
|
||||
* **Form:** apply transformations ([#2550](https://github.com/nuxt/ui/issues/2550)) ([75c5e87](https://github.com/nuxt/ui/commit/75c5e87724e7abdf0a6751d7a1dbbafb947f373f))
|
||||
* **FormField:** add `error-pattern` prop ([#2601](https://github.com/nuxt/ui/issues/2601)) ([143612e](https://github.com/nuxt/ui/commit/143612ec737d1c7571398601c3222f2eed37996e))
|
||||
* **InputMenu/SelectMenu:** add `create-item` prop ([#2472](https://github.com/nuxt/ui/issues/2472)) ([f516d7b](https://github.com/nuxt/ui/commit/f516d7b36da51565f4ab05a4c9cfe5e5b4015124))
|
||||
* **InputNumber:** implement component ([#2577](https://github.com/nuxt/ui/issues/2577)) ([bd2f077](https://github.com/nuxt/ui/commit/bd2f077fe8e645d5fce8b1eb5a6eb1068b3e8f7c))
|
||||
* **Link:** allow partial query match for its active state ([#2664](https://github.com/nuxt/ui/issues/2664)) ([7329900](https://github.com/nuxt/ui/commit/7329900ae549430b88567a09cbb585d3cf0a6d32))
|
||||
* **locale:** add Persian language ([#2682](https://github.com/nuxt/ui/issues/2682)) ([14fb21b](https://github.com/nuxt/ui/commit/14fb21be0034ffc0ba5d213734c00f12e0d6bea8))
|
||||
* **locale:** add Polish language ([#2678](https://github.com/nuxt/ui/issues/2678)) ([2fc36c8](https://github.com/nuxt/ui/commit/2fc36c878c67967ec91e4f6999575bad45521d44))
|
||||
* **locale:** add support for Arabic ([#2582](https://github.com/nuxt/ui/issues/2582)) ([602a667](https://github.com/nuxt/ui/commit/602a667343be22b72383ab3cf42f36ec9e135082))
|
||||
* **locale:** add support for Czech translation ([#2593](https://github.com/nuxt/ui/issues/2593)) ([4889d30](https://github.com/nuxt/ui/commit/4889d30b448296de42e146dc5771738837c31f8c))
|
||||
* **locale:** add support for Italian ([#2583](https://github.com/nuxt/ui/issues/2583)) ([4fbbb25](https://github.com/nuxt/ui/commit/4fbbb25f68b0b5ee76e50f2da776a74d54acc041))
|
||||
* **locale:** provide `code` ([#2611](https://github.com/nuxt/ui/issues/2611)) ([8a8b1ee](https://github.com/nuxt/ui/commit/8a8b1ee2e1628bc5439ef109d3c68b69bf631f81))
|
||||
* **locale:** provide `dir` on `defineLocale` ([#2620](https://github.com/nuxt/ui/issues/2620)) ([937585c](https://github.com/nuxt/ui/commit/937585cb3f8bc902d60a4b5904711598204aee2d))
|
||||
* **locale:** translate chinese ([#2580](https://github.com/nuxt/ui/issues/2580)) ([febda5c](https://github.com/nuxt/ui/commit/febda5c2b67374d1358a66694543b77037d239c6))
|
||||
* **locale:** translate Spanish ([#2644](https://github.com/nuxt/ui/issues/2644)) ([8ed434c](https://github.com/nuxt/ui/commit/8ed434c105b75ae02aa7493a235cebb64d518d09))
|
||||
* **locale:** typing `dir` ([#2643](https://github.com/nuxt/ui/issues/2643)) ([e55c0e2](https://github.com/nuxt/ui/commit/e55c0e25947e7bcef931b26dafaad120f488a627))
|
||||
* **module:** support i18n in components ([#2553](https://github.com/nuxt/ui/issues/2553)) ([2636240](https://github.com/nuxt/ui/commit/26362408b161108487b889ff001bec9166059c79))
|
||||
* **NavigationMenu:** control items `open` & `defaultOpen` on vertical ([30218f1](https://github.com/nuxt/ui/commit/30218f1b5b0a56207fd4db224ffa0401ac194a04)), closes [#2608](https://github.com/nuxt/ui/issues/2608)
|
||||
* **PinInput:** implement component ([#2570](https://github.com/nuxt/ui/issues/2570)) ([95aa6f6](https://github.com/nuxt/ui/commit/95aa6f68b316d02c28d1124d9a826bca55cde81f))
|
||||
* **Popover:** add `prevent-close` prop ([ea97759](https://github.com/nuxt/ui/commit/ea97759c2c219bdf5c48b652b47d293ddf513a00)), closes [#2245](https://github.com/nuxt/ui/issues/2245)
|
||||
* **SelectMenu:** use `UInput` in search to handle props like icon ([ff1e079](https://github.com/nuxt/ui/commit/ff1e0798d384d40ad82a95fe5faa16acb080efe3)), closes [#2021](https://github.com/nuxt/ui/issues/2021)
|
||||
* **Table:** add `caption` prop ([446f9c1](https://github.com/nuxt/ui/commit/446f9c1085e96187afdc5c1d7ce3450f8df1a2e1))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **App:** missing `vue` imports ([ddb4690](https://github.com/nuxt/ui/commit/ddb46905e7e3480ab578bcd8a478f25dff60081a))
|
||||
* **App:** remove `dir` prop ([#2630](https://github.com/nuxt/ui/issues/2630)) ([7cc26d0](https://github.com/nuxt/ui/commit/7cc26d098dff70923bcd9d414d675018951b1967))
|
||||
* **Breadcrumb/Carousel/Pagination:** handle icons in RTL mode ([#2633](https://github.com/nuxt/ui/issues/2633)) ([e5119a9](https://github.com/nuxt/ui/commit/e5119a9ca4e217ef769904323c16bd8c0cbc02d9))
|
||||
* **Breadcrumb:** render as `nav` ([756f791](https://github.com/nuxt/ui/commit/756f791a1a8dd3a4a88c212b4e4f775584decb55)), closes [#2649](https://github.com/nuxt/ui/issues/2649)
|
||||
* **Button:** improve neutral solid variant hover ([8d85498](https://github.com/nuxt/ui/commit/8d85498ee197ec0b26cdd7c4b08f84fec45ddd8f))
|
||||
* **Carousel:** use `dir` from locale ([#2647](https://github.com/nuxt/ui/issues/2647)) ([1fbbfe8](https://github.com/nuxt/ui/commit/1fbbfe8df06b3b8b294615ac328d582c5230aa8b))
|
||||
* **ContextMenu/DropdownMenu:** relative imports with prefix ([47f58f5](https://github.com/nuxt/ui/commit/47f58f52ef2d03176a184a3ca2154f5cea655edb))
|
||||
* **css:** `--font-family-sans` renamed to `--font-sans` ([#2680](https://github.com/nuxt/ui/issues/2680)) ([b2fa657](https://github.com/nuxt/ui/commit/b2fa65734bb59186520c985f7c73fc34a0cb8b37))
|
||||
* **css:** remove useless spacing override ([8d00265](https://github.com/nuxt/ui/commit/8d0026558a21efbbca08e9939844f7479a0d6cce))
|
||||
* **FormField:** missing conditions to apply container classes ([#2631](https://github.com/nuxt/ui/issues/2631)) ([9241ba1](https://github.com/nuxt/ui/commit/9241ba1230b0fde41595634362d83c92c66b7699))
|
||||
* **Form:** match `error-pattern` on input validation ([#2606](https://github.com/nuxt/ui/issues/2606)) ([3584a33](https://github.com/nuxt/ui/commit/3584a3328b8588f024557c9908242bc374853419))
|
||||
* **InputMenu/SelectMenu:** init `filter` with `labelKey` ([18931ac](https://github.com/nuxt/ui/commit/18931acdb316bc72a3e5ed6d20985688ad5c8d99))
|
||||
* **InputMenu/SelectMenu:** look in `items` only with `value-attribute` ([0ceafe1](https://github.com/nuxt/ui/commit/0ceafe1d54000f3eb49562b00c188d82fa23c4ee)), closes [#2464](https://github.com/nuxt/ui/issues/2464)
|
||||
* **InputMenu/SelectMenu:** multiple not working with generic boolean casting ([503f701](https://github.com/nuxt/ui/commit/503f701c7ecdfe27e9057e5ddebfc7e03889d61b)), closes [#2541](https://github.com/nuxt/ui/issues/2541)
|
||||
* **InputMenu/SelectMenu:** use `isEqual` from `ohash` ([f943f88](https://github.com/nuxt/ui/commit/f943f88fcc9f4678d8f7bd224799e289a0c57dd8))
|
||||
* **Link:** missing relative import ([#2588](https://github.com/nuxt/ui/issues/2588)) ([95a0bbc](https://github.com/nuxt/ui/commit/95a0bbc581a40677f620eed3170f9a423976214b))
|
||||
* **locale:** Improve German translation ([#2676](https://github.com/nuxt/ui/issues/2676)) ([992be91](https://github.com/nuxt/ui/commit/992be91823fe1877254ccd092c71c77dd3ff42f7))
|
||||
* **locale:** it translation ([#2623](https://github.com/nuxt/ui/issues/2623)) ([73e25ed](https://github.com/nuxt/ui/commit/73e25ed23562f755ea4c66e6c5fb06dd18caac1e))
|
||||
* **locale:** Italian translation ([#2584](https://github.com/nuxt/ui/issues/2584)) ([d167c9b](https://github.com/nuxt/ui/commit/d167c9b807a82fdf0fd280ce8417a66f86d7ed72))
|
||||
* **Modal/Slideover:** prevent `esc` with `prevent-close` prop ([9e2cc5b](https://github.com/nuxt/ui/commit/9e2cc5b12567472044726924a3896b4b0e7993a1)), closes [#2501](https://github.com/nuxt/ui/issues/2501)
|
||||
* **module:** remove `fast-deep-equal` in favor of custom `isEqual` ([37a3597](https://github.com/nuxt/ui/commit/37a359701f4b2ce4a9b0727b64c0e3eea6be00b4))
|
||||
* **module:** skip devtools renderer page injection if router integration is disabled ([#2571](https://github.com/nuxt/ui/issues/2571)) ([afe4003](https://github.com/nuxt/ui/commit/afe40033b088d8aedb73fa8387a0284ef78444e4))
|
||||
* **PinInput:** missing `useFormField` import ([601f4b2](https://github.com/nuxt/ui/commit/601f4b2cd2027817b935e02a0b4584dc3dce655f))
|
||||
* **Textarea:** `autoresize` does not work when initializing `modelValue` ([#2681](https://github.com/nuxt/ui/issues/2681)) ([d3a079a](https://github.com/nuxt/ui/commit/d3a079a644db3dfe2f4e9567973550d74b3ba905))
|
||||
* **Toaster:** teleport to `body` ([b0be26d](https://github.com/nuxt/ui/commit/b0be26d67feab467ac5862edd82e52df03a5091c)), closes [#2404](https://github.com/nuxt/ui/issues/2404)
|
||||
* **Toast:** unreachable behind overlays ([#2650](https://github.com/nuxt/ui/issues/2650)) ([0daac5b](https://github.com/nuxt/ui/commit/0daac5bafb756c3a2dfaf2bf166c30c0eb476e08))
|
||||
* **useLocale:** missing import in various components ([#2603](https://github.com/nuxt/ui/issues/2603)) ([df7a61a](https://github.com/nuxt/ui/commit/df7a61a97a14b3d7943baee6a74686134dfdb10b))
|
||||
|
||||
### Reverts
|
||||
|
||||
* Revert "docs(ComponentCode/ComponentExample): use relative imports" ([5deadc7](https://github.com/nuxt/ui/commit/5deadc709640bbfd3ec14c1c9363deb55e765d6a))
|
||||
|
||||
## [3.0.0-alpha.8](https://github.com/nuxt/ui/compare/v3.0.0-alpha.7...v3.0.0-alpha.8) (2024-11-07)
|
||||
|
||||
### ⚠ BREAKING CHANGES
|
||||
|
||||
10
README.md
10
README.md
@@ -1,4 +1,4 @@
|
||||
[](https://ui.nuxt.com)
|
||||
[](https://ui.nuxt.com)
|
||||
|
||||
# Nuxt UI
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
[![License][license-src]][license-href]
|
||||
[![Nuxt][nuxt-src]][nuxt-href]
|
||||
|
||||
We're thrilled to introduce Nuxt UI v3, a significant upgrade to our UI library that delivers extensive improvements and robust new capabilities. This major update harnesses the combined strengths of [Radix Vue](https://www.radix-vue.com/), [Tailwind CSS v4](https://tailwindcss.com/blog/tailwindcss-v4-alpha), and [Tailwind Variants](https://www.tailwind-variants.org/) to offer developers an unparalleled set of tools for creating sophisticated, accessible, and highly performant user interfaces.
|
||||
We're thrilled to introduce Nuxt UI v3, a significant upgrade to our UI library that delivers extensive improvements and robust new capabilities. This major update harnesses the combined strengths of [Reka UI](https://reka-ui.com/), [Tailwind CSS v4](https://tailwindcss.com/docs/v4-beta), and [Tailwind Variants](https://www.tailwind-variants.org/) to offer developers an unparalleled set of tools for creating sophisticated, accessible, and highly performant user interfaces.
|
||||
|
||||
> [!NOTE]
|
||||
> You are on the `v3` development branch, check out the [dev branch](https://github.com/nuxt/ui) for Nuxt UI v2.
|
||||
@@ -99,13 +99,13 @@ Learn more in the [installation guide](https://ui3.nuxt.dev/getting-started/inst
|
||||
- [nuxt/icon](https://github.com/nuxt/icon)
|
||||
- [nuxt/fonts](https://github.com/nuxt/fonts)
|
||||
- [nuxt-modules/color-mode](https://github.com/nuxt-modules/color-mode)
|
||||
- [radix-vue/radix-vue](https://github.com/radix-vue/radix-vue)
|
||||
- [unovue/reka-ui](https://github.com/unovue/reka-ui)
|
||||
- [tailwindlabs/tailwindcss](https://github.com/tailwindlabs/tailwindcss)
|
||||
- [vueuse/vueuse](https://github.com/vueuse/vueuse)
|
||||
|
||||
## License
|
||||
|
||||
Licensed under the [MIT license](https://github.com/nuxt/ui/blob/dev/LICENSE.md).
|
||||
Licensed under the [MIT license](https://github.com/nuxt/ui/blob/v3/LICENSE.md).
|
||||
|
||||
<!-- Badges -->
|
||||
[npm-version-src]: https://img.shields.io/npm/v/@nuxt/ui/next.svg?style=flat&colorA=18181B&colorB=28CF8D
|
||||
@@ -115,7 +115,7 @@ Licensed under the [MIT license](https://github.com/nuxt/ui/blob/dev/LICENSE.md)
|
||||
[npm-downloads-href]: https://npm.chart.dev/@nuxt/ui
|
||||
|
||||
[license-src]: https://img.shields.io/github/license/nuxt/ui.svg?style=flat&colorA=18181B&colorB=28CF8D
|
||||
[license-href]: https://github.com/nuxt/ui/blob/main/LICENSE.md
|
||||
[license-href]: https://github.com/nuxt/ui/blob/v3/LICENSE.md
|
||||
|
||||
[nuxt-src]: https://img.shields.io/badge/Nuxt-18181B?logo=nuxt.js
|
||||
[nuxt-href]: https://nuxt.com
|
||||
|
||||
@@ -17,6 +17,10 @@ export default defineCommand({
|
||||
name: {
|
||||
description: 'Locale name to create. For example: English.',
|
||||
required: true
|
||||
},
|
||||
dir: {
|
||||
description: 'Locale direction. For example: rtl.',
|
||||
default: 'ltr'
|
||||
}
|
||||
},
|
||||
async setup({ args }) {
|
||||
@@ -32,6 +36,11 @@ export default defineCommand({
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
if (!['ltr', 'rtl'].includes(args.dir)) {
|
||||
consola.error(`🚨 Direction ${args.dir} not supported!`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
if (!args.code.match(/^[a-z]{2}(?:_[a-z]{2,4})?$/)) {
|
||||
consola.error(`🚨 ${args.code} is not a valid locale code!\nExample: en or en_us`)
|
||||
process.exit(1)
|
||||
@@ -45,7 +54,9 @@ export default defineCommand({
|
||||
// Create new locale file
|
||||
await fsp.copyFile(originLocaleFilePath, newLocaleFilePath)
|
||||
const localeFile = await fsp.readFile(newLocaleFilePath, 'utf-8')
|
||||
const rewrittenLocaleFile = localeFile.replace(/defineLocale\('(.*)'/, `defineLocale('${args.name}', '${normalizeLocale(args.code)}'`)
|
||||
const rewrittenLocaleFile = localeFile
|
||||
.replace(/name: '(.*)',/, `name: '${args.name}',`)
|
||||
.replace(/code: '(.*)',/, `code: '${normalizeLocale(args.code)}',${(args.dir && args.dir !== 'ltr') ? `\n dir: '${args.dir}',` : ''}`)
|
||||
await fsp.writeFile(newLocaleFilePath, rewrittenLocaleFile)
|
||||
|
||||
consola.success(`🪄 Generated ${newLocaleFilePath}`)
|
||||
|
||||
@@ -55,7 +55,7 @@ export interface ${upperName}Slots {
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Primitive } from 'radix-vue'
|
||||
import { Primitive } from 'reka-ui'
|
||||
|
||||
const props = withDefaults(defineProps<${upperName}Props>(), { as: 'div' })
|
||||
defineSlots<${upperName}Slots>()
|
||||
@@ -72,7 +72,7 @@ const ui = ${camelName}()
|
||||
: `
|
||||
<script lang="ts">
|
||||
import { tv, type VariantProps } from 'tailwind-variants'
|
||||
import type { ${upperName}RootProps, ${upperName}RootEmits } from 'radix-vue'
|
||||
import type { ${upperName}RootProps, ${upperName}RootEmits } from 'reka-ui'
|
||||
import type { AppConfig } from '@nuxt/schema'
|
||||
import _appConfig from '#build/app.config'
|
||||
import theme from '#build/${path}/${prose ? 'prose/' : ''}${content ? 'content/' : ''}${kebabName}'
|
||||
@@ -94,7 +94,7 @@ export interface ${upperName}Slots {}
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ${upperName}Root, useForwardPropsEmits } from 'radix-vue'
|
||||
import { ${upperName}Root, useForwardPropsEmits } from 'reka-ui'
|
||||
import { reactivePick } from '@vueuse/core'
|
||||
|
||||
const props = defineProps<${upperName}Props>()
|
||||
@@ -149,7 +149,7 @@ import ComponentRender from '../${content ? '../' : ''}component-render'
|
||||
describe('${upperName}', () => {
|
||||
it.each([
|
||||
// Props
|
||||
['with as', { props: { as: 'div' } }],
|
||||
['with as', { props: { as: 'section' } }],
|
||||
['with class', { props: { class: '' } }],
|
||||
['with ui', { props: { ui: {} } }],
|
||||
// Slots
|
||||
@@ -163,9 +163,54 @@ describe('${upperName}', () => {
|
||||
}
|
||||
}
|
||||
|
||||
const doc = ({ name, pro }) => {
|
||||
const kebabName = kebabCase(name)
|
||||
const upperName = splitByCase(name).map(p => upperFirst(p)).join('')
|
||||
|
||||
return {
|
||||
filename: `docs/content/${pro ? 'pro' : '3.components'}/${kebabName}.md`,
|
||||
contents: `---
|
||||
description:
|
||||
links: ${pro
|
||||
? ''
|
||||
: `
|
||||
- label: ${upperName}
|
||||
icon: i-custom-reka-ui
|
||||
to: https://www.reka-ui.com/components/${kebabName}.html`}
|
||||
- label: GitHub
|
||||
icon: i-simple-icons-github
|
||||
to: https://github.com/nuxt/${pro ? 'ui-pro' : 'ui'}/tree/v3/src/runtime/components/${upperName}.vue
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
## Examples
|
||||
|
||||
## API
|
||||
|
||||
### Props
|
||||
|
||||
:component-props
|
||||
|
||||
### Slots
|
||||
|
||||
:component-slots
|
||||
|
||||
### Emits
|
||||
|
||||
:component-emits
|
||||
|
||||
## Theme
|
||||
|
||||
:component-theme
|
||||
`
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
playground,
|
||||
component,
|
||||
theme,
|
||||
test
|
||||
test,
|
||||
doc
|
||||
}
|
||||
|
||||
@@ -168,7 +168,7 @@ const isDark = computed({
|
||||
@import '@nuxt/ui';
|
||||
|
||||
@theme {
|
||||
--font-family-sans: 'DM Sans', sans-serif;
|
||||
--font-sans: 'DM Sans', sans-serif;
|
||||
|
||||
--color-primary-50: var(--ui-color-primary-50);
|
||||
--color-primary-100: var(--ui-color-primary-100);
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
"dependencies": {
|
||||
"@nuxt/ui": "latest",
|
||||
"knitwork": "^1.1.0",
|
||||
"nuxt": "^3.14.159",
|
||||
"prettier": "^3.3.3",
|
||||
"nuxt": "^3.14.1592",
|
||||
"prettier": "^3.4.2",
|
||||
"zod": "^3.23.8"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ const route = useRoute()
|
||||
const appConfig = useAppConfig()
|
||||
const colorMode = useColorMode()
|
||||
|
||||
const { data: navigation } = await useAsyncData('navigation', () => queryCollectionNavigation('content'))
|
||||
const { data: navigation } = await useAsyncData('navigation', () => queryCollectionNavigation('content', ['framework', 'module']))
|
||||
const { data: files } = useLazyAsyncData('search', () => queryCollectionSearchSections('content'), {
|
||||
server: false
|
||||
})
|
||||
@@ -73,24 +73,10 @@ useServerSeoMeta({
|
||||
twitterCard: 'summary_large_image'
|
||||
})
|
||||
|
||||
const updatedNavigation = computed(() => navigation.value?.map(item => ({
|
||||
...item,
|
||||
children: item.children?.map((child: typeof item) => ({
|
||||
...child,
|
||||
...(child.path === '/getting-started/installation' && {
|
||||
title: 'Installation',
|
||||
active: route.path.startsWith('/getting-started/installation'),
|
||||
children: []
|
||||
}),
|
||||
...(child.path === '/getting-started/i18n' && {
|
||||
title: 'I18n',
|
||||
active: route.path.startsWith('/getting-started/i18n'),
|
||||
children: []
|
||||
})
|
||||
})) || []
|
||||
})))
|
||||
const { frameworks, modules } = useSharedData()
|
||||
const { mappedNavigation, filteredNavigation } = useContentNavigation(navigation)
|
||||
|
||||
provide('navigation', updatedNavigation)
|
||||
provide('navigation', mappedNavigation)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -98,7 +84,7 @@ provide('navigation', updatedNavigation)
|
||||
<NuxtLoadingIndicator color="#FFF" />
|
||||
|
||||
<template v-if="!route.path.startsWith('/examples')">
|
||||
<Banner />
|
||||
<!-- <Banner /> -->
|
||||
|
||||
<Header :links="links" />
|
||||
</template>
|
||||
@@ -108,10 +94,24 @@ provide('navigation', updatedNavigation)
|
||||
</NuxtLayout>
|
||||
|
||||
<template v-if="!route.path.startsWith('/examples')">
|
||||
<Footer />
|
||||
<!-- <Footer /> -->
|
||||
|
||||
<ClientOnly>
|
||||
<LazyUContentSearch v-model:search-term="searchTerm" :files="files" :navigation="navigation" :fuse="{ resultLimit: 42 }" />
|
||||
<LazyUContentSearch
|
||||
v-model:search-term="searchTerm"
|
||||
:files="files"
|
||||
:groups="[{
|
||||
id: 'framework',
|
||||
label: 'Framework',
|
||||
items: frameworks
|
||||
}, {
|
||||
id: 'module',
|
||||
label: 'Module',
|
||||
items: modules
|
||||
}]"
|
||||
:navigation="filteredNavigation"
|
||||
:fuse="{ resultLimit: 42 }"
|
||||
/>
|
||||
</ClientOnly>
|
||||
</template>
|
||||
</UApp>
|
||||
@@ -121,12 +121,12 @@ provide('navigation', updatedNavigation)
|
||||
@import "tailwindcss";
|
||||
@import "@nuxt/ui-pro";
|
||||
|
||||
@source "../content/**/*.md";
|
||||
@source "../content";
|
||||
|
||||
@theme {
|
||||
--container-8xl: 90rem;
|
||||
|
||||
--font-family-sans: 'Public Sans', sans-serif;
|
||||
--font-sans: 'Public Sans', sans-serif;
|
||||
|
||||
--color-green-50: #EFFDF5;
|
||||
--color-green-100: #D9FBE8;
|
||||
@@ -144,4 +144,11 @@ provide('navigation', updatedNavigation)
|
||||
:root {
|
||||
--ui-container: var(--container-8xl);
|
||||
}
|
||||
|
||||
html[data-framework="nuxt"] .vue-only,
|
||||
html[data-framework="vue"] .nuxt-only,
|
||||
html[data-module="ui-pro"] .ui-only,
|
||||
html[data-module="ui"] .ui-pro-only {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
10
docs/app/assets/icons/icones-js.svg
Normal file
10
docs/app/assets/icons/icones-js.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 283.46 283.46">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1{fill:#231815;}
|
||||
@media (prefers-color-scheme: dark) { .cls-1{fill:#ffffff;} }
|
||||
</style>
|
||||
</defs>
|
||||
<path class="cls-1" d="M144.89,89.86c-33.46,0-54.44,14.56-66.14,26.76a86,86,0,0,0-23.69,58.94c0,22.64,8.81,43.48,24.8,58.67,15.7,14.92,36.9,23.14,59.68,23.14,23.81,0,46-8.49,62.49-23.91,17-15.9,26.37-37.93,26.37-62C228.4,120.37,185.94,89.86,144.89,89.86Zm.49,153.67a61.49,61.49,0,0,1-46.45-20.4c-12.33-13.76-18.85-32.64-18.85-54.62,0-20.7,6.07-37.67,17.57-49.07,10.11-10,24.39-15.62,40.19-15.74,19,0,35.22,6.56,46.76,19,12.6,13.58,19.27,34,19.27,58.95C203.87,224.39,174.49,243.53,145.38,243.53Z"/>
|
||||
<polygon class="cls-1" points="198.75 74.96 179.45 74.96 142.09 37.83 104.51 74.96 86.14 74.96 138.09 24.25 146.81 24.25 198.75 74.96"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 855 B |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
23
docs/app/components/FrameworkSelect.vue
Normal file
23
docs/app/components/FrameworkSelect.vue
Normal file
@@ -0,0 +1,23 @@
|
||||
<script setup lang="ts">
|
||||
const { framework, frameworks } = useSharedData()
|
||||
|
||||
const value = ref<string | undefined>(undefined)
|
||||
|
||||
onMounted(() => {
|
||||
value.value = framework.value
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTabs
|
||||
v-model="value"
|
||||
:items="frameworks"
|
||||
:content="false"
|
||||
color="neutral"
|
||||
:ui="{
|
||||
indicator: 'bg-[var(--ui-bg)]',
|
||||
trigger: 'px-1 data-[state=active]:text-[var(--ui-text-highlighted)]'
|
||||
}"
|
||||
@update:model-value="(framework = $event as string)"
|
||||
/>
|
||||
</template>
|
||||
@@ -7,6 +7,7 @@ const props = defineProps<{
|
||||
}>()
|
||||
|
||||
const config = useRuntimeConfig().public
|
||||
const { module } = useSharedData()
|
||||
|
||||
const navigation = inject<Ref<ContentNavigationItem[]>>('navigation')
|
||||
|
||||
@@ -20,13 +21,32 @@ defineShortcuts({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UHeader :ui="{ left: 'min-w-0' }">
|
||||
<UHeader :ui="{ left: 'min-w-0', toggle: '-mr-1.5' }" mode="drawer" :menu="{ shouldScaleBackground: true }">
|
||||
<template #left>
|
||||
<NuxtLink to="/" class="flex items-end gap-2 font-bold text-xl text-[var(--ui-text-highlighted)] min-w-0 focus-visible:outline-[var(--ui-primary)]" aria-label="Nuxt UI">
|
||||
<Logo class="w-auto h-6 shrink-0" />
|
||||
|
||||
<UBadge :label="`v${config.version}`" variant="subtle" size="sm" class="-mb-[2px] rounded-[var(--ui-radius)] font-semibold inline-block truncate" />
|
||||
<NuxtLink to="/" class="flex items-end gap-2 font-bold text-xl text-[var(--ui-text-highlighted)] min-w-0 focus-visible:outline-[var(--ui-primary)] shrink-0" aria-label="Nuxt UI">
|
||||
<LogoPro class="w-auto h-6 shrink-0 ui-pro-only" />
|
||||
<Logo class="w-auto h-6 shrink-0 ui-only" />
|
||||
</NuxtLink>
|
||||
|
||||
<UDropdownMenu
|
||||
v-slot="{ open }"
|
||||
:modal="false"
|
||||
:items="[{ label: `v${config.version}`, active: true, color: 'primary', checked: true, type: 'checkbox' }, { label: module === 'ui-pro' ? 'v1.5' : 'v2.19', to: module === 'ui-pro' ? 'https://ui.nuxt.com/pro' : 'https://ui.nuxt.com' }]"
|
||||
:ui="{ content: 'w-(--reka-dropdown-menu-trigger-width) min-w-0' }"
|
||||
size="xs"
|
||||
>
|
||||
<UButton
|
||||
:label="`v${config.version}`"
|
||||
variant="subtle"
|
||||
trailing-icon="i-lucide-chevron-down"
|
||||
size="xs"
|
||||
class="-mb-[6px] font-semibold rounded-full truncate"
|
||||
:class="[open && 'bg-[var(--ui-primary)]/15 ']"
|
||||
:ui="{
|
||||
trailingIcon: ['transition-transform duration-200', open ? 'rotate-180' : undefined].filter(Boolean).join(' ')
|
||||
}"
|
||||
/>
|
||||
</UDropdownMenu>
|
||||
</template>
|
||||
|
||||
<UNavigationMenu :items="items" variant="link" />
|
||||
@@ -38,11 +58,11 @@ defineShortcuts({
|
||||
<UContentSearchButton />
|
||||
</UTooltip>
|
||||
|
||||
<UTooltip text="Open on GitHub" :kbds="['meta', 'G']">
|
||||
<UTooltip text="Open on GitHub" :kbds="['meta', 'G']" class="hidden lg:flex">
|
||||
<UButton
|
||||
color="neutral"
|
||||
variant="ghost"
|
||||
to="https://github.com/nuxt/ui/tree/v3"
|
||||
to="https://github.com/nuxt/ui"
|
||||
target="_blank"
|
||||
icon="i-simple-icons-github"
|
||||
aria-label="GitHub"
|
||||
@@ -51,9 +71,14 @@ defineShortcuts({
|
||||
</template>
|
||||
|
||||
<template #content>
|
||||
<UNavigationMenu orientation="vertical" :items="links" class="-ml-2.5" />
|
||||
<UNavigationMenu orientation="vertical" :items="links" class="-mx-2.5" />
|
||||
|
||||
<USeparator type="dashed" class="my-4" />
|
||||
<USeparator type="dashed" class="mt-4 mb-6" />
|
||||
|
||||
<div class="flex flex-col gap-2 w-[calc(100%+1.25rem)] mb-5.5 -mx-2.5">
|
||||
<ModuleSelect />
|
||||
<FrameworkSelect />
|
||||
</div>
|
||||
|
||||
<UContentNavigation :navigation="navigation" highlight />
|
||||
</template>
|
||||
|
||||
14
docs/app/components/LogoPro.vue
Normal file
14
docs/app/components/LogoPro.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<svg width="1352" height="200" viewBox="0 0 1352 200" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M377 200C379.16 200 381 198.209 381 196V103C381 103 386 112 395 127L434 194C435.785 197.74 439.744 200 443 200H470V50H443C441.202 50 439 51.4941 439 54V148L421 116L385 55C383.248 51.8912 379.479 50 376 50H350V200H377Z" fill="currentColor" />
|
||||
<path d="M726 92H739C742.314 92 745 89.3137 745 86V60H773V92H800V116H773V159C773 169.5 778.057 174 787 174H800V200H783C759.948 200 745 185.071 745 160V116H726V92Z" fill="currentColor" />
|
||||
<path d="M591 92V154C591 168.004 585.742 179.809 578 188C570.258 196.191 559.566 200 545 200C530.434 200 518.742 196.191 511 188C503.389 179.809 498 168.004 498 154V92H514C517.412 92 520.769 92.622 523 95C525.231 97.2459 526 98.5652 526 102V154C526 162.059 526.457 167.037 530 171C533.543 174.831 537.914 176 545 176C552.217 176 555.457 174.831 559 171C562.543 167.037 563 162.059 563 154V102C563 98.5652 563.769 96.378 566 94C567.96 91.9107 570.028 91.9599 573 92C573.411 92.0055 574.586 92 575 92H591Z" fill="currentColor" />
|
||||
<path d="M676 144L710 92H684C680.723 92 677.812 93.1758 676 96L660 120L645 97C643.188 94.1758 639.277 92 636 92H611L645 143L608 200H634C637.25 200 640.182 196.787 642 194L660 167L679 195C680.818 197.787 683.75 200 687 200H713L676 144Z" fill="currentColor" />
|
||||
<path d="M168 200H279C282.542 200 285.932 198.756 289 197C292.068 195.244 295.23 193.041 297 190C298.77 186.959 300.002 183.51 300 179.999C299.998 176.488 298.773 173.04 297 170.001L222 41C220.23 37.96 218.067 35.7552 215 34C211.933 32.2448 207.542 31 204 31C200.458 31 197.067 32.2448 194 34C190.933 35.7552 188.77 37.96 187 41L168 74L130 9.99764C128.228 6.95784 126.068 3.75491 123 2C119.932 0.245087 116.542 0 113 0C109.458 0 106.068 0.245087 103 2C99.9323 3.75491 96.7717 6.95784 95 9.99764L2 170.001C0.226979 173.04 0.00154312 176.488 1.90993e-06 179.999C-0.0015393 183.51 0.229648 186.959 2 190C3.77035 193.04 6.93245 195.244 10 197C13.0675 198.756 16.4578 200 20 200H90C117.737 200 137.925 187.558 152 164L186 105L204 74L259 168H186L168 200ZM89 168H40L113 42L150 105L125.491 147.725C116.144 163.01 105.488 168 89 168Z" fill="var(--ui-primary)" />
|
||||
<path d="M958 60.0001H938C933.524 60.0001 929.926 59.9395 927 63C924.074 65.8905 925 67.5792 925 72V141C925 151.372 923.648 156.899 919 162C914.352 166.931 908.468 169 899 169C889.705 169 882.648 166.931 878 162C873.352 156.899 873 151.372 873 141V72.0001C873 67.5793 872.926 65.8906 870 63.0001C867.074 59.9396 863.476 60.0001 859 60.0001H840V141C840 159.023 845.016 173.458 855 184C865.156 194.542 879.893 200 899 200C918.107 200 932.844 194.542 943 184C953.156 173.458 958 159.023 958 141V60.0001Z" fill="currentColor" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1000 60.0233L1020 60V77L1020 128V156.007L1020 181L1020 189.004C1020 192.938 1019.98 194.429 1017 197.001C1014.02 199.725 1009.56 200 1005 200H986.001V181.006L986 130.012V70.0215C986 66.1576 986.016 64.5494 989 62.023C991.819 59.6358 995.437 60.0233 1000 60.0233Z" fill="currentColor" />
|
||||
<path d="M1060 200V60H1117C1126.67 60 1134.98 61.2896 1142 65C1149.16 68.7104 1155.29 74.3744 1159 81C1162.71 87.6256 1164 95.3867 1164 104C1164 112.481 1162.71 120.374 1159 127C1155.29 133.626 1149.16 138.157 1142 142C1134.98 145.71 1126.67 148 1117 148H1090V200H1060ZM1115 123C1121.63 123 1126.69 121.578 1130 118C1133.31 114.29 1135 109.433 1135 104C1135 98.567 1133.31 93.5778 1130 90C1126.69 86.2896 1121.63 85 1115 85H1090V123H1115Z" fill="var(--ui-primary)" />
|
||||
<path d="M1226 123C1219.37 123 1214.31 124.965 1211 130C1207.69 135.035 1206 142.122 1206 151V200H1178V100H1200C1203.31 100 1206 102.686 1206 106V116C1208.65 109.904 1211.16 106.518 1215 104C1218.98 101.482 1224.77 100 1231 100H1242V123H1226Z" fill="var(--ui-primary)" />
|
||||
<path d="M1299 200C1288.93 200 1280.08 197.373 1272 193C1263.92 188.495 1257.51 182.818 1253 175C1248.49 167.049 1246 157.806 1246 148C1246 138.194 1248.49 129.818 1253 122C1257.51 114.049 1263.92 107.373 1272 103C1280.08 98.4946 1288.93 97 1299 97C1309.07 97 1318.92 98.4946 1327 103C1335.08 107.373 1340.49 114.049 1345 122C1349.51 129.818 1352 138.194 1352 148C1352 157.806 1349.51 167.049 1345 175C1340.49 182.818 1335.08 188.495 1327 193C1318.92 197.373 1309.07 200 1299 200ZM1299 176C1306.42 176 1312.36 173.168 1317 168C1321.64 162.832 1324 156.216 1324 148C1324 139.652 1321.64 133.168 1317 128C1312.36 122.832 1306.42 120 1299 120C1291.58 120 1285.64 122.832 1281 128C1276.36 133.168 1274 139.652 1274 148C1274 156.216 1276.36 162.832 1281 168C1285.64 173.168 1291.58 176 1299 176Z" fill="var(--ui-primary)" />
|
||||
</svg>
|
||||
</template>
|
||||
20
docs/app/components/ModuleSelect.vue
Normal file
20
docs/app/components/ModuleSelect.vue
Normal file
@@ -0,0 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
const { module, modules } = useSharedData()
|
||||
|
||||
const value = ref<string | undefined>(undefined)
|
||||
|
||||
onMounted(() => {
|
||||
value.value = module.value
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTabs
|
||||
v-model="value"
|
||||
:items="modules"
|
||||
:content="false"
|
||||
color="neutral"
|
||||
:ui="{ indicator: 'bg-[var(--ui-bg)]', trigger: 'px-1 data-[state=active]:text-[var(--ui-text-highlighted)]' }"
|
||||
@update:model-value="(module = $event as string)"
|
||||
/>
|
||||
</template>
|
||||
@@ -3,9 +3,42 @@
|
||||
import json5 from 'json5'
|
||||
import { upperFirst, camelCase, kebabCase } from 'scule'
|
||||
import { hash } from 'ohash'
|
||||
import { CalendarDate } from '@internationalized/date'
|
||||
import * as theme from '#build/ui'
|
||||
import { get, set } from '#ui/utils'
|
||||
|
||||
interface Cast {
|
||||
get: (args: any) => any
|
||||
template: (args: any) => string
|
||||
}
|
||||
|
||||
type CastDateValue = [number, number, number]
|
||||
|
||||
const castMap: Record<string, Cast> = {
|
||||
'DateValue': {
|
||||
get: (args: CastDateValue) => new CalendarDate(...args),
|
||||
template: (value: CalendarDate) => {
|
||||
return value ? `new CalendarDate(${value.year}, ${value.month}, ${value.day})` : 'null'
|
||||
}
|
||||
},
|
||||
'DateValue[]': {
|
||||
get: (args: CastDateValue[]) => args.map(date => new CalendarDate(...date)),
|
||||
template: (value: CalendarDate[]) => {
|
||||
return value ? `[${value.map(date => `new CalendarDate(${date.year}, ${date.month}, ${date.day})`).join(', ')}]` : '[]'
|
||||
}
|
||||
},
|
||||
'DateRange': {
|
||||
get: (args: { start: CastDateValue, end: CastDateValue }) => ({ start: new CalendarDate(...args.start), end: new CalendarDate(...args.end) }),
|
||||
template: (value: { start: CalendarDate, end: CalendarDate }) => {
|
||||
if (!value.start || !value.end) {
|
||||
return `{ start: null, end: null }`
|
||||
}
|
||||
|
||||
return `{ start: new CalendarDate(${value.start.year}, ${value.start.month}, ${value.start.day}), end: new CalendarDate(${value.end.year}, ${value.end.month}, ${value.end.day}) }`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
/** Override the slug taken from the route */
|
||||
slug?: string
|
||||
@@ -18,6 +51,8 @@ const props = defineProps<{
|
||||
external?: string[]
|
||||
/** List of props to use with `v-model` */
|
||||
model?: string[]
|
||||
/** List of props to cast from code and selection */
|
||||
cast?: { [key: string]: string }
|
||||
/** List of items for each prop */
|
||||
items?: { [key: string]: string[] }
|
||||
props?: { [key: string]: any }
|
||||
@@ -45,7 +80,17 @@ const camelName = camelCase(props.slug ?? route.params.slug?.[route.params.slug.
|
||||
const name = `U${upperFirst(camelName)}`
|
||||
const component = defineAsyncComponent(() => import(`#ui/components/${upperFirst(camelName)}.vue`))
|
||||
|
||||
const componentProps = reactive({ ...(props.props || {}) })
|
||||
const componentProps = reactive({
|
||||
...Object.fromEntries(Object.entries(props.props || {}).map(([key, value]) => {
|
||||
const cast = props.cast?.[key]
|
||||
|
||||
if (cast && !castMap[cast]) {
|
||||
throw new Error(`Unknown cast: ${cast}`)
|
||||
}
|
||||
|
||||
return [key, cast ? castMap[cast]!.get(value) : value]
|
||||
}))
|
||||
})
|
||||
const componentEvents = reactive({
|
||||
...Object.fromEntries((props.model || []).map(key => [`onUpdate:${key}`, (e: any) => setComponentProp(key, e)])),
|
||||
...(componentProps.modelValue ? { [`onUpdate:modelValue`]: (e: any) => setComponentProp('modelValue', e) } : {})
|
||||
@@ -96,7 +141,7 @@ const options = computed(() => {
|
||||
return {
|
||||
name: key,
|
||||
label: key,
|
||||
type: prop?.type,
|
||||
type: props?.cast?.[key] ?? prop?.type,
|
||||
items
|
||||
}
|
||||
})
|
||||
@@ -117,7 +162,10 @@ const code = computed(() => {
|
||||
<script setup lang="ts">
|
||||
`
|
||||
for (const key of props.external) {
|
||||
code += `const ${key === 'modelValue' ? 'value' : key} = ref(${json5.stringify(componentProps[key], null, 2).replace(/,([ |\t\n]+[}|\]])/g, '$1')})
|
||||
const cast = props.cast?.[key]
|
||||
const value = cast ? castMap[cast]!.template(componentProps[key]) : json5.stringify(componentProps[key], null, 2).replace(/,([ |\t\n]+[}|\]])/g, '$1')
|
||||
|
||||
code += `const ${key === 'modelValue' ? 'value' : key} = ref(${value})
|
||||
`
|
||||
}
|
||||
code += `<\/script>
|
||||
@@ -164,7 +212,7 @@ const code = computed(() => {
|
||||
continue
|
||||
}
|
||||
|
||||
code += ` ${prop?.type.includes('number') ? ':' : ''}${name}="${value}"`
|
||||
code += ` ${typeof value === 'number' ? ':' : ''}${name}="${value}"`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,7 +268,7 @@ const { data: ast } = await useAsyncData(`component-code-${name}-${hash({ props:
|
||||
<template>
|
||||
<div class="my-5">
|
||||
<div>
|
||||
<div v-if="options.length" class="flex items-center gap-2.5 border border-[var(--ui-color-neutral-200)] dark:border-[var(--ui-color-neutral-700)] border-b-0 relative rounded-t-[calc(var(--ui-radius)*1.5)] px-4 py-2.5 overflow-x-auto">
|
||||
<div v-if="options.length" class="flex items-center gap-2.5 border border-[var(--ui-border-muted)] border-b-0 relative rounded-t-[calc(var(--ui-radius)*1.5)] px-4 py-2.5 overflow-x-auto">
|
||||
<template v-for="option in options" :key="option.name">
|
||||
<UFormField
|
||||
:label="option.label"
|
||||
@@ -232,7 +280,7 @@ const { data: ast } = await useAsyncData(`component-code-${name}-${hash({ props:
|
||||
container: 'mt-0'
|
||||
}"
|
||||
>
|
||||
<USelectMenu
|
||||
<USelect
|
||||
v-if="option.items?.length"
|
||||
:model-value="getComponentProp(option.name)"
|
||||
:items="option.items"
|
||||
@@ -240,7 +288,6 @@ const { data: ast } = await useAsyncData(`component-code-${name}-${hash({ props:
|
||||
color="neutral"
|
||||
variant="soft"
|
||||
class="rounded-[var(--ui-radius)] rounded-l-none min-w-12"
|
||||
:search-input="false"
|
||||
:class="[option.name.toLowerCase().endsWith('color') && 'pl-6']"
|
||||
:ui="{ itemLeadingChip: 'size-2' }"
|
||||
@update:model-value="setComponentProp(option.name, $event)"
|
||||
@@ -255,7 +302,7 @@ const { data: ast } = await useAsyncData(`component-code-${name}-${hash({ props:
|
||||
class="size-2"
|
||||
/>
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</USelect>
|
||||
<UInput
|
||||
v-else
|
||||
:type="option.type?.includes('number') ? 'number' : 'text'"
|
||||
@@ -269,12 +316,12 @@ const { data: ast } = await useAsyncData(`component-code-${name}-${hash({ props:
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div v-if="component" class="flex justify-center border border-b-0 border-[var(--ui-color-neutral-200)] dark:border-[var(--ui-color-neutral-700)] relative p-4 z-[1]" :class="[!options.length && 'rounded-t-[calc(var(--ui-radius)*1.5)]', props.class]">
|
||||
<div v-if="component" class="flex justify-center border border-b-0 border-[var(--ui-border-muted)] relative p-4 z-[1]" :class="[!options.length && 'rounded-t-[calc(var(--ui-radius)*1.5)]', props.class]">
|
||||
<component :is="component" v-bind="{ ...componentProps, ...componentEvents }">
|
||||
<template v-for="slot in Object.keys(slots || {})" :key="slot" #[slot]>
|
||||
<MDCSlot :name="slot" unwrap="p">
|
||||
<slot :name="slot" mdc-unwrap="p">
|
||||
{{ slots?.[slot] }}
|
||||
</MDCSlot>
|
||||
</slot>
|
||||
</template>
|
||||
</component>
|
||||
</div>
|
||||
|
||||
@@ -117,8 +117,8 @@ const optionsValues = ref(props.options?.reduce((acc, option) => {
|
||||
<template>
|
||||
<div class="my-5">
|
||||
<template v-if="preview">
|
||||
<div class="border border-[var(--ui-color-neutral-200)] dark:border-[var(--ui-color-neutral-700)] relative z-[1]" :class="[{ 'border-b-0 rounded-t-[calc(var(--ui-radius)*1.5)]': props.source, 'rounded-[calc(var(--ui-radius)*1.5)]': !props.source }]">
|
||||
<div v-if="props.options?.length || !!slots.options" class="flex gap-4 p-4 border-b border-[var(--ui-color-neutral-200)] dark:border-[var(--ui-color-neutral-700)]">
|
||||
<div class="border border-[var(--ui-border-muted)] relative z-[1]" :class="[{ 'border-b-0 rounded-t-[calc(var(--ui-radius)*1.5)]': props.source, 'rounded-[calc(var(--ui-radius)*1.5)]': !props.source }]">
|
||||
<div v-if="props.options?.length || !!slots.options" class="flex gap-4 p-4 border-b border-[var(--ui-border-muted)]">
|
||||
<slot name="options" />
|
||||
|
||||
<UFormField
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
import { upperFirst, camelCase } from 'scule'
|
||||
import type { ComponentMeta } from 'vue-component-meta'
|
||||
import * as theme from '#build/ui'
|
||||
import * as themePro from '#build/ui-pro'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
ignore?: string[]
|
||||
pro?: boolean
|
||||
}>(), {
|
||||
ignore: () => [
|
||||
'activeClass',
|
||||
@@ -32,7 +34,7 @@ const route = useRoute()
|
||||
const camelName = camelCase(route.params.slug?.[route.params.slug.length - 1] ?? '')
|
||||
const name = `U${upperFirst(camelName)}`
|
||||
|
||||
const componentTheme = (theme as any)[camelName]
|
||||
const componentTheme = ((props.pro ? themePro : theme) as any)[camelName]
|
||||
const meta = await fetchComponentMeta(name as any)
|
||||
|
||||
const metaProps: ComputedRef<ComponentMeta['props']> = computed(() => {
|
||||
|
||||
@@ -35,7 +35,7 @@ const schemaProps = computed(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Collapsible v-if="schemaProps?.length" class="mt-1">
|
||||
<ProseCollapsible v-if="schemaProps?.length" class="mt-1">
|
||||
<ProseUl>
|
||||
<ProseLi v-for="schemaProp in schemaProps" :key="schemaProp.name">
|
||||
<HighlightInlineType :type="`${schemaProp.name}${schemaProp.required === false ? '?' : ''}: ${schemaProp.type}`" />
|
||||
@@ -43,5 +43,5 @@ const schemaProps = computed(() => {
|
||||
<MDC v-if="schemaProp.description" :value="schemaProp.description" class="text-[var(--ui-text-muted)] my-1" />
|
||||
</ProseLi>
|
||||
</ProseUl>
|
||||
</Collapsible>
|
||||
</ProseCollapsible>
|
||||
</template>
|
||||
|
||||
@@ -2,16 +2,26 @@
|
||||
import json5 from 'json5'
|
||||
import { camelCase } from 'scule'
|
||||
import * as theme from '#build/ui'
|
||||
import * as themePro from '#build/ui-pro'
|
||||
|
||||
const props = defineProps<{
|
||||
pro?: boolean
|
||||
}>()
|
||||
|
||||
const route = useRoute()
|
||||
const { framework } = useSharedData()
|
||||
|
||||
const name = camelCase(route.params.slug?.[route.params.slug.length - 1] ?? '')
|
||||
|
||||
const strippedCompoundVariants = ref(false)
|
||||
|
||||
function stripCompoundVariants(component?: any) {
|
||||
if (component?.compoundVariants) {
|
||||
component.compoundVariants = component.compoundVariants.filter((compoundVariant: any) => {
|
||||
const strippedTheme = computed(() => {
|
||||
const strippedTheme = {
|
||||
...((props.pro ? themePro : theme) as any)[name]
|
||||
}
|
||||
|
||||
if (strippedTheme?.compoundVariants) {
|
||||
strippedTheme.compoundVariants = strippedTheme.compoundVariants.filter((compoundVariant: any) => {
|
||||
if (compoundVariant.color) {
|
||||
if (!['primary', 'neutral'].includes(compoundVariant.color)) {
|
||||
strippedCompoundVariants.value = true
|
||||
@@ -40,26 +50,47 @@ function stripCompoundVariants(component?: any) {
|
||||
})
|
||||
}
|
||||
|
||||
return component
|
||||
}
|
||||
return strippedTheme
|
||||
})
|
||||
|
||||
const component = computed(() => {
|
||||
return {
|
||||
ui: {
|
||||
[name]: stripCompoundVariants((theme as any)[name])
|
||||
[props.pro ? 'uiPro' : 'ui']: {
|
||||
[name]: strippedTheme.value
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const { data: ast } = await useAsyncData(`component-theme-${name}`, async () => {
|
||||
const md = `
|
||||
::code-collapse
|
||||
::code-collapse{class="nuxt-only"}
|
||||
|
||||
\`\`\`ts [app.config.ts]
|
||||
export default defineAppConfig(${json5.stringify(component.value, null, 2).replace(/,([ |\t\n]+[}|\])])/g, '$1')})
|
||||
\`\`\`\
|
||||
|
||||
::
|
||||
|
||||
::code-collapse{class="vue-only"}
|
||||
|
||||
\`\`\`ts [vite.config.ts]
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import ui from '@nuxt/ui/vite'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
ui(${json5.stringify(component.value, null, 2).replace(/,([ |\t\n]+[}|\])])/g, '$1')
|
||||
.split('\n')
|
||||
.map((line, i) => i === 0 ? line : ` ${line}`)
|
||||
.join('\n')})
|
||||
]
|
||||
})
|
||||
\`\`\`
|
||||
|
||||
::
|
||||
|
||||
${strippedCompoundVariants.value
|
||||
? `
|
||||
::callout{icon="i-simple-icons-github" to="https://github.com/nuxt/ui/blob/v3/src/theme/${name}.ts"}
|
||||
@@ -69,7 +100,7 @@ Some colors in \`compoundVariants\` are omitted for readability. Check out the s
|
||||
`
|
||||
|
||||
return parseMarkdown(md)
|
||||
})
|
||||
}, { watch: [framework] })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
12
docs/app/components/content/FrameworkOnly.vue
Normal file
12
docs/app/components/content/FrameworkOnly.vue
Normal file
@@ -0,0 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { Slot } from 'reka-ui'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot class="nuxt-only">
|
||||
<slot name="nuxt" />
|
||||
</Slot>
|
||||
<Slot class="vue-only">
|
||||
<slot name="vue" />
|
||||
</Slot>
|
||||
</template>
|
||||
@@ -2,8 +2,13 @@
|
||||
import json5 from 'json5'
|
||||
import icons from '../../../../src/theme/icons'
|
||||
|
||||
const appConfig = useAppConfig()
|
||||
const { framework, module } = useSharedData()
|
||||
|
||||
const { data: ast } = await useAsyncData(`icons-theme`, async () => {
|
||||
const md = `
|
||||
::code-collapse{class="ui-only nuxt-only"}
|
||||
|
||||
\`\`\`ts [app.config.ts]
|
||||
export default defineAppConfig(${json5.stringify({
|
||||
ui: {
|
||||
@@ -11,10 +16,52 @@ export default defineAppConfig(${json5.stringify({
|
||||
}
|
||||
}, null, 2).replace(/,([ |\t\n]+[}|\])])/g, '$1')})
|
||||
\`\`\`\
|
||||
|
||||
::
|
||||
|
||||
::code-collapse{class="ui-pro-only nuxt-only"}
|
||||
|
||||
\`\`\`ts [app.config.ts]
|
||||
export default defineAppConfig(${json5.stringify({
|
||||
ui: {
|
||||
icons: appConfig.ui.icons
|
||||
}
|
||||
}, null, 2).replace(/,([ |\t\n]+[}|\])])/g, '$1')})
|
||||
\`\`\`\
|
||||
|
||||
::
|
||||
|
||||
::caution{class="ui-pro-only vue-only"}
|
||||
Nuxt UI Pro v3 does not support Vue yet.
|
||||
::
|
||||
|
||||
::code-collapse{class="vue-only"}
|
||||
|
||||
\`\`\`ts [vite.config.ts]
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import ui from '@nuxt/ui/vite'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
ui(${json5.stringify({
|
||||
ui: {
|
||||
icons
|
||||
}
|
||||
}, null, 2).replace(/,([ |\t\n]+[}|\])])/g, '$1')
|
||||
.split('\n')
|
||||
.map((line, i) => i === 0 ? line : ` ${line}`)
|
||||
.join('\n')})
|
||||
]
|
||||
})
|
||||
\`\`\`
|
||||
|
||||
::
|
||||
`
|
||||
|
||||
return parseMarkdown(md)
|
||||
})
|
||||
}, { watch: [framework, module] })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
12
docs/app/components/content/ModuleOnly.vue
Normal file
12
docs/app/components/content/ModuleOnly.vue
Normal file
@@ -0,0 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { Slot } from 'reka-ui'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot class="ui-only">
|
||||
<slot name="ui" />
|
||||
</Slot>
|
||||
<Slot class="ui-pro-only">
|
||||
<slot name="ui-pro" />
|
||||
</Slot>
|
||||
</template>
|
||||
@@ -7,28 +7,60 @@ const props = withDefaults(defineProps<{
|
||||
default: 'en'
|
||||
})
|
||||
|
||||
const getLocaleKeys = () => Object.keys(locales) as Array<keyof typeof locales>
|
||||
const localesList = getLocaleKeys().map(locale => [locale, locales[locale].name])
|
||||
function getEmojiFlag(locale: string): string {
|
||||
// Map language codes to default country codes
|
||||
const languageToCountry: Record<string, string> = {
|
||||
en: 'gb',
|
||||
ar: 'sa',
|
||||
cs: 'cz',
|
||||
zh: 'cn',
|
||||
ja: 'jp',
|
||||
ko: 'kr'
|
||||
}
|
||||
|
||||
// Get base language code before any region specifier
|
||||
const baseLanguage = locale.split('-')[0]?.toLowerCase() || locale
|
||||
|
||||
// Use mapped country code or extract from locale if it contains a region
|
||||
const countryCode = languageToCountry[baseLanguage] || locale.replace(/^.*-/, '').slice(0, 2)
|
||||
|
||||
return countryCode
|
||||
.split('')
|
||||
.map((char: string) => {
|
||||
const codePoint = char.toUpperCase().codePointAt(0)
|
||||
return codePoint ? String.fromCodePoint(0x1F1A5 + codePoint) : ''
|
||||
})
|
||||
.join('')
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- eslint-disable vue/singleline-html-element-content-newline -->
|
||||
<template>
|
||||
<div>
|
||||
<ProseUl>
|
||||
<ProseLi v-for="[key, label] in localesList" :key="key">
|
||||
<ProseCode>{{ key }}</ProseCode> - {{ label }}
|
||||
<template v-if="key === props.default">
|
||||
(default)
|
||||
</template>
|
||||
</ProseLi>
|
||||
</ProseUl>
|
||||
<Note to="https://github.com/nuxt/ui/tree/v3/src/runtime/locale" target="_blank">
|
||||
<ProseP>
|
||||
By default, the <ProseCode>{{ props.default }}</ProseCode> locale is used.
|
||||
</ProseP>
|
||||
<div class="grid gap-6 grid-cols-2 md:grid-cols-3">
|
||||
<div v-for="locale in locales" :key="locale.code">
|
||||
<div class="flex gap-3 items-center">
|
||||
<UAvatar size="xl">
|
||||
{{ getEmojiFlag(locale.code) }}
|
||||
</UAvatar>
|
||||
|
||||
<div class="text-sm">
|
||||
<div class="font-semibold">{{ locale.name }}</div>
|
||||
<div class="mt-1">Code: <ProseCode class="text-xs">{{ locale.code }}</ProseCode></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ProseNote to="https://github.com/nuxt/ui/tree/v3/src/runtime/locale" target="_blank">
|
||||
If you need additional languages, you can contribute by creating a PR to add a new locale in <ProseCode>src/runtime/locale/</ProseCode>.
|
||||
</Note>
|
||||
<Tip>
|
||||
</ProseNote>
|
||||
<ProseTip>
|
||||
You can use the <ProseCode>nuxt-ui</ProseCode> CLI to create a new locale:
|
||||
|
||||
<ProsePre language="bash">nuxt-ui make locale --code "en" --name "English"</ProsePre>
|
||||
</Tip>
|
||||
</ProseTip>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
import { CalendarDate, DateFormatter, getLocalTimeZone } from '@internationalized/date'
|
||||
|
||||
const df = new DateFormatter('en-US', {
|
||||
dateStyle: 'medium'
|
||||
})
|
||||
|
||||
const modelValue = shallowRef(new CalendarDate(2022, 1, 10))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UPopover>
|
||||
<UButton color="neutral" variant="subtle" icon="i-lucide-calendar">
|
||||
{{ df.format(modelValue.toDate(getLocalTimeZone())) }}
|
||||
</UButton>
|
||||
|
||||
<template #content>
|
||||
<UCalendar v-model="modelValue" class="p-2" />
|
||||
</template>
|
||||
</UPopover>
|
||||
</template>
|
||||
@@ -0,0 +1,35 @@
|
||||
<script setup lang="ts">
|
||||
import { CalendarDate, DateFormatter, getLocalTimeZone } from '@internationalized/date'
|
||||
|
||||
const df = new DateFormatter('en-US', {
|
||||
dateStyle: 'medium'
|
||||
})
|
||||
|
||||
const modelValue = shallowRef({
|
||||
start: new CalendarDate(2022, 1, 20),
|
||||
end: new CalendarDate(2022, 2, 10)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UPopover>
|
||||
<UButton color="neutral" variant="subtle" icon="i-lucide-calendar">
|
||||
<template v-if="modelValue.start">
|
||||
<template v-if="modelValue.end">
|
||||
{{ df.format(modelValue.start.toDate(getLocalTimeZone())) }} - {{ df.format(modelValue.end.toDate(getLocalTimeZone())) }}
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
{{ df.format(modelValue.start.toDate(getLocalTimeZone())) }}
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
Pick a date
|
||||
</template>
|
||||
</UButton>
|
||||
|
||||
<template #content>
|
||||
<UCalendar v-model="modelValue" class="p-2" :number-of-months="2" range />
|
||||
</template>
|
||||
</UPopover>
|
||||
</template>
|
||||
@@ -0,0 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import { CalendarDate } from '@internationalized/date'
|
||||
import type { Matcher } from 'reka-ui/date'
|
||||
|
||||
const modelValue = shallowRef({
|
||||
start: new CalendarDate(2022, 1, 1),
|
||||
end: new CalendarDate(2022, 1, 9)
|
||||
})
|
||||
|
||||
const isDateDisabled: Matcher = (date) => {
|
||||
return date.day >= 10 && date.day <= 16
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UCalendar v-model="modelValue" :is-date-disabled="isDateDisabled" range />
|
||||
</template>
|
||||
@@ -0,0 +1,30 @@
|
||||
<script setup lang="ts">
|
||||
import { CalendarDate } from '@internationalized/date'
|
||||
|
||||
const modelValue = shallowRef(new CalendarDate(2022, 1, 10))
|
||||
|
||||
function getColorByDate(date: Date) {
|
||||
const isWeekend = date.getDay() % 6 == 0
|
||||
const isDayMeeting = date.getDay() % 3 == 0
|
||||
|
||||
if (isWeekend) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
if (isDayMeeting) {
|
||||
return 'error'
|
||||
}
|
||||
|
||||
return 'success'
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UCalendar v-model="modelValue">
|
||||
<template #day="{ day }">
|
||||
<UChip :show="!!getColorByDate(day.toDate('UTC'))" :color="getColorByDate(day.toDate('UTC'))" size="2xs">
|
||||
{{ day.day }}
|
||||
</UChip>
|
||||
</template>
|
||||
</UCalendar>
|
||||
</template>
|
||||
@@ -0,0 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { CalendarDate } from '@internationalized/date'
|
||||
|
||||
const modelValue = shallowRef(new CalendarDate(2023, 9, 10))
|
||||
const minDate = new CalendarDate(2023, 9, 1)
|
||||
const maxDate = new CalendarDate(2023, 9, 30)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UCalendar v-model="modelValue" :min-value="minDate" :max-value="maxDate" />
|
||||
</template>
|
||||
@@ -0,0 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import { CalendarDate } from '@internationalized/date'
|
||||
import type { Matcher } from 'reka-ui/date'
|
||||
|
||||
const modelValue = shallowRef({
|
||||
start: new CalendarDate(2022, 1, 1),
|
||||
end: new CalendarDate(2022, 1, 9)
|
||||
})
|
||||
|
||||
const isDateUnavailable: Matcher = (date) => {
|
||||
return date.day >= 10 && date.day <= 16
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UCalendar v-model="modelValue" :is-date-unavailable="isDateUnavailable" range />
|
||||
</template>
|
||||
@@ -0,0 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
const color = ref('#00C16A')
|
||||
|
||||
const chip = computed(() => ({ backgroundColor: color.value }))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UPopover>
|
||||
<UButton label="Choose color" color="neutral" variant="outline">
|
||||
<template #leading>
|
||||
<span :style="chip" class="size-3 rounded-full" />
|
||||
</template>
|
||||
</UButton>
|
||||
|
||||
<template #content>
|
||||
<UColorPicker v-model="color" class="p-2" />
|
||||
</template>
|
||||
</UPopover>
|
||||
</template>
|
||||
@@ -14,7 +14,7 @@ const groups = computed(() => [{
|
||||
id: 'users',
|
||||
label: searchTerm.value ? `Users matching “${searchTerm.value}”...` : 'Users',
|
||||
items: users.value || [],
|
||||
filter: false
|
||||
ignoreFilter: true
|
||||
}])
|
||||
</script>
|
||||
|
||||
@@ -11,8 +11,8 @@ const items = [
|
||||
level: 2
|
||||
},
|
||||
{
|
||||
id: '/getting-started#radix-vue-3',
|
||||
label: 'Radix Vue',
|
||||
id: '/getting-started#reka-ui-radix-vue',
|
||||
label: 'Reka UI',
|
||||
level: 3
|
||||
},
|
||||
{
|
||||
|
||||
@@ -72,7 +72,7 @@ const users = [
|
||||
}
|
||||
]
|
||||
|
||||
const searchTerm = ref('')
|
||||
const searchTerm = ref('B')
|
||||
|
||||
function onSelect() {
|
||||
searchTerm.value = ''
|
||||
|
||||
@@ -32,7 +32,7 @@ const items = computed(() => [{
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UContextMenu :items="items" class="w-48">
|
||||
<UContextMenu :items="items" :ui="{ content: 'w-48' }">
|
||||
<div class="flex items-center justify-center rounded-md border border-dashed border-[var(--ui-border-accented)] text-sm aspect-video w-72">
|
||||
Right click here
|
||||
</div>
|
||||
|
||||
@@ -25,7 +25,7 @@ const items = [
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UContextMenu :items="items" class="w-48">
|
||||
<UContextMenu :items="items" :ui="{ content: 'w-48' }">
|
||||
<div class="flex items-center justify-center rounded-md border border-dashed border-[var(--ui-border-accented)] text-sm aspect-video w-72">
|
||||
Right click here
|
||||
</div>
|
||||
|
||||
@@ -12,7 +12,7 @@ const items = [{
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UContextMenu :items="items" class="w-48">
|
||||
<UContextMenu :items="items" :ui="{ content: 'w-48' }">
|
||||
<div class="flex items-center justify-center rounded-md border border-dashed border-[var(--ui-border-accented)] text-sm aspect-video w-72">
|
||||
Right click here
|
||||
</div>
|
||||
@@ -22,7 +22,7 @@ const items = [{
|
||||
</template>
|
||||
|
||||
<template #refresh-trailing>
|
||||
<UIcon v-if="loading" name="i-lucide-refresh-ccw" class="shrink-0 size-5 text-[var(--ui-primary)] animate-spin" />
|
||||
<UIcon v-if="loading" name="i-lucide-refresh-cw" class="shrink-0 size-5 text-[var(--ui-primary)] animate-spin" />
|
||||
</template>
|
||||
</UContextMenu>
|
||||
</template>
|
||||
|
||||
@@ -13,7 +13,7 @@ const groups = computed(() => [{
|
||||
id: 'users',
|
||||
label: searchTerm.value ? `Users matching “${searchTerm.value}”...` : 'Users',
|
||||
items: users.value || [],
|
||||
filter: false
|
||||
ignoreFilter: true
|
||||
}])
|
||||
</script>
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ const items = computed(() => [{
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UDropdownMenu :items="items" :content="{ align: 'start' }" class="w-48">
|
||||
<UDropdownMenu :items="items" :content="{ align: 'start' }" :ui="{ content: 'w-48' }">
|
||||
<UButton label="Open" color="neutral" variant="outline" icon="i-lucide-menu" />
|
||||
</UDropdownMenu>
|
||||
</template>
|
||||
|
||||
@@ -25,7 +25,7 @@ const items = [
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UDropdownMenu :items="items" class="w-48">
|
||||
<UDropdownMenu :items="items" :ui="{ content: 'w-48' }">
|
||||
<UButton label="Open" color="neutral" variant="outline" icon="i-lucide-menu" />
|
||||
|
||||
<template #profile-trailing>
|
||||
|
||||
@@ -13,7 +13,7 @@ const items = [{
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UDropdownMenu :items="items" class="w-48">
|
||||
<UDropdownMenu :items="items" :ui="{ content: 'w-48' }">
|
||||
<UButton label="Open" color="neutral" variant="outline" icon="i-lucide-menu" />
|
||||
|
||||
<template #profile-trailing>
|
||||
|
||||
@@ -18,7 +18,7 @@ const items = [{
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UDropdownMenu v-model:open="open" :items="items" class="w-48">
|
||||
<UDropdownMenu v-model:open="open" :items="items" :ui="{ content: 'w-48' }">
|
||||
<UButton label="Open" color="neutral" variant="outline" icon="i-lucide-menu" />
|
||||
</UDropdownMenu>
|
||||
</template>
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { FormSubmitEvent } from '@nuxt/ui'
|
||||
|
||||
const schema = z.object({
|
||||
input: z.string().min(10),
|
||||
inputNumber: z.number().min(10),
|
||||
inputMenu: z.any().refine(option => option?.value === 'option-2', {
|
||||
message: 'Select Option 2'
|
||||
}),
|
||||
@@ -29,10 +30,11 @@ const schema = z.object({
|
||||
radioGroup: z.string().refine(value => value === 'option-2', {
|
||||
message: 'Select Option 2'
|
||||
}),
|
||||
slider: z.number().max(20, { message: 'Must be less than 20' })
|
||||
slider: z.number().max(20, { message: 'Must be less than 20' }),
|
||||
pin: z.string().regex(/^\d$/).array().length(5)
|
||||
})
|
||||
|
||||
type Schema = z.output<typeof schema>
|
||||
type Schema = z.input<typeof schema>
|
||||
|
||||
const state = reactive<Partial<Schema>>({})
|
||||
|
||||
@@ -52,10 +54,10 @@ async function onSubmit(event: FormSubmitEvent<any>) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UForm ref="form" :state="state" :schema="schema" @submit="onSubmit">
|
||||
<UForm ref="form" :state="state" :schema="schema" class="w-full" @submit="onSubmit">
|
||||
<div class="grid grid-cols-3 gap-4">
|
||||
<UFormField label="Input" name="input">
|
||||
<UInput v-model="state.input" placeholder="john@lennon.com" class="w-40" />
|
||||
<UInput v-model="state.input" placeholder="john@lennon.com" class="w-full" />
|
||||
</UFormField>
|
||||
|
||||
<div class="flex flex-col gap-4">
|
||||
@@ -73,34 +75,40 @@ async function onSubmit(event: FormSubmitEvent<any>) {
|
||||
</UFormField>
|
||||
|
||||
<UFormField name="select" label="Select">
|
||||
<USelect v-model="state.select" :items="items" class="w-48" />
|
||||
<USelect v-model="state.select" :items="items" class="w-full" />
|
||||
</UFormField>
|
||||
|
||||
<UFormField name="selectMenu" label="Select Menu">
|
||||
<USelectMenu v-model="state.selectMenu" :items="items" class="w-48" />
|
||||
<USelectMenu v-model="state.selectMenu" :items="items" class="w-full" />
|
||||
</UFormField>
|
||||
|
||||
<UFormField name="selectMenuMultiple" label="Select Menu (Multiple)">
|
||||
<USelectMenu v-model="state.selectMenuMultiple" multiple :items="items" class="w-48" />
|
||||
<USelectMenu v-model="state.selectMenuMultiple" multiple :items="items" class="w-full" />
|
||||
</UFormField>
|
||||
|
||||
<UFormField name="inputMenu" label="Input Menu">
|
||||
<UInputMenu v-model="state.inputMenu" :items="items" />
|
||||
<UInputMenu v-model="state.inputMenu" :items="items" class="w-full" />
|
||||
</UFormField>
|
||||
|
||||
<UFormField name="inputMenuMultiple" label="Input Menu (Multiple)">
|
||||
<UInputMenu v-model="state.inputMenuMultiple" multiple :items="items" />
|
||||
<UInputMenu v-model="state.inputMenuMultiple" multiple :items="items" class="w-full" />
|
||||
</UFormField>
|
||||
|
||||
<span />
|
||||
<UFormField name="inputNumber" label="Input Number">
|
||||
<UInputNumber v-model="state.inputNumber" class="w-full" />
|
||||
</UFormField>
|
||||
|
||||
<UFormField label="Textarea" name="textarea">
|
||||
<UTextarea v-model="state.textarea" />
|
||||
<UTextarea v-model="state.textarea" class="w-full" />
|
||||
</UFormField>
|
||||
|
||||
<UFormField name="radioGroup">
|
||||
<URadioGroup v-model="state.radioGroup" legend="Radio group" :items="items" />
|
||||
</UFormField>
|
||||
|
||||
<UFormField name="pin" label="Pin Input" :error-pattern="/(pin)\..*/">
|
||||
<UPinInput v-model="state.pin" />
|
||||
</UFormField>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 mt-8">
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
|
||||
const value = ref('Backlog')
|
||||
|
||||
function onCreate(item: string) {
|
||||
items.value.push(item)
|
||||
|
||||
value.value = item
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UInputMenu
|
||||
v-model="value"
|
||||
create-item
|
||||
:items="items"
|
||||
class="w-48"
|
||||
@create="onCreate"
|
||||
/>
|
||||
</template>
|
||||
@@ -16,7 +16,7 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
|
||||
<UInputMenu
|
||||
:items="users || []"
|
||||
:loading="status === 'pending'"
|
||||
:filter="['label', 'email']"
|
||||
:filter-fields="['label', 'email']"
|
||||
icon="i-lucide-user"
|
||||
placeholder="Select user"
|
||||
class="w-80"
|
||||
|
||||
@@ -20,7 +20,7 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
|
||||
v-model:search-term="searchTerm"
|
||||
:items="users || []"
|
||||
:loading="status === 'pending'"
|
||||
:filter="false"
|
||||
ignore-filter
|
||||
icon="i-lucide-user"
|
||||
placeholder="Select user"
|
||||
>
|
||||
@@ -0,0 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
const value = ref(1500)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UInputNumber
|
||||
v-model="value"
|
||||
:format-options="{
|
||||
style: 'currency',
|
||||
currency: 'EUR',
|
||||
currencyDisplay: 'code',
|
||||
currencySign: 'accounting'
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
@@ -0,0 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
const value = ref(5)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UInputNumber
|
||||
v-model="value"
|
||||
:format-options="{
|
||||
signDisplay: 'exceptZero',
|
||||
minimumFractionDigits: 1
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
@@ -0,0 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
const retries = ref(0)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UFormField label="Retries" help="Specify number of attempts" required>
|
||||
<UInputNumber v-model="retries" placeholder="Enter retries" />
|
||||
</UFormField>
|
||||
</template>
|
||||
@@ -0,0 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
const value = ref(0.05)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UInputNumber
|
||||
v-model="value"
|
||||
:step="0.01"
|
||||
:format-options="{
|
||||
style: 'percent'
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
@@ -0,0 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
const value = ref(5)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UInputNumber v-model="value">
|
||||
<template #decrement>
|
||||
<UButton size="xs" icon="i-lucide-minus" />
|
||||
</template>
|
||||
|
||||
<template #increment>
|
||||
<UButton size="xs" icon="i-lucide-plus" />
|
||||
</template>
|
||||
</UInputNumber>
|
||||
</template>
|
||||
@@ -6,7 +6,7 @@ const value = ref('Click to clear')
|
||||
<UInput
|
||||
v-model="value"
|
||||
placeholder="Type something..."
|
||||
:ui="{ trailing: 'pr-0.5' }"
|
||||
:ui="{ trailing: 'pe-1' }"
|
||||
>
|
||||
<template v-if="value?.length" #trailing>
|
||||
<UButton
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<script setup lang="ts">
|
||||
const value = ref('npx nuxi module add ui')
|
||||
const copied = ref(false)
|
||||
|
||||
function copy() {
|
||||
navigator.clipboard.writeText(value.value)
|
||||
copied.value = true
|
||||
|
||||
setTimeout(() => {
|
||||
copied.value = false
|
||||
}, 2000)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UInput
|
||||
v-model="value"
|
||||
:ui="{ trailing: 'pr-0.5' }"
|
||||
>
|
||||
<template v-if="value?.length" #trailing>
|
||||
<UTooltip text="Copy to clipboard" :content="{ side: 'right' }">
|
||||
<UButton
|
||||
:color="copied ? 'success' : 'neutral'"
|
||||
variant="link"
|
||||
size="sm"
|
||||
:icon="copied ? 'i-lucide-copy-check' : 'i-lucide-copy'"
|
||||
aria-label="Copy to clipboard"
|
||||
@click="copy"
|
||||
/>
|
||||
</UTooltip>
|
||||
</template>
|
||||
</UInput>
|
||||
</template>
|
||||
@@ -0,0 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
const input = useTemplateRef('input')
|
||||
|
||||
defineShortcuts({
|
||||
'/': () => {
|
||||
input.value?.inputRef?.focus()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UInput
|
||||
ref="input"
|
||||
icon="i-lucide-search"
|
||||
placeholder="Search..."
|
||||
>
|
||||
<template #trailing>
|
||||
<UKbd value="/" />
|
||||
</template>
|
||||
</UInput>
|
||||
</template>
|
||||
@@ -40,7 +40,7 @@ const text = computed(() => {
|
||||
placeholder="Password"
|
||||
:color="color"
|
||||
:type="show ? 'text' : 'password'"
|
||||
:ui="{ trailing: 'pr-0.5' }"
|
||||
:ui="{ trailing: 'pe-1' }"
|
||||
:aria-invalid="score < 4"
|
||||
aria-describedby="password-strength"
|
||||
class="w-full"
|
||||
|
||||
@@ -8,7 +8,7 @@ const password = ref('password')
|
||||
v-model="password"
|
||||
placeholder="Password"
|
||||
:type="show ? 'text' : 'password'"
|
||||
:ui="{ trailing: 'pr-0.5' }"
|
||||
:ui="{ trailing: 'pe-1' }"
|
||||
>
|
||||
<template #trailing>
|
||||
<UButton
|
||||
|
||||
@@ -13,7 +13,7 @@ const groups = computed(() => [{
|
||||
id: 'users',
|
||||
label: searchTerm.value ? `Users matching “${searchTerm.value}”...` : 'Users',
|
||||
items: users.value || [],
|
||||
filter: false
|
||||
ignoreFilter: true
|
||||
}])
|
||||
</script>
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ const items = [
|
||||
:items="items"
|
||||
class="justify-center"
|
||||
:ui="{
|
||||
viewport: 'sm:w-[var(--radix-navigation-menu-viewport-width)]',
|
||||
viewport: 'sm:w-[var(--reka-navigation-menu-viewport-width)]',
|
||||
childList: 'sm:w-96',
|
||||
childLinkDescription: 'text-balance line-clamp-2'
|
||||
}"
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
|
||||
const value = ref('Backlog')
|
||||
|
||||
function onCreate(item: string) {
|
||||
items.value.push(item)
|
||||
|
||||
value.value = item
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USelectMenu
|
||||
v-model="value"
|
||||
create-item
|
||||
:items="items"
|
||||
class="w-48"
|
||||
@create="onCreate"
|
||||
/>
|
||||
</template>
|
||||
@@ -16,7 +16,7 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
|
||||
<USelectMenu
|
||||
:items="users || []"
|
||||
:loading="status === 'pending'"
|
||||
:filter="['label', 'email']"
|
||||
:filter-fields="['label', 'email']"
|
||||
icon="i-lucide-user"
|
||||
placeholder="Select user"
|
||||
class="w-80"
|
||||
|
||||
@@ -20,7 +20,7 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
|
||||
v-model:search-term="searchTerm"
|
||||
:items="users || []"
|
||||
:loading="status === 'pending'"
|
||||
:filter="false"
|
||||
ignore-filter
|
||||
icon="i-lucide-user"
|
||||
placeholder="Select user"
|
||||
class="w-48"
|
||||
@@ -26,7 +26,7 @@ function getUserAvatar(value: string) {
|
||||
<template #leading="{ modelValue, ui }">
|
||||
<UAvatar
|
||||
v-if="modelValue"
|
||||
v-bind="getUserAvatar(modelValue)"
|
||||
v-bind="getUserAvatar(modelValue as string)"
|
||||
:size="ui.leadingAvatarSize()"
|
||||
:class="ui.leadingAvatar()"
|
||||
/>
|
||||
|
||||
@@ -34,7 +34,7 @@ function getChip(value: string) {
|
||||
<template #leading="{ modelValue, ui }">
|
||||
<UChip
|
||||
v-if="modelValue"
|
||||
v-bind="getChip(modelValue)"
|
||||
v-bind="getChip(modelValue as string)"
|
||||
inset
|
||||
standalone
|
||||
:size="ui.itemLeadingChipSize()"
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<script setup lang="ts">
|
||||
const items = [
|
||||
{
|
||||
title: 'Address',
|
||||
description: 'Add your address here',
|
||||
icon: 'i-lucide-house'
|
||||
}, {
|
||||
title: 'Shipping',
|
||||
description: 'Set your preferred shipping method',
|
||||
icon: 'i-lucide-truck'
|
||||
}, {
|
||||
title: 'Checkout',
|
||||
description: 'Confirm your order'
|
||||
}
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UStepper ref="stepper" :items="items" class="w-full">
|
||||
<template #content="{ item }">
|
||||
<Placeholder class="aspect-video">
|
||||
This is the {{ item?.title }} step.
|
||||
</Placeholder>
|
||||
</template>
|
||||
</UStepper>
|
||||
</template>
|
||||
@@ -0,0 +1,41 @@
|
||||
<script setup lang="ts">
|
||||
const items = [
|
||||
{
|
||||
slot: 'address',
|
||||
title: 'Address',
|
||||
description: 'Add your address here',
|
||||
icon: 'i-lucide-house'
|
||||
}, {
|
||||
slot: 'shipping',
|
||||
title: 'Shipping',
|
||||
description: 'Set your preferred shipping method',
|
||||
icon: 'i-lucide-truck'
|
||||
}, {
|
||||
slot: 'checkout',
|
||||
title: 'Checkout',
|
||||
description: 'Confirm your order'
|
||||
}
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UStepper :items="items" class="w-full">
|
||||
<template #address>
|
||||
<Placeholder class="aspect-video">
|
||||
Address
|
||||
</Placeholder>
|
||||
</template>
|
||||
|
||||
<template #shipping>
|
||||
<Placeholder class="aspect-video">
|
||||
Shipping
|
||||
</Placeholder>
|
||||
</template>
|
||||
|
||||
<template #checkout>
|
||||
<Placeholder class="aspect-video">
|
||||
Checkout
|
||||
</Placeholder>
|
||||
</template>
|
||||
</UStepper>
|
||||
</template>
|
||||
@@ -0,0 +1,37 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
|
||||
const items = [
|
||||
{
|
||||
title: 'Address',
|
||||
description: 'Add your address here',
|
||||
icon: 'i-lucide-house'
|
||||
}, {
|
||||
title: 'Shipping',
|
||||
description: 'Set your preferred shipping method',
|
||||
icon: 'i-lucide-truck'
|
||||
}, {
|
||||
title: 'Checkout',
|
||||
description: 'Confirm your order'
|
||||
}
|
||||
]
|
||||
|
||||
const active = ref(0)
|
||||
|
||||
// Note: This is for demonstration purposes only. Don't do this at home.
|
||||
onMounted(() => {
|
||||
setInterval(() => {
|
||||
active.value = (active.value + 1) % items.length
|
||||
}, 2000)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UStepper v-model="active" :items="items" class="w-full">
|
||||
<template #content="{ item }">
|
||||
<Placeholder class="aspect-video">
|
||||
This is the {{ item?.title }} step.
|
||||
</Placeholder>
|
||||
</template>
|
||||
</UStepper>
|
||||
</template>
|
||||
@@ -0,0 +1,51 @@
|
||||
<script setup lang="ts">
|
||||
const items = [
|
||||
{
|
||||
slot: 'address',
|
||||
title: 'Address',
|
||||
description: 'Add your address here',
|
||||
icon: 'i-lucide-house'
|
||||
}, {
|
||||
slot: 'shipping',
|
||||
title: 'Shipping',
|
||||
description: 'Set your preferred shipping method',
|
||||
icon: 'i-lucide-truck'
|
||||
}, {
|
||||
slot: 'checkout',
|
||||
title: 'Checkout',
|
||||
description: 'Confirm your order'
|
||||
}
|
||||
]
|
||||
|
||||
const stepper = useTemplateRef('stepper')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full">
|
||||
<UStepper ref="stepper" :items="items">
|
||||
<template #content="{ item }">
|
||||
<Placeholder class="aspect-video">
|
||||
{{ item.title }}
|
||||
</Placeholder>
|
||||
</template>
|
||||
</UStepper>
|
||||
|
||||
<div class="flex gap-2 justify-between mt-4">
|
||||
<UButton
|
||||
leading-icon="i-lucide-arrow-left"
|
||||
:disabled="!stepper?.hasPrev"
|
||||
@click="stepper?.prev()"
|
||||
>
|
||||
Prev
|
||||
</UButton>
|
||||
|
||||
<UButton
|
||||
trailing-icon="i-lucide-arrow-right"
|
||||
:disabled="!stepper?.hasNext"
|
||||
@click="stepper?.next()"
|
||||
>
|
||||
Next
|
||||
</UButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -143,14 +143,13 @@ const data = ref<Payment[]>([{
|
||||
const columns: TableColumn<Payment>[] = [{
|
||||
id: 'select',
|
||||
header: ({ table }) => h(UCheckbox, {
|
||||
'modelValue': table.getIsAllPageRowsSelected(),
|
||||
'indeterminate': table.getIsSomePageRowsSelected(),
|
||||
'onUpdate:modelValue': (value: boolean) => table.toggleAllPageRowsSelected(!!value),
|
||||
'modelValue': table.getIsSomePageRowsSelected() ? 'indeterminate' : table.getIsAllPageRowsSelected(),
|
||||
'onUpdate:modelValue': (value: boolean | 'indeterminate') => table.toggleAllPageRowsSelected(!!value),
|
||||
'ariaLabel': 'Select all'
|
||||
}),
|
||||
cell: ({ row }) => h(UCheckbox, {
|
||||
'modelValue': row.getIsSelected(),
|
||||
'onUpdate:modelValue': (value: boolean) => row.toggleSelected(!!value),
|
||||
'onUpdate:modelValue': (value: boolean | 'indeterminate') => row.toggleSelected(!!value),
|
||||
'ariaLabel': 'Select row'
|
||||
}),
|
||||
enableSorting: false,
|
||||
|
||||
@@ -48,14 +48,13 @@ const data = ref<Payment[]>([{
|
||||
const columns: TableColumn<Payment>[] = [{
|
||||
id: 'select',
|
||||
header: ({ table }) => h(UCheckbox, {
|
||||
'modelValue': table.getIsAllPageRowsSelected(),
|
||||
'indeterminate': table.getIsSomePageRowsSelected(),
|
||||
'onUpdate:modelValue': (value: boolean) => table.toggleAllPageRowsSelected(!!value),
|
||||
'modelValue': table.getIsSomePageRowsSelected() ? 'indeterminate' : table.getIsAllPageRowsSelected(),
|
||||
'onUpdate:modelValue': (value: boolean | 'indeterminate') => table.toggleAllPageRowsSelected(!!value),
|
||||
'ariaLabel': 'Select all'
|
||||
}),
|
||||
cell: ({ row }) => h(UCheckbox, {
|
||||
'modelValue': row.getIsSelected(),
|
||||
'onUpdate:modelValue': (value: boolean) => row.toggleSelected(!!value),
|
||||
'onUpdate:modelValue': (value: boolean | 'indeterminate') => row.toggleSelected(!!value),
|
||||
'ariaLabel': 'Select row'
|
||||
})
|
||||
}, {
|
||||
|
||||
@@ -10,7 +10,7 @@ function showToast() {
|
||||
title: 'Uh oh! Something went wrong.',
|
||||
description: props.description,
|
||||
actions: [{
|
||||
icon: 'i-lucide-refresh-ccw',
|
||||
icon: 'i-lucide-refresh-cw',
|
||||
label: 'Retry',
|
||||
color: 'neutral',
|
||||
variant: 'outline',
|
||||
|
||||
@@ -56,7 +56,7 @@ defineProps({
|
||||
<h1 class="m-0 text-[75px] font-semibold mb-2 text-white flex items-center">
|
||||
<span>{{ title }}</span>
|
||||
</h1>
|
||||
<p v-if="description" class="text-[32px] text-[#94a3b8] leading-tight">
|
||||
<p v-if="description" class="text-[32px] text-[#94a3b8] leading-tight text-balance">
|
||||
{{ description.slice(0, 200) }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
40
docs/app/composables/useContentNavigation.ts
Normal file
40
docs/app/composables/useContentNavigation.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import type { ContentNavigationItem } from '@nuxt/content'
|
||||
|
||||
function processNavigationItem(item: ContentNavigationItem, parent?: ContentNavigationItem): any {
|
||||
if (item.shadow) {
|
||||
return item.children?.flatMap(child => processNavigationItem(child, item))
|
||||
}
|
||||
|
||||
return {
|
||||
...item,
|
||||
title: parent?.title && parent.title !== 'Pro' ? parent.title : item.title,
|
||||
badge: parent?.badge || item.badge,
|
||||
class: [item.framework && `${item.framework}-only`, item.module && `${item.module}-only`].filter(Boolean),
|
||||
children: item.children?.length ? item.children?.flatMap(child => processNavigationItem(child)) : undefined
|
||||
}
|
||||
}
|
||||
|
||||
export const useContentNavigation = (navigation: Ref<ContentNavigationItem[] | undefined>) => {
|
||||
const { framework, module } = useSharedData()
|
||||
|
||||
const mappedNavigation = computed(() => navigation.value?.map(item => processNavigationItem(item)))
|
||||
const filteredNavigation = computed(() => mappedNavigation.value?.map((item) => {
|
||||
return {
|
||||
...item,
|
||||
children: item.children?.filter((child: any) => {
|
||||
if (child.framework && child.framework !== framework.value) {
|
||||
return false
|
||||
}
|
||||
if (child.module && child.module !== module.value) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
}))
|
||||
|
||||
return {
|
||||
mappedNavigation,
|
||||
filteredNavigation
|
||||
}
|
||||
}
|
||||
36
docs/app/composables/useSharedData.ts
Normal file
36
docs/app/composables/useSharedData.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
export function useSharedData() {
|
||||
const framework = useCookie('nuxt-ui-framework', { default: () => 'nuxt' })
|
||||
const frameworks = computed(() => [{
|
||||
label: 'Nuxt',
|
||||
icon: 'i-simple-icons-nuxtdotjs',
|
||||
value: 'nuxt',
|
||||
onSelect: () => framework.value = 'nuxt'
|
||||
}, {
|
||||
label: 'Vue',
|
||||
icon: 'i-simple-icons-vuedotjs',
|
||||
value: 'vue',
|
||||
disabled: module.value === 'ui-pro',
|
||||
onSelect: () => framework.value = 'vue'
|
||||
}].map(f => ({ ...f, active: framework.value === f.value })))
|
||||
|
||||
const module = useCookie('nuxt-ui-module', { default: () => 'ui' })
|
||||
const modules = computed(() => [{
|
||||
label: 'nuxt/ui',
|
||||
icon: 'i-lucide-box',
|
||||
value: 'ui',
|
||||
onSelect: () => module.value = 'ui'
|
||||
}, {
|
||||
label: 'nuxt/ui-pro',
|
||||
icon: 'i-lucide-boxes',
|
||||
value: 'ui-pro',
|
||||
disabled: framework.value === 'vue',
|
||||
onSelect: () => module.value = 'ui-pro'
|
||||
}].map(m => ({ ...m, active: module.value === m.value })))
|
||||
|
||||
return {
|
||||
framework,
|
||||
frameworks,
|
||||
module,
|
||||
modules
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import colors from 'tailwindcss/colors'
|
||||
// import { debounce } from 'perfect-debounce'
|
||||
import type { NuxtError } from '#app'
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -10,8 +11,20 @@ const route = useRoute()
|
||||
const appConfig = useAppConfig()
|
||||
const colorMode = useColorMode()
|
||||
|
||||
const { data: navigation } = await useAsyncData('navigation', () => queryCollectionNavigation('content'))
|
||||
const { data: files } = await useAsyncData('files', () => queryCollectionSearchSections('content', { ignoredTags: ['style'] }))
|
||||
const { data: navigation } = await useAsyncData('navigation', () => queryCollectionNavigation('content', ['framework', 'module']))
|
||||
const { data: files } = useLazyAsyncData('search', () => queryCollectionSearchSections('content'), {
|
||||
server: false
|
||||
})
|
||||
|
||||
const searchTerm = ref('')
|
||||
|
||||
// watch(searchTerm, debounce((query: string) => {
|
||||
// if (!query) {
|
||||
// return
|
||||
// }
|
||||
|
||||
// useTrackEvent('Search', { props: { query: `${query} - ${searchTerm.value?.commandPaletteRef.results.length} results` } })
|
||||
// }, 500))
|
||||
|
||||
const links = computed(() => [{
|
||||
label: 'Docs',
|
||||
@@ -68,23 +81,40 @@ useServerSeoMeta({
|
||||
twitterCard: 'summary_large_image'
|
||||
})
|
||||
|
||||
provide('navigation', navigation)
|
||||
const { frameworks, modules } = useSharedData()
|
||||
const { mappedNavigation, filteredNavigation } = useContentNavigation(navigation)
|
||||
|
||||
provide('navigation', mappedNavigation)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UApp>
|
||||
<NuxtLoadingIndicator color="#FFF" />
|
||||
|
||||
<Banner />
|
||||
<!-- <Banner /> -->
|
||||
|
||||
<Header :links="links" />
|
||||
|
||||
<UError :error="error" />
|
||||
|
||||
<Footer />
|
||||
<!-- <Footer /> -->
|
||||
|
||||
<ClientOnly>
|
||||
<LazyUContentSearch :files="files" :navigation="navigation" :fuse="{ resultLimit: 42 }" />
|
||||
<LazyUContentSearch
|
||||
v-model:search-term="searchTerm"
|
||||
:files="files"
|
||||
:groups="[{
|
||||
id: 'framework',
|
||||
label: 'Framework',
|
||||
items: frameworks
|
||||
}, {
|
||||
id: 'module',
|
||||
label: 'Module',
|
||||
items: modules
|
||||
}]"
|
||||
:navigation="filteredNavigation"
|
||||
:fuse="{ resultLimit: 42 }"
|
||||
/>
|
||||
</ClientOnly>
|
||||
</UApp>
|
||||
</template>
|
||||
|
||||
@@ -10,6 +10,13 @@ const navigation = inject<Ref<ContentNavigationItem[]>>('navigation')
|
||||
<UPage>
|
||||
<template #left>
|
||||
<UPageAside>
|
||||
<template #top>
|
||||
<div class="flex flex-col gap-2 w-[calc(100%+1.25rem)] -mx-2.5">
|
||||
<ModuleSelect />
|
||||
<FrameworkSelect />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<UContentNavigation :navigation="navigation" highlight />
|
||||
</UPageAside>
|
||||
</template>
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { ContentNavigationItem } from '@nuxt/content'
|
||||
import { findPageBreadcrumb, mapContentNavigation } from '#ui-pro/utils/content'
|
||||
|
||||
const route = useRoute()
|
||||
const { framework, module } = useSharedData()
|
||||
|
||||
definePageMeta({
|
||||
layout: 'docs'
|
||||
@@ -13,18 +14,59 @@ if (!page.value) {
|
||||
throw createError({ statusCode: 404, statusMessage: 'Page not found', fatal: true })
|
||||
}
|
||||
|
||||
const { data: surround } = await useAsyncData(`${route.path}-surround`, () => queryCollectionItemSurroundings('content', route.path, {
|
||||
fields: ['description']
|
||||
}))
|
||||
const { data: surround } = await useAsyncData(`${route.path}-surround`, () => {
|
||||
return queryCollectionItemSurroundings('content', route.path, {
|
||||
fields: ['description']
|
||||
}).orWhere(group => group.where('framework', '=', framework.value).where('framework', 'IS NULL'))
|
||||
.orWhere(group => group.where('module', '=', module.value).where('module', 'IS NULL'))
|
||||
}, {
|
||||
watch: [framework, module]
|
||||
})
|
||||
|
||||
const navigation = inject<Ref<ContentNavigationItem[]>>('navigation')
|
||||
|
||||
const breadcrumb = computed(() => mapContentNavigation(findPageBreadcrumb(navigation?.value, page.value)).map(({ icon, ...link }) => link))
|
||||
|
||||
if (!import.meta.prerender) {
|
||||
// Redirect to the correct framework version if the page is not the current framework
|
||||
watch(framework, () => {
|
||||
if (page.value?.framework && page.value?.framework !== framework.value) {
|
||||
if (route.path.endsWith(`/${page.value?.framework}`)) {
|
||||
navigateTo(`${route.path.split('/').slice(0, -1).join('/')}/${framework.value}`)
|
||||
} else {
|
||||
navigateTo(`/getting-started`)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Redirect to the correct module version if the page is not the current module
|
||||
watch(module, () => {
|
||||
if (page.value?.module && page.value?.module !== module.value) {
|
||||
if (page.value?.module === 'ui-pro' && route.path.includes('/pro')) {
|
||||
navigateTo(`${route.path.replace('/pro', '')}`)
|
||||
} else if (page.value?.module === 'ui' && !route.path.includes('/pro')) {
|
||||
navigateTo(`${route.path.replace(`/${framework.value}`, '')}/pro/${framework.value}`)
|
||||
} else {
|
||||
navigateTo(`/getting-started`)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Update the framework/module if the page has different ones
|
||||
watch(page, () => {
|
||||
if (page.value?.framework && page.value?.framework !== framework.value) {
|
||||
framework.value = page.value?.framework as string
|
||||
}
|
||||
if (page.value?.module && page.value?.module !== module.value) {
|
||||
module.value = page.value?.module as string
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
useSeoMeta({
|
||||
titleTemplate: '%s - Nuxt UI v3',
|
||||
title: typeof page.value.navigation === 'object' ? page.value.navigation.title : page.value.title,
|
||||
ogTitle: `${typeof page.value.navigation === 'object' ? page.value.navigation.title : page.value.title} - Nuxt UI v3`,
|
||||
titleTemplate: `%s - Nuxt UI ${page.value.module === 'ui-pro' ? 'Pro' : ''} v3${page.value.framework === 'vue' ? ' for Vue' : ''}`,
|
||||
title: page.value.navigation?.title ? page.value.navigation.title : page.value.title,
|
||||
ogTitle: `${page.value.navigation?.title ? page.value.navigation.title : page.value.title} - Nuxt UI ${page.value.module === 'ui-pro' ? 'Pro' : ''} v3${page.value.framework === 'vue' ? ' for Vue' : ''}`,
|
||||
description: page.value.description,
|
||||
ogDescription: page.value.description
|
||||
})
|
||||
@@ -74,27 +116,13 @@ const communityLinks = computed(() => [{
|
||||
<MDC v-if="page.description" :value="page.description" unwrap="p" />
|
||||
</template>
|
||||
|
||||
<template #links>
|
||||
<UDropdownMenu v-if="page.select" v-slot="{ open }" :items="page.select.items" :content="{ align: 'end' }">
|
||||
<UButton
|
||||
color="neutral"
|
||||
variant="subtle"
|
||||
v-bind="page.select.items.find((item: any) => item.to === route.path)"
|
||||
block
|
||||
trailing-icon="i-lucide-chevron-down"
|
||||
:class="[open && 'bg-[var(--ui-bg-accented)]/75']"
|
||||
:ui="{
|
||||
trailingIcon: ['transition-transform duration-200', open ? 'rotate-180' : undefined].filter(Boolean).join(' ')
|
||||
}"
|
||||
class="w-[128px]"
|
||||
/>
|
||||
</UDropdownMenu>
|
||||
|
||||
<template v-if="page.links?.length" #links>
|
||||
<UButton
|
||||
v-for="link in page.links"
|
||||
:key="link.label"
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
target="_blank"
|
||||
v-bind="link"
|
||||
>
|
||||
<template v-if="link.avatar" #leading>
|
||||
|
||||
@@ -20,7 +20,7 @@ const src = computed(() => `https://volta.net/embed/${token}?theme=${colorMode.v
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-[calc(100vh-var(--ui-header-height)-var(--ui-header-height)-48px-1px)]">
|
||||
<div class="h-[calc(100vh-var(--ui-header-height))]">
|
||||
<ClientOnly>
|
||||
<iframe :src="src" width="100%" height="100%" />
|
||||
</ClientOnly>
|
||||
|
||||
38
docs/app/plugins/data.ts
Normal file
38
docs/app/plugins/data.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
export default defineNuxtPlugin({
|
||||
enforce: 'post',
|
||||
setup() {
|
||||
const { framework, module } = useSharedData()
|
||||
|
||||
if (import.meta.client) {
|
||||
useHead({
|
||||
htmlAttrs: {
|
||||
'data-framework': framework,
|
||||
'data-module': module
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (import.meta.server) {
|
||||
useHead({
|
||||
script: [{
|
||||
innerHTML: `
|
||||
function getCookie(name) {
|
||||
var value = '; ' + window.document.cookie;
|
||||
var parts = value.split('; ' + name + '=');
|
||||
if (parts.length === 2) {
|
||||
return parts.pop()?.split(';').shift();
|
||||
}
|
||||
}
|
||||
|
||||
var f = getCookie('nuxt-ui-framework');
|
||||
document.documentElement.setAttribute('data-framework', f || 'nuxt');
|
||||
var m = getCookie('nuxt-ui-module');
|
||||
document.documentElement.setAttribute('data-module', m || 'ui');
|
||||
`.replace(/\s+/g, ' '),
|
||||
type: 'text/javascript',
|
||||
tagPriority: -1
|
||||
}]
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -1,27 +1,45 @@
|
||||
import { defineCollection, z } from '@nuxt/content'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const schema = z.object({
|
||||
framework: z.string().optional(),
|
||||
module: z.string().optional(),
|
||||
navigation: z.object({
|
||||
title: z.string().optional()
|
||||
}),
|
||||
links: z.array(z.object({
|
||||
label: z.string(),
|
||||
icon: z.string(),
|
||||
avatar: z.object({
|
||||
src: z.string(),
|
||||
alt: z.string()
|
||||
}).optional(),
|
||||
to: z.string(),
|
||||
target: z.string().optional()
|
||||
}))
|
||||
})
|
||||
|
||||
const pro = process.env.NUXT_UI_PRO_PATH
|
||||
? {
|
||||
cwd: resolve(__dirname, process.env.NUXT_UI_PRO_PATH, 'docs'),
|
||||
include: 'content/**',
|
||||
prefix: '/'
|
||||
}
|
||||
: process.env.NUXT_GITHUB_TOKEN
|
||||
? {
|
||||
repository: 'https://github.com/nuxt/ui-pro/tree/v3',
|
||||
include: 'docs/content/**',
|
||||
prefix: '/',
|
||||
authToken: process.env.NUXT_GITHUB_TOKEN
|
||||
}
|
||||
: undefined
|
||||
|
||||
export const collections = {
|
||||
content: defineCollection({
|
||||
type: 'page',
|
||||
source: '**/*',
|
||||
schema: z.object({
|
||||
links: z.array(z.object({
|
||||
label: z.string(),
|
||||
icon: z.string(),
|
||||
avatar: z.object({
|
||||
src: z.string(),
|
||||
alt: z.string()
|
||||
}).optional(),
|
||||
to: z.string(),
|
||||
target: z.string().optional()
|
||||
})),
|
||||
select: z.object({
|
||||
items: z.array(z.object({
|
||||
label: z.string(),
|
||||
icon: z.string(),
|
||||
to: z.string()
|
||||
}))
|
||||
})
|
||||
})
|
||||
source: [{
|
||||
include: '**/*'
|
||||
}, pro!].filter(Boolean),
|
||||
schema
|
||||
})
|
||||
}
|
||||
|
||||
@@ -8,12 +8,12 @@ We're thrilled to introduce this major update to our UI library, bringing signif
|
||||
|
||||
## What's New in v3?
|
||||
|
||||
### Radix Vue
|
||||
### Reka UI (Radix Vue)
|
||||
|
||||
We've transitioned from [Headless UI](https://headlessui.com/) to [Radix Vue](https://www.radix-vue.com/) as our core component foundation. This shift brings several key advantages:
|
||||
We've transitioned from [Headless UI](https://headlessui.com/) to [Reka UI](https://reka-ui.com/) as our core component foundation. This shift brings several key advantages:
|
||||
|
||||
- **Extensive Component Library**: With 55+ primitives, Radix Vue significantly expands our component offerings.
|
||||
- **Active Development**: Radix Vue's growing popularity ensures ongoing improvements and updates.
|
||||
- **Extensive Component Library**: With 55+ primitives, Reka UI significantly expands our component offerings.
|
||||
- **Active Development**: Reka UI's growing popularity ensures ongoing improvements and updates.
|
||||
- **Enhanced Accessibility**: Built-in accessibility features align with our commitment to inclusive design.
|
||||
- **Vue 3 Optimization**: Seamless integration with Vue 3 and the Composition API.
|
||||
|
||||
@@ -21,15 +21,15 @@ This transition empowers Nuxt UI to become a more comprehensive and flexible UI
|
||||
|
||||
### Tailwind CSS v4
|
||||
|
||||
Nuxt UI v3 integrates the latest Tailwind CSS v4 alpha (announced March 6, 2024), bringing significant improvements:
|
||||
Nuxt UI v3 integrates the latest Tailwind CSS v4 beta (released Nov 21, 2024), bringing significant improvements:
|
||||
|
||||
- **Faster Builds**: Up to 10x faster, especially for larger projects.
|
||||
- **Unified Toolchain**: Built-in features like vendor prefixing, nesting support, and modern CSS transforms.
|
||||
- **CSS-First Configuration**: New `@theme` directive for easy customization without JavaScript.
|
||||
- **Optimized Performance**: Smaller engine footprint and more efficient processing.
|
||||
- **Built for performance**: Full builds in the new engine are up to 5x faster, and incremental builds are over 100x faster — and measured in microseconds.
|
||||
- **Unified toolchain**: Built-in import handling, vendor prefixing, and syntax transforms, with no additional tooling required.
|
||||
- **CSS-first configuration**: A reimagined developer experience where you customize and extend the framework directly in CSS instead of a JavaScript configuration file.
|
||||
- **Designed for the modern web**: Built on native cascade layers, wide-gamut colors, and including first-class support for modern CSS features like container queries, @starting-style, popovers, and more.
|
||||
|
||||
::note
|
||||
For a comprehensive overview of Tailwind CSS v4 alpha features, visit the [official announcement](https://tailwindcss.com/blog/tailwindcss-v4-alpha).
|
||||
::note{to="https://tailwindcss.com/docs/v4-beta" target="_blank"}
|
||||
For a comprehensive overview of Tailwind CSS v4 beta features, read the **prerelease documentation**.
|
||||
::
|
||||
|
||||
### Tailwind Variants
|
||||
@@ -71,7 +71,7 @@ You can now use Nuxt UI in any Vue project without Nuxt by adding the Vite and V
|
||||
- **Developer Experience**: Complete TypeScript support with IntelliSense and auto-completion
|
||||
|
||||
::tip{to="/getting-started/installation/vue"}
|
||||
Learn how to install and configure Nuxt UI in a Vue project in the [Vue installation guide](/getting-started/installation/vue).
|
||||
Learn how to install and configure Nuxt UI in a Vue project in the **Vue installation guide**.
|
||||
::
|
||||
|
||||
## Migration
|
||||
@@ -90,6 +90,10 @@ Key points to consider:
|
||||
The transition to v3 involves significant changes, including new component structures, updated theming approaches, and revised TypeScript definitions. We recommend a careful, incremental upgrade process, starting with thorough testing in a development environment.
|
||||
::
|
||||
|
||||
::accordion-item{label="Is Nuxt UI v3 compatible with standalone Vue projects?"}
|
||||
Nuxt UI is now compatible with Vue! You can follow the [installation guide](/getting-started/installation/vue) to get started.
|
||||
::
|
||||
|
||||
::accordion-item{label="What about Nuxt UI Pro?"}
|
||||
We've also rebuilt Nuxt UI Pro from scratch and released a `v3.0.0-alpha.x` package but it only contains the components to build this documentation yet. This will be a free update, so the license you buy now will be valid for v3. We're actively working to finish the rewrite of all Nuxt UI Pro components.
|
||||
::
|
||||
@@ -98,16 +102,12 @@ Key points to consider:
|
||||
Nuxt UI v3 is currently designed to work exclusively with Tailwind CSS. While there's interest in UnoCSS support, implementing it would require significant changes to the theme structure due to differences in class naming conventions. As a result, we don't have plans to add UnoCSS support in v3.
|
||||
::
|
||||
|
||||
::accordion-item{label="Is Nuxt UI v3 compatible with standalone Vue projects?"}
|
||||
We're planning to add Vue support in the near future. For now, Nuxt UI v3 is only available for Nuxt. Track progress on Vue compatibility [in this issue](https://github.com/nuxt/ui/issues/2129).
|
||||
::
|
||||
|
||||
::accordion-item{label="How does Nuxt UI v3 handle accessibility?"}
|
||||
Nuxt UI v3 enhances accessibility through Radix Vue integration. This provides automatic ARIA attributes, keyboard navigation support, intelligent focus management, and screen reader announcements. While offering a strong foundation, proper implementation and testing in your specific use case remains crucial for full accessibility compliance. For more detailed information, refer to [Radix Vue's accessibility documentation](https://www.radix-vue.com/overview/accessibility.html).
|
||||
Nuxt UI v3 enhances accessibility through Reka UI integration. This provides automatic ARIA attributes, keyboard navigation support, intelligent focus management, and screen reader announcements. While offering a strong foundation, proper implementation and testing in your specific use case remains crucial for full accessibility compliance. For more detailed information, refer to [Reka UI's accessibility documentation](https://reka-ui.com/docs/overview/accessibility).
|
||||
::
|
||||
|
||||
::accordion-item{label="What is the testing approach for Nuxt UI v3?"}
|
||||
Nuxt UI v3 ensures reliability with 800+ Vitest tests, covering core functionality and accessibility. This robust testing suite supports the library's stability and serves as a reference for developers.
|
||||
Nuxt UI v3 ensures reliability with 1000+ Vitest tests, covering core functionality and accessibility. This robust testing suite supports the library's stability and serves as a reference for developers.
|
||||
::
|
||||
|
||||
::accordion-item{label="Is this version stable and suitable for production use?"}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
shadow: true
|
||||
@@ -1,19 +1,18 @@
|
||||
---
|
||||
title: Install in a Nuxt app
|
||||
navigation.title: Nuxt
|
||||
title: Installation
|
||||
description: 'Learn how to install and configure Nuxt UI in your Nuxt application.'
|
||||
select:
|
||||
items:
|
||||
- label: Nuxt
|
||||
icon: i-logos-nuxt-icon
|
||||
to: /getting-started/installation/nuxt
|
||||
- label: Vue
|
||||
icon: i-logos-vue
|
||||
to: /getting-started/installation/vue
|
||||
framework: nuxt
|
||||
module: ui
|
||||
---
|
||||
|
||||
::callout{to="/getting-started/installation/vue" icon="i-logos-vue" class="hidden"}
|
||||
Looking for the **Vue** version?
|
||||
::
|
||||
|
||||
## Setup
|
||||
|
||||
### Add to a Nuxt project
|
||||
|
||||
::steps{level="4"}
|
||||
|
||||
#### Install the Nuxt UI v3 alpha package
|
||||
@@ -57,7 +56,7 @@ export default defineNuxtConfig({
|
||||
@import "@nuxt/ui";
|
||||
```
|
||||
|
||||
::note
|
||||
::tip
|
||||
Use the `css` property in your `nuxt.config.ts` to import your CSS file.
|
||||
|
||||
```ts [nuxt.config.ts]
|
||||
@@ -68,9 +67,10 @@ export default defineNuxtConfig({
|
||||
```
|
||||
::
|
||||
|
||||
::tip
|
||||
::callout{icon="i-simple-icons-visualstudiocode"}
|
||||
It's recommended to install the [Tailwind CSS IntelliSense](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss) extension for VSCode and add the following settings:
|
||||
```json
|
||||
|
||||
```json [settings.json]
|
||||
"files.associations": {
|
||||
"*.css": "tailwindcss"
|
||||
},
|
||||
@@ -81,8 +81,43 @@ It's recommended to install the [Tailwind CSS IntelliSense](https://marketplace.
|
||||
|
||||
::
|
||||
|
||||
#### Wrap your app with App component
|
||||
|
||||
```vue [app.vue]
|
||||
<template>
|
||||
<UApp>
|
||||
<NuxtPage />
|
||||
</UApp>
|
||||
</template>
|
||||
```
|
||||
|
||||
::note{to="/components/app"}
|
||||
The `App` component provides global configurations and is required for **Toast** and **Tooltip** components to work.
|
||||
::
|
||||
|
||||
::
|
||||
|
||||
### Use our Nuxt Starter
|
||||
|
||||
Start your project with a Nuxt template with Nuxt UI v3 pre-configured by using our [Nuxt UI Starter](https://github.com/nuxt/starter/tree/ui3).
|
||||
|
||||
Create a new project locally by running the following command:
|
||||
|
||||
```bash [Terminal]
|
||||
npx nuxi@latest init -t ui3 <my-app>
|
||||
```
|
||||
|
||||
::note
|
||||
The `<my-app>` argument is the name of the directory where the project will be created, replace it with your project name.
|
||||
::
|
||||
|
||||
Once the installation is complete, navigate into your project and start the development server:
|
||||
|
||||
```bash [Terminal]
|
||||
cd <my-app>
|
||||
npm run dev
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
You can customize Nuxt UI by providing options in your `nuxt.config.ts`.
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
---
|
||||
navigation.title: Vue
|
||||
title: Install in a Vue app
|
||||
title: Installation
|
||||
description: 'Learn how to install and configure Nuxt UI in your Vue application.'
|
||||
select:
|
||||
items:
|
||||
- label: Nuxt
|
||||
icon: i-logos-nuxt-icon
|
||||
to: /getting-started/installation/nuxt
|
||||
- label: Vue
|
||||
icon: i-logos-vue
|
||||
to: /getting-started/installation/vue
|
||||
framework: vue
|
||||
module: ui
|
||||
---
|
||||
|
||||
::callout{to="/getting-started/installation/nuxt" icon="i-logos-nuxt-icon" class="hidden"}
|
||||
Looking for the **Nuxt** version?
|
||||
::
|
||||
|
||||
## Setup
|
||||
|
||||
::steps{level="4"}
|
||||
@@ -94,7 +91,7 @@ app.mount('#app')
|
||||
@import "@nuxt/ui";
|
||||
```
|
||||
|
||||
::note
|
||||
::tip
|
||||
Import the CSS file in your `main.ts`.
|
||||
|
||||
```ts [main.ts]{1}
|
||||
@@ -112,9 +109,10 @@ app.mount('#app')
|
||||
```
|
||||
::
|
||||
|
||||
::tip
|
||||
::callout{icon="i-simple-icons-visualstudiocode"}
|
||||
It's recommended to install the [Tailwind CSS IntelliSense](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss) extension for VSCode and add the following settings:
|
||||
```json
|
||||
|
||||
```json [settings.json]
|
||||
"files.associations": {
|
||||
"*.css": "tailwindcss"
|
||||
},
|
||||
@@ -125,6 +123,20 @@ It's recommended to install the [Tailwind CSS IntelliSense](https://marketplace.
|
||||
|
||||
::
|
||||
|
||||
#### Wrap your app with App component
|
||||
|
||||
```vue [App.vue]
|
||||
<template>
|
||||
<UApp>
|
||||
<RouterView />
|
||||
</UApp>
|
||||
</template>
|
||||
```
|
||||
|
||||
::note{to="/components/app"}
|
||||
The `App` component provides global configurations and is required for **Toast** and **Tooltip** components to work.
|
||||
::
|
||||
|
||||
::
|
||||
|
||||
## Options
|
||||
@@ -156,10 +168,6 @@ export default defineConfig({
|
||||
|
||||
Use the `ui` option to provide configuration for Nuxt UI.
|
||||
|
||||
::warning
|
||||
In the rest of the docs, there are references to the `app.config.ts` file (which is a Nuxt feature). When using Nuxt UI in a project without Nuxt, this configuration is passed in this `ui` option instead.
|
||||
::
|
||||
|
||||
```ts [vite.config.ts]
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
|
||||
@@ -5,18 +5,21 @@ description: 'Learn how to customize Nuxt UI components using Tailwind CSS v4, C
|
||||
|
||||
## Tailwind CSS
|
||||
|
||||
Nuxt UI v3 uses Tailwind CSS v4 alpha which doesn't have a documentation yet, let's have a look on how to use it.
|
||||
Nuxt UI v3 uses Tailwind CSS v4 beta, you can read the [prerelease documentation](https://tailwindcss.com/docs/v4-beta) for more information.
|
||||
|
||||
### `@theme`
|
||||
|
||||
Tailwind CSS v4 takes a CSS-first configuration approach, you now customize your theme with CSS variables inside a `@theme` directive:
|
||||
|
||||
::module-only
|
||||
#ui
|
||||
:::div
|
||||
```css [main.css]
|
||||
@import "tailwindcss";
|
||||
@import "@nuxt/ui";
|
||||
|
||||
@theme {
|
||||
--font-family-sans: 'Public Sans', sans-serif;
|
||||
--font-sans: 'Public Sans', sans-serif;
|
||||
|
||||
--breakpoint-3xl: 1920px;
|
||||
|
||||
@@ -33,11 +36,39 @@ Tailwind CSS v4 takes a CSS-first configuration approach, you now customize your
|
||||
--color-green-950: #052E16;
|
||||
}
|
||||
```
|
||||
:::
|
||||
|
||||
#ui-pro
|
||||
:::div
|
||||
```css [main.css]
|
||||
@import "tailwindcss";
|
||||
@import "@nuxt/ui-pro";
|
||||
|
||||
@theme {
|
||||
--font-sans: 'Public Sans', sans-serif;
|
||||
|
||||
--breakpoint-3xl: 1920px;
|
||||
|
||||
--color-green-50: #EFFDF5;
|
||||
--color-green-100: #D9FBE8;
|
||||
--color-green-200: #B3F5D1;
|
||||
--color-green-300: #75EDAE;
|
||||
--color-green-400: #00DC82;
|
||||
--color-green-500: #00C16A;
|
||||
--color-green-600: #00A155;
|
||||
--color-green-700: #007F45;
|
||||
--color-green-800: #016538;
|
||||
--color-green-900: #0A5331;
|
||||
--color-green-950: #052E16;
|
||||
}
|
||||
```
|
||||
:::
|
||||
::
|
||||
|
||||
The `@theme` directive tells Tailwind to make new utilities and variants available based on these variables. It's the equivalent of the `theme.extend` key in Tailwind CSS v3 `tailwind.config.ts` file.
|
||||
|
||||
::note
|
||||
You can learn more about this on [https://tailwindcss.com/blog/tailwindcss-v4-alpha](https://tailwindcss.com/blog/tailwindcss-v4-alpha#css-first-configuration).
|
||||
::note{to="https://tailwindcss.com/docs/v4-beta#css-first-configuration" target="_blank"}
|
||||
Learn more about Tailwind CSS v4 CSS-first configuration approach.
|
||||
::
|
||||
|
||||
### `@source`
|
||||
@@ -46,30 +77,60 @@ You can use the `@source` directive to add explicit content glob patterns if you
|
||||
|
||||
This can be useful when writing Tailwind classes in markdown files with [`@nuxt/content`](https://github.com/nuxt/content):
|
||||
|
||||
::module-only
|
||||
#ui
|
||||
:::div
|
||||
```css [main.css]
|
||||
@import "tailwindcss";
|
||||
@import "@nuxt/ui";
|
||||
|
||||
@source "../content/**/*.md";
|
||||
@source "../content";
|
||||
```
|
||||
:::
|
||||
|
||||
::note{to="https://github.com/tailwindlabs/tailwindcss/pull/14078"}
|
||||
You can learn more about the `@source` directive in this pull request.
|
||||
#ui-pro
|
||||
:::div
|
||||
```css [main.css]
|
||||
@import "tailwindcss";
|
||||
@import "@nuxt/ui-pro";
|
||||
|
||||
@source "../content";
|
||||
```
|
||||
:::
|
||||
::
|
||||
|
||||
::note{to="https://tailwindcss.com/docs/v4-beta#adding-content-sources"}
|
||||
Learn how to add content sources in Tailwind CSS v4.
|
||||
::
|
||||
|
||||
### `@plugin`
|
||||
|
||||
You can use the `@plugin` directive to import Tailwind CSS plugins.
|
||||
|
||||
::module-only
|
||||
#ui
|
||||
:::div
|
||||
```css [main.css]
|
||||
@import "tailwindcss";
|
||||
@import "@nuxt/ui";
|
||||
|
||||
@plugin "@tailwindcss/typography";
|
||||
```
|
||||
:::
|
||||
|
||||
::note{to="https://github.com/tailwindlabs/tailwindcss/pull/14264"}
|
||||
You can learn more about the `@plugin` directive in this pull request.
|
||||
#ui-pro
|
||||
:::div
|
||||
```css [main.css]
|
||||
@import "tailwindcss";
|
||||
@import "@nuxt/ui-pro";
|
||||
|
||||
@plugin "@tailwindcss/typography";
|
||||
```
|
||||
:::
|
||||
::
|
||||
|
||||
::note{to="https://tailwindcss.com/docs/v4-beta#using-plugins"}
|
||||
Learn more about using plugins in Tailwind CSS v4.
|
||||
::
|
||||
|
||||
## Design system
|
||||
@@ -78,8 +139,14 @@ Nuxt UI extends Tailwind CSS's theming capabilities, providing a flexible design
|
||||
|
||||
### Colors
|
||||
|
||||
::framework-only
|
||||
#nuxt
|
||||
Nuxt UI leverages Nuxt [App Config](https://nuxt.com/docs/guide/directory-structure/app-config#app-config-file) to provide customizable color aliases based on [Tailwind CSS colors](https://tailwindcss.com/docs/customizing-colors#color-palette-reference):
|
||||
|
||||
#vue
|
||||
Nuxt UI leverages Vite config to provide customizable color aliases based on [Tailwind CSS colors](https://tailwindcss.com/docs/customizing-colors#color-palette-reference):
|
||||
::
|
||||
|
||||
| Color | Default | Description |
|
||||
| --- | --- | --- |
|
||||
| `primary`{color="primary"} | `green` | Main brand color, used as the default color for components. |
|
||||
@@ -90,6 +157,9 @@ Nuxt UI leverages Nuxt [App Config](https://nuxt.com/docs/guide/directory-struct
|
||||
| `error`{color="error"} | `red` | Used for form error validation states. |
|
||||
| `neutral` | `slate` | Neutral color for backgrounds, text, etc. |
|
||||
|
||||
::framework-only
|
||||
#nuxt
|
||||
::div
|
||||
You can configure these color aliases at runtime in your `app.config.ts` file under the `ui.colors` key, allowing for dynamic theme customization without requiring an application rebuild:
|
||||
|
||||
```ts [app.config.ts]
|
||||
@@ -102,6 +172,34 @@ export default defineAppConfig({
|
||||
}
|
||||
})
|
||||
```
|
||||
::
|
||||
|
||||
#vue
|
||||
::div
|
||||
You can configure these color aliases at runtime in your `vite.config.ts` file under the `ui.colors` key:
|
||||
|
||||
```ts [vite.config.ts]
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import ui from '@nuxt/ui/vite'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
ui({
|
||||
ui: {
|
||||
colors: {
|
||||
primary: 'blue',
|
||||
neutral: 'zinc'
|
||||
}
|
||||
}
|
||||
})
|
||||
]
|
||||
})
|
||||
```
|
||||
::
|
||||
|
||||
::
|
||||
|
||||
::note
|
||||
Try the :prose-icon{name="i-lucide-swatch-book" class="text-[var(--ui-primary)]"} theme picker in the header above to change `primary` and `neutral` colors.
|
||||
@@ -118,8 +216,10 @@ slots:
|
||||
---
|
||||
::
|
||||
|
||||
::tip
|
||||
You can add you own dynamic color aliases in your `app.config.ts`, you just have to make sure to define them in the [`ui.theme.colors`](/getting-started/installation#themecolors) option in your `nuxt.config.ts` file.
|
||||
::framework-only
|
||||
#nuxt
|
||||
:::tip
|
||||
You can add you own dynamic color aliases in your `app.config.ts`, you just have to make sure to define them in the [`ui.theme.colors`](/getting-started/installation/nuxt#themecolors) option in your `nuxt.config.ts` file.
|
||||
|
||||
```ts [app.config.ts]
|
||||
export default defineAppConfig({
|
||||
@@ -140,7 +240,34 @@ export default defineNuxtConfig({
|
||||
}
|
||||
})
|
||||
```
|
||||
:::
|
||||
|
||||
#vue
|
||||
:::tip
|
||||
You can add you own dynamic color aliases in your `vite.config.ts`, you just have to make sure to also define them in the [`theme.colors`](/getting-started/installation/vue#themecolors) option of the `ui` plugin.
|
||||
|
||||
```ts [vite.config.ts]
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import ui from '@nuxt/ui/vite'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
ui({
|
||||
ui: {
|
||||
colors: {
|
||||
tertiary: 'indigo'
|
||||
}
|
||||
},
|
||||
theme: {
|
||||
colors: ['primary', 'secondary', 'tertiary', 'info', 'success', 'warning', 'error']
|
||||
}
|
||||
})
|
||||
]
|
||||
})
|
||||
```
|
||||
:::
|
||||
::
|
||||
|
||||
::warning
|
||||
@@ -148,6 +275,9 @@ These color aliases are not automatically defined as Tailwind CSS colors, so cla
|
||||
|
||||
However, you can generate these classes using Tailwind's `@theme` directive, allowing you to use custom color utility classes while maintaining dynamic color aliases:
|
||||
|
||||
::module-only
|
||||
#ui
|
||||
:::div
|
||||
```css [main.css]
|
||||
@import "tailwindcss";
|
||||
@import "@nuxt/ui";
|
||||
@@ -166,6 +296,31 @@ However, you can generate these classes using Tailwind's `@theme` directive, all
|
||||
--color-primary-950: var(--ui-color-primary-950);
|
||||
}
|
||||
```
|
||||
:::
|
||||
|
||||
#ui-pro
|
||||
:::div
|
||||
```css [main.css]
|
||||
@import "tailwindcss";
|
||||
@import "@nuxt/ui-pro";
|
||||
|
||||
@theme {
|
||||
--color-primary-50: var(--ui-color-primary-50);
|
||||
--color-primary-100: var(--ui-color-primary-100);
|
||||
--color-primary-200: var(--ui-color-primary-200);
|
||||
--color-primary-300: var(--ui-color-primary-300);
|
||||
--color-primary-400: var(--ui-color-primary-400);
|
||||
--color-primary-500: var(--ui-color-primary-500);
|
||||
--color-primary-600: var(--ui-color-primary-600);
|
||||
--color-primary-700: var(--ui-color-primary-700);
|
||||
--color-primary-800: var(--ui-color-primary-800);
|
||||
--color-primary-900: var(--ui-color-primary-900);
|
||||
--color-primary-950: var(--ui-color-primary-950);
|
||||
}
|
||||
```
|
||||
:::
|
||||
::
|
||||
|
||||
::
|
||||
|
||||
### Tokens
|
||||
@@ -209,6 +364,9 @@ You can use these variables in classes like `text-[var(--ui-primary)]`, it will
|
||||
::tip
|
||||
You can change which shade is used for each color on light and dark mode:
|
||||
|
||||
::module-only
|
||||
#ui
|
||||
:::div
|
||||
```css [main.css]
|
||||
@import "tailwindcss";
|
||||
@import "@nuxt/ui";
|
||||
@@ -221,6 +379,25 @@ You can change which shade is used for each color on light and dark mode:
|
||||
--ui-primary: var(--ui-color-primary-200);
|
||||
}
|
||||
```
|
||||
:::
|
||||
|
||||
#ui-pro
|
||||
:::div
|
||||
```css [main.css]
|
||||
@import "tailwindcss";
|
||||
@import "@nuxt/ui-pro";
|
||||
|
||||
:root {
|
||||
--ui-primary: var(--ui-color-primary-700);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--ui-primary: var(--ui-color-primary-200);
|
||||
}
|
||||
```
|
||||
:::
|
||||
::
|
||||
|
||||
::
|
||||
|
||||
#### Neutral Palette
|
||||
@@ -314,6 +491,9 @@ body {
|
||||
::tip
|
||||
You can customize these CSS variables to tailor the appearance of your application:
|
||||
|
||||
::module-only
|
||||
#ui
|
||||
:::div
|
||||
```css [main.css]
|
||||
@import "tailwindcss";
|
||||
@import "@nuxt/ui";
|
||||
@@ -328,6 +508,27 @@ You can customize these CSS variables to tailor the appearance of your applicati
|
||||
--ui-border: var(--ui-color-neutral-900);
|
||||
}
|
||||
```
|
||||
:::
|
||||
|
||||
#ui-pro
|
||||
:::div
|
||||
```css [main.css]
|
||||
@import "tailwindcss";
|
||||
@import "@nuxt/ui-pro";
|
||||
|
||||
:root {
|
||||
--ui-bg: var(--ui-color-neutral-50);
|
||||
--ui-text: var(--ui-color-neutral-900);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--ui-bg: var(--ui-color-neutral-950);
|
||||
--ui-border: var(--ui-color-neutral-900);
|
||||
}
|
||||
```
|
||||
:::
|
||||
::
|
||||
|
||||
::
|
||||
|
||||
#### Border Radius
|
||||
@@ -347,6 +548,9 @@ Try the :prose-icon{name="i-lucide-swatch-book" class="text-[var(--ui-primary)]"
|
||||
::tip
|
||||
You can customize the default radius value using the default Tailwind CSS variables or a value of your choice:
|
||||
|
||||
::module-only
|
||||
#ui
|
||||
:::div
|
||||
```css [main.css]
|
||||
@import "tailwindcss";
|
||||
@import "@nuxt/ui";
|
||||
@@ -355,6 +559,21 @@ You can customize the default radius value using the default Tailwind CSS variab
|
||||
--ui-radius: var(--radius-sm);
|
||||
}
|
||||
```
|
||||
:::
|
||||
|
||||
#ui-pro
|
||||
:::div
|
||||
```css [main.css]
|
||||
@import "tailwindcss";
|
||||
@import "@nuxt/ui-pro";
|
||||
|
||||
:root {
|
||||
--ui-radius: var(--radius-sm);
|
||||
}
|
||||
```
|
||||
:::
|
||||
::
|
||||
|
||||
::
|
||||
|
||||
#### Container
|
||||
@@ -370,6 +589,9 @@ Nuxt UI uses a global `--ui-container` CSS variable to define the width of the c
|
||||
::tip
|
||||
You can customize the default container width using the default Tailwind CSS variables or a value of your choice:
|
||||
|
||||
::module-only
|
||||
#ui
|
||||
:::div
|
||||
```css [main.css]
|
||||
@import "tailwindcss";
|
||||
@import "@nuxt/ui";
|
||||
@@ -382,6 +604,25 @@ You can customize the default container width using the default Tailwind CSS var
|
||||
--ui-container: var(--container-8xl);
|
||||
}
|
||||
```
|
||||
:::
|
||||
|
||||
#ui-pro
|
||||
:::div
|
||||
```css [main.css]
|
||||
@import "tailwindcss";
|
||||
@import "@nuxt/ui-pro";
|
||||
|
||||
@theme {
|
||||
--container-8xl: 90rem;
|
||||
}
|
||||
|
||||
:root {
|
||||
--ui-container: var(--container-8xl);
|
||||
}
|
||||
```
|
||||
:::
|
||||
::
|
||||
|
||||
::
|
||||
|
||||
## Components theme
|
||||
@@ -490,26 +731,41 @@ props:
|
||||
---
|
||||
::
|
||||
|
||||
The `defaultVariants` property specifies the default values for each variant. It determines how a component looks and behaves when no prop is provided. These default values can be customized in your [`app.config.ts`](#appconfigts) to adjust the standard appearance of components throughout your application.
|
||||
The `defaultVariants` property specifies the default values for each variant. It determines how a component looks and behaves when no prop is provided.
|
||||
|
||||
::framework-only
|
||||
#nuxt
|
||||
:::tip
|
||||
These default values can be customized in your [`app.config.ts`](#config) to adjust the standard appearance of components throughout your application.
|
||||
:::
|
||||
|
||||
#vue
|
||||
:::tip
|
||||
These default values can be customized in your [`vite.config.ts`](#config) to adjust the standard appearance of components throughout your application.
|
||||
:::
|
||||
::
|
||||
|
||||
## Customize theme
|
||||
|
||||
You have multiple ways to customize the appearance of Nuxt UI components, you can do it for all components at once or on a per-component basis.
|
||||
|
||||
::note
|
||||
Tailwind Variants uses [tailwind-merge](https://github.com/dcastil/tailwind-merge) under the hood to merge classes so you don't have to worry about conflicting classes.
|
||||
Tailwind Variants uses [`tailwind-merge`](https://github.com/dcastil/tailwind-merge) under the hood to merge classes so you don't have to worry about conflicting classes.
|
||||
::
|
||||
|
||||
::tip
|
||||
You can explore the theme for each component in two ways:
|
||||
|
||||
- Check the `Theme` section in the documentation of each individual component.
|
||||
- Browse the source code directly in the GitHub repository at https://github.com/nuxt/ui/tree/v3/src/theme.
|
||||
- Browse the source code directly in the GitHub repository at [`v3/src/theme`](https://github.com/nuxt/ui/tree/v3/src/theme).
|
||||
::
|
||||
|
||||
### `app.config.ts`
|
||||
### Config
|
||||
|
||||
You can override the theme of components inside your `app.config.ts` by using the exact same structure as the theme object.
|
||||
::framework-only
|
||||
#nuxt
|
||||
::div
|
||||
You can override the theme of components globally inside your `app.config.ts` by using the exact same structure as the theme object.
|
||||
|
||||
Let's say you want to change the font weight of all your buttons, you can do it like this:
|
||||
|
||||
@@ -524,14 +780,47 @@ export default defineAppConfig({
|
||||
}
|
||||
})
|
||||
```
|
||||
::
|
||||
|
||||
#vue
|
||||
::div
|
||||
You can override the theme of components globally inside your `vite.config.ts` by using the exact same structure as the theme object.
|
||||
|
||||
Let's say you want to change the font weight of all your buttons, you can do it like this:
|
||||
|
||||
```ts [vite.config.ts]
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import ui from '@nuxt/ui/vite'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
ui({
|
||||
ui: {
|
||||
button: {
|
||||
slots: {
|
||||
base: 'font-bold'
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
]
|
||||
})
|
||||
```
|
||||
::
|
||||
|
||||
::
|
||||
|
||||
::note
|
||||
In this example, the `font-bold` class will override the default `font-medium` class on all buttons.
|
||||
::
|
||||
|
||||
### `ui` prop
|
||||
### Props
|
||||
|
||||
You can also override a component's **slots** using the `ui` prop. This has priority over the `app.config.ts` configuration and `variants` resolution.
|
||||
#### `ui` prop
|
||||
|
||||
You can also override a component's **slots** using the `ui` prop. This has priority over the global configuration and `variants` resolution.
|
||||
|
||||
::component-code{slug="button"}
|
||||
---
|
||||
@@ -560,9 +849,9 @@ slots:
|
||||
In this example, the `trailingIcon` slot is overwritten with `size-3` even though the `md` size variant would apply a `size-5` class to it.
|
||||
::
|
||||
|
||||
### `class` prop
|
||||
#### `class` prop
|
||||
|
||||
The `class` prop allows you to override the classes of the `root` or `base` slot. This has priority over the `app.config.ts` configuration and `variants` resolution.
|
||||
The `class` prop allows you to override the classes of the `root` or `base` slot. This has priority over the global configuration and `variants` resolution.
|
||||
|
||||
::component-code{slug="button"}
|
||||
---
|
||||
|
||||
1
docs/content/1.getting-started/4.icons/.navigation.yml
Normal file
1
docs/content/1.getting-started/4.icons/.navigation.yml
Normal file
@@ -0,0 +1 @@
|
||||
shadow: true
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
title: Icons
|
||||
description: 'Nuxt UI integrates with Nuxt Icon to access over 200,000+ icons from Iconify.'
|
||||
framework: nuxt
|
||||
links:
|
||||
- label: 'Iconify'
|
||||
to: https://iconify.design/
|
||||
@@ -12,14 +13,14 @@ links:
|
||||
icon: i-simple-icons-github
|
||||
---
|
||||
|
||||
::callout{to="/getting-started/icons/vue" icon="i-logos-vue" class="hidden"}
|
||||
Looking for the **Vue** version?
|
||||
::
|
||||
|
||||
## Usage
|
||||
|
||||
Nuxt UI automatically registers the [`@nuxt/icon`](https://github.com/nuxt/icon) module for you, so there's no additional setup required.
|
||||
|
||||
::note
|
||||
You can use any name from the https://icones.js.org collection.
|
||||
::
|
||||
|
||||
### Icon Component
|
||||
|
||||
You can use the [Icon](/components/icon) component with a `name` prop to display an icon:
|
||||
@@ -32,6 +33,10 @@ props:
|
||||
---
|
||||
::
|
||||
|
||||
::note
|
||||
You can use any name from the https://icones.js.org collection.
|
||||
::
|
||||
|
||||
### Component Props
|
||||
|
||||
Some components also have an `icon` prop to display an icon, like the [Button](/components/button) for example:
|
||||
@@ -119,6 +124,6 @@ Read more about this in the `@nuxt/icon` documentation.
|
||||
|
||||
## Theme
|
||||
|
||||
You can change the default icons used by Nuxt UI components in your `app.config.ts`:
|
||||
You can change the default icons used by components in your `app.config.ts`:
|
||||
|
||||
:icons-theme
|
||||
55
docs/content/1.getting-started/4.icons/2.vue.md
Normal file
55
docs/content/1.getting-started/4.icons/2.vue.md
Normal file
@@ -0,0 +1,55 @@
|
||||
---
|
||||
title: Icons
|
||||
description: 'Nuxt UI integrates with Iconify to access over 200,000+ icons.'
|
||||
framework: vue
|
||||
links:
|
||||
- label: 'Iconify'
|
||||
to: https://iconify.design/
|
||||
target: _blank
|
||||
icon: i-simple-icons-iconify
|
||||
---
|
||||
|
||||
::callout{to="/getting-started/icons/nuxt" icon="i-logos-nuxt-icon" class="hidden"}
|
||||
Looking for the **Nuxt** version?
|
||||
::
|
||||
|
||||
## Usage
|
||||
|
||||
### Icon Component
|
||||
|
||||
You can use the [Icon](/components/icon) component with a `name` prop to display an icon:
|
||||
|
||||
::component-code{slug="icon"}
|
||||
---
|
||||
props:
|
||||
name: 'i-lucide-lightbulb'
|
||||
class: 'size-5'
|
||||
---
|
||||
::
|
||||
|
||||
::note
|
||||
You can use any name from the https://icones.js.org collection.
|
||||
::
|
||||
|
||||
### Component Props
|
||||
|
||||
Some components also have an `icon` prop to display an icon, like the [Button](/components/button) for example:
|
||||
|
||||
::component-code{slug="button"}
|
||||
---
|
||||
ignore:
|
||||
- color
|
||||
- variant
|
||||
props:
|
||||
icon: i-lucide-sun
|
||||
variant: subtle
|
||||
slots:
|
||||
default: Button
|
||||
---
|
||||
::
|
||||
|
||||
## Theme
|
||||
|
||||
You can change the default icons used by Nuxt UI components in your `vite.config.ts`:
|
||||
|
||||
:icons-theme
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
title: Fonts
|
||||
description: 'Nuxt UI integrates with Nuxt Fonts to provide plug-and-play font optimization.'
|
||||
framework: nuxt
|
||||
links:
|
||||
- label: 'nuxt/fonts'
|
||||
to: https://github.com/nuxt/fonts
|
||||
@@ -10,19 +11,40 @@ links:
|
||||
|
||||
## Usage
|
||||
|
||||
Nuxt UI automatically registers the [`@nuxt/fonts`](https://github.com/nuxt/fonts) module for you, so there's no additional setup required. To use a font in your Nuxt UI application, you can simply declare it in your CSS:
|
||||
Nuxt UI automatically registers the [`@nuxt/fonts`](https://github.com/nuxt/fonts) module for you, so there's no additional setup required. To use a font in your Nuxt UI application, you can simply declare it in your CSS. It will be automatically loaded and optimized for you.
|
||||
|
||||
::module-only
|
||||
#ui
|
||||
:::div
|
||||
```css [main.css]
|
||||
@import "tailwindcss";
|
||||
@import "@nuxt/ui";
|
||||
|
||||
@theme {
|
||||
--font-family-sans: 'Public Sans', sans-serif;
|
||||
--font-sans: 'Public Sans', sans-serif;
|
||||
}
|
||||
```
|
||||
:::
|
||||
|
||||
That's it! Nuxt Fonts will detect this and you should immediately see the web font loaded in your browser.
|
||||
#ui-pro
|
||||
:::div
|
||||
```css [main.css]
|
||||
@import "tailwindcss";
|
||||
@import "@nuxt/ui-pro";
|
||||
|
||||
::note{to="https://fonts.nuxt.com/advanced" target="_blank"}
|
||||
Read more about how `@nuxt/fonts` work behind the scenes to optimize your fonts.
|
||||
@theme {
|
||||
--font-sans: 'Public Sans', sans-serif;
|
||||
}
|
||||
```
|
||||
:::
|
||||
::
|
||||
|
||||
You can disable the `@nuxt/fonts` module with the `ui.fonts` option in your `nuxt.config.ts`:
|
||||
|
||||
```ts [nuxt.config.ts]
|
||||
export default defineNuxtConfig({
|
||||
ui: {
|
||||
fonts: false
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
---
|
||||
title: Color Mode
|
||||
description: 'Nuxt UI integrates with Nuxt Color Mode to allow for easy switching between light and dark themes.'
|
||||
links:
|
||||
- label: 'nuxtjs/color-mode'
|
||||
to: https://github.com/nuxt-modules/color-mode
|
||||
target: _blank
|
||||
icon: i-simple-icons-github
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
Nuxt UI automatically registers the [`@nuxtjs/color-mode`](https://github.com/nuxt-modules/color-mode) module for you, so there's no additional setup required.
|
||||
|
||||
You can disable dark mode by setting the `preference` to `light` instead of `system` in your `nuxt.config.ts`.
|
||||
|
||||
```ts [nuxt.config.ts]
|
||||
export default defineNuxtConfig({
|
||||
colorMode: {
|
||||
preference: 'light'
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
::tip
|
||||
If you're stuck in dark mode even after changing this setting, you might need to remove the `nuxt-color-mode` entry from your browser's local storage.
|
||||
::
|
||||
@@ -0,0 +1 @@
|
||||
shadow: true
|
||||
58
docs/content/1.getting-started/6.color-mode/1.nuxt.md
Normal file
58
docs/content/1.getting-started/6.color-mode/1.nuxt.md
Normal file
@@ -0,0 +1,58 @@
|
||||
---
|
||||
title: Color Mode
|
||||
description: 'Nuxt UI integrates with Nuxt Color Mode to allow for easy switching between light and dark themes.'
|
||||
framework: nuxt
|
||||
links:
|
||||
- label: 'nuxtjs/color-mode'
|
||||
to: https://github.com/nuxt-modules/color-mode
|
||||
target: _blank
|
||||
icon: i-simple-icons-github
|
||||
---
|
||||
|
||||
::callout{to="/getting-started/color-mode/vue" icon="i-logos-vue" class="hidden"}
|
||||
Looking for the **Vue** version?
|
||||
::
|
||||
|
||||
## Usage
|
||||
|
||||
Nuxt UI automatically registers the [`@nuxtjs/color-mode`](https://github.com/nuxt-modules/color-mode) module for you, so there's no additional setup required. You can simply use the `useColorMode` composable to switch between light and dark modes:
|
||||
|
||||
```vue [ColorModeButton.vue]
|
||||
<script setup>
|
||||
const colorMode = useColorMode()
|
||||
|
||||
const isDark = computed({
|
||||
get() {
|
||||
return colorMode.value === 'dark'
|
||||
},
|
||||
set() {
|
||||
colorMode.preference = colorMode.value === 'dark' ? 'light' : 'dark'
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ClientOnly v-if="!colorMode?.forced">
|
||||
<UButton
|
||||
:icon="isDark ? 'i-lucide-moon' : 'i-lucide-sun'"
|
||||
color="neutral"
|
||||
variant="ghost"
|
||||
@click="isDark = !isDark"
|
||||
/>
|
||||
|
||||
<template #fallback>
|
||||
<div class="size-8" />
|
||||
</template>
|
||||
</ClientOnly>
|
||||
</template>
|
||||
```
|
||||
|
||||
You can disable the `@nuxtjs/color-mode` module with the `ui.colorMode` option in your `nuxt.config.ts`:
|
||||
|
||||
```ts [nuxt.config.ts]
|
||||
export default defineNuxtConfig({
|
||||
ui: {
|
||||
colorMode: false
|
||||
}
|
||||
})
|
||||
```
|
||||
47
docs/content/1.getting-started/6.color-mode/2.vue.md
Normal file
47
docs/content/1.getting-started/6.color-mode/2.vue.md
Normal file
@@ -0,0 +1,47 @@
|
||||
---
|
||||
title: Color Mode
|
||||
description: 'Nuxt UI integrates with VueUse to allow for easy switching between light and dark themes.'
|
||||
framework: vue
|
||||
---
|
||||
|
||||
::callout{to="/getting-started/color-mode/nuxt" icon="i-logos-nuxt-icon" class="hidden"}
|
||||
Looking for the **Nuxt** version?
|
||||
::
|
||||
|
||||
## Usage
|
||||
|
||||
Nuxt UI automatically registers the [useDark](https://vueuse.org/core/useDark) composable as a Vue plugin, so there's no additional setup required. You can simply use it to switch between light and dark modes:
|
||||
|
||||
```vue [ColorModeButton.vue]
|
||||
<script setup>
|
||||
import { useColorMode } from '@vueuse/core'
|
||||
|
||||
const mode = useColorMode()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UButton
|
||||
:icon="mode === 'dark' ? 'i-lucide-moon' : 'i-lucide-sun'"
|
||||
color="neutral"
|
||||
variant="ghost"
|
||||
@click="mode = mode === 'dark' ? 'light' : 'dark'"
|
||||
/>
|
||||
</template>
|
||||
```
|
||||
|
||||
You can disable this plugin with the `colorMode` option in your `vite.config.ts`:
|
||||
|
||||
```ts [vite.config.ts]
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import ui from '@nuxt/ui/vite'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
ui({
|
||||
colorMode: false
|
||||
})
|
||||
]
|
||||
})
|
||||
```
|
||||
2
docs/content/1.getting-started/7.i18n/.navigation.yml
Normal file
2
docs/content/1.getting-started/7.i18n/.navigation.yml
Normal file
@@ -0,0 +1,2 @@
|
||||
badge: New
|
||||
shadow: true
|
||||
@@ -1,22 +1,20 @@
|
||||
---
|
||||
navigation.title: Nuxt
|
||||
title: Internationalization (i18n) in a Nuxt app
|
||||
description: 'Learn how to internationalize your Nuxt app and support multi-directional support (LTR/RTL).'
|
||||
select:
|
||||
items:
|
||||
- label: Nuxt
|
||||
icon: i-logos-nuxt-icon
|
||||
to: /getting-started/i18n/nuxt
|
||||
- label: Vue
|
||||
icon: i-logos-vue
|
||||
to: /getting-started/i18n/vue
|
||||
title: Internationalization (i18n)
|
||||
description: 'Learn how to internationalize your Nuxt app with multi-directional support (LTR/RTL).'
|
||||
framework: nuxt
|
||||
---
|
||||
|
||||
::note{to="/components/app"}
|
||||
Nuxt UI provides an [App](/components/app) component that wraps your app to provide global configurations.
|
||||
::callout{to="/getting-started/i18n/vue" icon="i-logos-vue" class="hidden"}
|
||||
Looking for the **Vue** version?
|
||||
::
|
||||
|
||||
## Locale
|
||||
## Usage
|
||||
|
||||
::note{to="/components/app"}
|
||||
Nuxt UI provides an **App** component that wraps your app to provide global configurations.
|
||||
::
|
||||
|
||||
### Locale
|
||||
|
||||
Use the `locale` prop with the locale you want to use from `@nuxt/ui/locale`:
|
||||
|
||||
@@ -38,8 +36,13 @@ You also have the option to add your own locale using `defineLocale`:
|
||||
|
||||
```vue [app.vue]
|
||||
<script setup lang="ts">
|
||||
const locale = defineLocale('My custom locale', 'en', {
|
||||
// implement pairs
|
||||
const locale = defineLocale({
|
||||
name: 'My custom locale',
|
||||
code: 'en',
|
||||
dir: 'ltr',
|
||||
messages: {
|
||||
// implement pairs
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -51,7 +54,7 @@ const locale = defineLocale('My custom locale', 'en', {
|
||||
```
|
||||
|
||||
::tip
|
||||
Look at the second parameter, there you need to pass the iso code of the language. Example:
|
||||
Look at the `code` parameter, there you need to pass the iso code of the language. Example:
|
||||
* `hi` Hindi (language)
|
||||
* `de-AT`: German (language) as used in Austria (region)
|
||||
::
|
||||
@@ -125,65 +128,36 @@ const { locale } = useI18n()
|
||||
|
||||
::
|
||||
|
||||
### Supported languages
|
||||
|
||||
:supported-languages
|
||||
|
||||
## Direction
|
||||
|
||||
Use the `dir` prop with `ltr` or `rtl` to set the global reading direction of your app:
|
||||
|
||||
```vue [app.vue]
|
||||
<template>
|
||||
<UApp dir="rtl">
|
||||
<NuxtPage />
|
||||
</UApp>
|
||||
</template>
|
||||
```
|
||||
|
||||
### Dynamic direction
|
||||
|
||||
To dynamically change the global reading direction of your app, you can use VueUse's [useTextDirection](https://vueuse.org/core/useTextDirection/) composable to detect and switch between LTR and RTL text directions.
|
||||
Each locale has a `dir` property which will be used by the `App` component to set the directionality of all components.
|
||||
|
||||
::steps{level="4"}
|
||||
|
||||
#### Install the `@vueuse/core` package
|
||||
|
||||
::code-group{sync="pm"}
|
||||
|
||||
```bash [pnpm]
|
||||
pnpm add @vueuse/core
|
||||
```
|
||||
|
||||
```bash [yarn]
|
||||
yarn add @vueuse/core
|
||||
```
|
||||
|
||||
```bash [npm]
|
||||
npm install @vueuse/core
|
||||
```
|
||||
|
||||
```bash [bun]
|
||||
bun add @vueuse/core
|
||||
```
|
||||
|
||||
::
|
||||
|
||||
#### Set the `dir` prop using `useTextDirection`
|
||||
In a multilingual application, you might want to set the `lang` and `dir` attributes on the `<html>` element dynamically based on the user's locale, which you can do with the [useHead](https://nuxt.com/docs/api/composables/use-head) composable:
|
||||
|
||||
```vue [app.vue]
|
||||
<script setup lang="ts">
|
||||
import { useTextDirection } from '@vueuse/core'
|
||||
import * as locales from '@nuxt/ui/locale'
|
||||
|
||||
const textDirection = useTextDirection({ initialValue: 'ltr' })
|
||||
const dir = computed(() => textDirection.value === 'rtl' ? 'rtl' : 'ltr')
|
||||
const { locale } = useI18n()
|
||||
|
||||
const lang = computed(() => locales[locale.value].code)
|
||||
const dir = computed(() => locales[locale.value].dir)
|
||||
|
||||
useHead({
|
||||
htmlAttrs: {
|
||||
lang,
|
||||
dir
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UApp :dir="dir">
|
||||
<UApp :locale="locales[locale]">
|
||||
<NuxtPage />
|
||||
</UApp>
|
||||
</template>
|
||||
```
|
||||
|
||||
::
|
||||
## Supported languages
|
||||
|
||||
:supported-languages
|
||||
|
||||
@@ -1,22 +1,20 @@
|
||||
---
|
||||
navigation.title: Vue
|
||||
title: Internationalization (i18n) in a Vue app
|
||||
description: 'Learn how to internationalize your Vue app and support multi-directional support (LTR/RTL).'
|
||||
select:
|
||||
items:
|
||||
- label: Nuxt
|
||||
icon: i-logos-nuxt-icon
|
||||
to: /getting-started/i18n/nuxt
|
||||
- label: Vue
|
||||
icon: i-logos-vue
|
||||
to: /getting-started/i18n/vue
|
||||
title: Internationalization (i18n)
|
||||
description: 'Learn how to internationalize your Vue app with multi-directional support (LTR/RTL).'
|
||||
framework: vue
|
||||
---
|
||||
|
||||
::note{to="/components/app"}
|
||||
Nuxt UI provides an [App](/components/app) component that wraps your app to provide global configurations.
|
||||
::callout{to="/getting-started/i18n/nuxt" icon="i-logos-nuxt-icon" class="hidden"}
|
||||
Looking for the **Nuxt** version?
|
||||
::
|
||||
|
||||
## Locale
|
||||
## Usage
|
||||
|
||||
::note{to="/components/app"}
|
||||
Nuxt UI provides an **App** component that wraps your app to provide global configurations.
|
||||
::
|
||||
|
||||
### Locale
|
||||
|
||||
Use the `locale` prop with the locale you want to use from `@nuxt/ui/locale`:
|
||||
|
||||
@@ -40,8 +38,13 @@ You also have the option to add your locale using `defineLocale`:
|
||||
<script setup lang="ts">
|
||||
import { defineLocale } from '@nuxt/ui/runtime/composables/defineLocale'
|
||||
|
||||
const locale = defineLocale('My custom locale', 'en', {
|
||||
// implement pairs
|
||||
const locale = defineLocale({
|
||||
name: 'My custom locale',
|
||||
code: 'en',
|
||||
dir: 'ltr',
|
||||
messages: {
|
||||
// implement pairs
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -53,7 +56,7 @@ const locale = defineLocale('My custom locale', 'en', {
|
||||
```
|
||||
|
||||
::tip
|
||||
Look at the second parameter, there you need to pass the iso code of the language. Example:
|
||||
Look at the `code` parameter, there you need to pass the iso code of the language. Example:
|
||||
* `hi` Hindi (language)
|
||||
* `de-AT`: German (language) as used in Austria (region)
|
||||
::
|
||||
@@ -135,64 +138,39 @@ const { locale } = useI18n()
|
||||
|
||||
::
|
||||
|
||||
## Supported languages
|
||||
|
||||
:supported-languages
|
||||
|
||||
## Direction
|
||||
|
||||
Use the `dir` prop with `ltr` or `rtl` to set the global reading direction of your app:
|
||||
|
||||
```vue [App.vue]
|
||||
<template>
|
||||
<UApp dir="rtl">
|
||||
<NuxtPage />
|
||||
</UApp>
|
||||
</template>
|
||||
```
|
||||
|
||||
### Dynamic direction
|
||||
|
||||
To dynamically change the global reading direction of your app, you can use VueUse's [useTextDirection](https://vueuse.org/core/useTextDirection/) composable to detect and switch between LTR and RTL text directions.
|
||||
Each locale has a `dir` property which will be used by the `App` component to set the directionality of all components.
|
||||
|
||||
::steps{level="4"}
|
||||
|
||||
#### Install the `@vueuse/core` package
|
||||
|
||||
::code-group{sync="pm"}
|
||||
|
||||
```bash [pnpm]
|
||||
pnpm add @vueuse/core
|
||||
```
|
||||
|
||||
```bash [yarn]
|
||||
yarn add @vueuse/core
|
||||
```
|
||||
|
||||
```bash [npm]
|
||||
npm install @vueuse/core
|
||||
```
|
||||
|
||||
```bash [bun]
|
||||
bun add @vueuse/core
|
||||
```
|
||||
|
||||
::
|
||||
|
||||
#### Set the `dir` prop using `useTextDirection`
|
||||
In a multilingual application, you might want to set the `lang` and `dir` attributes on the `<html>` element dynamically based on the user's locale, which you can do with the [useHead](https://unhead.unjs.io/usage/composables/use-head) composable:
|
||||
|
||||
```vue [App.vue]
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useTextDirection } from '@vueuse/core'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useHead } from '@unhead/vue'
|
||||
import * as locales from '@nuxt/ui/locale'
|
||||
|
||||
const textDirection = useTextDirection()
|
||||
const dir = computed(() => textDirection.value === 'rtl' ? 'rtl' : 'ltr')
|
||||
const { locale } = useI18n()
|
||||
|
||||
const lang = computed(() => locales[locale.value].code)
|
||||
const dir = computed(() => locales[locale.value].dir)
|
||||
|
||||
useHead({
|
||||
htmlAttrs: {
|
||||
lang,
|
||||
dir
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UApp :dir="dir">
|
||||
<UApp :locale="locales[locale]">
|
||||
<RouterView />
|
||||
</UApp>
|
||||
</template>
|
||||
```
|
||||
|
||||
## Supported languages
|
||||
|
||||
:supported-languages
|
||||
|
||||
@@ -18,7 +18,7 @@ const toast = useToast()
|
||||
- When removing a toast, there's a 200ms delay before it's actually removed from the state, allowing for exit animations.
|
||||
|
||||
::warning
|
||||
Make sure to wrap your app with the [App](/components/app) component which uses our [Toaster](https://github.com/nuxt/ui/blob/v3/src/runtime/components/Toaster.vue) component which uses the [ToastProvider](https://www.radix-vue.com/components/toast.html#provider) component from Radix Vue.
|
||||
Make sure to wrap your app with the [`App`](/components/app) component which uses our [`Toaster`](https://github.com/nuxt/ui/blob/v3/src/runtime/components/Toaster.vue) component which uses the [`ToastProvider`](https://reka-ui.com/docs/components/toast#provider) component from Reka UI.
|
||||
::
|
||||
|
||||
::tip{to="/components/toast"}
|
||||
|
||||
@@ -9,13 +9,13 @@ links:
|
||||
|
||||
## Usage
|
||||
|
||||
This component implements Radix Vue [ConfigProvider](https://www.radix-vue.com/utilities/config-provider.html) to provide global configuration to all components:
|
||||
This component implements Reka UI [ConfigProvider](https://reka-ui.com/docs/utilities/config-provider) to provide global configuration to all components:
|
||||
|
||||
- Enables all primitives to inherit global reading direction.
|
||||
- Enables changing the behavior of scroll body when setting body lock.
|
||||
- Much more controls to prevent layout shifts.
|
||||
|
||||
It's also using [ToastProvider](https://www.radix-vue.com/components/toast.html#provider) and [TooltipProvider](https://www.radix-vue.com/components/tooltip.html#provider) to provide global toasts and tooltips, as well as programmatic modals and slideovers.
|
||||
It's also using [ToastProvider](https://reka-ui.com/docs/components/toast#provider) and [TooltipProvider](https://reka-ui.com/docs/components/tooltip#provider) to provide global toasts and tooltips, as well as programmatic modals and slideovers.
|
||||
|
||||
Use it as at the root of your app:
|
||||
|
||||
@@ -27,12 +27,16 @@ Use it as at the root of your app:
|
||||
</template>
|
||||
```
|
||||
|
||||
::tip{to="/getting-started/i18n/nuxt#locale"}
|
||||
::framework-only
|
||||
#nuxt
|
||||
:::tip{to="/getting-started/i18n/nuxt#locale"}
|
||||
Learn how to use the `locale` prop to change the locale of your app.
|
||||
::
|
||||
:::
|
||||
|
||||
::tip{to="/getting-started/i18n/nuxt#direction"}
|
||||
Learn how to use the `dir` prop to change the global reading direction of your app.
|
||||
#vue
|
||||
:::tip{to="/getting-started/i18n/vue#locale"}
|
||||
Learn how to use the `locale` prop to change the locale of your app.
|
||||
:::
|
||||
::
|
||||
|
||||
## API
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
description: A stacked set of collapsible panels.
|
||||
links:
|
||||
- label: Accordion
|
||||
icon: i-custom-radix-vue
|
||||
to: https://www.radix-vue.com/components/accordion.html
|
||||
icon: i-custom-reka-ui
|
||||
to: https://reka-ui.com/docs/components/accordion
|
||||
- label: GitHub
|
||||
icon: i-simple-icons-github
|
||||
to: https://github.com/nuxt/ui/tree/v3/src/runtime/components/Accordion.vue
|
||||
@@ -104,6 +104,38 @@ props:
|
||||
---
|
||||
::
|
||||
|
||||
### Unmount
|
||||
|
||||
Use the `unmount-on-hide` prop to prevent the content from being unmounted when the accordion is collapsed. Defaults to `true`.
|
||||
|
||||
::component-code
|
||||
---
|
||||
ignore:
|
||||
- items
|
||||
external:
|
||||
- items
|
||||
hide:
|
||||
- class
|
||||
props:
|
||||
class: 'px-4'
|
||||
unmountOnHide: false
|
||||
items:
|
||||
- label: 'Icons'
|
||||
icon: 'i-lucide-smile'
|
||||
content: 'You have nothing to do, @nuxt/icon will handle it automatically.'
|
||||
- label: 'Colors'
|
||||
icon: 'i-lucide-swatch-book'
|
||||
content: 'Choose a primary and a neutral color from your Tailwind CSS theme.'
|
||||
- label: 'Components'
|
||||
icon: 'i-lucide-box'
|
||||
content: 'You can customize components by using the `class` / `ui` props or in your app.config.ts.'
|
||||
---
|
||||
::
|
||||
|
||||
::note
|
||||
You can inspect the DOM to see each item's content being rendered.
|
||||
::
|
||||
|
||||
### Disabled
|
||||
|
||||
Use the `disabled` property to disable the Accordion.
|
||||
@@ -168,8 +200,16 @@ props:
|
||||
---
|
||||
::
|
||||
|
||||
::tip{to="/getting-started/icons#theme"}
|
||||
::framework-only
|
||||
#nuxt
|
||||
:::tip{to="/getting-started/icons/nuxt#theme"}
|
||||
You can customize this icon globally in your `app.config.ts` under `ui.icons.chevronDown` key.
|
||||
:::
|
||||
|
||||
#vue
|
||||
:::tip{to="/getting-started/icons/vue#theme"}
|
||||
You can customize this icon globally in your `vite.config.ts` under `ui.icons.chevronDown` key.
|
||||
:::
|
||||
::
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -178,8 +178,16 @@ props:
|
||||
---
|
||||
::
|
||||
|
||||
::tip{to="/getting-started/icons#theme"}
|
||||
::framework-only
|
||||
#nuxt
|
||||
:::tip{to="/getting-started/icons/nuxt#theme"}
|
||||
You can customize this icon globally in your `app.config.ts` under `ui.icons.close` key.
|
||||
:::
|
||||
|
||||
#vue
|
||||
:::tip{to="/getting-started/icons/vue#theme"}
|
||||
You can customize this icon globally in your `vite.config.ts` under `ui.icons.close` key.
|
||||
:::
|
||||
::
|
||||
|
||||
### Actions
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
description: An img element with fallback and Nuxt Image support.
|
||||
links:
|
||||
- label: Avatar
|
||||
icon: i-custom-radix-vue
|
||||
to: https://www.radix-vue.com/components/avatar.html
|
||||
icon: i-custom-reka-ui
|
||||
to: https://reka-ui.com/docs/components/avatar
|
||||
- label: GitHub
|
||||
icon: i-simple-icons-github
|
||||
to: https://github.com/nuxt/ui/tree/v3/src/runtime/components/Avatar.vue
|
||||
|
||||
@@ -67,8 +67,16 @@ props:
|
||||
---
|
||||
::
|
||||
|
||||
::tip{to="/getting-started/icons#theme"}
|
||||
::framework-only
|
||||
#nuxt
|
||||
:::tip{to="/getting-started/icons/nuxt#theme"}
|
||||
You can customize this icon globally in your `app.config.ts` under `ui.icons.chevronRight` key.
|
||||
:::
|
||||
|
||||
#vue
|
||||
:::tip{to="/getting-started/icons/vue#theme"}
|
||||
You can customize this icon globally in your `vite.config.ts` under `ui.icons.chevronRight` key.
|
||||
:::
|
||||
::
|
||||
|
||||
## Examples
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user