Compare commits

...

89 Commits

Author SHA1 Message Date
514e9a24f1 Update showcase.yml 2025-07-21 20:15:42 +02:00
Hugo Richard
9f60443731 docs(modal/slideover): improve programmatic examples (#4556)
Co-authored-by: Eugen Istoc <eugenistoc@gmail.com>
2025-07-21 19:07:55 +02:00
Benjamin Canac
b22891abe6 fix(NavigationMenu/Tabs): display badge when not undefined 2025-07-21 18:29:48 +02:00
Benjamin Canac
9cda333631 docs(installation): add vue isolate section 2025-07-21 18:07:10 +02:00
J-Michalek
62ab01655c feat(Tabs): add badge on items (#4553)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2025-07-21 16:55:27 +02:00
Alex
f33660035f feat(Kbd): add color prop & soft variant (#4549) 2025-07-21 14:22:07 +02:00
renovate[bot]
657ec228b5 chore(deps): update nuxt framework to ^4.0.1 (v3) (#4562)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-21 11:51:01 +02:00
Hugo Richard
e9d515cb85 docs(migration): update form validation error property name (#4557) 2025-07-21 11:24:49 +02:00
renovate[bot]
f32cfeef9e chore(deps): update all non-major dependencies (v3) (#4527)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-21 10:25:24 +02:00
Alex
6b6ec8cb2c fix(ColorPicker): update color conversion logic (#4550) 2025-07-19 19:49:18 +02:00
Benjamin Canac
e2695ee7e4 feat(Drawer): add nested prop
Resolves #4320
2025-07-18 15:55:24 +02:00
Benjamin Canac
cad7c45c08 chore: remove future.compatibilityVersion 2025-07-17 11:20:59 +02:00
Benjamin Canac
5db3b0f98c docs(icons): add app prefix to assets 2025-07-17 10:56:08 +02:00
J-Michalek
6ca7c8b7bf feat(InputMenu): emit remove-tag event (#4511) 2025-07-16 22:28:45 +02:00
kyyy
bb99345f5b fix(RadioGroup): improve type safety for normalizeItem function (#4535)
Co-authored-by: Sandro Circi <sandro.circi@digitoolmedia.com>
2025-07-16 21:58:05 +02:00
Thilo Hettmer
c64c4cdea0 fix(FormField): resolve minor accessibility and rendering issues (#4515)
Co-authored-by: Romain Hamel <rom.hml@gmail.com>
2025-07-16 21:55:57 +02:00
renovate[bot]
8b42365bf4 chore(deps): lock file maintenance (v3) (#4539)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-16 15:37:18 +02:00
Benjamin Canac
cb160e6971 fix(InputMenu): reset search term on mounted
Resolves #3993
2025-07-16 12:52:58 +02:00
Benjamin Canac
4d4234d2f8 fix(InputMenu/SelectMenu): improve display value without valueKey
Resolves #4528
2025-07-16 12:43:38 +02:00
renovate[bot]
6f38d3ea8a chore(deps): update nuxt framework to v4 (v3) (major) (#4532)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2025-07-16 12:09:41 +02:00
Benjamin Canac
1b14b5dcd9 cli(templates): fix component type 2025-07-16 10:22:17 +02:00
Artem Alesenko
7ef19333f0 feat(Table): add support for colspan and rowspan (#4460)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2025-07-14 12:18:17 +02:00
renovate[bot]
d983af93b3 chore(deps): update dependency zod to v4 (v3) (#4523)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2025-07-14 11:58:02 +02:00
J-Michalek
1db21d1b00 feat(Table): add style to table and column meta (#4513)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2025-07-14 10:56:28 +02:00
J-Michalek
6f2ce5c610 refactor(components): unite syntax for emits declaration (#4512) 2025-07-14 10:46:47 +02:00
Benjamin Canac
488707e148 fix(InputMenu/SelectMenu): filter null items in search 2025-07-14 10:40:29 +02:00
J-Michalek
ef473c3848 fix(defineShortcuts): always pass event to shotcut handler (#4516) 2025-07-14 10:34:20 +02:00
renovate[bot]
93dff3264f chore(deps): update all non-major dependencies (v3) (#4479)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-14 10:23:04 +02:00
renovate[bot]
5da9084da3 chore(deps): update nuxt framework to ^3.17.7 (v3) (#4518)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-14 09:58:50 +02:00
Benjamin Canac
c92f908b8d fix(InputMenu/SelectMenu): only filter non-null fields
Resolves #4509
2025-07-12 12:15:29 +02:00
Benjamin Canac
45553dc3fe chore(github): update stale workflow 2025-07-12 12:05:24 +02:00
Benjamin Canac
55e06e97e7 fix(Carousel): improve accessibility
Resolves #4494
2025-07-10 14:18:36 +02:00
Eugen Istoc
a813ea700e test(useOverlay): add composable tests (#4482)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2025-07-10 12:04:18 +02:00
Benjamin Canac
a4d0ca7396 fix(FormField): improve error type with boolean
Resolves #4496
2025-07-10 12:04:06 +02:00
Eugen Istoc
5ad7dabbdc fix(useOverlay): don't use patch when passing props to open (#4497) 2025-07-10 10:45:55 +02:00
Benjamin Canac
d8160ba6ef fix(vue): stub clearError 2025-07-09 17:48:30 +02:00
Alex
fc24e03cc4 fix(Carousel/Tree): add type to button elements for accessibility (#4493) 2025-07-09 17:43:49 +02:00
Benjamin Canac
1902492cf2 docs(pricing): add banner 2025-07-09 16:17:24 +02:00
Benjamin Canac
0c525638d7 chore(deps): update @nuxt/ui-pro 2025-07-09 16:17:09 +02:00
Benjamin Canac
35f90b9920 feat(module): add theme.defaultVariants option (#4400) 2025-07-09 14:37:49 +02:00
Benjamin Canac
836f74849b fix(NavigationMenu/Tabs): proxy fallthrough attributes 2025-07-09 12:58:35 +02:00
Benjamin Canac
78f92a24f8 fix(module): merge user's options when installing modules 2025-07-09 12:24:18 +02:00
Benjamin Canac
52908c19f1 docs(app): update banner 2025-07-08 15:38:00 +02:00
J-Michalek
513cca25f6 docs(input-menu/select-menu/select): add example with full item width (#4419)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2025-07-08 12:43:49 +02:00
Benjamin Canac
c1427a3264 docs(ComponentSlots): hide ui slot prop content 2025-07-08 11:48:05 +02:00
Eugen Istoc
6519a74de4 fix(useOverlay): improve props handling by merging existing and new (#4478) 2025-07-08 10:51:34 +02:00
Benjamin Canac
da05c37ffe docs(input): hide password reveal button in edge
Resolves #4484
2025-07-08 10:39:25 +02:00
Benjamin Canac
ec569e427b feat(Toast): progress bar with Progress component 2025-07-07 17:10:05 +02:00
Benjamin Canac
1d052ec565 fix(Toast): only show progress when open
Resolves #4464
2025-07-07 16:40:59 +02:00
J-Michalek
1ba8a55bcb fix(Carousel): add aria-current attribute to active dot (#4447)
Co-authored-by: Jakub <jakub.michalek@freelo.io>
2025-07-07 12:09:57 +02:00
Hugo Richard
63730d684b feat(CommandPalette): add footer slot (#4457)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2025-07-07 12:01:26 +02:00
renovate[bot]
9ab184cc24 chore(deps): lock file maintenance (v3) (#4472)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-07 11:11:01 +02:00
renovate[bot]
ad0e4ddbf4 chore(deps): update vue-tsc to ^3.0.1 (v3) (#4470)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-07 10:43:27 +02:00
renovate[bot]
6a93556aed chore(deps): update all non-major dependencies (v3) (#4469)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-07 10:29:55 +02:00
TonyWeb
9debce737c fix(Button/Link): merge active-class / inactive-class with app config (#4446)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2025-07-03 16:18:27 +02:00
Sylvain Marroufin
772631cde9 fix(defineShortcuts): allow extra keys to be combined with shift (#4456) 2025-07-03 15:51:50 +02:00
Teages
d7aefa53b2 fix(useOverlay): support infering close argument from complex emits (#4414)
Co-authored-by: Eugen Istoc <eugenistoc@gmail.com>
2025-07-03 10:45:19 +02:00
renovate[bot]
8922c7388e chore(deps): update all non-major dependencies (v3) (#4448)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2025-07-02 17:31:53 +02:00
Igor G
c355cacd43 feat(Table): add footer support to display column summary (#4194)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2025-07-02 16:57:21 +02:00
renovate[bot]
a0e71d9e29 chore(deps): update vue-tsc to v3 (v3) (major) (#4445)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2025-07-02 14:25:37 +02:00
renovate[bot]
127e06ae83 chore(deps): update all non-major dependencies (v3) (#4443)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2025-07-02 11:39:23 +02:00
Benjamin Canac
09c1ed8bf4 chore(renovate): group vue-tsc & vue-component-type-helpers 2025-07-02 11:20:57 +02:00
renovate[bot]
a05102fab3 chore(deps): update dependency reka-ui to v2.3.2 (v3) (#4439)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-02 11:07:08 +02:00
renovate[bot]
09caf44d0d chore(deps): update nuxt framework to ^3.17.6 (v3) (#4437)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-02 10:13:13 +02:00
Estéban
15482aae76 docs(icons): add warning about dash collections in vue (#4307)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2025-07-01 18:00:12 +02:00
Benjamin Canac
f903ec396f feat(Table): add row hover event
Resolves #2435
2025-07-01 16:32:07 +02:00
Benjamin Canac
b00e07f13d feat(Popover): add reference prop 2025-07-01 16:13:31 +02:00
Benjamin Canac
5c573b37b6 docs(prettier): upgrade version 2025-07-01 13:16:38 +02:00
Benjamin Canac
f62c5ec20c feat(Table): add support for context menu
Resolves #4259
2025-07-01 13:15:00 +02:00
Benjamin Canac
b96a1ccbab feat(InputTags): add max-length prop
Resolves #4405
2025-07-01 10:35:24 +02:00
VALERIY SINEVICH
4ce654076c fix(Table): handle reactive columns (#4412) 2025-06-30 15:20:35 +02:00
Romain Hamel
fb9e7bb856 feat(Input/Textarea): add default-value prop (#4404)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2025-06-30 15:04:52 +02:00
Benjamin Canac
69a7b957d5 feat(Tooltip): add reference prop
Resolves #4430
2025-06-30 15:01:59 +02:00
Mike Newbon
3b67d54833 fix(Carousel): resolve plugins with page transitions (#4380)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2025-06-30 14:55:07 +02:00
Benjamin Canac
df8f20232f fix(Button): add active styles to behave like hover on mobile
Resolves #991
2025-06-30 12:35:36 +02:00
J-Michalek
347694b4b5 fix(Table): add scope attribute to headers (#4417) 2025-06-30 12:33:11 +02:00
renovate[bot]
021880328b chore(deps): lock file maintenance (v3) (#4429)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-30 11:39:01 +02:00
renovate[bot]
9c1f423555 chore(deps): lock file maintenance (v3) (#4428)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-30 10:51:22 +02:00
renovate[bot]
6cb737e038 chore(deps): update all non-major dependencies (v3) (#4391)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-30 10:20:52 +02:00
renovate[bot]
231b82fe4c chore(deps): update tailwindcss to ^4.1.11 (v3) (#4425)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-30 10:05:49 +02:00
Benjamin Canac
57a5037b13 fix(vue): handle override when importing from @nuxt/ui 2025-06-26 16:40:35 +02:00
Benjamin Canac
752e2b69bd fix(theme): colors autocomplete in app config 2025-06-26 12:45:57 +02:00
Daniel Roe
6237663a01 chore: update dev:prepare command too (#4399) 2025-06-26 12:20:05 +02:00
Daniel Roe
44cfa00e4d chore: use workspace to run vite commands (#4398) 2025-06-26 10:16:40 +02:00
Benjamin Canac
8cbbab9a6b chore(deps): update @nuxt/ui-pro 2025-06-25 17:48:48 +02:00
Benjamin Canac
2d51e20939 docs(content): update badges 2025-06-25 17:48:37 +02:00
Benjamin Canac
268e29b041 chore(release): v3.2.0 2025-06-25 17:00:05 +02:00
Benjamin Canac
b0364b96b7 fix(SelectMenu): dynamic input size 2025-06-25 16:04:04 +02:00
Benjamin Canac
ba3c6e8788 fix(InputMenu/SelectMenu): dynamic empty size
Resolves #4377
2025-06-25 16:01:04 +02:00
291 changed files with 8729 additions and 4631 deletions

View File

@@ -33,5 +33,5 @@ jobs:
Thank you for your understanding and support!
— Nuxt UI Team
exempt-issue-labels: 'feature,announcement'
operations-per-run: 300
exempt-issue-labels: 'feature,announcement,release,reka-ui,upstream'
operations-per-run: 300

1
.nuxtrc Normal file
View File

@@ -0,0 +1 @@
experimental.normalizeComponentNames=false

View File

@@ -1,5 +1,51 @@
# Changelog
## [3.2.0](https://github.com/nuxt/ui/compare/v3.1.3...v3.2.0) (2025-06-25)
### ⚠ BREAKING CHANGES
* **useOverlay:** correct spelling of `unmount` function (#4051)
### Features
* **Avatar:** add `chip` prop ([#4224](https://github.com/nuxt/ui/issues/4224)) ([03ac395](https://github.com/nuxt/ui/commit/03ac395164c02c964361c68743268b1bc90aae59))
* **Carousel:** allow customization of active dot color ([#4229](https://github.com/nuxt/ui/issues/4229)) ([2ee1c5a](https://github.com/nuxt/ui/commit/2ee1c5ac2e20ab9ce2f4037a8e8c64e561b0428b))
* **CommandPalette:** handle `children` in items ([#4226](https://github.com/nuxt/ui/issues/4226)) ([59c26ec](https://github.com/nuxt/ui/commit/59c26ec1230375a24fbaf8a630a696ae854700c7))
* **extendLocale:** new composable ([0f558fc](https://github.com/nuxt/ui/commit/0f558fc0d014d51549222accfc50286d1770d1aa)), closes [#3729](https://github.com/nuxt/ui/issues/3729)
* **Form:** expose loading state to default slot ([#4247](https://github.com/nuxt/ui/issues/4247)) ([ea0c459](https://github.com/nuxt/ui/commit/ea0c459306be585bacaaf5b433114d072550c824))
* **InputTags:** new component ([#4261](https://github.com/nuxt/ui/issues/4261)) ([54bb228](https://github.com/nuxt/ui/commit/54bb2282c58d3bf5a7dde4cdee687c68efd934a0))
* **locale:** add Luxembourgish language ([#4264](https://github.com/nuxt/ui/issues/4264)) ([43cbb94](https://github.com/nuxt/ui/commit/43cbb94ee25106b414fc8fe979fa65ebaa9ccc76))
* **Modal/Slideover:** add `actions` slot ([#4358](https://github.com/nuxt/ui/issues/4358)) ([8156971](https://github.com/nuxt/ui/commit/81569713e9da9d5531ecdf4614660b84c686fa81))
* **Modal/Slideover:** add `close` method in slots ([#4219](https://github.com/nuxt/ui/issues/4219)) ([5835eb5](https://github.com/nuxt/ui/commit/5835eb5f0f835b5f03646dec78f85b2f556a109b))
* **Select/SelectMenu/Tabs:** expose trigger refs ([7a2bd4e](https://github.com/nuxt/ui/commit/7a2bd4e6179373902ba6f285903ea896fd1d378f)), closes [#4292](https://github.com/nuxt/ui/issues/4292)
* **Select/SelectMenu:** handle dynamic `autofocus` ([1a4de49](https://github.com/nuxt/ui/commit/1a4de49c1665c9ef65279315be0393d6272447b9)), closes [#4324](https://github.com/nuxt/ui/issues/4324)
* **Table:** add `body-top` / `body-bottom` slots ([#4354](https://github.com/nuxt/ui/issues/4354)) ([595fc64](https://github.com/nuxt/ui/commit/595fc64515613fe82c3a56fc5518f2e3fcce6e19))
* **Timeline:** add `reverse` prop ([#4316](https://github.com/nuxt/ui/issues/4316)) ([5170cfd](https://github.com/nuxt/ui/commit/5170cfd7eb44a25c64673cf12979f9ca1049695f))
* **Timeline:** new component ([#4215](https://github.com/nuxt/ui/issues/4215)) ([8017767](https://github.com/nuxt/ui/commit/80177679f2aa0d7f0e39e639a02d527a06e6172c))
### Bug Fixes
* **Card/Drawer/Modal:** prevent scrollbars overflow ([#4368](https://github.com/nuxt/ui/issues/4368)) ([c3adc38](https://github.com/nuxt/ui/commit/c3adc381c90dad7152e27fc303ee678efc7c4c94))
* **components:** remove default `md` size on buttons ([#4357](https://github.com/nuxt/ui/issues/4357)) ([be41aed](https://github.com/nuxt/ui/commit/be41aed1f3d3476801e1840dbb8766926bc93c05))
* **defineShortcuts:** allow `meta_-` shortcut ([#4321](https://github.com/nuxt/ui/issues/4321)) ([4e7c1c9](https://github.com/nuxt/ui/commit/4e7c1c9c305b45dd76d4c238e70a6aeedae78c8b))
* **Form:** conditionally type form data via `transform` prop ([#4188](https://github.com/nuxt/ui/issues/4188)) ([37abcc6](https://github.com/nuxt/ui/commit/37abcc6a5b0a678be626673af5067956657a50d6))
* **Form:** expose reactive fields ([#4386](https://github.com/nuxt/ui/issues/4386)) ([1a8feb7](https://github.com/nuxt/ui/commit/1a8feb751e6827c414ef82fe9fb259ba7dcc7e08))
* **InputMenu/SelectMenu:** dynamic `empty` size ([ba3c6e8](https://github.com/nuxt/ui/commit/ba3c6e8788ed75d86d4406749797da52d7816b84)), closes [#4377](https://github.com/nuxt/ui/issues/4377)
* **InputTags:** extend emits interface ([8781a07](https://github.com/nuxt/ui/commit/8781a079096def0d3bae5b8d896db0df6ce37e23))
* **Modal/Slideover:** don't emit `close:prevent` on `closeAutoFocus` ([150b334](https://github.com/nuxt/ui/commit/150b334b1d242c6dc132193e23359c03e6f35666))
* **NavigationMenu:** nested accordion context at every level ([#4363](https://github.com/nuxt/ui/issues/4363)) ([2fa8db6](https://github.com/nuxt/ui/commit/2fa8db64ddf4c92a19e73774143518d87d001b72))
* **NavigationMenu:** set content `max-height` in `horizontal` orientation ([62bc7b2](https://github.com/nuxt/ui/commit/62bc7b25a2d205d8dffb47a109196f91ff3e823a)), closes [#4208](https://github.com/nuxt/ui/issues/4208)
* **Pagination:** match default button `size` ([#4350](https://github.com/nuxt/ui/issues/4350)) ([4dd56c8](https://github.com/nuxt/ui/commit/4dd56c8111e5a224105b82d541b7742b46abb34a))
* **Select/SelectMenu:** display falsy values ([7df7ee3](https://github.com/nuxt/ui/commit/7df7ee336a925d7ee07f866551dad9350785c9fc))
* **Select/SelectMenu:** prevent empty string display when multiple ([483e473](https://github.com/nuxt/ui/commit/483e473e3f5681cc97c3766ea47283dc95f76345))
* **SelectMenu:** dynamic input size ([b0364b9](https://github.com/nuxt/ui/commit/b0364b96b73b9e543781a35962c03b5a983352c4))
* **Table:** use `tr` as separator ([#4083](https://github.com/nuxt/ui/issues/4083)) ([edca3bc](https://github.com/nuxt/ui/commit/edca3bcb743c7eb63e6abbaa801d3858342a8777))
* **Toast:** calc height on next tick ([3bf5acb](https://github.com/nuxt/ui/commit/3bf5acb683f0ad09735b2417d265d6fcfd901b11)), closes [#4265](https://github.com/nuxt/ui/issues/4265)
* **Toaster:** smoother visibility transition for stacked toasts ([#4367](https://github.com/nuxt/ui/issues/4367)) ([abfd0ed](https://github.com/nuxt/ui/commit/abfd0ede036fa2953f9abc841d77ac71bbd3bba9))
* **useOverlay:** correct spelling of `unmount` function ([#4051](https://github.com/nuxt/ui/issues/4051)) ([546df57](https://github.com/nuxt/ui/commit/546df572fca60325315bed17c9be3367052fb7a9))
* **useOverlay:** set props to original props when `defaultOpen` is set ([#4308](https://github.com/nuxt/ui/issues/4308)) ([66355ba](https://github.com/nuxt/ui/commit/66355ba301d569b9f44527bafc5f8f09bcda63c0))
* **useOverlay:** use original props when not provided to `open` ([#4269](https://github.com/nuxt/ui/issues/4269)) ([bf56e15](https://github.com/nuxt/ui/commit/bf56e15a2eed7d51199d5641649a822e91ca41ba))
## [3.1.3](https://github.com/nuxt/ui/compare/v3.1.2...v3.1.3) (2025-05-26)
### ⚠ BREAKING CHANGES

View File

@@ -35,7 +35,7 @@ ${pro ? `import type { ComponentConfig } from '@nuxt/ui'` : ''}
import theme from '#build/${path}/${prose ? 'prose/' : ''}${content ? 'content/' : ''}${kebabName}'
${!pro ? `import type { ComponentConfig } from '../types/utils'` : ''}
type ${upperName} = ComponentConfig<typeof theme, AppConfig, ${upperName}${pro ? `, '${key}'` : ''}>
type ${upperName} = ComponentConfig<typeof theme, AppConfig, '${camelName}'${pro ? `, '${key}'` : ''}>
export interface ${upperName}Props {
/**
@@ -80,7 +80,7 @@ ${pro ? `import type { ComponentConfig } from '@nuxt/ui'` : ''}
import theme from '#build/${path}/${prose ? 'prose/' : ''}${content ? 'content/' : ''}${kebabName}'
${!pro ? `import type { ComponentConfig } from '../types/utils'` : ''}
type ${upperName} = ComponentConfig<typeof theme, AppConfig, ${upperName}${pro ? `, '${key}'` : ''}>
type ${upperName} = ComponentConfig<typeof theme, AppConfig, '${camelName}'${pro ? `, '${key}'` : ''}>
export interface ${upperName}Props extends Pick<${upperName}RootProps> {
class?: any
@@ -147,7 +147,8 @@ const test = ({ name, prose, content }) => {
? undefined
: `
import { describe, it, expect } from 'vitest'
import ${upperName}, { type ${upperName}Props, type ${upperName}Slots } from '../../${content ? '../' : ''}src/runtime/components/${content ? 'content/' : ''}${upperName}.vue'
import ${upperName} from '../../${content ? '../' : ''}src/runtime/components/${content ? 'content/' : ''}${upperName}.vue'
import type { ${upperName}Props, ${upperName}Slots } from '../../${content ? '../' : ''}src/runtime/components/${content ? 'content/' : ''}${upperName}.vue'
import ComponentRender from '../${content ? '../' : ''}component-render'
describe('${upperName}', () => {

View File

@@ -53,7 +53,7 @@ provide('navigation', mappedNavigation)
<NuxtLoadingIndicator color="var(--ui-primary)" :height="2" />
<template v-if="!route.path.startsWith('/examples')">
<!-- <Banner /> -->
<Banner />
<Header :links="links" />
</template>

View File

@@ -1,18 +1,19 @@
<template>
<UBanner
id="ui3-launch"
icon="i-lucide-rocket"
:actions="[
{
label: 'Discover Nuxt UI Pro',
to: '/pro/pricing',
trailingIcon: 'i-lucide-arrow-right'
}
]"
id="nuxtlabs-join-vercel"
title="NuxtLabs is joining Vercel"
icon="i-simple-icons-vercel"
to="https://nuxtlabs.com/?utm_source=nuxt-ui&utm_medium=banner&utm_campaign=nuxtlabs-vercel"
target="_blank"
close
>
<template #title>
<span class="font-semibold">Nuxt UI v3</span> is officially released.
</template>
</UBanner>
:actions="[{
label: 'Read the announcement',
color: 'neutral',
variant: 'outline',
trailingIcon: 'i-lucide-arrow-right',
to: 'https://nuxtlabs.com/?utm_source=nuxt-ui&utm_medium=banner&utm_campaign=nuxtlabs-vercel',
target: '_blank',
class: 'ring-0'
}]"
/>
</template>

View File

@@ -34,7 +34,7 @@ const meta = await fetchComponentMeta(name as any)
</ProseCode>
</ProseTd>
<ProseTd>
<HighlightInlineType v-if="slot.type" :type="slot.type" />
<HighlightInlineType v-if="slot.type" :type="slot.type.replace(/ui:\s*\{[^}]*\}/g, 'ui: {}')" />
<MDC v-if="slot.description" :value="slot.description" class="text-toned mt-1" :cache-key="`${kebabCase(route.path)}-${slot.name}-description`" />
</ProseTd>

View File

@@ -14,8 +14,8 @@ const items = [
v-slot="{ item }"
orientation="vertical"
:items="items"
class="w-full max-w-xs mx-auto"
:ui="{ container: 'h-[336px]' }"
class="w-full max-w-xs mx-auto"
>
<img :src="item" width="320" height="320" class="rounded-lg">
</UCarousel>

View File

@@ -0,0 +1,78 @@
<script setup lang="ts">
const groups = [
{
id: 'actions',
items: [
{
label: 'Add new file',
suffix: 'Create a new file in the current directory',
icon: 'i-lucide-file-plus',
kbds: ['meta', 'N']
},
{
label: 'Add new folder',
suffix: 'Create a new folder in the current directory',
icon: 'i-lucide-folder-plus',
kbds: ['meta', 'F']
},
{
label: 'Search files',
suffix: 'Search across all files in the project',
icon: 'i-lucide-search',
kbds: ['meta', 'P']
},
{
label: 'Settings',
suffix: 'Open application settings',
icon: 'i-lucide-settings',
kbds: ['meta', ',']
}
]
},
{
id: 'recent',
label: 'Recent',
items: [
{
label: 'project.vue',
suffix: 'components/',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'readme.md',
suffix: 'docs/',
icon: 'i-vscode-icons-file-type-markdown'
},
{
label: 'package.json',
suffix: 'root/',
icon: 'i-vscode-icons-file-type-node'
}
]
}
]
</script>
<template>
<UCommandPalette :groups="groups" class="flex-1 h-80">
<template #footer>
<div class="flex items-center justify-between gap-2">
<UIcon name="i-simple-icons-nuxtdotjs" class="size-5 text-dimmed ml-1" />
<div class="flex items-center gap-1">
<UButton color="neutral" variant="ghost" label="Open Command" class="text-dimmed" size="xs">
<template #trailing>
<UKbd value="enter" />
</template>
</UButton>
<USeparator orientation="vertical" class="h-4" />
<UButton color="neutral" variant="ghost" label="Actions" class="text-dimmed" size="xs">
<template #trailing>
<UKbd value="meta" />
<UKbd value="k" />
</template>
</UButton>
</div>
</div>
</template>
</UCommandPalette>
</template>

View File

@@ -0,0 +1,15 @@
<template>
<UDrawer :ui="{ content: 'h-full', overlay: 'bg-inverted/30' }">
<UButton label="Open" color="neutral" variant="subtle" trailing-icon="i-lucide-chevron-up" />
<template #footer>
<UDrawer nested :ui="{ content: 'h-full', overlay: 'bg-inverted/30' }">
<UButton color="neutral" variant="outline" label="Open nested" />
<template #content>
<Placeholder class="flex-1 m-4" />
</template>
</UDrawer>
</template>
</UDrawer>
</template>

View File

@@ -1,5 +1,6 @@
<script setup lang="ts">
import { object, string, nonempty, refine, type Infer } from 'superstruct'
import { object, string, nonempty, refine } from 'superstruct'
import type { Infer } from 'superstruct'
import type { FormSubmitEvent } from '@nuxt/ui'
const schema = object({

View File

@@ -1,5 +1,6 @@
<script setup lang="ts">
import { object, string, type InferType } from 'yup'
import { object, string } from 'yup'
import type { InferType } from 'yup'
import type { FormSubmitEvent } from '@nuxt/ui'
const schema = object({

View File

@@ -0,0 +1,31 @@
<script setup lang="ts">
const { data: users } = await useFetch('https://jsonplaceholder.typicode.com/users', {
key: 'typicode-users-email',
transform: (data: { id: number, name: string, email: string }[]) => {
return data?.map(user => ({
label: user.name,
value: String(user.id),
email: user.email,
avatar: { src: `https://i.pravatar.cc/120?img=${user.id}` }
}))
},
lazy: true
})
</script>
<template>
<UInputMenu
:items="users"
icon="i-lucide-user"
placeholder="Select user"
:ui="{ content: 'min-w-fit' }"
>
<template #item-label="{ item }">
{{ item.label }}
<span class="text-muted">
{{ item.email }}
</span>
</template>
</UInputMenu>
</template>

View File

@@ -35,6 +35,7 @@ const items = ref([
}
}
] satisfies InputMenuItem[])
const value = ref(items.value[0])
</script>

View File

@@ -40,9 +40,9 @@ const text = computed(() => {
placeholder="Password"
:color="color"
:type="show ? 'text' : 'password'"
:ui="{ trailing: 'pe-1' }"
:aria-invalid="score < 4"
aria-describedby="password-strength"
:ui="{ trailing: 'pe-1' }"
class="w-full"
>
<template #trailing>

View File

@@ -24,3 +24,10 @@ const password = ref('')
</template>
</UInput>
</template>
<style>
/* Hide the password reveal button in Edge */
::-ms-reveal {
display: none;
}
</style>

View File

@@ -6,14 +6,12 @@ const count = ref(0)
const toast = useToast()
const overlay = useOverlay()
const modal = overlay.create(LazyModalExample, {
props: {
count: count.value
}
})
const modal = overlay.create(LazyModalExample)
async function open() {
const instance = modal.open()
const instance = modal.open({
count: count.value
})
const shouldIncrement = await instance.result

View File

@@ -62,13 +62,13 @@ const items = [
<template>
<UNavigationMenu
:items="items"
class="w-full justify-center"
:ui="{
viewport: 'sm:w-(--reka-navigation-menu-viewport-width)',
content: 'sm:w-auto',
childList: 'sm:w-96',
childLinkDescription: 'text-balance line-clamp-2'
}"
class="w-full justify-center"
>
<template #docs-content="{ item }">
<ul class="grid gap-2 p-4 lg:w-[500px] lg:grid-cols-[minmax(0,.75fr)_minmax(0,1fr)]">

View File

@@ -0,0 +1,43 @@
<script setup lang="ts">
const open = ref(false)
const anchor = ref({ x: 0, y: 0 })
const reference = computed(() => ({
getBoundingClientRect: () =>
({
width: 0,
height: 0,
left: anchor.value.x,
right: anchor.value.x,
top: anchor.value.y,
bottom: anchor.value.y,
...anchor.value
} as DOMRect)
}))
</script>
<template>
<UPopover
:open="open"
:reference="reference"
:content="{ side: 'top', sideOffset: 16, updatePositionStrategy: 'always' }"
>
<div
class="flex items-center justify-center rounded-md border border-dashed border-accented text-sm aspect-video w-72"
@pointerenter="open = true"
@pointerleave="open = false"
@pointermove="(ev) => {
anchor.x = ev.clientX
anchor.y = ev.clientY
}"
>
Hover me
</div>
<template #content>
<div class="p-4">
{{ anchor.x.toFixed(0) }} - {{ anchor.y.toFixed(0) }}
</div>
</template>
</UPopover>
</template>

View File

@@ -0,0 +1,32 @@
<script setup lang="ts">
const { data: users } = await useFetch('https://jsonplaceholder.typicode.com/users', {
key: 'typicode-users-email',
transform: (data: { id: number, name: string, email: string }[]) => {
return data?.map(user => ({
label: user.name,
value: String(user.id),
email: user.email,
avatar: { src: `https://i.pravatar.cc/120?img=${user.id}` }
}))
},
lazy: true
})
</script>
<template>
<USelectMenu
:items="users"
icon="i-lucide-user"
placeholder="Select user"
:ui="{ content: 'min-w-fit' }"
class="w-48"
>
<template #item-label="{ item }">
{{ item.label }}
<span class="text-muted">
{{ item.email }}
</span>
</template>
</USelectMenu>
</template>

View File

@@ -35,6 +35,7 @@ const items = ref([
}
}
] satisfies SelectMenuItem[])
const value = ref(items.value[0])
</script>

View File

@@ -24,6 +24,7 @@ const items = ref([
}
}
] satisfies SelectMenuItem[])
const value = ref(items.value[0])
</script>

View File

@@ -23,6 +23,7 @@ const items = ref([
icon: 'i-lucide-circle-check'
}
] satisfies SelectMenuItem[])
const value = ref(items.value[0])
</script>

View File

@@ -0,0 +1,35 @@
<script setup lang="ts">
const value = ref<string>()
const { data: users } = await useFetch('https://jsonplaceholder.typicode.com/users', {
key: 'typicode-users-email',
transform: (data: { id: number, name: string, email: string }[]) => {
return data?.map(user => ({
label: user.name,
email: user.email,
value: String(user.id),
avatar: { src: `https://i.pravatar.cc/120?img=${user.id}` }
}))
},
lazy: true
})
</script>
<template>
<USelect
v-model="value"
:items="users"
placeholder="Select user"
value-key="value"
:ui="{ content: 'min-w-fit' }"
class="w-48"
>
<template #item-label="{ item }">
{{ item.label }}
<span class="text-muted">
{{ item.email }}
</span>
</template>
</USelect>
</template>

View File

@@ -24,8 +24,8 @@ function getUserAvatar(value: string) {
:loading="status === 'pending'"
icon="i-lucide-user"
placeholder="Select user"
class="w-48"
value-key="value"
class="w-48"
>
<template #leading="{ modelValue, ui }">
<UAvatar

View File

@@ -35,6 +35,7 @@ const items = ref([
}
}
] satisfies SelectItem[])
const value = ref(items.value[0]?.value)
const avatar = computed(() => items.value.find(item => item.value === value.value)?.avatar)

View File

@@ -23,6 +23,7 @@ const items = ref([
icon: 'i-lucide-circle-check'
}
] satisfies SelectItem[])
const value = ref(items.value[0]?.value)
const icon = computed(() => items.value.find(item => item.value === value.value)?.icon)

View File

@@ -6,14 +6,12 @@ const count = ref(0)
const toast = useToast()
const overlay = useOverlay()
const slideover = overlay.create(LazySlideoverExample, {
props: {
count: count.value
}
})
const slideover = overlay.create(LazySlideoverExample)
async function open() {
const instance = slideover.open()
const instance = slideover.open({
count: count.value
})
const shouldIncrement = await instance.result

View File

@@ -0,0 +1,106 @@
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { TableColumn, TableRow } from '@nuxt/ui'
const UBadge = resolveComponent('UBadge')
type Payment = {
id: string
date: string
status: 'paid' | 'failed' | 'refunded'
email: string
amount: number
}
const data = ref<Payment[]>([{
id: '4600',
date: '2024-03-11T15:30:00',
status: 'paid',
email: 'james.anderson@example.com',
amount: 594
}, {
id: '4599',
date: '2024-03-11T10:10:00',
status: 'failed',
email: 'mia.white@example.com',
amount: 276
}, {
id: '4598',
date: '2024-03-11T08:50:00',
status: 'refunded',
email: 'william.brown@example.com',
amount: 315
}, {
id: '4597',
date: '2024-03-10T19:45:00',
status: 'paid',
email: 'emma.davis@example.com',
amount: 529
}, {
id: '4596',
date: '2024-03-10T15:55:00',
status: 'paid',
email: 'ethan.harris@example.com',
amount: 639
}])
const columns: TableColumn<Payment>[] = [{
accessorKey: 'id',
header: '#',
cell: ({ row }) => `#${row.getValue('id')}`
}, {
accessorKey: 'date',
header: 'Date',
cell: ({ row }) => {
return new Date(row.getValue('date')).toLocaleString('en-US', {
day: 'numeric',
month: 'short',
hour: '2-digit',
minute: '2-digit',
hour12: false
})
}
}, {
accessorKey: 'status',
header: 'Status',
cell: ({ row }) => {
const color = ({
paid: 'success' as const,
failed: 'error' as const,
refunded: 'neutral' as const
})[row.getValue('status') as string]
return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () => row.getValue('status'))
}
}, {
accessorKey: 'email',
header: 'Email'
}, {
accessorKey: 'amount',
header: () => h('div', { class: 'text-right' }, 'Amount'),
footer: ({ column }) => {
const total = column.getFacetedRowModel().rows.reduce((acc: number, row: TableRow<Payment>) => acc + Number.parseFloat(row.getValue('amount')), 0)
const formatted = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(total)
return h('div', { class: 'text-right font-medium' }, `Total: ${formatted}`)
},
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
const formatted = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
return h('div', { class: 'text-right font-medium' }, formatted)
}
}]
</script>
<template>
<UTable :data="data" :columns="columns" class="flex-1" />
</template>

View File

@@ -1,7 +1,8 @@
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { TableColumn } from '@nuxt/ui'
import { getGroupedRowModel, type GroupingOptions } from '@tanstack/vue-table'
import { getGroupedRowModel } from '@tanstack/vue-table'
import type { GroupingOptions } from '@tanstack/vue-table'
const UBadge = resolveComponent('UBadge')

View File

@@ -0,0 +1,159 @@
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { ContextMenuItem, TableColumn, TableRow } from '@nuxt/ui'
import { useClipboard } from '@vueuse/core'
const UBadge = resolveComponent('UBadge')
const UCheckbox = resolveComponent('UCheckbox')
const toast = useToast()
const { copy } = useClipboard()
type Payment = {
id: string
date: string
status: 'paid' | 'failed' | 'refunded'
email: string
amount: number
}
const data = ref<Payment[]>([{
id: '4600',
date: '2024-03-11T15:30:00',
status: 'paid',
email: 'james.anderson@example.com',
amount: 594
}, {
id: '4599',
date: '2024-03-11T10:10:00',
status: 'failed',
email: 'mia.white@example.com',
amount: 276
}, {
id: '4598',
date: '2024-03-11T08:50:00',
status: 'refunded',
email: 'william.brown@example.com',
amount: 315
}, {
id: '4597',
date: '2024-03-10T19:45:00',
status: 'paid',
email: 'emma.davis@example.com',
amount: 529
}, {
id: '4596',
date: '2024-03-10T15:55:00',
status: 'paid',
email: 'ethan.harris@example.com',
amount: 639
}])
const columns: TableColumn<Payment>[] = [{
id: 'select',
header: ({ table }) => h(UCheckbox, {
'modelValue': table.getIsSomePageRowsSelected() ? 'indeterminate' : table.getIsAllPageRowsSelected(),
'onUpdate:modelValue': (value: boolean | 'indeterminate') => table.toggleAllPageRowsSelected(!!value),
'aria-label': 'Select all'
}),
cell: ({ row }) => h(UCheckbox, {
'modelValue': row.getIsSelected(),
'onUpdate:modelValue': (value: boolean | 'indeterminate') => row.toggleSelected(!!value),
'aria-label': 'Select row'
})
}, {
accessorKey: 'id',
header: '#',
cell: ({ row }) => `#${row.getValue('id')}`
}, {
accessorKey: 'date',
header: 'Date',
cell: ({ row }) => {
return new Date(row.getValue('date')).toLocaleString('en-US', {
day: 'numeric',
month: 'short',
hour: '2-digit',
minute: '2-digit',
hour12: false
})
}
}, {
accessorKey: 'status',
header: 'Status',
cell: ({ row }) => {
const color = ({
paid: 'success' as const,
failed: 'error' as const,
refunded: 'neutral' as const
})[row.getValue('status') as string]
return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () => row.getValue('status'))
}
}, {
accessorKey: 'email',
header: 'Email'
}, {
accessorKey: 'amount',
header: () => h('div', { class: 'text-right' }, 'Amount'),
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
const formatted = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
return h('div', { class: 'text-right font-medium' }, formatted)
}
}]
const items = ref<ContextMenuItem[]>([])
function getRowItems(row: TableRow<Payment>) {
return [{
type: 'label' as const,
label: 'Actions'
}, {
label: 'Copy payment ID',
onSelect() {
copy(row.original.id)
toast.add({
title: 'Payment ID copied to clipboard!',
color: 'success',
icon: 'i-lucide-circle-check'
})
}
}, {
label: row.getIsExpanded() ? 'Collapse' : 'Expand',
onSelect() {
row.toggleExpanded()
}
}, {
type: 'separator' as const
}, {
label: 'View customer'
}, {
label: 'View payment details'
}]
}
function onContextmenu(_e: Event, row: TableRow<Payment>) {
items.value = getRowItems(row)
}
</script>
<template>
<UContextMenu :items="items">
<UTable
:data="data"
:columns="columns"
class="flex-1"
@contextmenu="onContextmenu"
>
<template #expanded="{ row }">
<pre>{{ row.original }}</pre>
</template>
</UTable>
</UContextMenu>
</template>

View File

@@ -0,0 +1,157 @@
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { TableColumn, TableRow } from '@nuxt/ui'
const UBadge = resolveComponent('UBadge')
const UCheckbox = resolveComponent('UCheckbox')
type Payment = {
id: string
date: string
status: 'paid' | 'failed' | 'refunded'
email: string
amount: number
}
const data = ref<Payment[]>([{
id: '4600',
date: '2024-03-11T15:30:00',
status: 'paid',
email: 'james.anderson@example.com',
amount: 594
}, {
id: '4599',
date: '2024-03-11T10:10:00',
status: 'failed',
email: 'mia.white@example.com',
amount: 276
}, {
id: '4598',
date: '2024-03-11T08:50:00',
status: 'refunded',
email: 'william.brown@example.com',
amount: 315
}, {
id: '4597',
date: '2024-03-10T19:45:00',
status: 'paid',
email: 'emma.davis@example.com',
amount: 529
}, {
id: '4596',
date: '2024-03-10T15:55:00',
status: 'paid',
email: 'ethan.harris@example.com',
amount: 639
}])
const columns: TableColumn<Payment>[] = [{
id: 'select',
header: ({ table }) => h(UCheckbox, {
'modelValue': table.getIsSomePageRowsSelected() ? 'indeterminate' : table.getIsAllPageRowsSelected(),
'onUpdate:modelValue': (value: boolean | 'indeterminate') => table.toggleAllPageRowsSelected(!!value),
'aria-label': 'Select all'
}),
cell: ({ row }) => h(UCheckbox, {
'modelValue': row.getIsSelected(),
'onUpdate:modelValue': (value: boolean | 'indeterminate') => row.toggleSelected(!!value),
'aria-label': 'Select row'
})
}, {
accessorKey: 'id',
header: '#',
cell: ({ row }) => `#${row.getValue('id')}`
}, {
accessorKey: 'date',
header: 'Date',
cell: ({ row }) => {
return new Date(row.getValue('date')).toLocaleString('en-US', {
day: 'numeric',
month: 'short',
hour: '2-digit',
minute: '2-digit',
hour12: false
})
}
}, {
accessorKey: 'status',
header: 'Status',
cell: ({ row }) => {
const color = ({
paid: 'success' as const,
failed: 'error' as const,
refunded: 'neutral' as const
})[row.getValue('status') as string]
return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () => row.getValue('status'))
}
}, {
accessorKey: 'email',
header: 'Email'
}, {
accessorKey: 'amount',
header: () => h('div', { class: 'text-right' }, 'Amount'),
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
const formatted = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
return h('div', { class: 'text-right font-medium' }, formatted)
}
}]
const anchor = ref({ x: 0, y: 0 })
const reference = computed(() => ({
getBoundingClientRect: () =>
({
width: 0,
height: 0,
left: anchor.value.x,
right: anchor.value.x,
top: anchor.value.y,
bottom: anchor.value.y,
...anchor.value
} as DOMRect)
}))
const open = ref(false)
const openDebounced = refDebounced(open, 10)
const selectedRow = ref<TableRow<Payment> | null>(null)
function onHover(_e: Event, row: TableRow<Payment> | null) {
selectedRow.value = row
open.value = !!row
}
</script>
<template>
<div class="flex w-full flex-1 gap-1">
<UTable
:data="data"
:columns="columns"
class="flex-1"
@pointermove="(ev: PointerEvent) => {
anchor.x = ev.clientX
anchor.y = ev.clientY
}"
@hover="onHover"
/>
<UPopover
:content="{ side: 'top', sideOffset: 16, updatePositionStrategy: 'always' }"
:open="openDebounced"
:reference="reference"
>
<template #content>
<div class="p-4">
{{ selectedRow?.original?.id }}
</div>
</template>
</UPopover>
</div>
</template>

View File

@@ -112,7 +112,7 @@ function onSelect(row: TableRow<Payment>, e?: Event) {
</script>
<template>
<div class=" flex w-full flex-1 gap-1">
<div class="flex w-full flex-1 gap-1">
<div class="flex-1">
<UTable
ref="table"

View File

@@ -26,7 +26,7 @@ const state = reactive({
</script>
<template>
<UTabs :items="items" variant="link" class="gap-4 w-full" :ui="{ trigger: 'grow' }">
<UTabs :items="items" variant="link" :ui="{ trigger: 'grow' }" class="gap-4 w-full">
<template #account="{ item }">
<p class="text-muted mb-4">
{{ item.description }}

View File

@@ -27,8 +27,8 @@ const items: TimelineItem[] = [{
<template>
<UTimeline
:items="items"
:ui="{ item: 'even:flex-row-reverse even:-translate-x-[calc(100%-2rem)] even:text-right' }"
:default-value="2"
:ui="{ item: 'even:flex-row-reverse even:-translate-x-[calc(100%-2rem)] even:text-right' }"
class="translate-x-[calc(50%-1rem)]"
/>
</template>

View File

@@ -42,11 +42,11 @@ const items = [{
<UTimeline
:items="items"
size="xs"
class="w-96"
:ui="{
date: 'float-end ms-1',
description: 'px-3 py-2 ring ring-default mt-2 rounded-md text-default'
}"
class="w-96"
>
<template #title="{ item }">
<span>{{ item.username }}</span>

View File

@@ -0,0 +1,16 @@
<script setup lang="ts">
const toast = useToast()
function showToast() {
toast.add({
title: 'Uh oh! Something went wrong.',
description: 'There was a problem with your request.',
icon: 'i-lucide-wifi',
progress: false
})
}
</script>
<template>
<UButton label="Show toast" color="neutral" variant="outline" @click="showToast" />
</template>

View File

@@ -7,12 +7,12 @@ const appConfig = useAppConfig()
<UFormField
label="toaster.duration"
size="sm"
class="inline-flex ring ring-accented rounded-sm"
:ui="{
wrapper: 'bg-elevated/50 rounded-l-sm flex border-r border-accented',
label: 'text-muted px-2 py-1.5',
container: 'mt-0'
}"
class="inline-flex ring ring-accented rounded-sm"
>
<UInput
v-model="appConfig.toaster.duration"

View File

@@ -7,12 +7,12 @@ const appConfig = useAppConfig()
<UFormField
label="toaster.expand"
size="sm"
class="inline-flex ring ring-accented rounded-sm"
:ui="{
wrapper: 'bg-elevated/50 rounded-l-sm flex border-r border-accented',
label: 'text-muted px-2 py-1.5',
container: 'mt-0'
}"
class="inline-flex ring ring-accented rounded-sm"
>
<USelectMenu
v-model="appConfig.toaster.expand"

View File

@@ -10,12 +10,12 @@ const appConfig = useAppConfig()
<UFormField
label="toaster.position"
size="sm"
class="inline-flex ring ring-accented rounded-sm"
:ui="{
wrapper: 'bg-elevated/50 rounded-l-sm flex border-r border-accented',
label: 'text-muted px-2 py-1.5',
container: 'mt-0'
}"
class="inline-flex ring ring-accented rounded-sm"
>
<USelectMenu
v-model="appConfig.toaster.position"

View File

@@ -0,0 +1,41 @@
<script setup lang="ts">
const open = ref(false)
const anchor = ref({ x: 0, y: 0 })
const reference = computed(() => ({
getBoundingClientRect: () =>
({
width: 0,
height: 0,
left: anchor.value.x,
right: anchor.value.x,
top: anchor.value.y,
bottom: anchor.value.y,
...anchor.value
} as DOMRect)
}))
</script>
<template>
<UTooltip
:open="open"
:reference="reference"
:content="{ side: 'top', sideOffset: 16, updatePositionStrategy: 'always' }"
>
<div
class="flex items-center justify-center rounded-md border border-dashed border-accented text-sm aspect-video w-72"
@pointerenter="open = true"
@pointerleave="open = false"
@pointermove="(ev) => {
anchor.x = ev.clientX
anchor.y = ev.clientY
}"
>
Hover me
</div>
<template #content>
{{ anchor.x.toFixed(0) }} - {{ anchor.y.toFixed(0) }}
</template>
</UTooltip>
</template>

View File

@@ -1,5 +1,5 @@
import { onMounted, watch } from 'vue'
import FaviconSvg from 'public/icon.svg?raw'
import FaviconSvg from '../../public/icon.svg?raw'
export function useFaviconFromTheme() {
const colorMode = useColorMode()

View File

@@ -59,7 +59,7 @@ provide('navigation', mappedNavigation)
<UApp>
<NuxtLoadingIndicator color="#FFF" />
<!-- <Banner /> -->
<Banner />
<Header :links="links" />

View File

@@ -5,6 +5,17 @@ pricing:
title: Upgrade to Nuxt UI [Pro]{class="text-primary"}.
description: On top of 40+ open source components from Nuxt UI, Pro gives you access to 50+ premium Vue components to create beautiful & responsive Nuxt applications in minutes. It includes all primitives to build landing pages, documentations, blogs, dashboards or entire SaaS products.
freePlan:
description: "**NuxtLabs is joining Vercel** :tada: As part of this transition, Nuxt UI is becoming even more accessible.<br><br> **In September, we're launching Nuxt UI v4**: a free, open-source library that unifies Nuxt UI and Nuxt UI Pro, offering 100+ components and a complete free Figma Kit for everyone."
orientation: horizontal
button:
label: Read the announcement
to: 'https://nuxtlabs.com/?utm_source=nuxt-ui&utm_medium=banner&utm_campaign=nuxtlabs-vercel'
target: _blank
color: 'neutral'
trailingIcon: 'i-lucide-arrow-right'
ui:
trailingIcon: 'ms-0'
devPlan:
title: Free in development
description: Try Nuxt UI Pro for free in development, no credit card required. Upgrade when ready to deploy.
orientation: horizontal
@@ -13,6 +24,9 @@ pricing:
to: '/getting-started/installation/pro/nuxt'
color: 'neutral'
variant: 'subtle'
trailingIcon: 'i-lucide-arrow-right'
ui:
trailingIcon: 'ms-0'
figma:
title: Figma Kit Pro
description: Get all Nuxt UI Pro components in a Figma kit to design your next application before coding. Everything you need, from wire-framing to high-fidelity web integration.

View File

@@ -34,10 +34,19 @@ useSeoMeta({
<div class="flex flex-col bg-default gap-8 lg:gap-0">
<UPricingPlan
v-bind="page.pricing.freePlan"
variant="naked"
class="lg:rounded-none border-x border-default border-t border-b lg:border-b-0"
class="lg:rounded-none ring-primary/15 ring-inset -mb-px bg-primary/5 z-[1]"
:ui="{ description: 'mt-0 text-primary' }"
>
<template #description>
<MDC :value="page.pricing.freePlan.description" unwrap="p" />
</template>
</UPricingPlan>
<UPricingPlan
v-bind="page.pricing.devPlan"
class="lg:rounded-none ring-inset -mb-px"
/>
<UPricingPlans compact>
<UPricingPlans compact class="-space-x-px">
<UPricingPlan
v-for="(plan, index) in page.pricing.plans"
:key="index"
@@ -47,18 +56,17 @@ useSeoMeta({
:discount="plan.discount"
:billing-period="plan.billing_period"
:billing-cycle="plan.billing_cycle"
:variant="plan.highlight ? 'soft' : 'outline'"
:class="['lg:rounded-none', { 'border-2 lg:border lg:border-x-0 border-primary lg:border-default': plan.highlight }]"
:variant="plan.highlight ? 'subtle' : 'outline'"
class="lg:rounded-none ring-inset -mb-px"
:features="plan.features"
:button="plan.button"
/>
</UPricingPlans>
<UPricingPlan
v-bind="page.pricing.figma"
variant="naked"
:billing-period="page.pricing.figma.billing_period"
:billing-cycle="page.pricing.figma.billing_cycle"
class="lg:rounded-none border lg:border-y-0 border-default"
class="lg:rounded-none ring-inset -mb-px"
>
<template #features>
<li v-for="(feature, index) in page.pricing.figma.features" :key="index" class="flex items-center gap-2 min-w-0">

View File

@@ -16,12 +16,12 @@ function handleMessage(message) {
async function handleFormatMessage(message) {
if (!globalThis.prettier) {
await Promise.all([
import('https://cdn.jsdelivr.net/npm/prettier@3.5.2/standalone.js'),
import('https://cdn.jsdelivr.net/npm/prettier@3.5.2/plugins/babel.js'),
import('https://cdn.jsdelivr.net/npm/prettier@3.5.2/plugins/estree.js'),
import('https://cdn.jsdelivr.net/npm/prettier@3.5.2/plugins/html.js'),
import('https://cdn.jsdelivr.net/npm/prettier@3.5.2/plugins/markdown.js'),
import('https://cdn.jsdelivr.net/npm/prettier@3.5.2/plugins/typescript.js')
import('https://cdn.jsdelivr.net/npm/prettier@3.6.2/standalone.js'),
import('https://cdn.jsdelivr.net/npm/prettier@3.6.2/plugins/babel.js'),
import('https://cdn.jsdelivr.net/npm/prettier@3.6.2/plugins/estree.js'),
import('https://cdn.jsdelivr.net/npm/prettier@3.6.2/plugins/html.js'),
import('https://cdn.jsdelivr.net/npm/prettier@3.6.2/plugins/markdown.js'),
import('https://cdn.jsdelivr.net/npm/prettier@3.6.2/plugins/typescript.js')
])
}

View File

@@ -225,6 +225,27 @@ export default defineNuxtConfig({
This option adds the `transition-colors` class on components with hover or active states.
::
### `theme.defaultVariants` :badge{label="Soon" class="align-text-top"}
Use the `theme.defaultVariants` option to override the default `color` and `size` variants for components.
- Default: `{ color: 'primary', size: 'md' }`{lang="ts-type"}
```ts [nuxt.config.ts]
export default defineNuxtConfig({
modules: ['@nuxt/ui'],
css: ['~/assets/css/main.css'],
ui: {
theme: {
defaultVariants: {
color: 'neutral',
size: 'sm'
}
}
}
})
```
## Continuous Releases
Nuxt UI uses [pkg.pr.new](https://github.com/stackblitz-labs/pkg.pr.new) for continuous preview releases, providing developers with instant access to the latest features and bug fixes without waiting for official releases.

View File

@@ -183,7 +183,28 @@ It's recommended to install the [Tailwind CSS IntelliSense](https://marketplace.
```
::note{to="/components/app"}
The `App` component provides global configurations and is required for **Toast**, **Tooltip** components to work as well as **Programmatic Overlays**.
The `App` component sets up global config and is required for **Toast**, **Tooltip** and **programmatic overlays**.
::
#### Add the `isolate` class to your root container
```html [index.html]{9}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Nuxt UI</title>
</head>
<body>
<div id="app" class="isolate"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
```
::note
This ensures styles are scoped to your app and prevents issues with overlays and stacking contexts.
::
::
@@ -333,6 +354,32 @@ export default defineConfig({
This option adds the `transition-colors` class on components with hover or active states.
::
### `theme.defaultVariants` :badge{label="Soon" class="align-text-top"}
Use the `theme.defaultVariants` option to override the default `color` and `size` variants for components.
- Default: `{ color: 'primary', size: 'md' }`{lang="ts-type"}
```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({
theme: {
defaultVariants: {
color: 'neutral',
size: 'sm'
}
}
})
]
})
```
### `inertia`
Use the `inertia` option to enable compatibility with [Inertia.js](https://inertiajs.com/).

View File

@@ -536,6 +536,33 @@ import { ModalExampleComponent } from '#components'
</script>
```
### Changed form validation
- The error object property for targeting form fields has been renamed from `path` to `name`:
```diff
<script setup lang="ts">
const validate = (state: any): FormError[] => {
const errors = []
if (!state.email) {
errors.push({
- path: 'email',
+ name: 'email',
message: 'Required'
})
}
if (!state.password) {
errors.push({
- path: 'password',
+ name: 'password',
message: 'Required'
})
}
return errors
}
</script>
```
---
::warning

View File

@@ -87,7 +87,7 @@ Read more about this in the `@nuxt/icon` documentation.
You can use local SVG files to create a custom Iconify collection.
For example, place your icons' SVG files under a folder of your choice, for example, `./assets/icons`:
For example, place your icons' SVG files under a folder of your choice, for example, `./app/assets/icons`:
```bash
assets/icons
@@ -104,7 +104,7 @@ export default defineNuxtConfig({
icon: {
customCollections: [{
prefix: 'custom',
dir: './assets/icons'
dir: './app/assets/icons'
}]
}
})

View File

@@ -32,6 +32,12 @@ props:
You can use any name from the <https://icones.js.org> collection.
::
::warning
When using collections with a dash (`-`), you need to separate the icon name from the collection name with a colon (`:`) as `@iconify/vue` does not handle this case like `@nuxt/icon`. For example, instead of `i-simple-icons-github` you need to write `i-simple-icons:github` or `simple-icons:github`.
Learn more about the [Iconify naming convention](https://iconify.design/docs/icon-components/vue/#icon).
::
### Component Props
Some components also have an `icon` prop to display an icon, like the [Button](/components/button) for example:

View File

@@ -125,7 +125,7 @@ Look at the `code` parameter, there you need to pass the iso code of the languag
::
### Extend locale :badge{label="Soon" class="align-text-top"}
### Extend locale :badge{label="New" class="align-text-top"}
You can customize an existing locale by overriding its `messages` or `code` using the `extendLocale` composable:

View File

@@ -127,7 +127,7 @@ Look at the `code` parameter, there you need to pass the iso code of the languag
::
### Extend locale :badge{label="Soon" class="align-text-top"}
### Extend locale :badge{label="New" class="align-text-top"}
You can customize an existing locale by overriding its `messages` or `code` using the `extendLocale` composable:

View File

@@ -9,7 +9,6 @@ links:
- label: GitHub
icon: i-simple-icons-github
to: https://github.com/nuxt/ui/tree/v3/src/runtime/components/CheckboxGroup.vue
navigation.badge: New
---

View File

@@ -156,7 +156,7 @@ props:
---
::
### Variant :badge{label="New" class="align-text-top"}
### Variant
Use the `variant` prop to change the variant of the Checkbox.
@@ -190,7 +190,7 @@ props:
---
::
### Indicator :badge{label="New" class="align-text-top"}
### Indicator
Use the `indicator` prop to change the position or hide the indicator. Defaults to `start`.

View File

@@ -52,8 +52,8 @@ Each group contains an `items` array of objects that define the commands. Each i
- `loading?: boolean`{lang="ts-type"}
- `disabled?: boolean`{lang="ts-type"}
- [`slot?: string`{lang="ts-type"}](#with-custom-slot)
- `placeholder?: string`{lang="ts-type"} :badge{label="Soon"}
- `children?: CommandPaletteItem[]`{lang="ts-type"} :badge{label="Soon"}
- `placeholder?: string`{lang="ts-type"}
- `children?: CommandPaletteItem[]`{lang="ts-type"}
- `onSelect?(e?: Event): void`{lang="ts-type"}
- `class?: any`{lang="ts-type"}
- `ui?: { item?: ClassNameValue, itemLeadingIcon?: ClassNameValue, itemLeadingAvatarSize?: ClassNameValue, itemLeadingAvatar?: ClassNameValue, itemLeadingChipSize?: ClassNameValue, itemLeadingChip?: ClassNameValue, itemLabel?: ClassNameValue, itemLabelPrefix?: ClassNameValue, itemLabelBase?: ClassNameValue, itemLabelSuffix?: ClassNameValue, itemTrailing?: ClassNameValue, itemTrailingKbds?: ClassNameValue, itemTrailingKbdsSize?: ClassNameValue, itemTrailingHighlightedIcon?: ClassNameValue, itemTrailingIcon?: ClassNameValue }`{lang="ts-type"}
@@ -327,7 +327,7 @@ You can customize this icon globally in your `vite.config.ts` under `ui.icons.ch
:::
::
### Trailing Icon :badge{label="Soon" class="align-text-top"}
### Trailing Icon :badge{label="New" class="align-text-top"}
Use the `trailing-icon` prop to customize the trailing [Icon](/components/icon) when an item has children. Defaults to `i-lucide-chevron-right`.
@@ -565,7 +565,7 @@ You can customize this icon globally in your `vite.config.ts` under `ui.icons.cl
:::
::
### Back :badge{label="Soon" class="align-text-top"}
### Back :badge{label="New" class="align-text-top"}
Use the `back` prop to customize or hide the back button (with `false` value) displayed when navigating into a submenu.
@@ -604,7 +604,7 @@ props:
---
::
### Back Icon :badge{label="Soon" class="align-text-top"}
### Back Icon :badge{label="New" class="align-text-top"}
Use the `back-icon` prop to customize the back button [Icon](/components/icon). Defaults to `i-lucide-arrow-left`.
@@ -717,7 +717,7 @@ props:
This example uses the `@update:model-value` event to reset the search term when an item is selected.
::
### With children in items :badge{label="Soon" class="align-text-top"}
### With children in items :badge{label="New" class="align-text-top"}
You can create hierarchical menus by using the `children` property in items. When an item has children, it will automatically display a chevron icon and enable navigation into a submenu.
@@ -877,6 +877,20 @@ props:
This can be useful when using the CommandPalette inside a [`Modal`](/components/modal) for example.
::
### With footer slot :badge{label="Soon" class="align-text-top"}
Use the `#footer` slot to add custom content at the bottom of the CommandPalette, such as keyboard shortcuts help or additional actions.
::component-example
---
collapse: true
name: 'command-palette-footer-slot-example'
class: '!p-0'
props:
autofocus: false
---
::
### With custom slot
Use the `slot` property to customize a specific item or group.

View File

@@ -328,6 +328,17 @@ name: 'drawer-responsive-example'
---
::
### Nested drawers :badge{label="Soon" class="align-text-top"}
You can nest drawers within each other by using the `nested` prop.
::component-example
---
prettier: true
name: 'drawer-nested-example'
---
::
### With footer slot
Use the `#footer` slot to add content after the Drawer's body.

View File

@@ -757,6 +757,33 @@ name: 'input-menu-filter-fields-example'
---
::
### With full content width
You can expand the content to the full width of its items by using the `ui.content` key.
::component-example
---
name: 'input-menu-content-width-example'
collapse: true
---
::
::tip
You can also change the content width globally in your `app.config.ts`:
```
export default defineAppConfig({
ui: {
inputMenu: {
slots: {
content: 'min-w-fit'
}
}
}
})
```
::
### As a CountryPicker
This example demonstrates using the InputMenu as a country picker with lazy loading - countries are only fetched when the menu is opened.

View File

@@ -9,7 +9,7 @@ links:
- label: GitHub
icon: i-simple-icons-github
to: https://github.com/nuxt/ui/tree/v3/src/runtime/components/InputTags.vue
navigation.badge: Soon
navigation.badge: New
---
## Usage
@@ -51,6 +51,17 @@ props:
---
::
### Max Length :badge{label="Soon" class="align-text-top"}
Use the `max-length` prop to set the maximum number of characters allowed in a tag.
::component-code
---
props:
maxLength: 4
---
::
### Color
Use the `color` prop to change the ring color when the InputTags is focused.

View File

@@ -62,6 +62,19 @@ items:
---
::
### Color :badge{label="Soon" class="align-text-top"}
Use the `color` prop to change the color of the Kbd.
::component-code
---
props:
color: neutral
slots:
default: K
---
::
### Variant
Use the `variant` prop to change the variant of the Kbd.
@@ -69,6 +82,7 @@ Use the `variant` prop to change the variant of the Kbd.
::component-code
---
props:
color: neutral
variant: solid
slots:
default: K

View File

@@ -889,7 +889,7 @@ You can inspect the DOM to see each item's content being rendered.
## Examples
### With tooltip in items :badge{label="New" class="align-text-top"}
### With tooltip in items
When orientation is `vertical` and the menu is `collapsed`, you can set the `tooltip` prop to `true` to display a [Tooltip](/components/tooltip) around items with their label but you can also use the `tooltip` property on each item to override the default tooltip.
@@ -994,7 +994,7 @@ props:
---
::
### With popover in items :badge{label="New" class="align-text-top"}
### With popover in items
When orientation is `vertical` and the menu is `collapsed`, you can set the `popover` prop to `true` to display a [Popover](/components/popover) around items with their children but you can also use the `popover` property on each item to override the default popover.

View File

@@ -202,7 +202,17 @@ name: 'popover-command-palette-example'
---
::
### With anchor slot :badge{label="New" class="align-text-top"}
### With following cursor :badge{label="Soon" class="align-text-top"}
You can make the Popover follow the cursor when hovering over an element using the [`reference`](https://reka-ui.com/docs/components/tooltip#trigger) prop:
::component-example
---
name: 'popover-cursor-example'
---
::
### With anchor slot
You can use the `#anchor` slot to position the Popover against a custom element.

View File

@@ -159,7 +159,7 @@ props:
---
::
### Variant :badge{label="New" class="align-text-top"}
### Variant
Use the `variant` prop to change the variant of the RadioGroup.
@@ -240,7 +240,7 @@ props:
---
::
### Indicator :badge{label="New" class="align-text-top"}
### Indicator
Use the `indicator` prop to change the position or hide the indicator. Defaults to `start`.

View File

@@ -790,6 +790,33 @@ name: 'select-menu-filter-fields-example'
---
::
### With full content width
You can expand the content to the full width of its items by using the `ui.content` key.
::component-example
---
name: 'select-menu-content-width-example'
collapse: true
---
::
::tip
You can also change the content width globally in your `app.config.ts`:
```
export default defineAppConfig({
ui: {
selectMenu: {
slots: {
content: 'min-w-fit'
}
}
}
})
```
::
### As a CountryPicker
This example demonstrates using the SelectMenu as a country picker with lazy loading - countries are only fetched when the menu is opened.
@@ -801,6 +828,8 @@ name: 'select-menu-countries-example'
---
::
## API
### Props

View File

@@ -695,6 +695,33 @@ collapse: true
---
::
### With full content width
You can expand the content to the full width of its items by using the `ui.content` key.
::component-example
---
name: 'select-content-width-example'
collapse: true
---
::
::tip
You can also change the content width globally in your `app.config.ts`:
```
export default defineAppConfig({
ui: {
select: {
slots: {
content: 'min-w-fit'
}
}
}
})
```
::
## API
### Props

View File

@@ -136,7 +136,7 @@ props:
---
::
### Tooltip :badge{label="New" class="align-text-top"}
### Tooltip
Use the `tooltip` prop to display a [Tooltip](/components/tooltip) around the Slider thumbs with the current value. You can set it to `true` for default behavior or pass an object to customize it with any property from the [Tooltip](/components/tooltip#props) component.

View File

@@ -77,11 +77,15 @@ Use the `columns` prop as an array of [ColumnDef](https://tanstack.com/table/lat
- `accessorKey`: [The key of the row object to use when extracting the value for the column.]{class="text-muted"}
- `header`: [The header to display for the column. If a string is passed, it can be used as a default for the column ID. If a function is passed, it will be passed a props object for the header and should return the rendered header value (the exact type depends on the adapter being used).]{class="text-muted"}
- `footer`: [The footer to display for the column. Works exactly like header, but is displayed under the table.]{class="text-muted"}
- `cell`: [The cell to display each row for the column. If a function is passed, it will be passed a props object for the cell and should return the rendered cell value (the exact type depends on the adapter being used).]{class="text-muted"}
- `meta`: [Extra properties for the column.]{class="text-muted"}
- `class`:
- `td`: [The classes to apply to the `td` element.]{class="text-muted"}
- `th`: [The classes to apply to the `th` element.]{class="text-muted"}
- `style`:
- `td`: [The style to apply to the `td` element.]{class="text-muted"}
- `th`: [The style to apply to the `th` element.]{class="text-muted"}
In order to render components or other HTML elements, you will need to use the Vue [`h` function](https://vuejs.org/api/render-function.html#h) inside the `header` and `cell` props. This is different from other components that use slots but allows for more flexibility.
@@ -111,6 +115,8 @@ Use the `meta` prop as an object ([TableMeta](https://tanstack.com/table/latest/
- `class`:
- `tr`: [The classes to apply to the `tr` element.]{class="text-muted"}
- `style`:
- `tr`: [The style to apply to the `tr` element.]{class="text-muted"}
### Loading
@@ -161,7 +167,7 @@ props:
### Sticky
Use the `sticky` prop to make the header sticky.
Use the `sticky` prop to make the header or footer sticky.
::component-code
---
@@ -172,6 +178,10 @@ ignore:
- class
external:
- data
items:
sticky:
- true
- false
props:
sticky: true
data:
@@ -266,8 +276,8 @@ You can group rows based on a given column value and show/hide sub rows via some
#### Important parts:
* Add prop `grouping` to `UTable` component with an array of column ids you want to group by.
* Add prop `grouping-options` to `UTable`. It must include `getGroupedRowModel`, you can import it from `@tanstack/vue-table` or implement your own.
* Add `grouping` prop with an array of column ids you want to group by.
* Add `grouping-options` prop. It must include `getGroupedRowModel`, you can import it from `@tanstack/vue-table` or implement your own.
* Expand rows via `row.toggleExpanded()` method on any cell of the row. Keep in mind, it also toggles `#expanded` slot.
* Use `aggregateFn` on column definition to define how to aggregate the rows.
* `agregatedCell` renderer on column definition only works if there is no `cell` renderer.
@@ -304,19 +314,19 @@ class: '!p-0'
You can use the `row-selection` prop to control the selection state of the rows (can be binded with `v-model`).
::
### With `@select` event
### With row select event
You can add a `@select` listener to make rows clickable. The handler function receives the `TableRow` instance as the first argument and an optional `Event` as the second argument.
You can add a `@select` listener to make rows clickable with or without a checkbox column.
::note
You can use this to navigate to a page, open a modal or even to select the row manually.
The handler function receives the `TableRow` instance as the first argument and an optional `Event` as the second argument.
::
::component-example
---
prettier: true
collapse: true
name: 'table-row-selection-event-example'
name: 'table-row-select-event-example'
highlights:
- 123
- 130
@@ -324,6 +334,70 @@ class: '!p-0'
---
::
::tip
You can use this to navigate to a page, open a modal or even to select the row manually.
::
### With row context menu event :badge{label="Soon" class="align-text-top"}
You can add a `@contextmenu` listener to make rows right clickable and wrap the Table in a [ContextMenu](/components/context-menu) component to display row actions for example.
::note
The handler function receives the `Event` and `TableRow` instance as the first and second arguments respectively.
::
::component-example
---
prettier: true
collapse: true
name: 'table-row-context-menu-event-example'
highlights:
- 130
- 170
class: '!p-0'
---
::
### With row hover event :badge{label="Soon" class="align-text-top"}
You can add a `@hover` listener to make rows hoverable and use a [Popover](/components/popover) or a [Tooltip](/components/tooltip) component to display row details for example.
::note
The handler function receives the `Event` and `TableRow` instance as the first and second arguments respectively.
::
::component-example
---
prettier: true
collapse: true
name: 'table-row-hover-event-example'
highlights:
- 126
- 149
class: '!p-0'
---
::
::note
This example is similar as the Popover [with following cursor example](/components/popover#with-following-cursor) and uses a [`refDebounced`](https://vueuse.org/shared/refDebounced/#refdebounced) to prevent the Popover from opening and closing too quickly when moving the cursor from one row to another.
::
### With column footer :badge{label="Soon" class="align-text-top"}
You can add a `footer` property to the column definition to render a footer for the column.
::component-example
---
prettier: true
collapse: true
name: 'table-column-footer-example'
highlights:
- 94
- 108
class: '!p-0'
---
::
### With column sorting
You can update a column `header` to render a [Button](/components/button) component inside the `header` to toggle the sorting state using the TanStack Table [Sorting APIs](https://tanstack.com/table/latest/docs/api/features/sorting).

View File

@@ -19,12 +19,13 @@ Use the `items` prop as an array of objects with the following properties:
- `label?: string`{lang="ts-type"}
- `icon?: string`{lang="ts-type"}
- `avatar?: AvatarProps`{lang="ts-type"}
- `badge?: string | number | BadgeProps`{lang="ts-type"}
- `content?: string`{lang="ts-type"}
- `value?: string | number`{lang="ts-type"}
- `disabled?: boolean`{lang="ts-type"}
- [`slot?: string`{lang="ts-type"}](#with-custom-slot)
- `class?: any`{lang="ts-type"}
- `ui?: { trigger?: ClassNameValue, leadingIcon?: ClassNameValue, leadingAvatar?: ClassNameValue, label?: ClassNameValue, content?: ClassNameValue }`{lang="ts-type"}
- `ui?: { trigger?: ClassNameValue, leadingIcon?: ClassNameValue, leadingAvatar?: ClassNameValue, leadingAvatarSize?: ClassNameValue, label?: ClassNameValue, trailingBadge?: ClassNameValue, trailingBadgeSize?: ClassNameValue, content?: ClassNameValue }`{lang="ts-type"}
::component-code
---

View File

@@ -124,7 +124,7 @@ props:
---
::
### Icon :badge{label="New" class="align-text-top"}
### Icon
Use the `icon` prop to show an [Icon](/components/icon) inside the Textarea.
@@ -157,7 +157,7 @@ props:
---
::
### Avatar :badge{label="New" class="align-text-top"}
### Avatar
Use the `avatar` prop to show an [Avatar](/components/avatar) inside the Textarea.
@@ -176,7 +176,7 @@ props:
---
::
### Loading :badge{label="New" class="align-text-top"}
### Loading
Use the `loading` prop to show a loading icon on the Textarea.
@@ -192,7 +192,7 @@ props:
---
::
### Loading Icon :badge{label="New" class="align-text-top"}
### Loading Icon
Use the `loading-icon` prop to customize the loading icon. Defaults to `i-lucide-loader-circle`.

View File

@@ -6,7 +6,7 @@ links:
- label: GitHub
icon: i-simple-icons-github
to: https://github.com/nuxt/ui/tree/v3/src/runtime/components/Timeline.vue
navigation.badge: Soon
navigation.badge: New
---
## Usage

View File

@@ -107,7 +107,7 @@ name: 'toast-color-example'
### Close
Pass a `close` field to customize or hide the close button (with `false` value).
Pass a `close` field to customize or hide the close [Button](/components/button) (with `false` value).
::component-example
---
@@ -143,7 +143,7 @@ You can customize this icon globally in your `vite.config.ts` under `ui.icons.cl
### Actions
Pass an `actions` field to add some [Button](/components/button) actions to the Alert.
Pass an `actions` field to add some [Button](/components/button) actions to the Toast.
::component-example
---
@@ -155,9 +155,23 @@ name: 'toast-actions-example'
---
::
### Progress :badge{label="Soon" class="align-text-top"}
Pass a `progress` field to customize or hide the [Progress](/components/progress) bar (with `false` value).
::tip
The Progress bar inherits the Toast color by default, but you can override it using the `progress.color` field.
::
::component-example
---
name: 'toast-progress-example'
---
::
### Orientation
Use the `orientation` prop to change the orientation of the Toast.
Pass an `orientation` field to the `toast.add` method to change the orientation of the Toast.
::component-example
---

View File

@@ -186,6 +186,16 @@ name: 'tooltip-open-example'
In this example, leveraging [`defineShortcuts`](/composables/define-shortcuts), you can toggle the Tooltip by pressing :kbd{value="O"}.
::
### With following cursor :badge{label="Soon" class="align-text-top"}
You can make the Tooltip follow the cursor when hovering over an element using the [`reference`](https://reka-ui.com/docs/components/tooltip#trigger) prop:
::component-example
---
name: 'tooltip-cursor-example'
---
::
## API
### Props

View File

@@ -51,3 +51,5 @@ items:
url: https://wiredash.com/
- name: Zielgestalt
url: https://zielgestalt.de/
- name: Arthur Danjou's Porfolio
url: https://arthurdanjou.fr/

View File

@@ -143,10 +143,6 @@ export default defineNuxtConfig({
'/releases': { redirect: 'https://github.com/nuxt/ui/releases', prerender: false }
},
future: {
compatibilityVersion: 4
},
compatibilityDate: '2024-07-09',
nitro: {

View File

@@ -11,40 +11,40 @@
"dependencies": {
"@ai-sdk/vue": "^1.2.12",
"@iconify-json/logos": "^1.2.4",
"@iconify-json/lucide": "^1.2.51",
"@iconify-json/simple-icons": "^1.2.39",
"@iconify-json/lucide": "^1.2.57",
"@iconify-json/simple-icons": "^1.2.44",
"@iconify-json/vscode-icons": "^1.2.23",
"@nuxt/content": "^3.6.1",
"@nuxt/content": "^3.6.3",
"@nuxt/image": "^1.10.0",
"@nuxt/ui": "workspace:*",
"@nuxt/ui-pro": "https://pkg.pr.new/@nuxt/ui-pro@55e248c",
"@nuxt/ui-pro": "https://pkg.pr.new/@nuxt/ui-pro@17684e4",
"@nuxthub/core": "^0.9.0",
"@nuxtjs/plausible": "^1.2.0",
"@octokit/rest": "^22.0.0",
"@rollup/plugin-yaml": "^4.1.2",
"@vueuse/integrations": "^13.4.0",
"@vueuse/nuxt": "^13.4.0",
"ai": "^4.3.16",
"better-sqlite3": "^12.0.0",
"@vueuse/integrations": "^13.5.0",
"@vueuse/nuxt": "^13.5.0",
"ai": "^4.3.19",
"better-sqlite3": "^12.2.0",
"capture-website": "^4.2.0",
"joi": "^17.13.3",
"maska": "^3.1.1",
"motion-v": "^1.3.0",
"nuxt": "^3.17.5",
"nuxt-component-meta": "^0.11.0",
"maska": "^3.2.0",
"motion-v": "^1.5.0",
"nuxt": "^4.0.1",
"nuxt-component-meta": "^0.12.1",
"nuxt-llms": "^0.1.3",
"nuxt-og-image": "^5.1.7",
"prettier": "^3.6.0",
"nuxt-og-image": "^5.1.9",
"prettier": "^3.6.2",
"shiki-transformer-color-highlight": "^1.0.0",
"sortablejs": "^1.15.6",
"superstruct": "^2.0.2",
"ufo": "^1.6.1",
"valibot": "^1.1.0",
"workers-ai-provider": "^0.7.0",
"workers-ai-provider": "^0.7.2",
"yup": "^1.6.1",
"zod": "^3.25.67"
"zod": "^4.0.5"
},
"devDependencies": {
"wrangler": "^4.20.5"
"wrangler": "^4.25.0"
}
}

View File

@@ -1,8 +1,8 @@
{
"name": "@nuxt/ui",
"description": "A UI Library for Modern Web Apps, powered by Vue & Tailwind CSS.",
"version": "3.1.3",
"packageManager": "pnpm@10.12.2",
"version": "3.2.0",
"packageManager": "pnpm@10.13.1",
"repository": {
"type": "git",
"url": "git+https://github.com/nuxt/ui.git"
@@ -98,9 +98,9 @@
"prepack": "pnpm build",
"dev": "nuxt dev playground --uiDev",
"dev:build": "nuxt build playground",
"dev:vue": "vite playground-vue -- --uiDev",
"dev:vue:build": "vite build playground-vue",
"dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxt prepare playground && nuxt prepare docs && vite build playground-vue",
"dev:vue": "pnpm --filter playground-vue dev -- --uiDev",
"dev:vue:build": "pnpm --filter playground-vue build",
"dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxt prepare playground && nuxt prepare docs && pnpm dev:vue:build",
"docs": "nuxt dev docs --uiDev",
"docs:build": "nuxt build docs",
"lint": "eslint .",
@@ -115,17 +115,17 @@
"@internationalized/date": "^3.8.2",
"@internationalized/number": "^3.6.3",
"@nuxt/fonts": "^0.11.4",
"@nuxt/icon": "^1.14.0",
"@nuxt/kit": "^3.17.5",
"@nuxt/schema": "^3.17.5",
"@nuxt/icon": "^1.15.0",
"@nuxt/kit": "^4.0.1",
"@nuxt/schema": "^4.0.1",
"@nuxtjs/color-mode": "^3.5.2",
"@standard-schema/spec": "^1.0.0",
"@tailwindcss/postcss": "^4.1.10",
"@tailwindcss/vite": "^4.1.10",
"@tailwindcss/postcss": "^4.1.11",
"@tailwindcss/vite": "^4.1.11",
"@tanstack/vue-table": "^8.21.3",
"@unhead/vue": "^2.0.10",
"@vueuse/core": "^13.4.0",
"@vueuse/integrations": "^13.4.0",
"@unhead/vue": "^2.0.12",
"@vueuse/core": "^13.5.0",
"@vueuse/integrations": "^13.5.0",
"colortranslator": "^5.0.0",
"consola": "^3.4.2",
"defu": "^6.1.4",
@@ -143,31 +143,31 @@
"mlly": "^1.7.4",
"ohash": "^2.0.11",
"pathe": "^2.0.3",
"reka-ui": "2.3.1",
"reka-ui": "2.3.2",
"scule": "^1.3.0",
"tailwind-variants": "^1.0.0",
"tailwindcss": "^4.1.10",
"tailwindcss": "^4.1.11",
"tinyglobby": "^0.2.14",
"unplugin": "^2.3.5",
"unplugin-auto-import": "^19.3.0",
"unplugin-vue-components": "^28.7.0",
"unplugin-vue-components": "^28.8.0",
"vaul-vue": "0.4.1",
"vue-component-type-helpers": "^2.2.10"
"vue-component-type-helpers": "^3.0.1"
},
"devDependencies": {
"@nuxt/eslint-config": "^1.4.1",
"@nuxt/eslint-config": "^1.6.0",
"@nuxt/module-builder": "^1.0.1",
"@nuxt/test-utils": "^3.19.1",
"@nuxt/test-utils": "^3.19.2",
"@release-it/conventional-changelog": "^10.0.1",
"@vue/test-utils": "^2.4.6",
"embla-carousel": "^8.6.0",
"eslint": "^9.29.0",
"eslint": "^9.31.0",
"happy-dom": "^18.0.1",
"nuxt": "^3.17.5",
"release-it": "^19.0.3",
"nuxt": "^4.0.1",
"release-it": "^19.0.4",
"vitest": "^3.2.4",
"vitest-environment-nuxt": "^1.0.1",
"vue-tsc": "^2.2.10"
"vue-tsc": "^3.0.1"
},
"peerDependencies": {
"@inertiajs/vue3": "^2.0.7",
@@ -177,7 +177,7 @@
"valibot": "^1.0.0",
"vue-router": "^4.5.0",
"yup": "^1.6.0",
"zod": "^3.24.0"
"zod": "^3.24.0 || ^4.0.0"
},
"peerDependenciesMeta": {
"@inertiajs/vue3": {

View File

@@ -13,12 +13,12 @@
"@nuxt/ui": "workspace:*",
"vue": "^3.5.17",
"vue-router": "^4.5.1",
"zod": "^3.25.67"
"zod": "^4.0.5"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.2.4",
"@vitejs/plugin-vue": "^6.0.0",
"typescript": "^5.8.3",
"vite": "^6.3.5",
"vue-tsc": "^2.2.10"
"vite": "^7.0.5",
"vue-tsc": "^3.0.1"
}
}

View File

@@ -1,12 +1,16 @@
<script setup lang="ts">
const colorHex = ref('#9C27B0')
function handleColorChange(event: Event) {
colorHex.value = (event.target as HTMLInputElement).value
}
</script>
<template>
<div class="flex flex-col gap-5">
<div class="flex items-center gap-2">
<span :style="{ backgroundColor: colorHex }" class="inline-flex w-5 h-5 rounded" />
<code class="font-mono">{{ colorHex }}</code>
<UInput :model-value="colorHex" @change="handleColorChange" />
</div>
<USeparator />
<div class="flex justify-between gap-2">
@@ -21,6 +25,6 @@ const colorHex = ref('#9C27B0')
</UButton>
</div>
<USeparator />
<UColorPicker v-model="colorHex" @update:model-value="() => console.log('model update')" />
<UColorPicker v-model="colorHex" />
</div>
</template>

View File

@@ -166,7 +166,27 @@ defineShortcuts({
multiple
class="sm:max-h-80"
@update:model-value="onSelect"
/>
>
<template #footer>
<div class="flex items-center justify-between gap-2">
<UIcon name="i-simple-icons-nuxtdotjs" class="size-5 text-dimmed ml-1" />
<div class="flex items-center gap-1">
<UButton color="neutral" variant="ghost" label="Open Command" class="text-dimmed" size="xs">
<template #trailing>
<UKbd value="enter" />
</template>
</UButton>
<USeparator orientation="vertical" class="h-4" />
<UButton color="neutral" variant="ghost" label="Actions" class="text-dimmed" size="xs">
<template #trailing>
<UKbd value="meta" />
<UKbd value="k" />
</template>
</UButton>
</div>
</div>
</template>
</UCommandPalette>
</DefineTemplate>
<div class="flex-1 flex flex-col gap-12 w-full max-w-lg">

View File

@@ -28,6 +28,20 @@ const inset = ref(false)
</template>
</UDrawer>
<UDrawer title="Drawer with nested" :inset="inset" :ui="{ content: 'h-full' }" should-scale-background>
<UButton color="neutral" variant="outline" label="Open nested" />
<template #footer>
<UDrawer :inset="inset" nested :ui="{ content: 'h-full' }">
<UButton color="neutral" variant="outline" label="Open nested" />
<template #content>
<Placeholder class="flex-1 m-4" />
</template>
</UDrawer>
</template>
</UDrawer>
<UDrawer title="Drawer with bottom direction" direction="bottom" :inset="inset">
<UButton color="neutral" variant="outline" label="Open on bottom" />

View File

@@ -3,20 +3,16 @@ import theme from '#build/ui/kbd'
import { kbdKeysMap } from '@nuxt/ui/composables/useKbd.js'
const sizes = Object.keys(theme.variants.size) as Array<keyof typeof theme.variants.size>
const variants = Object.keys(theme.variants.variant) as Array<keyof typeof theme.variants.variant>
const colors = Object.keys(theme.variants.color) as Array<keyof typeof theme.variants.color>
const kbdKeys = Object.keys(kbdKeysMap)
</script>
<template>
<div class="flex flex-col gap-2">
<div class="flex items-center gap-1">
<UKbd value="meta" />
</div>
<div class="flex items-center gap-1">
<UKbd value="meta" variant="subtle" />
</div>
<div class="flex items-center gap-1">
<UKbd value="meta" variant="solid" />
<div v-for="color in colors" :key="color" class="flex items-center gap-1 ms-[-22px]">
<UKbd v-for="variant in variants" :key="`${color}-${variant}`" value="meta" :variant="variant" :color="color" />
</div>
<div class="flex items-center gap-1 ms-[-220px]">
<UKbd v-for="(kdbKey, index) in kbdKeys" :key="index" :value="kdbKey" />

View File

@@ -3,7 +3,7 @@ import { h, resolveComponent } from 'vue'
import { upperFirst } from 'scule'
import type { TableColumn, TableRow } from '@nuxt/ui'
import { getPaginationRowModel } from '@tanstack/vue-table'
import { useClipboard } from '@vueuse/core'
import { useClipboard, refDebounced } from '@vueuse/core'
const UButton = resolveComponent('UButton')
const UCheckbox = resolveComponent('UCheckbox')
@@ -147,6 +147,35 @@ const data = ref<Payment[]>([{
const currentID = ref(4601)
function getRowItems(row: TableRow<Payment>) {
return [{
type: 'label' as const,
label: 'Actions'
}, {
label: 'Copy payment ID',
onSelect() {
copy(row.original.id)
toast.add({
title: 'Payment ID copied to clipboard!',
color: 'success',
icon: 'i-lucide-circle-check'
})
}
}, {
label: row.getIsExpanded() ? 'Collapse' : 'Expand',
onSelect() {
row.toggleExpanded()
}
}, {
type: 'separator' as const
}, {
label: 'View customer'
}, {
label: 'View payment details'
}]
}
const columns: TableColumn<Payment>[] = [{
id: 'select',
header: ({ table }) => h(UCheckbox, {
@@ -213,6 +242,16 @@ const columns: TableColumn<Payment>[] = [{
}, {
accessorKey: 'amount',
header: () => h('div', { class: 'text-right' }, 'Amount'),
footer: ({ column }) => {
const total = column.getFacetedRowModel().rows.reduce((acc: number, row: TableRow<Payment>) => acc + Number.parseFloat(row.getValue('amount')), 0)
const formatted = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(total)
return h('div', { class: 'text-right font-medium' }, `Total: ${formatted}`)
},
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
@@ -227,38 +266,11 @@ const columns: TableColumn<Payment>[] = [{
id: 'actions',
enableHiding: false,
cell: ({ row }) => {
const items = [{
type: 'label',
label: 'Actions'
}, {
label: 'Copy payment ID',
onSelect() {
copy(row.original.id)
toast.add({
title: 'Payment ID copied to clipboard!',
color: 'success',
icon: 'i-lucide-circle-check'
})
}
}, {
label: row.getIsExpanded() ? 'Collapse' : 'Expand',
onSelect() {
row.toggleExpanded()
}
}, {
type: 'separator'
}, {
label: 'View customer'
}, {
label: 'View payment details'
}]
return h('div', { class: 'text-right' }, h(UDropdownMenu, {
'content': {
align: 'end'
},
items,
'items': getRowItems(row),
'aria-label': 'Actions dropdown'
}, () => h(UButton, {
'icon': 'i-lucide-ellipsis-vertical',
@@ -296,8 +308,41 @@ function randomize() {
data.value = data.value.sort(() => Math.random() - 0.5)
}
const rowSelection = ref<Record<string, boolean>>({})
function onSelect(row: TableRow<Payment>) {
console.log(row)
row.toggleSelected(!row.getIsSelected())
}
const contextmenuRow = ref<TableRow<Payment> | null>(null)
const contextmenuItems = computed(() => contextmenuRow.value ? getRowItems(contextmenuRow.value) : [])
function onContextmenu(e: Event, row: TableRow<Payment>) {
contextmenuRow.value = row
}
const popoverOpen = ref(false)
const popoverOpenDebounced = refDebounced(popoverOpen, 1)
const popoverAnchor = ref({ x: 0, y: 0 })
const popoverRow = ref<TableRow<Payment> | null>(null)
const reference = computed(() => ({
getBoundingClientRect: () =>
({
width: 0,
height: 0,
left: popoverAnchor.value.x,
right: popoverAnchor.value.x,
top: popoverAnchor.value.y,
bottom: popoverAnchor.value.y,
...popoverAnchor.value
} as DOMRect)
}))
function onHover(_e: Event, row: TableRow<Payment> | null) {
popoverRow.value = row
popoverOpen.value = !!row
}
onMounted(() => {
@@ -344,27 +389,44 @@ onMounted(() => {
</UDropdownMenu>
</div>
<UTable
ref="table"
:data="data"
:columns="columns"
:column-pinning="columnPinning"
:loading="loading"
:pagination="pagination"
:pagination-options="{
getPaginationRowModel: getPaginationRowModel()
}"
:ui="{
tr: 'divide-x divide-default'
}"
sticky
class="border border-accented rounded-sm"
@select="onSelect"
>
<template #expanded="{ row }">
<pre>{{ row.original }}</pre>
<UContextMenu :items="contextmenuItems">
<UTable
ref="table"
:data="data"
:columns="columns"
:column-pinning="columnPinning"
:row-selection="rowSelection"
:loading="loading"
:pagination="pagination"
:pagination-options="{
getPaginationRowModel: getPaginationRowModel()
}"
:ui="{
tr: 'divide-x divide-default'
}"
sticky
class="border border-accented rounded-sm"
@select="onSelect"
@contextmenu="onContextmenu"
@pointermove="(ev: PointerEvent) => {
popoverAnchor.x = ev.clientX
popoverAnchor.y = ev.clientY
}"
@hover="onHover"
>
<template #expanded="{ row }">
<pre>{{ row.original }}</pre>
</template>
</UTable>
</UContextMenu>
<UPopover :content="{ side: 'top', sideOffset: 16, updatePositionStrategy: 'always' }" :open="popoverOpenDebounced" :reference="reference">
<template #content>
<div class="p-4">
{{ popoverRow?.original?.id }}
</div>
</template>
</UTable>
</UPopover>
<div class="flex items-center justify-between gap-3">
<div class="text-sm text-muted">

View File

@@ -25,7 +25,8 @@ const items = [{
label: 'Tab3',
icon: 'i-lucide-bell',
content: 'Finally, this is the content for Tab3',
slot: 'custom' as const
slot: 'custom' as const,
badge: '300'
}]
</script>

View File

@@ -10,10 +10,6 @@ export default defineNuxtConfig({
css: ['~/assets/css/main.css'],
future: {
compatibilityVersion: 4
},
compatibilityDate: '2024-07-09',
vite: {

View File

@@ -9,17 +9,17 @@
"typecheck": "nuxt typecheck"
},
"dependencies": {
"@iconify-json/lucide": "^1.2.51",
"@iconify-json/simple-icons": "^1.2.39",
"@iconify-json/lucide": "^1.2.57",
"@iconify-json/simple-icons": "^1.2.44",
"@internationalized/date": "^3.8.2",
"@nuxt/ui": "workspace:*",
"@nuxthub/core": "^0.9.0",
"nuxt": "^3.17.5",
"zod": "^3.25.67"
"nuxt": "^4.0.1",
"zod": "^4.0.5"
},
"devDependencies": {
"typescript": "^5.8.3",
"vue-tsc": "^2.2.10"
"vue-tsc": "^3.0.1"
},
"resolutions": {
"unimport": "4.1.1"

4042
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -22,6 +22,12 @@
"reka-ui",
"vaul-vue"
]
}, {
"groupName": "vue-tsc",
"matchPackageNames": [
"vue-tsc",
"vue-component-type-helpers"
]
}, {
"matchDepTypes": ["peerDependencies"],
"enabled": false

View File

@@ -6,6 +6,9 @@ import { name, version } from '../package.json'
export type * from './runtime/types'
type Color = 'primary' | 'secondary' | 'success' | 'info' | 'warning' | 'error' | (string & {})
type Size = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | (string & {})
export interface ModuleOptions {
/**
* Prefix for components
@@ -38,7 +41,7 @@ export interface ModuleOptions {
* @defaultValue `['primary', 'secondary', 'success', 'info', 'warning', 'error']`
* @link https://ui.nuxt.com/getting-started/installation/nuxt#themecolors
*/
colors?: string[]
colors?: Color[]
/**
* Enable or disable transitions on components
@@ -46,6 +49,20 @@ export interface ModuleOptions {
* @link https://ui.nuxt.com/getting-started/installation/nuxt#themetransitions
*/
transitions?: boolean
defaultVariants?: {
/**
* The default color variant to use for components
* @defaultValue `'primary'`
*/
color?: Color
/**
* The default size variant to use for components
* @defaultValue `'md'`
*/
size?: Size
}
}
}
@@ -85,7 +102,7 @@ export default defineNuxtModule<ModuleOptions>({
async function registerModule(name: string, key: string, options: Record<string, any>) {
if (!hasNuxtModule(name)) {
await installModule(name, options)
await installModule(name, defu((nuxt.options as any)[key], options))
} else {
(nuxt.options as any)[key] = defu((nuxt.options as any)[key], options)
}

View File

@@ -11,7 +11,7 @@ import { defu } from 'defu'
/**
* This plugin adds all the Nuxt UI components as auto-imports.
*/
export default function ComponentImportPlugin(options: NuxtUIOptions & { prefix: NonNullable<NuxtUIOptions['prefix']> }, meta: UnpluginContextMeta) {
export default function ComponentImportPlugin(options: NuxtUIOptions & { prefix: NonNullable<NuxtUIOptions['prefix']>, extraRuntimeDir?: string }, meta: UnpluginContextMeta) {
const components = globSync('**/*.vue', { cwd: join(runtimeDir, 'components') })
const componentNames = new Set(components.map(c => `${options.prefix}${c.replace(/\.vue$/, '')}`))
@@ -50,13 +50,15 @@ export default function ComponentImportPlugin(options: NuxtUIOptions & { prefix:
name: 'nuxt:ui:components',
enforce: 'pre',
resolveId(id, importer) {
// only apply to runtime nuxt ui components
if (!importer || !normalize(importer).includes(runtimeDir)) {
if (!importer) {
return
}
if (!normalize(importer).includes(runtimeDir) && (!options.extraRuntimeDir || !normalize(importer).includes(options.extraRuntimeDir))) {
return
}
// only apply to relative imports
if (!RELATIVE_IMPORT_RE.test(id)) {
// only apply to relative imports or nuxt ui runtime components
if (!RELATIVE_IMPORT_RE.test(id) && !id.startsWith('@nuxt/ui/components/')) {
return
}

View File

@@ -3,7 +3,8 @@ import { normalize } from 'pathe'
import { resolvePathSync } from 'mlly'
import MagicString from 'magic-string'
import { runtimeDir, type NuxtUIOptions } from '../unplugin'
import { runtimeDir } from '../unplugin'
import type { NuxtUIOptions } from '../unplugin'
/**
* This plugin normalises Nuxt environment (#imports) and `import.meta.client` within the Nuxt UI components.

View File

@@ -4,7 +4,8 @@ import { genSafeVariableName } from 'knitwork'
import MagicString from 'magic-string'
import { resolvePathSync } from 'mlly'
import { runtimeDir, type NuxtUIOptions } from '../unplugin'
import { runtimeDir } from '../unplugin'
import type { NuxtUIOptions } from '../unplugin'
import type { UnpluginOptions } from 'unplugin'

View File

@@ -57,7 +57,7 @@ export interface AlertProps {
}
export interface AlertEmits {
(e: 'update:open', value: boolean): void
'update:open': [value: boolean]
}
export interface AlertSlots {

View File

@@ -42,14 +42,15 @@ export interface ButtonSlots {
</script>
<script setup lang="ts">
import { type Ref, computed, ref, inject } from 'vue'
import { computed, ref, inject } from 'vue'
import type { Ref } from 'vue'
import { defu } from 'defu'
import { useForwardProps } from 'reka-ui'
import { useAppConfig } from '#imports'
import { useComponentIcons } from '../composables/useComponentIcons'
import { useButtonGroup } from '../composables/useButtonGroup'
import { formLoadingInjectionKey } from '../composables/useFormField'
import { omit } from '../utils'
import { omit, mergeClasses } from '../utils'
import { tv } from '../utils/tv'
import { pickLinkProps } from '../utils/link'
import UIcon from './Icon.vue'
@@ -57,11 +58,7 @@ import UAvatar from './Avatar.vue'
import ULink from './Link.vue'
import ULinkBase from './LinkBase.vue'
const props = withDefaults(defineProps<ButtonProps>(), {
active: undefined,
activeClass: '',
inactiveClass: ''
})
const props = defineProps<ButtonProps>()
const slots = defineSlots<ButtonSlots>()
const appConfig = useAppConfig() as Button['AppConfig']
@@ -96,10 +93,10 @@ const ui = computed(() => tv({
variants: {
active: {
true: {
base: props.activeClass
base: mergeClasses(appConfig.ui?.button?.variants?.active?.true?.base, props.activeClass)
},
false: {
base: props.inactiveClass
base: mergeClasses(appConfig.ui?.button?.variants?.active?.false?.base, props.inactiveClass)
}
}
}

View File

@@ -118,7 +118,7 @@ export interface CarouselEmits {
import { computed, ref, watch, onMounted } from 'vue'
import useEmblaCarousel from 'embla-carousel-vue'
import { Primitive, useForwardProps } from 'reka-ui'
import { reactivePick, computedAsync } from '@vueuse/core'
import { reactivePick } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { useLocale } from '../composables/useLocale'
import { tv } from '../utils/tv'
@@ -175,41 +175,45 @@ const options = computed<EmblaOptionsType>(() => ({
direction: dir.value === 'rtl' ? 'rtl' : 'ltr'
}))
const plugins = computedAsync<EmblaPluginType[]>(async () => {
const plugins = []
const plugins = ref<EmblaPluginType[]>([])
async function loadPlugins() {
const emblaPlugins: EmblaPluginType[] = []
if (props.autoplay) {
const AutoplayPlugin = await import('embla-carousel-autoplay').then(r => r.default)
plugins.push(AutoplayPlugin(typeof props.autoplay === 'boolean' ? {} : props.autoplay))
emblaPlugins.push(AutoplayPlugin(typeof props.autoplay === 'boolean' ? {} : props.autoplay))
}
if (props.autoScroll) {
const AutoScrollPlugin = await import('embla-carousel-auto-scroll').then(r => r.default)
plugins.push(AutoScrollPlugin(typeof props.autoScroll === 'boolean' ? {} : props.autoScroll))
emblaPlugins.push(AutoScrollPlugin(typeof props.autoScroll === 'boolean' ? {} : props.autoScroll))
}
if (props.autoHeight) {
const AutoHeightPlugin = await import('embla-carousel-auto-height').then(r => r.default)
plugins.push(AutoHeightPlugin(typeof props.autoHeight === 'boolean' ? {} : props.autoHeight))
emblaPlugins.push(AutoHeightPlugin(typeof props.autoHeight === 'boolean' ? {} : props.autoHeight))
}
if (props.classNames) {
const ClassNamesPlugin = await import('embla-carousel-class-names').then(r => r.default)
plugins.push(ClassNamesPlugin(typeof props.classNames === 'boolean' ? {} : props.classNames))
emblaPlugins.push(ClassNamesPlugin(typeof props.classNames === 'boolean' ? {} : props.classNames))
}
if (props.fade) {
const FadePlugin = await import('embla-carousel-fade').then(r => r.default)
plugins.push(FadePlugin(typeof props.fade === 'boolean' ? {} : props.fade))
emblaPlugins.push(FadePlugin(typeof props.fade === 'boolean' ? {} : props.fade))
}
if (props.wheelGestures) {
const { WheelGesturesPlugin } = await import('embla-carousel-wheel-gestures')
plugins.push(WheelGesturesPlugin(typeof props.wheelGestures === 'boolean' ? {} : props.wheelGestures))
emblaPlugins.push(WheelGesturesPlugin(typeof props.wheelGestures === 'boolean' ? {} : props.wheelGestures))
}
return plugins
})
plugins.value = emblaPlugins
}
watch(() => [props.autoplay, props.autoScroll, props.autoHeight, props.classNames, props.fade, props.wheelGestures], loadPlugins, { immediate: true })
const [emblaRef, emblaApi] = useEmblaCarousel(options.value, plugins.value)
@@ -252,6 +256,7 @@ const scrollSnaps = ref<number[]>([])
function onInit(api: EmblaCarouselType) {
scrollSnaps.value = api?.scrollSnapList() || []
}
function onSelect(api: EmblaCarouselType) {
canScrollNext.value = api?.canScrollNext() || false
canScrollPrev.value = api?.canScrollPrev() || false
@@ -296,8 +301,7 @@ defineExpose({
<div
v-for="(item, index) in items"
:key="index"
role="group"
aria-roledescription="slide"
v-bind="dots ? { role: 'tabpanel' } : { 'role': 'group', 'aria-roledescription': 'slide' }"
:class="ui.item({ class: [props.ui?.item, isCarouselItem(item) && item.ui?.item, isCarouselItem(item) && item.class] })"
>
<slot :item="item" :index="index" />
@@ -329,10 +333,13 @@ defineExpose({
/>
</div>
<div v-if="dots" :class="ui.dots({ class: props.ui?.dots })">
<div v-if="dots" role="tablist" :aria-label="t('carousel.dots')" :class="ui.dots({ class: props.ui?.dots })">
<template v-for="(_, index) in scrollSnaps" :key="index">
<button
type="button"
role="tab"
:aria-label="t('carousel.goto', { slide: index + 1 })"
:aria-selected="selectedIndex === index"
:class="ui.dot({ class: props.ui?.dot, active: selectedIndex === index })"
:data-state="selectedIndex === index ? 'active' : undefined"
@click="scrollTo(index)"

View File

@@ -35,7 +35,7 @@ export interface ChipProps {
}
export interface ChipEmits {
(e: 'update:show', payload: boolean): void
'update:show': [payload: boolean]
}
export interface ChipSlots {

View File

@@ -31,7 +31,7 @@ function HSVtoHSL(hsv: HSVColor): HSLObject {
return {
H: hsv.h,
S: x === 0 || x === 200 ? 0 : Math.round(hsv.s * hsv.v / (x <= 100 ? x : 200 - x)),
L: Math.round(x / 2)
L: x / 2
}
}
@@ -102,7 +102,6 @@ const pickedColor = computed<HSVColor>({
},
set(value) {
const color = new ColorTranslator(HSVtoHSL(value), {
decimals: 2,
labUnit: 'percent',
cmykUnit: 'percent',
cmykFunction: 'cmyk'

View File

@@ -147,6 +147,7 @@ type SlotProps<T> = (props: { item: T, index: number }) => any
export type CommandPaletteSlots<G extends CommandPaletteGroup<T> = CommandPaletteGroup<any>, T extends CommandPaletteItem = CommandPaletteItem> = {
'empty'(props: { searchTerm?: string }): any
'footer'(props: { ui: { [K in keyof Required<CommandPalette['slots']>]: (props?: Record<string, any>) => string } }): any
'back'(props: { ui: { [K in keyof Required<CommandPalette['slots']>]: (props?: Record<string, any>) => string } }): any
'close'(props: { ui: { [K in keyof Required<CommandPalette['slots']>]: (props?: Record<string, any>) => string } }): any
'item': SlotProps<T>
@@ -444,5 +445,9 @@ function onSelect(e: Event, item: T) {
</slot>
</div>
</ListboxContent>
<div v-if="!!slots.footer" :class="ui.footer({ class: props.ui?.footer })">
<slot name="footer" :ui="ui" />
</div>
</ListboxRoot>
</template>

View File

@@ -47,7 +47,6 @@ import ULink from './Link.vue'
import UAvatar from './Avatar.vue'
import UIcon from './Icon.vue'
import UKbd from './Kbd.vue'
// eslint-disable-next-line import/no-self-import
import UContextMenuContent from './ContextMenuContent.vue'
const props = defineProps<ContextMenuContentProps<T>>()

View File

@@ -37,6 +37,11 @@ export interface DrawerProps extends Pick<DrawerRootProps, 'activeSnapPoint' | '
* @defaultValue true
*/
portal?: boolean | string | HTMLElement
/**
* Whether the drawer is nested in another drawer.
* @defaultValue false
*/
nested?: boolean
class?: any
ui?: Drawer['slots']
}
@@ -57,7 +62,7 @@ export interface DrawerSlots {
<script setup lang="ts">
import { computed, toRef } from 'vue'
import { VisuallyHidden, useForwardPropsEmits } from 'reka-ui'
import { DrawerRoot, DrawerTrigger, DrawerPortal, DrawerOverlay, DrawerContent, DrawerTitle, DrawerDescription, DrawerHandle } from 'vaul-vue'
import { DrawerRoot, DrawerRootNested, DrawerTrigger, DrawerPortal, DrawerOverlay, DrawerContent, DrawerTitle, DrawerDescription, DrawerHandle } from 'vaul-vue'
import { reactivePick } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { usePortal } from '../composables/usePortal'
@@ -90,7 +95,7 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.drawer || {}
</script>
<template>
<DrawerRoot v-bind="rootProps">
<component :is="nested ? DrawerRootNested : DrawerRoot" v-bind="rootProps">
<DrawerTrigger v-if="!!slots.default" as-child :class="props.class">
<slot />
</DrawerTrigger>
@@ -144,5 +149,5 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.drawer || {}
</slot>
</DrawerContent>
</DrawerPortal>
</DrawerRoot>
</component>
</template>

View File

@@ -53,7 +53,6 @@ import ULink from './Link.vue'
import UAvatar from './Avatar.vue'
import UIcon from './Icon.vue'
import UKbd from './Kbd.vue'
// eslint-disable-next-line import/no-self-import
import UDropdownMenuContent from './DropdownMenuContent.vue'
const props = defineProps<DropdownMenuContentProps<T>>()

Some files were not shown because too many files have changed in this diff Show More