Compare commits

..

144 Commits

Author SHA1 Message Date
Benjamin Canac
17a96416f0 chore(release): 2.14.2 2024-03-05 12:21:34 +01:00
renovate[bot]
4378268117 chore(deps): update devdependency @nuxt/ui-pro to v1.0.1-28492961.4d49b9c (#1471)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-04 21:38:46 +01:00
Benjamin Canac
622aef5ffe docs: limit search results to 42 2024-03-04 19:27:09 +01:00
Benjamin Canac
174af36000 docs: switch to @nuxt/fonts 2024-03-04 18:21:41 +01:00
Benjamin Canac
2d64b50559 fix(RadioGroup): add missing fieldset config
Resolves #1472
2024-03-04 18:06:49 +01:00
Benjamin Canac
272c19de70 fix(Tooltip): arrow not hidden on mobile
Resolves #1470
2024-03-04 16:27:01 +01:00
Alex Thorwaldson
6a1142b403 fix(Dropdown): active/inactive dropdown links (#1407)
Co-authored-by: gangan <44604921+shinGangan@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2024-03-04 16:11:41 +01:00
renovate[bot]
9937951fb7 chore(deps): update all non-major dependencies (#1440)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-04 16:11:05 +01:00
kmilogp
002129c299 fix(Modal): remove overflow-hidden (#1460)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2024-03-04 12:58:56 +01:00
Benjamin Canac
0c2f655a27 docs(Range): revert example as it shows in the docs 2024-03-04 12:47:51 +01:00
renovate[bot]
fb16735dec chore(deps): update devdependency vue-tsc to v2 (#1454)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-04 12:33:38 +01:00
Mukund Shah
aa6e523780 docs(Notification): add Notifications props and config (#1449) 2024-03-04 11:56:18 +01:00
David Parys
bfd15c1818 docs(Link): add IntelliSense section (#1442) 2024-03-04 11:55:17 +01:00
Rodion
027d85402b docs(FormGroup): wrong icon name in #help slot (#1451) 2024-03-04 11:54:44 +01:00
roiLeo
99b9467dc2 docs(SelectMenu): invalid anchor to creatable (#1453) 2024-03-04 11:53:52 +01:00
Benjamin Canac
70bf4a7392 fix(Dropdown): improve hover mode on touch devices 2024-03-04 11:47:39 +01:00
Benjamin Canac
b50fbcf760 fix(Popover): improve hover mode on touch devices 2024-03-04 11:47:34 +01:00
Benjamin Canac
b74bf9f666 chore(Tooltip): use mouseenter instead of mouseover 2024-03-04 11:45:01 +01:00
renovate[bot]
c0feca136a chore(deps): update all non-major dependencies (#1430)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-28 17:21:00 +01:00
Benjamin Canac
0a4a9e3d2c fix(HorizontalNavigation): add relative class to icon 2024-02-28 16:26:16 +01:00
Benjamin Canac
0b29dd4ca5 fix(VerticalNavigation): add relative class to icon
Resolves #1245
2024-02-28 16:25:20 +01:00
Benjamin Canac
9cce4456d0 fix(Notification): improve description alignment when no title provided 2024-02-28 12:52:53 +01:00
Benjamin Canac
ca4f06a313 fix(Alert): improve description alignment when no title provided
Resolves #1408
2024-02-28 12:52:43 +01:00
Benjamin Canac
7dd9ee528e docs(date-picker): improve examples responsive 2024-02-28 11:25:59 +01:00
Shoshana Connack
cdf6ff7152 docs(date-picker): include date-fns in installation (#1434) 2024-02-27 14:26:25 +01:00
Benjamin Canac
9c2104d947 docs: remove banner on examples 2024-02-27 09:18:53 +01:00
Benjamin Canac
d1c8026a1e docs(deps): bump @nuxt/ui-pro 2024-02-27 09:18:53 +01:00
Benjamin Canac
14efa81986 chore(deps): refresh lock 2024-02-27 09:18:53 +01:00
renovate[bot]
b3314dc16b chore(deps): update all non-major dependencies (#1406)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-27 09:18:53 +01:00
Benjamin Canac
06135f38ae docs(home): improve lighthouse 2024-02-26 12:56:40 +01:00
Benjamin Canac
dbbab8ded0 chore(CommandPaletteGroup): remove useless role attributes 2024-02-26 12:56:40 +01:00
Romain Hamel
6e77f1d514 fix(Checkbox): label interaction without FormGroup (#1427) 2024-02-26 12:55:30 +01:00
Benjamin Canac
4b6e80e364 fix(SelectMenu): check null model value
Resolves #1421
2024-02-26 11:37:32 +01:00
Benjamin Canac
8a1b112727 docs(deps): bump @nuxt/ui-pro 2024-02-26 11:37:32 +01:00
Benjamin Canac
961f0ae27b docs: update banner 2024-02-26 11:37:32 +01:00
Benjamin Canac
e819656a34 docs: move headlessui links before github 2024-02-26 11:37:32 +01:00
Sébastien Chopin
024e03acc9 docs(breadcrumb): add link to github (#1414) 2024-02-26 11:37:32 +01:00
renovate[bot]
462d7729c9 chore(deps): update devdependency @nuxt/ui-pro to v1.0.0-28478433.ed477f1 (#1405)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-26 11:37:31 +01:00
gangan
df857fd541 chore(renovate): update extends (#1380) 2024-02-26 11:37:31 +01:00
sdezza
9b208bf297 docs(modal): mention how to programatically close (#1423) 2024-02-26 11:24:02 +01:00
Benjamin Canac
5af9da4d3c chore(release): 2.14.1 2024-02-23 17:25:52 +01:00
Benjamin Canac
1d995136a5 chore(release-it): remove pnpm test 2024-02-23 17:22:24 +01:00
Benjamin Canac
ba15add4db fix(module): revert tailwind config from #1272 (#1404) 2024-02-23 16:35:08 +01:00
Benjamin Canac
0aca478c57 chore(deps): remove ^ from @nuxt/kit resolution 2024-02-23 10:54:53 +01:00
Benjamin Canac
0ee4f2b75b chore(release-it): update config 2024-02-22 17:57:39 +01:00
Benjamin Canac
6e53cb6281 chore(deps): refresh lock 2024-02-22 15:40:12 +01:00
renovate[bot]
de715304dc chore(deps): update all non-major dependencies (#1399)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2024-02-22 15:31:59 +01:00
Benjamin Canac
81854112e3 docs: add pricing banner (#1391) 2024-02-22 15:30:38 +01:00
Benjamin Canac
87526b9ec5 docs: rename UDocs components to UContent 2024-02-22 15:28:44 +01:00
Benjamin Canac
f79187825f docs(deps): bump @nuxt/ui-pro 2024-02-22 15:28:31 +01:00
Benjamin Canac
0443ac2c9d chore(deps): remove ^ from resolutions 2024-02-22 15:28:11 +01:00
Benjamin Canac
d2c51e3667 docs(home): improve pro divider 2024-02-22 15:09:02 +01:00
Benjamin Canac
d15d7fa01d docs(home): improve pro section 2024-02-22 13:07:23 +01:00
Benjamin Canac
df32b3131b chore(release): 2.14.0 2024-02-22 12:06:52 +01:00
Benjamin Canac
d96d17d7e6 chore(readme): update 2024-02-22 11:42:06 +01:00
Benjamin Canac
b6c69441f5 docs(index): invalid component links 2024-02-21 23:11:59 +01:00
Benjamin Canac
33f3372c6b docs(nuxt.config): highlight mdc 2024-02-21 23:11:47 +01:00
renovate[bot]
613ba2db64 chore(deps): update devdependency @nuxt/ui-pro to v0.7.5-28475621.09eb8fa (#1394)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-21 20:02:36 +01:00
Benjamin Canac
9f352976ce fix(utils): prevent merge of popper key
Resolves #1393
2024-02-21 16:51:42 +01:00
Benjamin Canac
f83cff7095 chore(utils): prevent default prop merge for chip and badge 2024-02-21 16:34:34 +01:00
Benjamin Canac
433c09a9f3 docs(nuxt.config): highlight postcss lang 2024-02-21 16:34:34 +01:00
renovate[bot]
930337bf88 chore(deps): update all non-major dependencies (#1381)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-21 14:21:15 +01:00
Benjamin Canac
81e48ba9fd docs(home): add video 2024-02-21 12:24:30 +01:00
Benjamin Canac
cb2fd1e940 docs: consistent app.vue and error.vue 2024-02-20 10:47:50 +01:00
Benjamin Canac
6d4eac0dec docs: update figma link 2024-02-20 10:43:56 +01:00
Benjamin Canac
f4f6a8fcc1 chore(deps): refresh lock 2024-02-19 16:41:22 +01:00
renovate[bot]
920070cce0 chore(deps): update all non-major dependencies (#1347)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-19 16:23:10 +01:00
Benjamin Canac
d12b00c005 chore(useFormGroup): indentation 2024-02-19 12:23:25 +01:00
Benjamin Canac
3a142896c3 chore(useButtonGroup): indentation 2024-02-19 12:22:50 +01:00
Benjamin Canac
58682cec0c docs(theming): broken link for ui.config file
Resolves #1372
2024-02-18 20:01:21 +01:00
Romain Hamel
37ef7a4e4f docs(form): improve form documentation (#1373) 2024-02-18 18:30:20 +01:00
Amir Reza Dalir
5266591c88 fix(Form): improve validate path type (#1370)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2024-02-18 11:15:15 +01:00
Romain Hamel
d4b6147fcc fix(Form): return false when silent validation fails (#1371) 2024-02-18 11:11:07 +01:00
Benjamin Canac
31232d4d72 chore(Table): use px-4 in td and th for consistency 2024-02-15 15:29:52 +01:00
Benjamin Canac
3fe35217cb feat(Table): display progress bar when loading (#1362) 2024-02-15 12:37:44 +01:00
renovate[bot]
04ef47376d chore(deps): update dorny/paths-filter action to v3 (#1360)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-15 12:32:02 +01:00
Benjamin Canac
aa2b1cae88 fix(Notification): remove required title to prevent warning when using slot 2024-02-15 12:22:50 +01:00
Benjamin Canac
e545b6f0a1 fix(Alert): remove required title to prevent warning when using slot 2024-02-15 12:22:33 +01:00
Benjamin Canac
db42d9cab7 chore(Progress): define ProgressColor type 2024-02-15 12:14:00 +01:00
Benjamin Canac
b11c773f32 chore(Range): export RangeColor type 2024-02-15 12:14:00 +01:00
Benjamin Canac
c34df13e65 chore(Toggle): export ToggleColor type 2024-02-15 12:14:00 +01:00
Benjamin Canac
a55a08a91e fix(Progress): prevent NaN percent display when indeterminate 2024-02-15 12:13:37 +01:00
Benjamin Canac
c488b28c3c docs: use lang="ts" everywhere 2024-02-14 17:41:10 +01:00
Benjamin Canac
300861a49e docs(deps): remove @nuxthq/studio 2024-02-14 14:47:49 +01:00
Benjamin Canac
09a8e2d8c2 chore(deps): refresh lock 2024-02-12 12:00:48 +01:00
Benjamin Canac
7eba5b539a chore(FormGroup): wrap label & description to ease styling 2024-02-12 11:59:18 +01:00
renovate[bot]
19d15b42f0 chore(deps): update all non-major dependencies (#1334)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-12 11:37:32 +01:00
Richard van Driest
e23d4aaf53 docs(table): correct spelling (#1340) 2024-02-09 11:40:02 +01:00
Benjamin Canac
e1fb8e438d chore(Modals): ensure modalState exists 2024-02-08 17:18:48 +01:00
Benjamin Canac
f682905b26 fix(Card): prevent body padding without default slot 2024-02-08 12:49:38 +01:00
Benjamin Canac
f5fa9fe163 docs: add New badges on edge 2024-02-07 21:48:21 +01:00
Benjamin Canac
627a44bb1f docs: remove New badges on edge 2024-02-07 21:42:33 +01:00
Benjamin Canac
ade99a8f05 chore(Modals): client only component 2024-02-07 21:36:51 +01:00
Benjamin Canac
3295954247 docs(table): prevent overflow on mobile 2024-02-07 21:36:32 +01:00
Benjamin Canac
4f532dbb72 docs(vertical-navigation): improve example 2024-02-07 21:36:18 +01:00
Benjamin Canac
ee0a8f01af docs(carousel): improve examples 2024-02-07 21:31:48 +01:00
Benjamin Canac
b8936070f9 Revert "docs: add missing overflow-hidden on components"
This reverts commit 34adcc1c04.
2024-02-07 21:04:56 +01:00
Neil Richter
6f29c620ab feat(Modal): open programmatically (#1319) 2024-02-07 16:53:17 +01:00
renovate[bot]
98a2d0f1af chore(deps): update all non-major dependencies (#1216)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2024-02-07 16:03:42 +01:00
Benjamin Canac
e08601900e docs(DatePicker): wrong version of v-calendar
Fixes #1333
2024-02-07 14:40:21 +01:00
Benjamin Canac
cf818fba47 docs(home): improve pro animation start time 2024-02-07 14:31:57 +01:00
Benjamin Canac
0c8a649035 docs: add postcss shiki lang highlighter 2024-02-07 14:12:54 +01:00
Benjamin Canac
843a978644 feat(Tabs): add unmount prop as false by default
Resolves #663
2024-02-07 14:12:54 +01:00
adjabaev
cbeede66bb feat(Divider): handle size prop (#1307)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2024-02-07 13:48:11 +01:00
Benjamin Canac
a506cbbcb0 chore(package.json): add missing module type 2024-02-07 12:44:35 +01:00
Benjamin Canac
bb40c31031 fix(module): prevent tailwind warn with bun
Fixes #809
2024-02-07 12:43:56 +01:00
Benjamin Canac
34adcc1c04 docs: add missing overflow-hidden on components 2024-02-06 23:09:27 +01:00
Olusola Olawale
ac42ec106f fix(Link): check disabled prop before navigating (#1321) 2024-02-06 21:45:51 +01:00
Benjamin Canac
c3ed940ac2 docs(deps): bump @nuxt/ui-pro-edge 2024-02-06 19:51:38 +01:00
Benjamin Canac
7c74c2f22a docs: improve config display 2024-02-06 19:51:21 +01:00
Benjamin Canac
d0f4530e85 fix(SelectMenu): revert component is after #1199 2024-02-06 18:15:27 +01:00
Benjamin Canac
f8b296fc60 fix(Meter): missing import of Icon component
Fixes #1328
2024-02-06 18:03:16 +01:00
Benjamin Canac
882247e5f4 fix(Accordion): style disclosure div after #1199 2024-02-06 17:52:58 +01:00
Farnabaz
a297c3b41e docs: move shiki highlighter to composable (#1325) 2024-02-06 16:12:27 +01:00
Benjamin Canac
45121916d0 docs(releases): use head.title
Fixes #1323
2024-02-06 15:38:18 +01:00
Benjamin Canac
6b82429e30 chore(deps): bump 2024-02-06 14:21:11 +01:00
Farnabaz
707753a743 docs(deps): update @nuxt/content (#1310)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2024-02-06 13:06:20 +01:00
Benjamin Canac
10db14475f fix(components): hydration attribute mismatch with vue 3.4 (#1199) 2024-02-06 12:42:19 +01:00
Inesh Bose
4a5f7b06cf chore(tailwind): put empty object in quotes (#1306) 2024-02-05 16:46:31 +01:00
pierre golfier
f643e7b316 feat(Textarea): add maxrows prop to restrict autoresize (#1302)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2024-02-05 16:18:20 +01:00
Benjamin Canac
5a5b284e96 fix(RadioGroup): pass help prop to radio children
Resolves #1313
2024-02-05 15:32:36 +01:00
Benjamin Canac
6699a0519d docs(deps): bump @nuxt/ui-pro-edge 2024-02-05 12:24:54 +01:00
Benjamin Canac
8b08edeee7 chore(module): pass resolve and runtimeDir to installTailwind 2024-02-05 12:16:58 +01:00
Benjamin Canac
41ecd2a3d5 feat(Carousel): expose methods to allow autoplay
Resolves #1300
2024-02-01 18:07:39 +01:00
Benjamin Canac
f36158133e docs: bump @nuxt/ui-pro-edge 2024-02-01 16:55:02 +01:00
Inesh Bose
f0ee1893ee refactor(module): provide tailwind config through template (#1272)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2024-02-01 15:06:28 +01:00
Benjamin Canac
f455dbdd22 docs(releases): lint 2024-02-01 12:57:15 +01:00
Romain Hamel
27c71fa40e feat(Form): use nuxt useId to bind input labels (#1211) 2024-01-31 18:22:02 +01:00
Benjamin Canac
be37daec56 chore: ignore ts errors after nuxt 3.10 2024-01-31 15:05:31 +01:00
Benjamin Canac
9676f51512 chore(deps): update nuxt resolutions 2024-01-31 14:38:22 +01:00
Benjamin Canac
f8ada8042a docs(vercel.json): ignore _payload.json in redirects 2024-01-31 13:52:00 +01:00
Jake
89e15b90b1 docs(carousel): add draggable="false" to image elements (#1297) 2024-01-31 13:04:19 +01:00
Benjamin Canac
5b008b789b docs: put back prerender: false 2024-01-31 13:02:12 +01:00
Benjamin Canac
25d35cf465 docs: display Edge badge in header 2024-01-31 12:08:55 +01:00
Benjamin Canac
ee662986ab docs: remove prerender: false 2024-01-31 11:52:04 +01:00
Benjamin Canac
946a39c739 feat(Input): handle type file
Resolves #563
2024-01-31 11:50:34 +01:00
Benjamin Canac
412cd75edd fix(module): put back all option in icons plugin
Fixes #1237
2024-01-31 10:53:39 +01:00
Benjamin Canac
d0471f66ea chore(deps): update @egoist/tailwindcss-icons 2024-01-31 10:52:14 +01:00
Benjamin Canac
a12f37e4d2 chore(deps): update 2024-01-30 17:32:34 +01:00
Benjamin Canac
b741b42c64 docs: active pro link on /pro/prose 2024-01-30 15:07:44 +01:00
Benjamin Canac
7f8c625b0e docs: add /getting-started/examples redirect 2024-01-30 14:44:00 +01:00
Benjamin Canac
83b6b04eea docs: configure image provider to ipx 2024-01-30 14:26:20 +01:00
Benjamin Canac
aac6fb4334 docs(releases): improve page 2024-01-30 13:14:16 +01:00
Benjamin Canac
ca9f47d7c0 docs: move vercel.json in root dir 2024-01-30 12:37:53 +01:00
135 changed files with 3685 additions and 3350 deletions

View File

@@ -53,7 +53,7 @@ jobs:
restore-keys: |
${{ runner.os }}-pnpm-store-
- uses: dorny/paths-filter@v2
- uses: dorny/paths-filter@v3
id: changes
with:
filters: |

1
.gitignore vendored
View File

@@ -9,3 +9,4 @@ dist
.vercel
.idea
.env
.data

View File

@@ -1,6 +1,7 @@
{
"git": {
"commitMessage": "chore(release): ${version}"
"commitMessage": "chore(release): ${version}",
"tagName": "v${version}"
},
"npm": {
"publish": false
@@ -11,7 +12,7 @@
"web": true
},
"hooks": {
"before:init": ["pnpm lint"]
"before:init": ["pnpm lint", "pnpm typecheck"]
},
"plugins": {
"@release-it/conventional-changelog": {

View File

@@ -1,5 +1,68 @@
# Changelog
## [2.14.2](https://github.com/nuxt/ui/compare/v2.14.1...v2.14.2) (2024-03-05)
### Bug Fixes
* **Alert:** improve `description` alignment when no title provided ([ca4f06a](https://github.com/nuxt/ui/commit/ca4f06a313314af5813007878a9b6c8f1003c6d1)), closes [#1408](https://github.com/nuxt/ui/issues/1408)
* **Checkbox:** label interaction without `FormGroup` ([#1427](https://github.com/nuxt/ui/issues/1427)) ([6e77f1d](https://github.com/nuxt/ui/commit/6e77f1d5144d7d87b0c76b43ecf3d731166c808b))
* **Dropdown:** active/inactive dropdown links ([#1407](https://github.com/nuxt/ui/issues/1407)) ([6a1142b](https://github.com/nuxt/ui/commit/6a1142b4032150def78c69080df455f7d2a25e7b))
* **Dropdown:** improve `hover` mode on touch devices ([70bf4a7](https://github.com/nuxt/ui/commit/70bf4a73921f47fcd41599874b595a6eed947f5a))
* **HorizontalNavigation:** add `relative` class to icon ([0a4a9e3](https://github.com/nuxt/ui/commit/0a4a9e3d2c4a7584570e4ab7ae6fe8265c960a33))
* **Modal:** remove `overflow-hidden` ([#1460](https://github.com/nuxt/ui/issues/1460)) ([002129c](https://github.com/nuxt/ui/commit/002129c29926df5a816288b916194ab28cf4c8a4))
* **Notification:** improve `description` alignment when no title provided ([9cce445](https://github.com/nuxt/ui/commit/9cce4456d03c52daca4d7347e60cbcd7f501317a))
* **Popover:** improve `hover` mode on touch devices ([b50fbcf](https://github.com/nuxt/ui/commit/b50fbcf760e908579e81f6e57234f2080e2bf035))
* **RadioGroup:** add missing `fieldset` config ([2d64b50](https://github.com/nuxt/ui/commit/2d64b50559946b166c02cfe921e63d746cdc09d4)), closes [#1472](https://github.com/nuxt/ui/issues/1472)
* **SelectMenu:** check `null` model value ([4b6e80e](https://github.com/nuxt/ui/commit/4b6e80e3646e263a83614830d9ec6adb0edf2ede)), closes [#1421](https://github.com/nuxt/ui/issues/1421)
* **Tooltip:** arrow not hidden on mobile ([272c19d](https://github.com/nuxt/ui/commit/272c19de708144b1b132b98a7287254974f4e144)), closes [#1470](https://github.com/nuxt/ui/issues/1470)
* **VerticalNavigation:** add `relative` class to icon ([0b29dd4](https://github.com/nuxt/ui/commit/0b29dd4ca560cac7d151132e086eab17c0498a5c)), closes [#1245](https://github.com/nuxt/ui/issues/1245)
## [2.14.1](https://github.com/nuxt/ui/compare/v2.14.0...v2.14.1) (2024-02-23)
### Bug Fixes
* **module:** revert tailwind config from [#1272](https://github.com/nuxt/ui/issues/1272) ([#1404](https://github.com/nuxt/ui/issues/1404)) ([ba15add](https://github.com/nuxt/ui/commit/ba15add4db5d2f84e987819628cbbf88edcbad57))
## [2.14.0](https://github.com/nuxt/ui/compare/v2.13.0...v2.14.0) (2024-02-22)
### Features
* **Carousel:** expose methods to allow autoplay ([41ecd2a](https://github.com/nuxt/ui/commit/41ecd2a3d553886db3e32d9f48a477268d93f3c6)), closes [#1300](https://github.com/nuxt/ui/issues/1300)
* **Divider:** handle `size` prop ([#1307](https://github.com/nuxt/ui/issues/1307)) ([cbeede6](https://github.com/nuxt/ui/commit/cbeede66bb3bd7778e03c19ebbf55bf7bd753cb8))
* **Form:** use nuxt `useId` to bind input labels ([#1211](https://github.com/nuxt/ui/issues/1211)) ([27c71fa](https://github.com/nuxt/ui/commit/27c71fa40ecb9f8524fee7f3d17a384bc8812d25))
* **Input:** handle type `file` ([946a39c](https://github.com/nuxt/ui/commit/946a39c73990dc352cf7b9a77bfaec339cdcab34)), closes [#563](https://github.com/nuxt/ui/issues/563)
* **Modal:** open programmatically ([#1319](https://github.com/nuxt/ui/issues/1319)) ([6f29c62](https://github.com/nuxt/ui/commit/6f29c620ab758e27be63f8af53674828b59fb6ed))
* **Table:** display progress bar when `loading` ([#1362](https://github.com/nuxt/ui/issues/1362)) ([3fe3521](https://github.com/nuxt/ui/commit/3fe35217cbc0cef7f41550c175e4e7ea2cc939a8))
* **Tabs:** add `unmount` prop as `false` by default ([843a978](https://github.com/nuxt/ui/commit/843a9786445f6170c1380e3b404151da52b5a154)), closes [#663](https://github.com/nuxt/ui/issues/663)
* **Textarea:** add `maxrows` prop to restrict autoresize ([#1302](https://github.com/nuxt/ui/issues/1302)) ([f643e7b](https://github.com/nuxt/ui/commit/f643e7b316639a79cf03da25250ab0fa85f466d5))
### Bug Fixes
* **Accordion:** style disclosure `div` after [#1199](https://github.com/nuxt/ui/issues/1199) ([882247e](https://github.com/nuxt/ui/commit/882247e5f40bf41fdfdffea501de5c898a7fb0b2))
* **Alert:** remove `required` title to prevent warning when using slot ([e545b6f](https://github.com/nuxt/ui/commit/e545b6f0a128475166dcea3c1028798b106805f3))
* **Card:** prevent `body` padding without default slot ([f682905](https://github.com/nuxt/ui/commit/f682905b26a22546634e9adc4b838a7741dbd7c9))
* **components:** hydration attribute mismatch with vue `3.4` ([#1199](https://github.com/nuxt/ui/issues/1199)) ([10db144](https://github.com/nuxt/ui/commit/10db14475f7a527180be3fcf33cc5d3af52452c9))
* **Form:** improve `validate` path type ([#1370](https://github.com/nuxt/ui/issues/1370)) ([5266591](https://github.com/nuxt/ui/commit/5266591c886422d5265e46e08e1276913d12bed1))
* **Form:** return false when silent validation fails ([#1371](https://github.com/nuxt/ui/issues/1371)) ([d4b6147](https://github.com/nuxt/ui/commit/d4b6147fcceb7ff9cebe1586bb3094b10f50acb5))
* **Link:** check `disabled` prop before navigating ([#1321](https://github.com/nuxt/ui/issues/1321)) ([ac42ec1](https://github.com/nuxt/ui/commit/ac42ec106ff259e1d44515e5fb3b5236559ac713))
* **Meter:** missing import of `Icon` component ([f8b296f](https://github.com/nuxt/ui/commit/f8b296fc60b93c4656fd397f8eb6b06b4a1dcd93)), closes [#1328](https://github.com/nuxt/ui/issues/1328)
* **module:** prevent tailwind warn with `bun` ([bb40c31](https://github.com/nuxt/ui/commit/bb40c3103174a039f65b31c65fcc5d40cb29ce6b)), closes [#809](https://github.com/nuxt/ui/issues/809)
* **module:** put back `all` option in icons plugin ([412cd75](https://github.com/nuxt/ui/commit/412cd75eddb6140d7d9b3358b04df1e61f22b481)), closes [#1237](https://github.com/nuxt/ui/issues/1237)
* **Notification:** remove `required` title to prevent warning when using slot ([aa2b1ca](https://github.com/nuxt/ui/commit/aa2b1cae8881dece9a629dc95a8f9df88f9bbd27))
* **Progress:** prevent `NaN` percent display when indeterminate ([a55a08a](https://github.com/nuxt/ui/commit/a55a08a91eca6f4c7ff3ad40ee566b6445d2dfd0))
* **RadioGroup:** pass `help` prop to radio children ([5a5b284](https://github.com/nuxt/ui/commit/5a5b284e967ca9cdb6c7df9809ed4f4569a65cfa)), closes [#1313](https://github.com/nuxt/ui/issues/1313)
* **SelectMenu:** revert component `is` after [#1199](https://github.com/nuxt/ui/issues/1199) ([d0f4530](https://github.com/nuxt/ui/commit/d0f4530e8572a08d544041dec1f24a51bbc3b1e8))
* **utils:** prevent merge of `popper` key ([9f35297](https://github.com/nuxt/ui/commit/9f352976ced5845a5fad00a6630d0166941a8a13)), closes [#1393](https://github.com/nuxt/ui/issues/1393)
### Reverts
* Revert "docs: add missing `overflow-hidden` on components" ([b893607](https://github.com/nuxt/ui/commit/b8936070f9e1f866a21d39f6c45140f86efebec4))
## [2.13.0](https://github.com/nuxt/ui/compare/v2.12.3...v2.13.0) (2024-01-30)

View File

@@ -1,4 +1,4 @@
[![nuxt-ui-social-card](https://repository-images.githubusercontent.com/428329515/43fec891-9030-4601-8233-5d45ba5c6013)](https://ui.nuxt.com)
[![nuxt-ui.png](https://repository-images.githubusercontent.com/428329515/43fec891-9030-4601-8233-5d45ba5c6013)](https://ui.nuxt.com)
# Nuxt UI
@@ -7,9 +7,9 @@
[![License][license-src]][license-href]
[![Nuxt][nuxt-src]][nuxt-href]
Nuxt UI provides everything related to UI when building Nuxt applications: components, icons, colors, dark mode and also keyboard shortcuts.
Nuxt UI is a module that provides a set of Vue components and composables built with [Tailwind CSS](https://tailwindcss.com/) and [Headless UI](https://headlessui.dev/) to help you build beautiful and accessible user interfaces.
Is has been developed by [NuxtLabs](https://nuxtlabs.com/) for [Volta](https://volta.net), [Nuxt Studio](https://nuxt.studio/) and the Nuxt community.
Its goal is to provide everything related to UI when building a Nuxt app. This includes components, icons, colors, dark mode but also keyboard shortcuts.
## Features
@@ -27,14 +27,14 @@ Read more on [ui.nuxt.com](https://ui.nuxt.com)
## Installation
```bash
# Using npm
# npm
npm install @nuxt/ui
# Using yarn
# yarn
yarn add @nuxt/ui
# Using pnpm
# pnpm
pnpm add @nuxt/ui
# bun
bun add @nuxt/ui
```
Then, register the module in your `nuxt.config.ts`:

View File

@@ -3,6 +3,8 @@
<div>
<NuxtLoadingIndicator />
<Banner v-if="!$route.path.startsWith('/examples')" />
<Header v-if="!$route.path.startsWith('/examples')" :links="links" />
<NuxtLayout>
@@ -12,7 +14,7 @@
<Footer v-if="!$route.path.startsWith('/examples')" />
<ClientOnly>
<LazyUDocsSearch ref="searchRef" :files="files" :navigation="navigation" :links="links" :fuse="{ resultLimit: 1000 }" />
<LazyUContentSearch ref="searchRef" :files="files" :navigation="navigation" :links="links" :fuse="{ resultLimit: 42 }" />
</ClientOnly>
<UNotifications>
@@ -20,6 +22,7 @@
<span v-html="title" />
</template>
</UNotifications>
<UModals />
</div>
</template>
@@ -50,7 +53,7 @@ const navigation = computed(() => {
]
}
return nav.value.filter(item => item._path !== '/dev')
return nav.value?.filter(item => item._path !== '/dev') || []
})
const color = computed(() => colorMode.value === 'dark' ? '#18181b' : 'white')
@@ -65,7 +68,7 @@ const links = computed(() => {
label: 'Pro',
icon: 'i-heroicons-square-3-stack-3d',
to: '/pro',
active: route.path.startsWith('/pro/getting-started') || route.path.startsWith('/pro/components')
active: route.path.startsWith('/pro/getting-started') || route.path.startsWith('/pro/components') || route.path.startsWith('/pro/prose')
}, {
label: 'Pricing',
icon: 'i-heroicons-credit-card',

View File

@@ -0,0 +1,58 @@
<script setup lang="ts">
const id = 'nuxt-ui-banner-1'
const to = '/pro/pricing'
const hideBanner = () => {
localStorage.setItem(id, 'true')
document.querySelector('html')?.classList.add('hide-banner')
}
if (process.server) {
useHead({
script: [{
key: 'prehydrate-template-banner',
innerHTML: `
if (localStorage.getItem('${id}') === 'true') {
document.querySelector('html').classList.add('hide-banner')
}`.replace(/\s+/g, ' '),
type: 'text/javascript'
}]
})
}
</script>
<template>
<div class="relative bg-primary hover:bg-primary/90 transition-[background] backdrop-blur z-50 app-banner">
<UContainer class="py-2">
<NuxtLink v-if="to" :to="to" class="focus:outline-none" aria-label="Nuxt UI Pro pricing" tabindex="-1">
<span class="absolute inset-0 " aria-hidden="true" />
</NuxtLink>
<div class="flex items-center justify-between gap-2">
<div class="lg:flex-1 hidden lg:flex items-center" />
<p class="text-sm font-medium text-white dark:text-gray-900">
<UIcon name="i-heroicons-rocket-launch" class="w-5 h-5 align-top flex-shrink-0 pointer-events-none mr-2" />
<span class="font-semibold">Nuxt UI Pro v1.0</span> is out with dashboard components!
</p>
<div class="flex items-center justify-end lg:flex-1">
<button
class="p-1.5 rounded-md inline-flex hover:bg-primary/90"
aria-label="Close banner"
@click.prevent="hideBanner"
>
<UIcon name="i-heroicons-x-mark-20-solid" class="w-5 h-5 text-white dark:text-gray-900" />
</button>
</div>
</div>
</UContainer>
</div>
</template>
<style scoped>
.hide-banner .app-banner {
display: none;
}
</style>

View File

@@ -28,7 +28,7 @@
const links = [{
icon: 'i-simple-icons-figma',
label: 'Figma Kit',
to: 'https://www.figma.com/community/file/1288455405058138934/nuxt-ui',
to: 'https://www.figma.com/community/file/1288455405058138934',
target: '_blank'
}, {
label: 'Playground',

View File

@@ -11,14 +11,15 @@
<Logo class="w-auto h-6" />
<UBadge v-if="$route.path.startsWith('/pro')" label="Pro" variant="subtle" size="xs" class="-mb-[2px] rounded font-semibold" />
<UBadge v-if="$route.path.startsWith('/dev')" label="Edge" variant="subtle" size="xs" class="-mb-[2px] rounded font-semibold" />
</NuxtLink>
</template>
<template #right>
<ColorPicker />
<UTooltip text="Search" :shortcuts="[metaSymbol, 'K']">
<UDocsSearchButton :label="null" />
<UTooltip text="Search" :shortcuts="[metaSymbol, 'K']" :popper="{ strategy: 'absolute' }">
<UContentSearchButton :label="null" />
</UTooltip>
<UColorModeButton />

View File

@@ -1,5 +1,5 @@
<template>
<UPopover mode="hover">
<UPopover mode="hover" :popper="{ strategy: 'absolute' }" :ui="{ width: 'w-[156px]' }">
<template #default="{ open }">
<UButton color="gray" variant="ghost" square :class="[open && 'bg-gray-50 dark:bg-gray-800']" aria-label="Color picker">
<UIcon name="i-heroicons-swatch-20-solid" class="w-5 h-5 text-primary-500 dark:text-primary-400" />

View File

@@ -51,11 +51,9 @@
</template>
<script setup lang="ts">
// @ts-expect-error
import { transformContent } from '@nuxt/content/transformers'
// @ts-ignore
import { useShikiHighlighter } from '@nuxtjs/mdc/runtime'
import { upperFirst, camelCase, kebabCase } from 'scule'
import { useShikiHighlighter } from '~/composables/useShikiHighlighter'
// eslint-disable-next-line vue/no-dupe-keys
const props = defineProps({
@@ -124,6 +122,7 @@ const componentProps = reactive({ ...props.props })
const { $prettier } = useNuxtApp()
const appConfig = useAppConfig()
const route = useRoute()
const highlighter = useShikiHighlighter()
let name = props.slug || `U${upperFirst(camelCase(route.params.slug[route.params.slug.length - 1]))}`
@@ -269,8 +268,6 @@ function renderObject (obj: any) {
return obj
}
const shikiHighlighter = useShikiHighlighter({})
const codeHighlighter = async (code: string, lang: string, theme: any, highlights: number[]) => shikiHighlighter.getHighlightedAST(code, lang, theme, { highlights })
const { data: ast } = await useAsyncData(
`${name}-ast-${JSON.stringify({ props: componentProps, slots: props.slots, code: props.code })}`,
async () => {
@@ -284,7 +281,7 @@ const { data: ast } = await useAsyncData(
return transformContent('content:_markdown.md', formatted, {
markdown: {
highlight: {
highlighter: codeHighlighter,
highlighter,
theme: {
light: 'material-theme-lighter',
default: 'material-theme',

View File

@@ -21,10 +21,8 @@
<script setup lang="ts">
import { camelCase } from 'scule'
import { fetchContentExampleCode } from '~/composables/useContentExamplesCode'
// @ts-expect-error
import { transformContent } from '@nuxt/content/transformers'
// @ts-ignore
import { useShikiHighlighter } from '@nuxtjs/mdc/runtime'
import { useShikiHighlighter } from '~/composables/useShikiHighlighter'
const props = defineProps({
component: {
@@ -78,15 +76,14 @@ if (['command-palette-theme-algolia', 'command-palette-theme-raycast', 'vertical
const instance = getCurrentInstance()
const camelName = camelCase(component)
const data = await fetchContentExampleCode(camelName)
const highlighter = useShikiHighlighter()
const hasCode = computed(() => !props.hiddenCode && (data?.code || instance.slots.code))
const shikiHighlighter = useShikiHighlighter({})
const codeHighlighter = async (code: string, lang: string, theme: any, highlights: number[]) => shikiHighlighter.getHighlightedAST(code, lang, theme, { highlights })
const { data: ast } = await useAsyncData(`content-example-${camelName}-ast`, () => transformContent('content:_markdown.md', `\`\`\`vue\n${data?.code ?? ''}\n\`\`\``, {
markdown: {
highlight: {
highlighter: codeHighlighter,
highlighter,
theme: {
light: 'material-theme-lighter',
default: 'material-theme',

View File

@@ -3,9 +3,10 @@
</template>
<script setup lang="ts">
// @ts-expect-error
import { transformContent } from '@nuxt/content/transformers'
import { upperFirst, camelCase } from 'scule'
import json5 from 'json5'
import { useShikiHighlighter } from '~/composables/useShikiHighlighter'
import * as config from '#ui/ui.config'
const props = defineProps({
@@ -16,6 +17,7 @@ const props = defineProps({
})
const route = useRoute()
const highlighter = useShikiHighlighter()
// eslint-disable-next-line vue/no-dupe-keys
const slug = props.slug || route.params.slug[route.params.slug.length - 1]
const camelName = camelCase(slug)
@@ -24,15 +26,18 @@ const name = `U${upperFirst(camelName)}`
const preset = config[camelName]
const { data: ast } = await useAsyncData(`${name}-preset`, () => transformContent('content:_markdown.md', `
\`\`\`json
${JSON.stringify(preset, null, 2)}
\`\`\`yml
${json5.stringify(preset, null, 2)}
\`\`\`\
`, {
highlight: {
theme: {
light: 'material-theme-lighter',
default: 'material-theme',
dark: 'material-theme-palenight'
markdown: {
highlight: {
highlighter,
theme: {
light: 'material-theme-lighter',
default: 'material-theme',
dark: 'material-theme-palenight'
}
}
}
}))

View File

@@ -11,6 +11,6 @@ const items = [
<template>
<UCarousel v-slot="{ item }" :items="items">
<img :src="item" width="300" height="400">
<img :src="item" width="300" height="400" draggable="false">
</UCarousel>
</template>

View File

@@ -11,6 +11,6 @@ const items = [
<template>
<UCarousel v-slot="{ item }" :items="items" :ui="{ item: 'basis-full' }" class="rounded-lg overflow-hidden" arrows>
<img :src="item" class="w-full">
<img :src="item" class="w-full" draggable="false">
</UCarousel>
</template>

View File

@@ -30,6 +30,6 @@ const items = [
arrows
class="w-64 mx-auto"
>
<img :src="item" class="w-full">
<img :src="item" class="w-full" draggable="false">
</UCarousel>
</template>

View File

@@ -0,0 +1,37 @@
<script setup lang="ts">
const items = [
'https://picsum.photos/1920/1080?random=1',
'https://picsum.photos/1920/1080?random=2',
'https://picsum.photos/1920/1080?random=3',
'https://picsum.photos/1920/1080?random=4',
'https://picsum.photos/1920/1080?random=5',
'https://picsum.photos/1920/1080?random=6'
]
const carouselRef = ref()
onMounted(() => {
setInterval(() => {
if (!carouselRef.value) return
if (carouselRef.value.page === carouselRef.value.pages) {
return carouselRef.value.select(0)
}
carouselRef.value.next()
}, 3000)
})
</script>
<template>
<UCarousel
ref="carouselRef"
v-slot="{ item }"
:items="items"
:ui="{ item: 'basis-full' }"
class="rounded-lg overflow-hidden"
indicators
>
<img :src="item" class="w-full" draggable="false">
</UCarousel>
</template>

View File

@@ -11,6 +11,6 @@ const items = [
<template>
<UCarousel v-slot="{ item }" :items="items" :ui="{ item: 'basis-full' }" class="rounded-lg overflow-hidden" indicators>
<img :src="item" class="w-full">
<img :src="item" class="w-full" draggable="false">
</UCarousel>
</template>

View File

@@ -11,6 +11,6 @@ const items = [
<template>
<UCarousel v-slot="{ item }" :items="items" :ui="{ item: 'basis-full md:basis-1/2 lg:basis-1/3' }" indicators class="rounded-lg overflow-hidden">
<img :src="item" class="w-full">
<img :src="item" class="w-full" draggable="false">
</UCarousel>
</template>

View File

@@ -11,6 +11,6 @@ const items = [
<template>
<UCarousel v-slot="{ item }" :items="items" :ui="{ item: 'basis-full md:basis-1/2 lg:basis-1/3' }">
<img :src="item" class="w-full">
<img :src="item" class="w-full" draggable="false">
</UCarousel>
</template>

View File

@@ -11,6 +11,6 @@ const items = [
<template>
<UCarousel v-slot="{ item }" :items="items" :ui="{ item: 'basis-full' }" class="w-64 mx-auto rounded-lg overflow-hidden">
<img :src="item" class="w-full">
<img :src="item" class="w-full" draggable="false">
</UCarousel>
</template>

View File

@@ -11,6 +11,6 @@ const items = [
<template>
<UCarousel v-slot="{ item }" :items="items" :ui="{ item: 'basis-full' }" class="rounded-lg overflow-hidden">
<img :src="item" class="w-full">
<img :src="item" class="w-full" draggable="false">
</UCarousel>
</template>

View File

@@ -19,15 +19,13 @@ const items = [{
</script>
<template>
<UCarousel :items="items" :ui="{ item: 'w-full' }">
<template #default="{ item, index }">
<div class="text-center mx-auto">
<img :src="item.avatar.src" :alt="item.name" class="rounded-full w-48 h-48 mb-2">
<UCarousel v-slot="{ item, index }" :items="items" :ui="{ item: 'w-full' }">
<div class="text-center mx-auto">
<img :src="item.avatar.src" :alt="item.name" class="rounded-full w-48 h-48 mb-2" draggable="false">
<p class="font-semibold">
{{ index + 1 }}. {{ item.name }}
</p>
</div>
</template>
<p class="font-semibold">
{{ index + 1 }}. {{ item.name }}
</p>
</div>
</UCarousel>
</template>

View File

@@ -23,11 +23,11 @@ const items = [
class="w-64 mx-auto"
>
<template #default="{ item }">
<img :src="item" class="w-full">
<img :src="item" class="w-full" draggable="false">
</template>
<template #indicator="{ onClick, index, active }">
<UButton :label="String(index)" :variant="active ? 'solid' : 'outline'" size="2xs" class="rounded-full min-w-6 justify-center" @click="onClick(index)" />
<template #indicator="{ onClick, page, active }">
<UButton :label="String(page)" :variant="active ? 'solid' : 'outline'" size="2xs" class="rounded-full min-w-6 justify-center" @click="onClick(page)" />
</template>
</UCarousel>
</template>

View File

@@ -20,7 +20,7 @@ const items = [
class="w-64 mx-auto"
>
<template #default="{ item }">
<img :src="item" class="w-full">
<img :src="item" class="w-full" draggable="false">
</template>
<template #prev="{ onClick, disabled }">
@@ -30,7 +30,7 @@ const items = [
</template>
<template #next="{ onClick, disabled }">
<button :disabled="disabled" class="" @click="onClick">
<button :disabled="disabled" @click="onClick">
Next
</button>
</template>

View File

@@ -11,6 +11,6 @@ const items = [
<template>
<UCarousel v-slot="{ item }" :items="items" :ui="{ item: 'snap-end' }">
<img :src="item" width="200" height="300">
<img :src="item" width="200" height="300" draggable="false">
</UCarousel>
</template>

View File

@@ -11,6 +11,6 @@ const items = [
<template>
<UCarousel v-slot="{ item }" :items="items" :ui="{ item: 'snap-start' }">
<img :src="item" width="200" height="300">
<img :src="item" width="200" height="300" draggable="false">
</UCarousel>
</template>

View File

@@ -27,8 +27,8 @@ function selectRange (duration: Duration) {
</UButton>
<template #panel="{ close }">
<div class="flex items-center divide-x divide-gray-200 dark:divide-gray-800">
<div class="flex flex-col py-4">
<div class="flex items-center sm:divide-x divide-gray-200 dark:divide-gray-800">
<div class="hidden sm:flex flex-col py-4">
<UButton
v-for="(range, index) in ranges"
:key="index"
@@ -37,6 +37,7 @@ function selectRange (duration: Duration) {
variant="ghost"
class="rounded-none px-6"
:class="[isRangeSelected(range.duration) ? 'bg-gray-100 dark:bg-gray-800' : 'hover:bg-gray-50 dark:hover:bg-gray-800/50']"
truncate
@click="selectRange(range.duration)"
/>
</div>

View File

@@ -10,8 +10,8 @@ const schema = objectAsync({
type Schema = Input<typeof schema>
const state = reactive({
email: undefined,
password: undefined
email: '',
password: ''
})
async function onSubmit (event: FormSubmitEvent<Schema>) {

View File

@@ -1,4 +1,4 @@
<script setup>
<script setup lang="ts">
const route = useRoute()
const links = [{

View File

@@ -1,4 +1,4 @@
<script setup>
<script setup lang="ts">
const route = useRoute()
const links = [{

View File

@@ -1,4 +1,4 @@
<script setup>
<script setup lang="ts">
const route = useRoute()
const links = [

View File

@@ -0,0 +1,17 @@
<script lang="ts" setup>
defineProps({
count: {
type: Number,
default: 0
}
})
</script>
<template>
<UModal>
<UCard>
<p>This modal was opened programmatically !</p>
<p>Count: {{ count }}</p>
</UCard>
</UModal>
</template>

View File

@@ -0,0 +1,17 @@
<script setup lang="ts">
import { ModalExampleComponent } from '#components'
const modal = useModal()
const count = ref(0)
function openModal () {
count.value += 1
modal.open(ModalExampleComponent, {
count: count.value
})
}
</script>
<template>
<UButton label="Reveal modal" @click="openModal" />
</template>

View File

@@ -3,5 +3,5 @@ const value = ref(50)
</script>
<template>
<URange v-model="value" name="range" />
<URange v-model="value" />
</template>

View File

@@ -19,7 +19,7 @@ const links = [{
:links="links"
:ui="{
wrapper: 'border-s border-gray-200 dark:border-gray-800 space-y-2',
base: 'group block border-s -ms-px lg:leading-6 before:hidden',
base: 'group block border-s -ms-px leading-6 before:hidden',
padding: 'p-0 ps-4',
rounded: '',
font: '',

View File

@@ -1,5 +1,7 @@
<script setup lang="ts">
import { useBreakpoints, breakpointsTailwind } from '@vueuse/core'
import { DatePicker as VCalendarDatePicker } from 'v-calendar'
// @ts-ignore
import type { DatePickerDate, DatePickerRangeObject } from 'v-calendar/dist/types/src/use/datePicker'
import 'v-calendar/dist/style.css'
@@ -24,6 +26,10 @@ const date = computed({
}
})
const breakpoints = useBreakpoints(breakpointsTailwind)
const smallerThanSm = breakpoints.smaller('sm')
const attrs = {
transparent: true,
borderless: true,
@@ -34,7 +40,7 @@ const attrs = {
</script>
<template>
<VCalendarDatePicker v-if="date && (date as DatePickerRangeObject)?.start && (date as DatePickerRangeObject)?.end" v-model.range="date" :columns="2" v-bind="{ ...attrs, ...$attrs }" />
<VCalendarDatePicker v-if="date && (date as DatePickerRangeObject)?.start && (date as DatePickerRangeObject)?.end" v-model.range="date" :columns="smallerThanSm ? 1 : 2" :rows="smallerThanSm ? 2 : 1" v-bind="{ ...attrs, ...$attrs }" />
<VCalendarDatePicker v-else v-model="date" v-bind="{ ...attrs, ...$attrs }" />
</template>

View File

@@ -26,7 +26,7 @@
</Transition>
</template>
<script setup>
<script setup lang="ts">
import { useElementSize } from '@vueuse/core'
const el = ref(null)

View File

@@ -0,0 +1,31 @@
import { createShikiHighlighter } from '@nuxtjs/mdc/runtime/highlighter/shiki'
import MaterialTheme from 'shiki/themes/material-theme.mjs'
import MaterialThemeLighter from 'shiki/themes/material-theme-lighter.mjs'
import MaterialThemePalenight from 'shiki/themes/material-theme-palenight.mjs'
import HtmlLang from 'shiki/langs/html.mjs'
import MdcLang from 'shiki/langs/mdc.mjs'
import VueLang from 'shiki/langs/vue.mjs'
import YamlLang from 'shiki/langs/yaml.mjs'
import PostcssLang from 'shiki/langs/postcss.mjs'
let highlighter
export const useShikiHighlighter = () => {
if (!highlighter) {
highlighter = createShikiHighlighter({
bundledThemes: {
'material-theme': MaterialTheme,
'material-theme-lighter': MaterialThemeLighter,
'material-theme-palenight': MaterialThemePalenight
},
bundledLangs: {
html: HtmlLang,
mdc: MdcLang,
vue: VueLang,
yml: YamlLang,
postcss: PostcssLang
}
})
}
return highlighter
}

View File

@@ -206,7 +206,7 @@ An example SFC using IntelliSense (note the `/*ui*/` prefix, also works with `re
<UCard :ui="ui" />
</template>
<script setup>
<script setup lang="ts">
const ui = /*ui*/ {
background: 'bg-white dark:bg-slate-900'
}

View File

@@ -25,7 +25,7 @@ Try to change the `primary` and `gray` colors by clicking on the :u-icon{name="i
As this module uses Tailwind CSS under the hood, you can use any of the [Tailwind CSS colors](https://tailwindcss.com/docs/customizing-colors#color-palette-reference) or your own custom colors. By default, the `primary` color is `green` and the `gray` color is `cool`.
When [using custom colors](https://tailwindcss.com/docs/customizing-colors#using-custom-colors) or [adding additional colors](https://tailwindcss.com/docs/customizing-colors#adding-additional-colors) through the `extend` key in your `tailwind.config.ts`, you'll need to make sure to define all the shades from `50` to `950` as most of them are used in the components config defined in [`ui.config.ts`](https://github.com/nuxt/ui/blob/dev/src/runtime/ui.config.ts). You can [generate your colors](https://tailwindcss.com/docs/customizing-colors#generating-colors) using tools such as https://uicolors.app/ for example.
When [using custom colors](https://tailwindcss.com/docs/customizing-colors#using-custom-colors) or [adding additional colors](https://tailwindcss.com/docs/customizing-colors#adding-additional-colors) through the `extend` key in your `tailwind.config.ts`, you'll need to make sure to define all the shades from `50` to `950` as most of them are used in the components config defined in [`ui.config/`](https://github.com/nuxt/ui/tree/dev/src/runtime/ui.config) directory. You can [generate your colors](https://tailwindcss.com/docs/customizing-colors#generating-colors) using tools such as https://uicolors.app/ for example.
```ts [tailwind.config.ts]
import type { Config } from 'tailwindcss'
@@ -106,7 +106,7 @@ This can also happen when you bind a dynamic color to a component: `<UBadge :col
### `app.config.ts`
Components are styled with Tailwind CSS but classes are all defined in the default [ui.config.ts](https://github.com/nuxt/ui/blob/dev/src/runtime/ui.config.ts) file. You can override those in your own `app.config.ts`.
You can find the config of each component in the [`ui.config/`](https://github.com/nuxt/ui/tree/dev/src/runtime/ui.config) directory. You can override those classes in your own `app.config.ts`.
```ts [app.config.ts]
export default defineAppConfig({
@@ -247,7 +247,7 @@ You can easily build a color mode button by using the `useColorMode` composable
#code
```vue
<script setup>
<script setup lang="ts">
const colorMode = useColorMode()
const isDark = computed({
get () {

View File

@@ -130,7 +130,7 @@ defineShortcuts({
To display shortcuts in your app according to the user's OS, you can use the `useShortcuts` composable.
```vue
<script setup>
<script setup lang="ts">
const { metaSymbol } = useShortcuts()
</script>

View File

@@ -1,12 +1,12 @@
---
description: Display togglable accordion panels.
links:
- label: GitHub
icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/elements/Accordion.vue
- label: Disclosure
icon: i-simple-icons-headlessui
to: 'https://headlessui.com/vue/disclosure'
- label: GitHub
icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/elements/Accordion.vue
---
## Usage

View File

@@ -1,6 +1,10 @@
---
title: Breadcrumb
description: A list of links that indicate the current page's location within a navigational hierarchy.
links:
- label: GitHub
icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/navigation/Breadcrumb.vue
---
## Usage

View File

@@ -4,8 +4,6 @@ links:
- label: GitHub
icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/elements/Carousel.vue
navigation:
badge: New
---
## Usage
@@ -100,6 +98,12 @@ The number of indicators will be automatically generated based on the number of
:component-example{component="carousel-example-indicators-size"}
## Autoplay :u-badge{label="New" class="align-middle ml-2 !rounded-full" variant="subtle"}
You can easily implement an autoplay behavior using the exposed [API](#api) through a template ref.
:component-example{component="carousel-example-autoplay"}
## Slots
### `default`
@@ -120,7 +124,7 @@ You can customize the position of the buttons through `ui.arrows.wrapper`.
### `indicator`
With the `indicators` prop enabled, use the `#indicator` slot to set the content of the indicators. You will have access to the `active`, `index` properties and `on-click` method in the slot scope.
With the `indicators` prop enabled, use the `#indicator` slot to set the content of the indicators. You will have access to the `active`, `page` properties and `on-click` method in the slot scope.
:component-example{component="carousel-example-slots-indicator"}
@@ -132,6 +136,28 @@ You can customize the position of the buttons through `ui.indicators.wrapper`.
:component-props
## API
When accessing the component via a template ref, you can use the following:
::field-group
::field{name="page" type="number"}
The current page.
::
::field{name="pages" type="number"}
The total number of pages.
::
::field{name="select (page)"}
Go to a specific page.
::
::field{name="next ()"}
Go to the next page.
::
::field{name="prev ()"}
Go to the previous page.
::
::
## Config
:component-preset

View File

@@ -2,12 +2,12 @@
title: CommandPalette
description: Add a customizable command palette to your app.
links:
- label: GitHub
icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/navigation/CommandPalette.vue
- label: 'Combobox'
icon: i-simple-icons-headlessui
to: 'https://headlessui.com/vue/combobox'
- label: GitHub
icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/navigation/CommandPalette.vue
---
## Usage

View File

@@ -16,15 +16,15 @@ Let's start by installing the `v-calendar` and `date-fns` dependency:
::code-group
```bash [pnpm]
pnpm add v-calendar
pnpm add v-calendar@next date-fns
```
```bash [yarn]
yarn add v-calendar
yarn add v-calendar@next date-fns
```
```bash [npm]
npm install v-calendar
npm install v-calendar@next date-fns
```
::

View File

@@ -66,21 +66,17 @@ excludedProps:
---
::
### Size
### Size :u-badge{label="New" class="align-middle ml-2 !rounded-full" variant="subtle"}
You can change the size of the divider by using the `ui` prop
Use the `size` prop to change the size of the divider.
::component-card
---
props:
label: Nuxt UI
ui:
border:
size:
horizontal: border-t-2
size: sm
excludedProps:
- label
- ui
---
::

View File

@@ -1,12 +1,12 @@
---
description: Display a list of actions in a dropdown menu.
links:
- label: GitHub
icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/elements/Dropdown.vue
- label: Menu
icon: i-simple-icons-headlessui
to: https://headlessui.com/vue/menu
- label: GitHub
icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/elements/Dropdown.vue
---
## Usage

View File

@@ -236,7 +236,7 @@ Use the `#help` slot to set the custom content for help.
::component-card
---
slots:
help: Here are some examples <UIcon name="i-heroicons-arrow-right-20-solid" />
help: Here are some examples <UIcon name="i-heroicons-information-circle" />
default: <UInput model-value="benjamincanac" placeholder="you@example.com" />
props:
label: 'Email'

View File

@@ -8,14 +8,63 @@ links:
## Usage
Use the Form component to validate form data using schema libraries such as [Yup](https://github.com/jquense/yup), [Zod](https://github.com/colinhacks/zod), [Joi](https://github.com/hapijs/joi), [Valibot](https://valibot.dev/) or your own validation logic. It works seamlessly with the FormGroup component to automatically display error messages around form elements.
Use the Form component to validate form data using schema libraries such as [Yup](https://github.com/jquense/yup), [Zod](https://github.com/colinhacks/zod), [Joi](https://github.com/hapijs/joi), [Valibot](https://valibot.dev/), or your own validation logic.
The Form component requires the `validate` and `state` props for form validation.
It works with the [FormGroup](/components/input) component to display error messages around form elements automatically.
- `state` - a reactive object that holds the current state of the form.
- `validate` - a function that takes the form's state as input and returns an array of `FormError` objects with the following fields:
- `message` - the error message to display.
- `path` - the path to the form element matching the `name`.
The form component requires two props:
- `state` - a reactive object holding the form's state.
- `schema` - a schema object from [Yup](#yup), [Zod](#zod), [Joi](#joi), or [Valibot](#valibot).
::callout{icon="i-heroicons-light-bulb"}
Note that **no validation library is included** by default, so ensure you **install the one you need**.
::
::tabs
::component-example{label="Yup"}
---
component: 'form-example-yup'
componentProps:
class: 'w-60'
---
::
::component-example{label="Zod"}
---
component: 'form-example-zod'
componentProps:
class: 'w-60'
---
::
::component-example{label="Joi"}
---
component: 'form-example-joi'
componentProps:
class: 'w-60'
---
::
::component-example{label="Valibot"}
---
component: 'form-example-valibot'
componentProps:
class: 'w-60'
---
::
::
## Custom validation
Use the `validate` prop to apply your own validation logic.
The validation function must return a list of errors with the following attributes:
- `message` - Error message for display.
- `path` - Path to the form element corresponding to the `name` attribute.
::callout{icon="i-heroicons-light-bulb"}
Note: this can be used alongside the `schema` prop to handle complex use cases.
::
::component-example
---
@@ -25,55 +74,7 @@ componentProps:
---
::
## Schema
You can provide a schema from [Yup](#yup), [Zod](#zod) or [Joi](#joi), [Valibot](#valibot) through the `schema` prop to validate the state. It's important to note that **none of these libraries are included** by default, so make sure to **install the one you need**.
### Yup
::component-example
---
component: 'form-example-yup'
componentProps:
class: 'w-60'
---
::
### Zod
::component-example
---
component: 'form-example-zod'
componentProps:
class: 'w-60'
---
::
### Joi
::component-example
---
component: 'form-example-joi'
componentProps:
class: 'w-60'
---
::
### Valibot
::component-example
---
component: 'form-example-valibot'
componentProps:
class: 'w-60'
---
::
## Other libraries
For other validation libraries, you can define your own component with custom validation logic.
Here is an example with [Vuelidate](https://github.com/vuelidate/vuelidate):
This can also be used to integrate with other validation libraries. Here is an example with [Vuelidate](https://github.com/vuelidate/vuelidate):
```vue
<script setup lang="ts">
@@ -132,9 +133,11 @@ async function onSubmit (event: FormSubmitEvent<any>) {
// ...
} catch (err) {
if (err.statusCode === 422) {
form.value.setErrors(err.data.errors.map((err) => {
form.value.setErrors(err.data.errors.map((err) => ({
// Map validation errors to { path: string, message: string }
}))
message: err.message,
path: err.path,
})))
}
}
}
@@ -156,10 +159,11 @@ async function onSubmit (event: FormSubmitEvent<any>) {
</UForm>
</template>
```
## Input events
The Form component automatically triggers validation upon `submit`, `input`, `blur` or `change` events. This ensures that any errors are displayed as soon as the user interacts with the form elements. You can control when validation happens this using the `validate-on` prop.
The Form component automatically triggers validation upon `submit`, `input`, `blur` or `change` events.
This ensures that any errors are displayed as soon as the user interacts with the form elements. You can control when validation happens this using the `validate-on` prop.
::callout{icon="i-heroicons-light-bulb"}
Note that the `input` event is not triggered until after the initial `blur` event. This is to prevent the form from being validated as the user is typing. You can override this behavior by setting the [`eager-validation`](/components/form-group#eager-validation) prop on [`FormGroup`](/components/form-group) to `true`.

View File

@@ -5,8 +5,6 @@ links:
- label: GitHub
icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/navigation/HorizontalNavigation.vue
navigation:
badge: New
---
## Usage

View File

@@ -2,12 +2,12 @@
title: InputMenu
description: Display an autocomplete input with real-time suggestions.
links:
- label: GitHub
icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/forms/InputMenu.vue
- label: 'Combobox'
icon: i-simple-icons-headlessui
to: 'https://headlessui.com/vue/combobox'
- label: GitHub
icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/forms/InputMenu.vue
---
## Usage

View File

@@ -71,12 +71,20 @@ props:
Use the `type` prop to change the input type, the default `type` is set to `text`, you can check all the available types at [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#input_types).
We have improved the implementation of certain types such as [Checkbox](/components/checkbox), [Radio](/components/radio-group), etc.
Some types have been implemented in their own components, such as [Checkbox](/components/checkbox), [Radio](/components/radio-group), etc. and others have been styled like `file` for example. :u-badge{label="New" class="!rounded-full" variant="subtle"}
::component-card
---
props:
type: 'password'
type: 'file'
size: sm
options:
- name: type
restriction: included
values:
- file
- password
- number
---
::

View File

@@ -33,6 +33,18 @@ It also renders an `<a>` tag when a `to` prop is provided, otherwise it defaults
It is used underneath by the [Button](/components/button), [Dropdown](/components/dropdown) and [VerticalNavigation](/components/vertical-navigation) components.
## IntelliSense
If you're using VSCode and wish to get autocompletion for the classes `active-class` and `inactive-class`, you can add the following settings to your `.vscode/settings.json`:
```json [.vscode/settings.json]
{
"tailwindCSS.classAttributes": [
"active-class",
"inactive-class"
],
}
```
## Props
:component-props

View File

@@ -1,12 +1,12 @@
---
description: Display a modal within your application.
links:
- label: GitHub
icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/overlays/Modal.vue
- label: 'Dialog'
icon: i-simple-icons-headlessui
to: 'https://headlessui.com/vue/dialog'
- label: GitHub
icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/overlays/Modal.vue
---
## Usage
@@ -40,7 +40,7 @@ Use the `prevent-close` prop to disable the outside click alongside the `esc` ke
You can still handle the `esc` shortcut yourself by using our [defineShortcuts](/getting-started/shortcuts#defineshortcuts) composable.
```vue
<script setup>
<script setup lang="ts">
const isOpen = ref(false)
defineShortcuts({
@@ -59,6 +59,28 @@ Set the `fullscreen` prop to `true` to enable it.
:component-example{component="modal-example-fullscreen"}
### Control programmatically :u-badge{label="New" class="align-middle ml-2 !rounded-full" variant="subtle"}
First of all, add the `Modals` component to your app, preferably inside `app.vue`.
```vue [app.vue]
<template>
<div>
<UContainer>
<NuxtPage />
</UContainer>
<UModals />
</div>
</template>
```
Then, you can use the `useModal` composable to control your modals within your app.
:component-example{component="modal-example-composable"}
Additionally, you can close the modal within the modal component by calling `modal.close()`.
## Props
:component-props

View File

@@ -42,7 +42,7 @@ Then, you can use the `useToast` composable to add notifications to your app:
When using `toast.add`, this will push a new notification to the stack displayed in `<UNotifications />`. All the props of the `Notification` component can be passed to `toast.add`.
```vue
<script setup>
<script setup lang="ts">
const toast = useToast()
onMounted(() => {
@@ -289,8 +289,14 @@ Slots defined in the `<UNotifications />` component are automatically passed dow
## Props
:component-props
::tabs
:component-props{label="Notification"}
:component-props{label="Notifications" slug="UNotifications"}
::
## Config
:component-preset
::tabs
:component-preset{label="Notification"}
:component-preset{label="Notifications" slug="Notifications"}
::

View File

@@ -1,12 +1,12 @@
---
description: Display a non-modal dialog that floats around a trigger element.
links:
- label: GitHub
icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/overlays/Popover.vue
- label: 'Popover'
icon: i-simple-icons-headlessui
to: 'https://headlessui.com/vue/popover'
- label: GitHub
icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/overlays/Popover.vue
---
## Usage

View File

@@ -2,12 +2,12 @@
title: SelectMenu
description: Display a select menu with advanced features.
links:
- label: GitHub
icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/forms/SelectMenu.vue
- label: 'Listbox'
icon: i-simple-icons-headlessui
to: 'https://headlessui.com/vue/listbox'
- label: GitHub
icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/forms/SelectMenu.vue
---
## Usage
@@ -257,7 +257,7 @@ componentProps:
Use the `#option-create` slot to customize the content displayed when the `creatable` prop is `true` and there is no options. You will have access to the `query` property in the slot scope.
::callout{icon="i-heroicons-light-bulb"}
An example is available in the [Create option](#create-option) section.
An example is available in the [Creatable](#creatable) section.
::
### `empty`

View File

@@ -1,12 +1,12 @@
---
description: Display a dialog that slides in from the edge of the screen.
links:
- label: GitHub
icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/overlays/Slideover.vue
- label: 'Dialog'
icon: i-simple-icons-headlessui
to: 'https://headlessui.com/vue/dialog'
- label: GitHub
icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/overlays/Slideover.vue
---
## Usage
@@ -40,7 +40,7 @@ Use the `prevent-close` prop to disable the outside click alongside the `esc` ke
You can still handle the `esc` shortcut yourself by using our [defineShortcuts](/getting-started/shortcuts#defineshortcuts) composable.
```vue
<script setup>
<script setup lang="ts">
const isOpen = ref(false)
defineShortcuts({

View File

@@ -12,6 +12,7 @@ Use the `rows` prop to set the data to display in the table. By default, the tab
::component-example{class="grid"}
---
extraClass: 'overflow-hidden'
padding: false
component: 'table-example-basic'
componentProps:
@@ -32,6 +33,7 @@ Use the `columns` prop to configure which columns to display. It's an array of o
::component-example{class="grid"}
---
extraClass: 'overflow-hidden'
padding: false
component: 'table-example-columns'
componentProps:
@@ -43,6 +45,7 @@ You can easily use the [SelectMenu](/components/select-menu) component to change
::component-example{class="grid"}
---
extraClass: 'overflow-hidden'
padding: false
component: 'table-example-columns-selectable'
componentProps:
@@ -56,6 +59,7 @@ Also, you can apply styles to `th` elements by passing a `class` to columns.
::component-example{class="grid"}
---
extraClass: 'overflow-hidden'
padding: false
component: 'table-example-style'
componentProps:
@@ -71,6 +75,7 @@ You may specify the default direction of each column through the `direction` pro
::component-example{class="grid"}
---
extraClass: 'overflow-hidden'
padding: false
component: 'table-example-columns-sortable'
componentProps:
@@ -88,7 +93,7 @@ You can specify a default sort for the table through the `sort` prop. It's an ob
This will set the default sort and will work even if no column is set as `sortable`.
```vue
<script setup>
<script setup lang="ts">
const sort = ref({
column: 'name',
direction: 'desc'
@@ -129,7 +134,7 @@ When fetching data from an API, we can take advantage of the [`useFetch`](https:
When doing so, you might want to set the `sort-mode` prop to `manual` to disable the automatic sorting and return the rows as is.
```vue
<script setup>
<script setup lang="ts">
// Ensure it uses `ref` instead of `reactive`.
const sort = ref({
column: 'name',
@@ -151,7 +156,7 @@ const { data, pending } = await useLazyFetch(() => `/api/users?orderBy=${sort.va
```
::callout{icon="i-heroicons-light-bulb" to="https://nuxt.com/docs/api/composables/use-fetch#params" target="_blank"}
We pass a function to `useLazyFetch` here make the url reactive but you can use the `query` / `params` options alongside `watch`.
We pass a function to `useLazyFetch` here to make the url reactive but you can use the `query` / `params` options alongside `watch`.
::
#### Custom sorting
@@ -160,6 +165,7 @@ Use the `sort-button` prop to customize the sort button in the header. You can p
::component-card{class="grid"}
---
extraClass: 'overflow-hidden'
padding: false
baseProps:
class: 'w-full'
@@ -240,6 +246,7 @@ Use a `v-model` to make the table selectable. The `v-model` will be an array of
::component-example{class="grid"}
---
extraClass: 'overflow-hidden'
padding: false
component: 'table-example-selectable'
componentProps:
@@ -257,6 +264,7 @@ You can use this to navigate to a page, open a modal or even to select the row m
::component-example{class="grid"}
---
extraClass: 'overflow-hidden'
padding: false
component: 'table-example-clickable'
componentProps:
@@ -270,6 +278,7 @@ You can easily use the [Input](/components/input) component to filter the rows.
::component-example{class="grid"}
---
extraClass: 'overflow-hidden'
padding: false
component: 'table-example-searchable'
componentProps:
@@ -283,6 +292,7 @@ You can easily use the [Pagination](/components/pagination) component to paginat
::component-example{class="grid"}
---
extraClass: 'overflow-hidden'
padding: false
component: 'table-example-paginable'
componentProps:
@@ -292,14 +302,15 @@ componentProps:
### Loading
Use the `loading` prop to display a loading state.
Use the `loading` prop to indicate that data is currently loading with an indeterminate [Progress](/components/progress#indeterminate) bar.
Use the `loading-state` prop to customize the `icon` and `label` or change them globally in `ui.table.default.loadingState`.
You can use the `progress` prop to customize the `color` and `animation` of the progress bar or change them globally in `ui.table.default.progress` (you can set it to `null` to hide the progress bar).
You can also set it to `null` to hide the loading state.
If there is no rows provided, a loading state will also be displayed. You can use the `loading-state` prop to customize the `icon` and `label` or change them globally in `ui.table.default.loadingState` (you can set it to `null` to hide the loading state).
::component-card
---
extraClass: 'overflow-hidden'
padding: false
baseProps:
class: 'w-full'
@@ -319,15 +330,19 @@ props:
loadingState:
icon: 'i-heroicons-arrow-path-20-solid'
label: "Loading..."
progress:
color: 'primary'
animation: 'carousel'
excludedProps:
- loadingState
- progress
---
::
This can be easily used with Nuxt `useAsyncData` composable.
```vue
<script setup>
<script setup lang="ts">
const columns = [...]
const { pending, data: people } = await useLazyAsyncData('people', () => $fetch('/api/people'))
@@ -348,6 +363,7 @@ You can also set it to `null` to hide the empty state.
::component-card
---
extraClass: 'overflow-hidden'
padding: false
baseProps:
class: 'w-full'
@@ -398,6 +414,7 @@ You can for example create an extra column for actions with a [Dropdown](/compon
::component-example{class="grid"}
---
extraClass: 'overflow-hidden'
padding: false
component: 'table-example-slots'
componentProps:
@@ -411,6 +428,7 @@ Use the `#loading-state` slot to customize the loading state.
::component-example{class="grid"}
---
extraClass: 'overflow-hidden'
padding: false
component: 'table-example-loading-slot'
componentProps:
@@ -424,6 +442,7 @@ Use the `#empty-state` slot to customize the empty state.
::component-example{class="grid"}
---
extraClass: 'overflow-hidden'
padding: false
component: 'table-example-empty-slot'
componentProps:
@@ -443,7 +462,16 @@ componentProps:
Here is an example of a Table component with all its features implemented.
:component-example{component="table-example-advanced" hiddenCode :padding="false" }
::component-example
---
extraClass: 'overflow-hidden'
padding: false
component: 'table-example-advanced'
componentProps:
class: 'flex-1'
hiddenCode: true
---
::
::callout{icon="i-simple-icons-github" to="https://github.com/nuxt/ui/blob/dev/docs/components/content/examples/TableExampleAdvanced.vue" target="_blank"}
Take a look at the component!

View File

@@ -119,6 +119,19 @@ props:
---
::
Use the `maxrows` prop to set a maximum number of rows when autoresizing. If set to `0`, the Textarea will infinitely grow up. :u-badge{label="New" class="!rounded-full" variant="subtle"}
::component-card
---
baseProps:
placeholder: 'Search...'
modelValue: 'Here is an autoresize Textarea, write new lines to make the Textarea grow up at a maximum of 5 rows...'
props:
autoresize: true
maxrows: 5
---
::
### Resize
Use the `resize` prop to enable the resize control.

View File

@@ -1,12 +1,12 @@
---
description: Display a toggle field.
links:
- label: GitHub
icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/forms/Toggle.vue
- label: 'Switch'
icon: i-simple-icons-headlessui
to: 'https://headlessui.com/vue/switch'
- label: GitHub
icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/forms/Toggle.vue
---
## Usage

View File

@@ -79,10 +79,10 @@ pro:
title: Upgrade to <span class="text-primary">Nuxt UI Pro</span>
description: 'Nuxt UI Pro is a collection of premium Vue components built on top of Nuxt UI to create beautiful & responsive Nuxt applications in minutes.<br>It includes all primitives to build landing pages, documentations, blogs, dashboards or entire SaaS products.'
links:
- label: View plans
- label: Buy now
to: /pro/pricing
color: gray
icon: i-heroicons-credit-card
color: black
trailingIcon: i-heroicons-arrow-right-20-solid
size: lg
- label: Explore templates
to: /pro/templates
@@ -178,7 +178,7 @@ pro:
</UPageBody>
<template #right>
<UDocsToc :links="page.body.toc.links" />
<UContentToc :links="page.body.toc.links" />
</template>
</UPage>
</template>

View File

@@ -1,7 +1,13 @@
navigation: false
title: Releases
description: Follow the latest releases and updates of Nuxt UI.
title: '<span class="text-primary">Nuxt UI</span> Releases'
head.title: Releases
description: Follow the latest releases and updates happening on the nuxt/ui repository.
links:
- label: Get Started
trailingIcon: i-heroicons-arrow-right-20-solid
to: /getting-started
size: md
color: black
- label: View on GitHub
icon: i-simple-icons-github
to: https://github.com/nuxt/ui/releases

View File

@@ -1,3 +1,4 @@
<!-- eslint-disable vue/no-v-html -->
<template>
<div>
<NuxtLoadingIndicator />
@@ -12,11 +13,18 @@
</UMain>
</UContainer>
<Footer />
<ClientOnly>
<LazyUDocsSearch :files="files" :navigation="navigation" :links="links" :fuse="{ resultLimit: 1000 }" />
<LazyUContentSearch :files="files" :navigation="navigation" :links="links" :fuse="{ resultLimit: 42 }" />
</ClientOnly>
<UNotifications />
<UNotifications>
<template #title="{ title }">
<span v-html="title" />
</template>
</UNotifications>
<UModals />
</div>
</template>
@@ -34,6 +42,7 @@ defineProps<{
}>()
const route = useRoute()
const colorMode = useColorMode()
const { branch } = useContentSource()
const { data: nav } = await useAsyncData('navigation', () => fetchContentNavigation())
@@ -52,9 +61,11 @@ const navigation = computed(() => {
]
}
return nav.value.filter(item => item._path !== '/dev')
return nav.value?.filter(item => item._path !== '/dev') || []
})
const color = computed(() => colorMode.value === 'dark' ? '#18181b' : 'white')
const links = computed(() => {
return [{
label: 'Docs',
@@ -65,7 +76,7 @@ const links = computed(() => {
label: 'Pro',
icon: 'i-heroicons-square-3-stack-3d',
to: '/pro',
active: route.path.startsWith('/pro/getting-started') || route.path.startsWith('/pro/components')
active: route.path.startsWith('/pro/getting-started') || route.path.startsWith('/pro/components') || route.path.startsWith('/pro/prose')
}, {
label: 'Pricing',
icon: 'i-heroicons-credit-card',
@@ -81,6 +92,21 @@ const links = computed(() => {
}].filter(Boolean)
})
// Head
useHead({
meta: [
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ key: 'theme-color', name: 'theme-color', content: color }
],
link: [
{ rel: 'icon', type: 'image/svg+xml', href: '/icon.svg' }
],
htmlAttrs: {
lang: 'en'
}
})
// Provide
provide('navigation', navigation)

View File

@@ -17,12 +17,10 @@ export default defineNuxtConfig({
].filter(Boolean),
modules: [
'@nuxt/content',
'@nuxt/fonts',
'@nuxt/image',
'nuxt-og-image',
// '@nuxthq/studio',
module,
'@nuxtjs/fontaine',
'@nuxtjs/google-fonts',
'@nuxtjs/plausible',
'@vueuse/nuxt',
'nuxt-component-meta',
@@ -40,6 +38,12 @@ export default defineNuxtConfig({
safelistColors: excludeColors(colors)
},
content: {
highlight: {
langs: [
'postcss',
'mdc'
]
},
sources: {
dev: {
prefix: '/dev',
@@ -67,6 +71,9 @@ export default defineNuxtConfig({
} : undefined
}
},
image: {
provider: 'ipx'
},
fontMetrics: {
fonts: ['DM Sans']
},

View File

@@ -1,35 +1,34 @@
{
"name": "@nuxt/ui-docs",
"private": true,
"type": "module",
"dependencies": {
"@nuxt/ui": "workspace:latest"
},
"devDependencies": {
"@iconify-json/heroicons": "^1.1.19",
"@iconify-json/simple-icons": "^1.1.90",
"@nuxt/content": "^2.11.0",
"@iconify-json/heroicons": "^1.1.20",
"@iconify-json/simple-icons": "^1.1.94",
"@nuxt/content": "^2.12.0",
"@nuxt/devtools": "^1.0.8",
"@nuxt/eslint-config": "^0.2.0",
"@nuxt/fonts": "^0.0.1",
"@nuxt/image": "^1.3.0",
"@nuxt/ui-pro": "npm:@nuxt/ui-pro-edge@0.7.3-28443525.f773d8f",
"@nuxthq/studio": "^1.0.10",
"@nuxtjs/fontaine": "^0.4.1",
"@nuxtjs/google-fonts": "^3.1.3",
"@nuxt/ui-pro": "npm:@nuxt/ui-pro-edge@1.0.1-28492961.4d49b9c",
"@nuxtjs/plausible": "^0.2.4",
"@octokit/rest": "^20.0.2",
"@vueuse/nuxt": "^10.7.2",
"@vueuse/nuxt": "^10.9.0",
"date-fns": "^3.3.1",
"eslint": "^8.56.0",
"joi": "^17.11.1",
"nuxt": "^3.9.3",
"eslint": "^8.57.0",
"joi": "^17.12.2",
"nuxt": "^3.10.3",
"nuxt-cloudflare-analytics": "^1.0.8",
"nuxt-component-meta": "^0.6.3",
"nuxt-og-image": "^2.2.4",
"prettier": "^3.2.4",
"prettier": "^3.2.5",
"typescript": "^5.3.3",
"ufo": "^1.3.2",
"ufo": "^1.4.0",
"v-calendar": "^3.1.2",
"valibot": "^0.25.0",
"valibot": "^0.29.0",
"yup": "^1.3.3",
"zod": "^3.22.4"
}

View File

@@ -7,11 +7,11 @@
<hr v-if="surround?.length">
<UDocsSurround :surround="surround" />
<UContentSurround :surround="surround" />
</UPageBody>
<template v-if="page?.body?.toc?.links?.length" #right>
<UDocsToc :links="page.body.toc.links">
<UContentToc :links="page.body.toc.links">
<template #bottom>
<div class="hidden lg:block space-y-6 !mt-6">
<UDivider v-if="page.body?.toc?.links?.length" type="dashed" />
@@ -30,7 +30,7 @@
</div>
</div>
</template>
</UDocsToc>
</UContentToc>
</template>
</UPage>
</template>
@@ -105,7 +105,7 @@ const communityLinks = computed(() => [{
const resourcesLinks = [{
icon: 'i-simple-icons-figma',
label: 'Figma Kit',
to: 'https://www.figma.com/community/file/1288455405058138934/nuxt-ui',
to: 'https://www.figma.com/community/file/1288455405058138934',
target: '_blank'
}, {
label: 'Playground',

View File

@@ -4,7 +4,7 @@
<ULandingHero :ui="{ base: 'relative z-[1]', container: 'max-w-4xl' }" class="mb-[calc(var(--header-height)*2)]">
<template #headline>
<UBadge variant="subtle" size="md" class="hover:bg-primary-100 dark:bg-primary-950/100 dark:hover:bg-primary-900 transition-color relative font-medium rounded-full shadow-none">
<NuxtLink :to="`https://github.com/nuxt/ui/releases/tag/v${config.version.split('.').slice(0, -1).join('.')}.0`" target="_blank" class="focus:outline-none" tabindex="-1">
<NuxtLink :to="`https://github.com/nuxt/ui/releases/tag/v${config.version.split('.').slice(0, -1).join('.')}.0`" target="_blank" class="focus:outline-none" aria-label="Go to last relase" tabindex="-1">
<span class="absolute inset-0" aria-hidden="true" />
</NuxtLink>
@@ -90,6 +90,7 @@
:width="card.image.width"
:height="card.image.height"
:alt="card.title"
loading="lazy"
class="object-cover w-full"
/>
</ULandingCard>
@@ -164,7 +165,13 @@
</ULandingSection>
<template v-if="navigation.find(item => item._path === '/pro')">
<ULandingHero :links="page.pro.links" :ui="{ title: 'sm:text-6xl' }">
<div class="relative">
<UDivider class="absolute inset-x-0" />
<div class="w-full relative overflow-hidden h-px bg-gradient-to-r from-gray-800 via-primary-400 to-gray-800 max-w-5xl mx-auto" />
</div>
<ULandingHero id="pro" :links="page.pro.links" :ui="{ title: 'sm:text-6xl' }" class="bg-gradient-to-b from-gray-50 dark:from-gray-950/50 to-white dark:to-gray-900 relative">
<template #title>
<span v-html="page.pro.title" />
</template>
@@ -172,6 +179,14 @@
<template #description>
<span v-html="page.pro.description" />
</template>
<div class="bg-gray-900/5 dark:bg-white/5 ring-1 ring-inset ring-gray-900/10 dark:ring-white/10 rounded-xl lg:-m-4 p-4">
<video preload="none" poster="https://res.cloudinary.com/nuxt/video/upload/so_3.3/v1708511800/ui-pro/video-nuxt-ui-pro_kwfbdh.jpg" controls class="rounded-lg">
<source src="https://res.cloudinary.com/nuxt/video/upload/v1708511800/ui-pro/video-nuxt-ui-pro_kwfbdh.webm" type="video/webm">
<source src="https://res.cloudinary.com/nuxt/video/upload/v1708511800/ui-pro/video-nuxt-ui-pro_kwfbdh.mp4" type="video/mp4">
<source src="https://res.cloudinary.com/nuxt/video/upload/v1708511800/ui-pro/video-nuxt-ui-pro_kwfbdh.ogg" type="video/ogg">
</video>
</div>
</ULandingHero>
<ULandingSection v-for="(section, index) in page.pro.sections" :key="index" v-bind="section" class="!pt-0">
@@ -240,7 +255,7 @@
</template>
<template #aside-top>
<UDocsSearchButton size="md" class="w-full" />
<UContentSearchButton size="md" class="w-full" />
</template>
<template #aside-default>
@@ -268,8 +283,8 @@
</div>
</template>
<template #docs-surround>
<UDocsSurround
<template #content-surround>
<UContentSurround
:surround="(surround as unknown as ParsedContent[])"
class="w-full gap-4"
:ui="{
@@ -286,9 +301,9 @@
/>
</template>
<template #docs-toc>
<template #content-toc>
<div class="absolute top-0 left-0 right-0 space-y-3">
<UDocsToc :links="toc" class="bg-transparent relative max-h-full overflow-hidden top-0" :ui="({ container: { base: '!pt-0 !pb-4' } } as any)" />
<UContentToc :links="toc" class="bg-transparent relative max-h-full overflow-hidden top-0" :ui="({ container: { base: '!pt-0 !pb-4' } } as any)" />
<UDivider type="dashed" :ui="{ border: { base: 'border-gray-800/10 dark:border-gray-200/10' } }" />
@@ -366,7 +381,7 @@
<template #description>
<span v-html="page.pro.landing?.description" />
</template>
<video poster="https://res.cloudinary.com/nuxt/video/upload/so_14.4/v1698923423/ui-pro/nuxt-ui-pro-landing-demo_yrh6nr.jpg" controls>
<video preload="none" poster="https://res.cloudinary.com/nuxt/video/upload/so_14.4/v1698923423/ui-pro/nuxt-ui-pro-landing-demo_yrh6nr.jpg" controls>
<source src="https://res.cloudinary.com/nuxt/video/upload/v1698923423/ui-pro/nuxt-ui-pro-landing-demo_yrh6nr.webm" type="video/webm">
<source src="https://res.cloudinary.com/nuxt/video/upload/v1698923423/ui-pro/nuxt-ui-pro-landing-demo_yrh6nr.mp4" type="video/mp4">
<source src="https://res.cloudinary.com/nuxt/video/upload/v1698923423/ui-pro/nuxt-ui-pro-landing-demo_yrh6nr.ogg" type="video/ogg">
@@ -379,7 +394,7 @@
<template #description>
<span v-html="page.pro.docs?.description" />
</template>
<video poster="https://res.cloudinary.com/nuxt/video/upload/v1698923398/ui-pro/nuxt-ui-pro-docs-demo_jm6ubr.jpg" controls>
<video preload="none" poster="https://res.cloudinary.com/nuxt/video/upload/v1698923398/ui-pro/nuxt-ui-pro-docs-demo_jm6ubr.jpg" controls>
<source src="https://res.cloudinary.com/nuxt/video/upload/v1698923398/ui-pro/nuxt-ui-pro-docs-demo_jm6ubr.webm" type="video/webm">
<source src="https://res.cloudinary.com/nuxt/video/upload/v1698923398/ui-pro/nuxt-ui-pro-docs-demo_jm6ubr.mp4" type="video/mp4">
<source src="https://res.cloudinary.com/nuxt/video/upload/v1698923398/ui-pro/nuxt-ui-pro-docs-demo_jm6ubr.ogg" type="video/ogg">
@@ -454,7 +469,7 @@ const landingBlocks = computed(() => isAfterStep(steps.landing) && isBeforeStep(
inactive: true,
children: [{
name: 'ULandingHero',
to: '/pro/components/landing/landing-hero',
to: '/pro/components/landing-hero',
class: [
'inset-4',
isAfterStep(steps.landing + 2) && '-top-[calc(var(--y)-var(--step-y)-1rem)] bottom-[calc(var(--y)-var(--step-y)+1rem)]'
@@ -469,7 +484,7 @@ const landingBlocks = computed(() => isAfterStep(steps.landing) && isBeforeStep(
}]
}, isAfterStep(steps.landing + 2) && {
name: 'ULandingSection',
to: '/pro/components/landing/landing-section',
to: '/pro/components/landing-section',
class: [
'inset-4',
isBeforeStep(steps.landing + 6) && '-top-[calc(var(--y)-var(--prev-step-y)-var(--height)-1rem)] bottom-[calc(var(--y)-var(--prev-step-y)-var(--height)+1rem)]',
@@ -486,7 +501,7 @@ const landingBlocks = computed(() => isAfterStep(steps.landing) && isBeforeStep(
class: 'inset-x-4 top-16'
}, {
name: 'ULandingGrid',
to: '/pro/components/landing/landing-grid',
to: '/pro/components/landing-grid',
class: ['inset-x-4 bottom-4 top-48', isAfterStep(steps.landing + 8) && 'grid grid-cols-4 gap-4 p-4'].filter(Boolean).join(' '),
inactive: isAfterStep(steps.landing + 8),
children: [isAfterStep(steps.landing + 9) ? {
@@ -494,7 +509,7 @@ const landingBlocks = computed(() => isAfterStep(steps.landing) && isBeforeStep(
class: '!relative'
} : {
name: 'ULandingCard',
to: '/pro/components/landing/landing-card',
to: '/pro/components/landing-card',
class: '!relative h-full',
inactive: false
}, isAfterStep(steps.landing + 9) ? {
@@ -502,7 +517,7 @@ const landingBlocks = computed(() => isAfterStep(steps.landing) && isBeforeStep(
class: '!relative h-full'
} : {
name: 'ULandingCard',
to: '/pro/components/landing/landing-card',
to: '/pro/components/landing-card',
class: '!relative h-full',
inactive: false
}, isAfterStep(steps.landing + 9) ? {
@@ -510,7 +525,7 @@ const landingBlocks = computed(() => isAfterStep(steps.landing) && isBeforeStep(
class: '!relative h-full'
} : {
name: 'ULandingCard',
to: '/pro/components/landing/landing-card',
to: '/pro/components/landing-card',
class: '!relative h-full',
inactive: false
}, isAfterStep(steps.landing + 9) ? {
@@ -518,14 +533,14 @@ const landingBlocks = computed(() => isAfterStep(steps.landing) && isBeforeStep(
class: '!relative h-full'
} : {
name: 'ULandingCard',
to: '/pro/components/landing/landing-card',
to: '/pro/components/landing-card',
class: '!relative h-full',
inactive: false
}]
}]
}, isAfterStep(steps.landing + 10) && {
name: 'ULandingSection',
to: '/pro/components/landing/landing-section',
to: '/pro/components/landing-section',
class: [
'inset-4',
isBeforeStep(steps.landing + 14) && '-top-[calc(var(--y)-var(--prev-step-y)-var(--height)-1rem)] bottom-[calc(var(--y)-var(--prev-step-y)-var(--height)+1rem)]'
@@ -550,12 +565,12 @@ const landingBlocks = computed(() => isAfterStep(steps.landing) && isBeforeStep(
const docsBlocks = computed(() => [isAfterStep(steps.docs) && {
name: 'UPage',
to: '/pro/components/page/page',
to: '/pro/components/page',
class: 'inset-x-0 top-20 bottom-20',
inactive: isAfterStep(steps.docs + 1),
children: [isAfterStep(steps.docs + 2) ? {
name: 'UAside',
to: '/pro/components/aside/aside',
to: '/pro/components/aside',
class: 'left-4 inset-y-4 w-64',
inactive: isAfterStep(steps.docs + 3),
children: [isAfterStep(steps.docs + 4) ? {
@@ -566,7 +581,7 @@ const docsBlocks = computed(() => [isAfterStep(steps.docs) && {
class: 'inset-x-4 top-4 h-9'
}, isAfterStep(steps.docs + 5) ? {
name: 'UNavigationTree',
to: '/pro/components/navigation/navigation-tree',
to: '/pro/components/navigation-tree',
class: ['inset-x-4 top-[4.25rem] bottom-4', isAfterStep(steps.docs + 6) && '!bg-transparent !border-0'].join(' '),
inactive: isAfterStep(steps.docs + 6),
children: [{
@@ -582,12 +597,12 @@ const docsBlocks = computed(() => [isAfterStep(steps.docs) && {
class: 'left-4 inset-y-4 w-64'
}, isAfterStep(steps.docs + 7) ? {
name: 'UPage',
to: '/pro/components/page/page',
to: '/pro/components/page',
class: 'left-72 right-4 inset-y-4',
inactive: isAfterStep(steps.docs + 8),
children: [...(isAfterStep(steps.docs + 9) ? [{
name: 'UPageHeader',
to: '/pro/components/page/page-header',
to: '/pro/components/page-header',
class: 'top-4 left-4 right-72 h-32',
inactive: isAfterStep(steps.docs + 10),
children: [{
@@ -596,18 +611,18 @@ const docsBlocks = computed(() => [isAfterStep(steps.docs) && {
}]
}, {
name: 'UPageBody',
to: '/pro/components/page/page-body',
to: '/pro/components/page-body',
class: 'top-40 left-4 right-72 bottom-4 overflow-y-auto',
inactive: isAfterStep(steps.docs + 11),
children: [{
slot: 'page-body',
class: 'inset-x-4 top-4 justify-start'
}, isAfterStep(steps.docs + 12) ? {
slot: 'docs-surround',
slot: 'content-surround',
class: 'bottom-4 inset-x-4 h-28'
} : {
name: 'UDocsSurround',
to: '/pro/components/docs/docs-surround',
name: 'UContentSurround',
to: '/pro/components/content-surround',
class: 'bottom-4 inset-x-4 h-28',
inactive: false
}]
@@ -615,12 +630,12 @@ const docsBlocks = computed(() => [isAfterStep(steps.docs) && {
name: '#default',
class: 'left-4 right-72 inset-y-4'
}]), isAfterStep(steps.docs + 13) ? {
name: 'UDocsToc',
to: '/pro/components/docs/docs-toc',
name: 'UContentToc',
to: '/pro/components/content-toc',
class: 'right-4 inset-y-4 w-64',
inactive: isAfterStep(steps.docs + 14),
children: [{
slot: 'docs-toc',
slot: 'content-toc',
class: 'inset-4 overflow-y-auto'
}]
} : {
@@ -635,7 +650,7 @@ const docsBlocks = computed(() => [isAfterStep(steps.docs) && {
const blocks = computed(() => [isAfterStep(steps.header) && {
name: 'UHeader',
to: '/pro/components/header/header',
to: '/pro/components/header',
class: 'h-16 inset-x-0 top-0',
inactive: isAfterStep(steps.header + 1),
children: [isAfterStep(steps.header + 2) ? {
@@ -659,7 +674,7 @@ const blocks = computed(() => [isAfterStep(steps.header) && {
}]
}, isAfterStep(steps.footer) && {
name: 'UFooter',
to: '/pro/components/footer/footer',
to: '/pro/components/footer',
class: 'h-16 inset-x-0 bottom-0',
inactive: isAfterStep(steps.footer + 1),
children: [isAfterStep(steps.footer + 2) ? {
@@ -700,9 +715,9 @@ function getStepY (step: number) {
// Hooks
onMounted(() => {
nextTick(() => {
start.value = top.value
})
setTimeout(() => {
start.value = top.value + y.value
}, 100)
})
// Slots Data
@@ -749,10 +764,12 @@ const navigationLinks = [{
const surround = [{
title: 'Introduction',
description: 'Fully styled and customizable components for Nuxt.'
description: 'Fully styled and customizable components for Nuxt.',
_path: '/'
}, {
title: 'Theming',
description: 'Learn how to customize the look and feel of the components.'
description: 'Learn how to customize the look and feel of the components.',
_path: '/'
}]
const md = `

View File

@@ -1,4 +1,4 @@
<script setup>
<script setup lang="ts">
const title = 'Playground'
const description = 'Play online with our interactive Nuxt Image playground.'

View File

@@ -6,7 +6,12 @@
<div class="h-px w-px rounded-full bg-transparent" />
</div>
<UPageHero :title="page.title" :description="page.description" :links="page.links" align="center" />
<ULandingHero :description="page.description" :links="page.links" align="center" :ui="{ title: 'sm:text-6xl' }" class="md:py-32">
<template #title>
<!-- eslint-disable-next-line vue/no-v-html -->
<span v-html="page.title" />
</template>
</ULandingHero>
<UPageBody>
<div class="h-[96px] w-0.5 bg-gray-200 dark:bg-gray-800 mx-auto rounded-t-full" />
@@ -54,18 +59,20 @@ const dates = computed(() => {
})
})
const title = page.value.head?.title || page.value.title
const description = page.value.head?.description || page.value.description
useSeoMeta({
titleTemplate: '%s - Nuxt UI',
title: page.value.title,
ogTitle: `${page.value.title} - Nuxt UI`,
description: page.value.description,
ogDescription: page.value.description
title,
description,
ogTitle: `${title} - Nuxt UI`,
ogDescription: description
})
defineOgImage({
component: 'Docs',
title: page.value.title,
description: page.value.description
title,
description
})
</script>

View File

@@ -1,4 +1,4 @@
<script setup>
<script setup lang="ts">
const title = 'Roadmap'
const description = 'Discover our Volta board for @nuxt/ui development status.'

View File

@@ -5,7 +5,7 @@ export default <Partial<Config>>{
theme: {
extend: {
fontFamily: {
sans: ['DM Sans', 'DM Sans fallback', ...defaultTheme.fontFamily.sans]
sans: ['DM Sans', ...defaultTheme.fontFamily.sans]
},
colors: {
green: {

View File

@@ -1,8 +1,9 @@
{
"name": "@nuxt/ui",
"version": "2.13.0",
"version": "2.14.2",
"repository": "nuxt/ui",
"homepage": "https://ui.nuxt.com",
"type": "module",
"license": "MIT",
"exports": {
".": {
@@ -32,27 +33,27 @@
"test": "vitest"
},
"dependencies": {
"@egoist/tailwindcss-icons": "^1.7.2",
"@egoist/tailwindcss-icons": "^1.7.4",
"@headlessui/tailwindcss": "^0.2.0",
"@headlessui/vue": "1.7.16",
"@iconify-json/heroicons": "^1.1.19",
"@nuxt/kit": "^3.9.3",
"@headlessui/vue": "^1.7.19",
"@iconify-json/heroicons": "^1.1.20",
"@nuxt/kit": "^3.10.3",
"@nuxtjs/color-mode": "^3.3.2",
"@nuxtjs/tailwindcss": "^6.11.2",
"@nuxtjs/tailwindcss": "^6.11.4",
"@popperjs/core": "^2.11.8",
"@tailwindcss/aspect-ratio": "^0.4.2",
"@tailwindcss/container-queries": "^0.1.1",
"@tailwindcss/forms": "^0.5.7",
"@tailwindcss/typography": "^0.5.10",
"@vueuse/core": "^10.7.2",
"@vueuse/integrations": "^10.7.2",
"@vueuse/math": "^10.7.2",
"@vueuse/core": "^10.9.0",
"@vueuse/integrations": "^10.9.0",
"@vueuse/math": "^10.9.0",
"defu": "^6.1.4",
"fuse.js": "^6.6.2",
"nuxt-icon": "^0.6.8",
"ohash": "^1.1.3",
"pathe": "^1.1.2",
"scule": "^1.2.0",
"scule": "^1.3.0",
"tailwind-merge": "^2.2.1",
"tailwindcss": "^3.4.1"
},
@@ -62,24 +63,25 @@
"@nuxt/test-utils": "^3.11.0",
"@release-it/conventional-changelog": "^8.0.1",
"@vue/test-utils": "^2.4.4",
"eslint": "^8.56.0",
"happy-dom": "^13.3.5",
"joi": "^17.11.1",
"nuxt": "^3.9.3",
"release-it": "^17.0.3",
"eslint": "^8.57.0",
"happy-dom": "^13.6.2",
"joi": "^17.12.2",
"nuxt": "^3.10.3",
"release-it": "^17.1.1",
"typescript": "^5.3.3",
"unbuild": "^2.0.0",
"valibot": "^0.25.0",
"vitest": "^1.2.2",
"valibot": "^0.29.0",
"vitest": "^1.3.1",
"vitest-environment-nuxt": "^1.0.0",
"vue-tsc": "^1.8.27",
"vue-tsc": "^2.0.4",
"yup": "^1.3.3",
"zod": "^3.22.4"
},
"resolutions": {
"@nuxt/kit": "3.9.3",
"@nuxt/schema": "3.9.3",
"@nuxt/kit": "3.10.3",
"@nuxt/schema": "3.10.3",
"tailwindcss": "3.4.1",
"vue": "3.3.13"
"@headlessui/vue": "1.7.19",
"vue": "3.4.21"
}
}

5212
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
{
"extends": [
"@nuxtjs"
"github>nuxt/renovate-config-nuxt"
]
}

View File

@@ -1,24 +1,21 @@
import { createRequire } from 'node:module'
import { defineNuxtModule, installModule, addComponentsDir, addImportsDir, createResolver, addPlugin } from '@nuxt/kit'
import defaultColors from 'tailwindcss/colors.js'
import { defaultExtractor as createDefaultExtractor } from 'tailwindcss/lib/lib/defaultExtractor.js'
import { iconsPlugin, getIconCollections, type CollectionNames, type IconsPluginOptions } from '@egoist/tailwindcss-icons'
import { name, version } from '../package.json'
import { generateSafelist, excludeColors, customSafelistExtractor } from './colors'
import createTemplates from './templates'
import { generateSafelist, excludeColors, customSafelistExtractor } from './colors'
import * as config from './runtime/ui.config'
import type { DeepPartial, Strategy } from './runtime/types/utils'
const defaultExtractor = createDefaultExtractor({ tailwindConfig: { separator: ':' } })
const _require = createRequire(import.meta.url)
const defaultColors = _require('tailwindcss/colors.js')
// @ts-ignore
delete defaultColors.lightBlue
// @ts-ignore
delete defaultColors.warmGray
// @ts-ignore
delete defaultColors.trueGray
// @ts-ignore
delete defaultColors.coolGray
// @ts-ignore
delete defaultColors.blueGray
type UI = {
@@ -31,11 +28,13 @@ type UI = {
declare module 'nuxt/schema' {
interface AppConfigInput {
// @ts-ignore
ui?: UI
}
}
declare module '@nuxt/schema' {
interface AppConfigInput {
// @ts-ignore
ui?: UI
}
}
@@ -66,7 +65,7 @@ export default defineNuxtModule<ModuleOptions>({
version,
configKey: 'ui',
compatibility: {
nuxt: '^3.0.0-rc.8'
nuxt: '^3.10.0'
}
},
defaults: {
@@ -148,9 +147,6 @@ export default defineNuxtModule<ModuleOptions>({
tailwindConfig.safelist = tailwindConfig.safelist || []
tailwindConfig.safelist.push(...generateSafelist(options.safelistColors || [], colors))
tailwindConfig.plugins = tailwindConfig.plugins || []
tailwindConfig.plugins.push(iconsPlugin(Array.isArray(options.icons) ? { collections: getIconCollections(options.icons) } : typeof options.icons === 'object' ? options.icons as IconsPluginOptions : {}))
})
createTemplates(nuxt)
@@ -168,7 +164,8 @@ export default defineNuxtModule<ModuleOptions>({
require('@tailwindcss/aspect-ratio'),
require('@tailwindcss/typography'),
require('@tailwindcss/container-queries'),
require('@headlessui/tailwindcss')
require('@headlessui/tailwindcss'),
iconsPlugin(Array.isArray(options.icons) || options.icons === 'all' ? { collections: getIconCollections(options.icons) } : typeof options.icons === 'object' ? options.icons as IconsPluginOptions : {})
],
content: {
files: [
@@ -199,6 +196,10 @@ export default defineNuxtModule<ModuleOptions>({
src: resolve(runtimeDir, 'plugins', 'colors')
})
addPlugin({
src: resolve(runtimeDir, 'plugins', 'modals')
})
// Components
addComponentsDir({

View File

@@ -4,7 +4,7 @@
<thead :class="ui.thead">
<tr :class="ui.tr.base">
<th v-if="modelValue" scope="col" :class="ui.checkbox.padding">
<UCheckbox :checked="indeterminate || selected.length === rows.length" :indeterminate="indeterminate" aria-label="Select all" @change="onChange" />
<UCheckbox :model-value="indeterminate || selected.length === rows.length" :indeterminate="indeterminate" aria-label="Select all" @change="onChange" />
</th>
<th v-for="(column, index) in columns" :key="index" scope="col" :class="[ui.th.base, ui.th.padding, ui.th.color, ui.th.font, ui.th.size, column.class]">
@@ -20,9 +20,15 @@
</slot>
</th>
</tr>
<tr v-if="loading && progress">
<td :colspan="0" :class="ui.progress.wrapper">
<UProgress v-bind="{ ...(ui.default.progress || {}), ...progress }" size="2xs" />
</td>
</tr>
</thead>
<tbody :class="ui.tbody">
<tr v-if="loadingState && loading">
<tr v-if="loadingState && loading && !rows.length">
<td :colspan="columns.length + (modelValue ? 1 : 0)">
<slot name="loading-state">
<div :class="ui.loadingState.wrapper">
@@ -72,12 +78,13 @@ import type { PropType } from 'vue'
import { upperFirst } from 'scule'
import { defu } from 'defu'
import { useVModel } from '@vueuse/core'
import UButton from '../elements/Button.vue'
import UIcon from '../elements/Icon.vue'
import UButton from '../elements/Button.vue'
import UProgress from '../elements/Progress.vue'
import UCheckbox from '../forms/Checkbox.vue'
import { useUI } from '../../composables/useUI'
import { mergeConfig, get } from '../../utils'
import type { Strategy, Button } from '../../types'
import type { Strategy, Button, ProgressColor, ProgressAnimation } from '../../types'
// @ts-expect-error
import appConfig from '#build/app.config'
import { table } from '#ui/ui.config'
@@ -102,8 +109,9 @@ function defaultSort (a: any, b: any, direction: 'asc' | 'desc') {
export default defineComponent({
components: {
UButton,
UIcon,
UButton,
UProgress,
UCheckbox
},
inheritAttrs: false,
@@ -160,6 +168,10 @@ export default defineComponent({
type: Object as PropType<{ icon: string, label: string }>,
default: () => config.default.emptyState
},
progress: {
type: Object as PropType<{ color: ProgressColor, animation: ProgressAnimation }>,
default: () => config.default.progress
},
class: {
type: [String, Object, Array] as PropType<any>,
default: () => ''

View File

@@ -1,6 +1,13 @@
<template>
<div :class="ui.wrapper">
<HDisclosure v-for="(item, index) in items" v-slot="{ open, close }" :key="index" :default-open="defaultOpen || item.defaultOpen">
<HDisclosure
v-for="(item, index) in items"
v-slot="{ open, close }"
:key="index"
as="div"
:class="ui.container"
:default-open="defaultOpen || item.defaultOpen"
>
<HDisclosureButton
:ref="() => buttonRefs[index] = { open, close }"
as="template"
@@ -47,7 +54,7 @@
<script lang="ts">
import { ref, computed, toRef, defineComponent } from 'vue'
import type { PropType } from 'vue'
import { Disclosure as HDisclosure, DisclosureButton as HDisclosureButton, DisclosurePanel as HDisclosurePanel } from '@headlessui/vue'
import { Disclosure as HDisclosure, DisclosureButton as HDisclosureButton, DisclosurePanel as HDisclosurePanel, provideUseId } from '@headlessui/vue'
import UIcon from '../elements/Icon.vue'
import UButton from '../elements/Button.vue'
import { useUI } from '../../composables/useUI'
@@ -56,6 +63,7 @@ import type { AccordionItem, Strategy } from '../../types'
// @ts-expect-error
import appConfig from '#build/app.config'
import { accordion, button } from '#ui/ui.config'
import { useId } from '#imports'
const config = mergeConfig<typeof accordion>(appConfig.ui.strategy, appConfig.ui.accordion, accordion)
@@ -146,6 +154,8 @@ export default defineComponent({
el.addEventListener('transitionend', done, { once: true })
}
provideUseId(() => useId())
return {
// eslint-disable-next-line vue/no-dupe-keys
ui,

View File

@@ -5,12 +5,12 @@
<UAvatar v-if="avatar" v-bind="{ size: ui.avatar.size, ...avatar }" :class="ui.avatar.base" />
<div :class="ui.inner">
<p :class="ui.title">
<p v-if="(title || $slots.title)" :class="ui.title">
<slot name="title" :title="title">
{{ title }}
</slot>
</p>
<p v-if="description || $slots.description" :class="ui.description">
<p v-if="description || $slots.description" :class="twMerge(ui.description, !(title && $slots.title) && 'mt-0 leading-5')">
<slot name="description" :description="description">
{{ description }}
</slot>
@@ -57,7 +57,7 @@ export default defineComponent({
props: {
title: {
type: String,
required: true
default: null
},
description: {
type: String,

View File

@@ -35,16 +35,16 @@
</div>
<div v-if="indicators" :class="ui.indicators.wrapper">
<template v-for="index in indicatorsCount" :key="index">
<slot name="indicator" :on-click="onClick" :active="index === currentIndex" :index="index">
<template v-for="page in pages" :key="page">
<slot name="indicator" :on-click="onClick" :active="page === currentPage" :page="page">
<button
type="button"
:class="[
ui.indicators.base,
index === currentIndex ? ui.indicators.active : ui.indicators.inactive
page === currentPage ? ui.indicators.active : ui.indicators.inactive
]"
:aria-label="`set slide ${index}`"
@click="onClick(index)"
:aria-label="`set slide ${page}`"
@click="onClick(page)"
/>
</slot>
</template>
@@ -103,7 +103,7 @@ export default defineComponent({
default: undefined
}
},
setup (props) {
setup (props, { expose }) {
const { ui, attrs } = useUI('carousel', toRef(props, 'ui'), config, toRef(props, 'class'))
const carouselRef = ref<HTMLElement>()
@@ -122,9 +122,9 @@ export default defineComponent({
itemWidth.value = entry?.target?.firstElementChild?.clientWidth || 0
})
const currentIndex = computed(() => Math.round(x.value / itemWidth.value) + 1)
const currentPage = computed(() => Math.round(x.value / itemWidth.value) + 1)
const indicatorsCount = computed(() => {
const pages = computed(() => {
if (!itemWidth.value) {
return 0
}
@@ -140,10 +140,18 @@ export default defineComponent({
x.value -= itemWidth.value
}
function onClick (index: number) {
x.value = (index - 1) * itemWidth.value
function onClick (page: number) {
x.value = (page - 1) * itemWidth.value
}
expose({
pages,
page: currentPage,
prev: onClickPrev,
next: onClickNext,
select: onClick
})
return {
// eslint-disable-next-line vue/no-dupe-keys
ui,
@@ -151,8 +159,8 @@ export default defineComponent({
isFirst,
isLast,
carouselRef,
indicatorsCount,
currentIndex,
pages,
currentPage,
onClickNext,
onClickPrev,
onClick,

View File

@@ -7,7 +7,8 @@
:disabled="disabled"
:class="ui.trigger"
role="button"
@mouseover="onMouseOver"
@mouseenter="onMouseEnter"
@touchstart.prevent="onTouchStart"
>
<slot :open="open" :disabled="disabled">
<button :disabled="disabled">
@@ -16,25 +17,25 @@
</slot>
</HMenuButton>
<div v-if="open && items.length" ref="container" :class="[ui.container, ui.width]" :style="containerStyle" @mouseover="onMouseOver">
<div v-if="open && items.length" ref="container" :class="[ui.container, ui.width]" :style="containerStyle">
<Transition appear v-bind="ui.transition">
<div>
<div v-if="popper.arrow" data-popper-arrow :class="Object.values(ui.arrow)" />
<HMenuItems :class="[ui.base, ui.divide, ui.ring, ui.rounded, ui.shadow, ui.background, ui.height]" static>
<div v-for="(subItems, index) of items" :key="index" :class="ui.padding">
<NuxtLink v-for="(item, subIndex) of subItems" :key="subIndex" v-slot="{ href, target, rel, navigate, isExternal }" v-bind="getNuxtLinkProps(item)" custom>
<NuxtLink v-for="(item, subIndex) of subItems" :key="subIndex" v-slot="{ href, target, rel, navigate, isExternal, isActive }" v-bind="getNuxtLinkProps(item)" custom>
<HMenuItem v-slot="{ active, disabled: itemDisabled, close }" :disabled="item.disabled">
<component
:is="!!href ? 'a' : 'button'"
:href="!itemDisabled ? href : undefined"
:rel="rel"
:target="target"
:class="twMerge(twJoin(ui.item.base, ui.item.padding, ui.item.size, ui.item.rounded, active ? ui.item.active : ui.item.inactive, itemDisabled && ui.item.disabled), item.class)"
:class="twMerge(twJoin(ui.item.base, ui.item.padding, ui.item.size, ui.item.rounded, active || isActive ? ui.item.active : ui.item.inactive, itemDisabled && ui.item.disabled), item.class)"
@click="onClick($event, item, { href, navigate, close, isExternal })"
>
<slot :name="item.slot || 'item'" :item="item">
<UIcon v-if="item.icon" :name="item.icon" :class="twMerge(twJoin(ui.item.icon.base, active ? ui.item.icon.active : ui.item.icon.inactive), item.iconClass)" />
<UIcon v-if="item.icon" :name="item.icon" :class="twMerge(twJoin(ui.item.icon.base, active || isActive ? ui.item.icon.active : ui.item.icon.inactive), item.iconClass)" />
<UAvatar v-else-if="item.avatar" v-bind="{ size: ui.item.avatar.size, ...item.avatar }" :class="ui.item.avatar.base" />
<span :class="twMerge(ui.item.label, item.labelClass)">{{ item.label }}</span>
@@ -57,7 +58,7 @@
<script lang="ts">
import { defineComponent, ref, computed, watch, toRef, onMounted, resolveComponent } from 'vue'
import type { PropType } from 'vue'
import { Menu as HMenu, MenuButton as HMenuButton, MenuItems as HMenuItems, MenuItem as HMenuItem } from '@headlessui/vue'
import { Menu as HMenu, MenuButton as HMenuButton, MenuItems as HMenuItems, MenuItem as HMenuItem, provideUseId } from '@headlessui/vue'
import { defu } from 'defu'
import { twMerge, twJoin } from 'tailwind-merge'
import UIcon from '../elements/Icon.vue'
@@ -70,6 +71,7 @@ import type { DropdownItem, PopperOptions, Strategy } from '../../types'
// @ts-expect-error
import appConfig from '#build/app.config'
import { dropdown } from '#ui/ui.config'
import { useId } from '#imports'
const config = mergeConfig<typeof dropdown>(appConfig.ui.strategy, appConfig.ui.dropdown, dropdown)
@@ -180,7 +182,19 @@ export default defineComponent({
}
})
function onMouseOver () {
function onTouchStart () {
if (!menuApi.value) {
return
}
if (menuApi.value.menuState === 0) {
menuApi.value.closeMenu()
} else {
menuApi.value.openMenu()
}
}
function onMouseEnter () {
if (props.mode !== 'hover' || !menuApi.value) {
return
}
@@ -251,6 +265,8 @@ export default defineComponent({
const NuxtLink = resolveComponent('NuxtLink')
provideUseId(() => useId())
return {
// eslint-disable-next-line vue/no-dupe-keys
ui,
@@ -260,7 +276,8 @@ export default defineComponent({
trigger,
container,
containerStyle,
onMouseOver,
onTouchStart,
onMouseEnter,
onMouseLeave,
onClick,
getNuxtLinkProps,

View File

@@ -23,7 +23,7 @@
:rel="rel"
:target="target"
:class="active !== undefined ? (active ? activeClass : inactiveClass) : resolveLinkClass(route, $route, { isActive, isExactActive })"
@click="(e) => !isExternal && navigate(e)"
@click="(e) => (!isExternal && !disabled) && navigate(e)"
>
<slot v-bind="{ isActive: active !== undefined ? active : (exact ? isExactActive : isActive) }" />
</a>

View File

@@ -31,6 +31,7 @@
import { computed, defineComponent, toRef } from 'vue'
import type { SlotsType, PropType } from 'vue'
import { twJoin } from 'tailwind-merge'
import UIcon from './Icon.vue'
import { useUI } from '../../composables/useUI'
import { mergeConfig } from '../../utils'
import type { Strategy, MeterColor, MeterSize } from '../../types'
@@ -41,6 +42,9 @@ import { meter } from '#ui/ui.config'
const config = mergeConfig<typeof meter>(appConfig.ui.strategy, appConfig.ui.meter, meter)
export default defineComponent({
components: {
UIcon
},
inheritAttrs: false,
slots: Object as SlotsType<{
indicator?: { percent: number, value: number },

View File

@@ -9,7 +9,7 @@
</slot>
<progress :class="progressClass" v-bind="{ value, max: realMax }">
{{ Math.round(percent) }}%
{{ percent !== undefined ? `${Math.round(percent)}%` : undefined }}
</progress>
<div v-if="isSteps" :class="stepsClass">
@@ -28,7 +28,7 @@ import type { PropType } from 'vue'
import { twJoin } from 'tailwind-merge'
import { useUI } from '../../composables/useUI'
import { mergeConfig } from '../../utils'
import type { Strategy, ProgressSize, ProgressAnimation } from '../../types'
import type { Strategy, ProgressSize, ProgressAnimation, ProgressColor } from '../../types'
// @ts-expect-error
import appConfig from '#build/app.config'
import { progress } from '#ui/ui.config'
@@ -65,7 +65,7 @@ export default defineComponent({
}
},
color: {
type: String,
type: String as PropType<ProgressColor>,
default: () => config.default.color,
validator (value: string) {
return appConfig.ui.colors.includes(value)
@@ -157,7 +157,7 @@ export default defineComponent({
return index === 0
}
function stepClasses (index: string|number) {
function stepClasses (index: string | number) {
index = Number(index)
const classes = [stepClass.value]
@@ -189,6 +189,10 @@ export default defineComponent({
})
const percent = computed(() => {
if (isIndeterminate.value) {
return undefined
}
switch (true) {
case props.value < 0: return 0
case props.value > (realMax.value as number): return 100
@@ -237,9 +241,11 @@ progress:indeterminate {
&:after {
animation: carousel 2s ease-in-out infinite;
}
&::-webkit-progress-value {
animation: carousel 2s ease-in-out infinite;
}
&::-moz-progress-bar {
animation: carousel 2s ease-in-out infinite;
}
@@ -249,9 +255,11 @@ progress:indeterminate {
&:after {
animation: carousel-inverse 2s ease-in-out infinite;
}
&::-webkit-progress-value {
animation: carousel-inverse 2s ease-in-out infinite;
}
&::-moz-progress-bar {
animation: carousel-inverse 2s ease-in-out infinite;
}
@@ -261,9 +269,11 @@ progress:indeterminate {
&:after {
animation: swing 3s ease-in-out infinite;
}
&::-webkit-progress-value {
animation: swing 3s ease-in-out infinite;
}
&::-moz-progress-bar {
animation: swing 3s ease-in-out infinite;
}
@@ -273,9 +283,11 @@ progress:indeterminate {
&::after {
animation: elastic 3s ease-in-out infinite;
}
&::-webkit-progress-value {
animation: elastic 3s ease-in-out infinite;
}
&::-moz-progress-bar {
animation: elastic 3s ease-in-out infinite;
}
@@ -283,26 +295,65 @@ progress:indeterminate {
}
@keyframes carousel {
0%, 100% { width: 50% }
0% { transform: translateX(-100%) }
100% { transform: translateX(200%) }
0%,
100% {
width: 50%
}
0% {
transform: translateX(-100%)
}
100% {
transform: translateX(200%)
}
}
@keyframes carousel-inverse {
0%, 100% { width: 50% }
0% { transform: translateX(200%) }
100% { transform: translateX(-100%) }
0%,
100% {
width: 50%
}
0% {
transform: translateX(200%)
}
100% {
transform: translateX(-100%)
}
}
@keyframes swing {
0%, 100% { width: 50% }
0%, 100% { transform: translateX(-25%) }
50% { transform: translateX(125%) }
0%,
100% {
width: 50%
}
0%,
100% {
transform: translateX(-25%)
}
50% {
transform: translateX(125%)
}
}
@keyframes elastic {
/* Firefox doesn't do "margin: 0 auto", we have to play with margin-left */
0%, 100% { width: 50%; margin-left: 25%; }
50% { width: 90%; margin-left: 5% }
}
</style>
0%,
100% {
width: 50%;
margin-left: 25%;
}
50% {
width: 90%;
margin-left: 5%
}
}</style>

View File

@@ -8,7 +8,6 @@
:required="required"
:value="value"
:disabled="disabled"
:checked="checked"
:indeterminate="indeterminate"
type="checkbox"
:class="inputClass"
@@ -40,6 +39,7 @@ import type { Strategy } from '../../types'
import appConfig from '#build/app.config'
import { checkbox } from '#ui/ui.config'
import colors from '#ui-colors'
import { useId } from '#app'
const config = mergeConfig<typeof checkbox>(appConfig.ui.strategy, appConfig.ui.checkbox, checkbox)
@@ -66,13 +66,9 @@ export default defineComponent({
type: Boolean,
default: false
},
checked: {
type: Boolean,
default: false
},
indeterminate: {
type: Boolean,
default: false
default: undefined
},
help: {
type: String,
@@ -110,7 +106,8 @@ export default defineComponent({
setup (props, { emit }) {
const { ui, attrs } = useUI('checkbox', toRef(props, 'ui'), config, toRef(props, 'class'))
const { emitFormChange, color, name, inputId } = useFormGroup(props)
const { emitFormChange, color, name, inputId: _inputId } = useFormGroup(props)
const inputId = _inputId.value ?? useId()
const toggle = computed({
get () {

View File

@@ -12,7 +12,7 @@ import type { ValidationError as JoiError, Schema as JoiSchema } from 'joi'
import type { ObjectSchema as YupObjectSchema, ValidationError as YupError } from 'yup'
import type { ObjectSchemaAsync as ValibotObjectSchema } from 'valibot'
import type { FormError, FormEvent, FormEventType, FormSubmitEvent, FormErrorEvent, Form } from '../../types/form'
import { uid } from '../../utils/uid'
import { useId } from '#imports'
class FormException extends Error {
constructor (message: string) {
@@ -49,7 +49,8 @@ export default defineComponent({
},
emits: ['submit', 'error'],
setup (props, { expose, emit }) {
const bus = useEventBus<FormEvent>(`form-${uid()}`)
const formId = useId()
const bus = useEventBus<FormEvent>(`form-${formId}`)
onMounted(() => {
bus.on(async (event) => {
@@ -108,11 +109,14 @@ export default defineComponent({
errors.value = await getErrors()
}
if (!opts.silent && errors.value.length > 0) {
if (errors.value.length > 0) {
if (opts.silent) return false
throw new FormException(
`Form validation failed: ${JSON.stringify(errors.value, null, 2)}`
)
}
return props.state
}

View File

@@ -1,22 +1,24 @@
<template>
<div :class="ui.wrapper" v-bind="attrs">
<div v-if="label || $slots.label" :class="[ui.label.wrapper, size]">
<label :for="inputId" :class="[ui.label.base, required ? ui.label.required : '']">
<slot v-if="$slots.label" name="label" v-bind="{ error, label, name, hint, description, help }" />
<template v-else>{{ label }}</template>
</label>
<span v-if="hint || $slots.hint" :class="[ui.hint]">
<slot v-if="$slots.hint" name="hint" v-bind="{ error, label, name, hint, description, help }" />
<template v-else>{{ hint }}</template>
</span>
</div>
<div :class="ui.inner">
<div v-if="label || $slots.label" :class="[ui.label.wrapper, size]">
<label :for="inputId" :class="[ui.label.base, required ? ui.label.required : '']">
<slot v-if="$slots.label" name="label" v-bind="{ error, label, name, hint, description, help }" />
<template v-else>{{ label }}</template>
</label>
<span v-if="hint || $slots.hint" :class="[ui.hint]">
<slot v-if="$slots.hint" name="hint" v-bind="{ error, label, name, hint, description, help }" />
<template v-else>{{ hint }}</template>
</span>
</div>
<p v-if="description || $slots.description" :class="[ui.description, size]">
<slot v-if="$slots.description" name="description" v-bind="{ error, label, name, hint, description, help }" />
<template v-else>
{{ description }}
</template>
</p>
<p v-if="description || $slots.description" :class="[ui.description, size]">
<slot v-if="$slots.description" name="description" v-bind="{ error, label, name, hint, description, help }" />
<template v-else>
{{ description }}
</template>
</p>
</div>
<div :class="[label ? ui.container : '']">
<slot v-bind="{ error }" />
@@ -46,6 +48,7 @@ import type { FormError, InjectedFormGroupValue, FormGroupSize, Strategy } from
// @ts-expect-error
import appConfig from '#build/app.config'
import { formGroup } from '#ui/ui.config'
import { useId } from '#imports'
const config = mergeConfig<typeof formGroup>(appConfig.ui.strategy, appConfig.ui.formGroup, formGroup)
@@ -112,7 +115,7 @@ export default defineComponent({
})
const size = computed(() => ui.value.size[props.size ?? config.default.size])
const inputId = ref()
const inputId = ref(useId())
provide<InjectedFormGroupValue>('form-group', {
error,

View File

@@ -236,6 +236,7 @@ export default defineComponent({
ui.value.form,
rounded.value,
ui.value.placeholder,
props.type === 'file' && [ui.value.file.base, ui.value.file.padding[size.value]],
ui.value.size[size.value],
props.padded ? ui.value.padding[size.value] : 'p-0',
variant?.replaceAll('{color}', color.value),

View File

@@ -98,7 +98,8 @@ import {
ComboboxButton as HComboboxButton,
ComboboxOptions as HComboboxOptions,
ComboboxOption as HComboboxOption,
ComboboxInput as HComboboxInput
ComboboxInput as HComboboxInput,
provideUseId
} from '@headlessui/vue'
import { computedAsync, useDebounceFn } from '@vueuse/core'
import { defu } from 'defu'
@@ -114,6 +115,7 @@ import type { InputSize, InputColor, InputVariant, PopperOptions, Strategy } fro
// @ts-expect-error
import appConfig from '#build/app.config'
import { input, inputMenu } from '#ui/ui.config'
import { useId } from '#imports'
const config = mergeConfig<typeof input>(appConfig.ui.strategy, appConfig.ui.input, input)
@@ -275,7 +277,6 @@ export default defineComponent({
emits: ['update:modelValue', 'update:query', 'open', 'close', 'change'],
setup (props, { emit, slots }) {
const { ui, attrs } = useUI('input', toRef(props, 'ui'), config, toRef(props, 'class'))
const { ui: uiMenu } = useUI('inputMenu', toRef(props, 'uiMenu'), configMenu)
const popper = computed<PopperOptions>(() => defu({}, props.popper, uiMenu.value.popper as PopperOptions))
@@ -428,6 +429,8 @@ export default defineComponent({
query.value = event.target.value
}
provideUseId(() => useId())
return {
// eslint-disable-next-line vue/no-dupe-keys
ui,

View File

@@ -26,27 +26,26 @@
</template>
<script lang="ts">
import { computed, defineComponent, inject, toRef, onMounted, ref } from 'vue'
import { computed, defineComponent, inject, toRef } from 'vue'
import type { PropType } from 'vue'
import { twMerge, twJoin } from 'tailwind-merge'
import { useUI } from '../../composables/useUI'
import { useFormGroup } from '../../composables/useFormGroup'
import { mergeConfig } from '../../utils'
import type { Strategy } from '../../types'
// @ts-expect-error
import appConfig from '#build/app.config'
import { radio } from '#ui/ui.config'
import colors from '#ui-colors'
import { uid } from '../../utils/uid'
import { useFormGroup } from '../../composables/useFormGroup'
import { useId } from '#imports'
const config = mergeConfig<typeof radio>(appConfig.ui.strategy, appConfig.ui.radio, radio)
export default defineComponent({
inheritAttrs: false,
props: {
id: {
type: String,
default: () => null
default: null
},
value: {
type: [String, Number, Boolean],
@@ -100,15 +99,10 @@ export default defineComponent({
setup (props, { emit }) {
const { ui, attrs } = useUI('radio', toRef(props, 'ui'), config, toRef(props, 'class'))
const inputId = props.id ?? useId()
const radioGroup = inject('radio-group', null)
const { emitFormChange, color, name } = radioGroup ?? useFormGroup(props, config)
const inputId = ref(props.id)
onMounted(() => {
if (!inputId.value) {
inputId.value = uid()
}
})
const pick = computed({
get () {

View File

@@ -1,6 +1,6 @@
<template>
<div :class="ui.wrapper">
<fieldset v-bind="attrs">
<fieldset v-bind="attrs" :class="ui.fieldset">
<legend v-if="legend || $slots.legend" :class="ui.legend">
<slot name="legend">
{{ legend }}
@@ -12,6 +12,7 @@
:label="option.label"
:model-value="modelValue"
:value="option.value"
:help="option.help"
:disabled="option.disabled || disabled"
:ui="uiRadio"
@change="onUpdate(option.value)"

View File

@@ -26,11 +26,10 @@ import { twMerge, twJoin } from 'tailwind-merge'
import { useUI } from '../../composables/useUI'
import { useFormGroup } from '../../composables/useFormGroup'
import { mergeConfig } from '../../utils'
import type { RangeSize, Strategy } from '../../types'
import type { RangeSize, RangeColor, Strategy } from '../../types'
// @ts-expect-error
import appConfig from '#build/app.config'
import { range } from '#ui/ui.config'
import colors from '#ui-colors'
const config = mergeConfig<typeof range>(appConfig.ui.strategy, appConfig.ui.range, range)
@@ -73,7 +72,7 @@ export default defineComponent({
}
},
color: {
type: String as PropType<typeof colors[number]>,
type: String as PropType<RangeColor>,
default: () => config.default.color,
validator (value: string) {
return appConfig.ui.colors.includes(value)

View File

@@ -134,7 +134,8 @@ import {
Listbox as HListbox,
ListboxButton as HListboxButton,
ListboxOptions as HListboxOptions,
ListboxOption as HListboxOption
ListboxOption as HListboxOption,
provideUseId
} from '@headlessui/vue'
import { computedAsync, useDebounceFn } from '@vueuse/core'
import { defu } from 'defu'
@@ -150,6 +151,7 @@ import type { SelectSize, SelectColor, SelectVariant, PopperOptions, Strategy }
// @ts-expect-error
import appConfig from '#build/app.config'
import { select, selectMenu } from '#ui/ui.config'
import { useId } from '#imports'
const config = mergeConfig<typeof select>(appConfig.ui.strategy, appConfig.ui.select, select)
@@ -331,7 +333,6 @@ export default defineComponent({
emits: ['update:modelValue', 'update:query', 'open', 'close', 'change'],
setup (props, { emit, slots }) {
const { ui, attrs } = useUI('select', toRef(props, 'ui'), config, toRef(props, 'class'))
const { ui: uiMenu } = useUI('selectMenu', toRef(props, 'uiMenu'), configMenu)
const popper = computed<PopperOptions>(() => defu({}, props.popper, uiMenu.value.popper as PopperOptions))
@@ -361,7 +362,7 @@ export default defineComponent({
} else {
return null
}
} else {
} else if (props.modelValue) {
if (props.valueAttribute) {
const option = props.options.find(option => option[props.valueAttribute] === props.modelValue)
return option ? option[props.optionAttribute] : null
@@ -369,6 +370,8 @@ export default defineComponent({
return ['string', 'number'].includes(typeof props.modelValue) ? props.modelValue : props.modelValue[props.optionAttribute]
}
}
return null
})
const selectClass = computed(() => {
@@ -512,6 +515,8 @@ export default defineComponent({
query.value = event.target.value
}
provideUseId(() => useId())
return {
// eslint-disable-next-line vue/no-dupe-keys
ui,

View File

@@ -66,6 +66,10 @@ export default defineComponent({
type: Number,
default: 3
},
maxrows: {
type: Number,
default: 0
},
autoresize: {
type: Boolean,
default: false
@@ -160,7 +164,7 @@ export default defineComponent({
const newRows = (scrollHeight - padding) / lineHeight
if (newRows > props.rows) {
textarea.value.rows = newRows
textarea.value.rows = props.maxrows ? Math.min(newRows, props.maxrows) : newRows
}
}
}

View File

@@ -21,17 +21,17 @@
<script lang="ts">
import { computed, toRef, defineComponent } from 'vue'
import type { PropType } from 'vue'
import { Switch as HSwitch } from '@headlessui/vue'
import { Switch as HSwitch, provideUseId } from '@headlessui/vue'
import { twMerge, twJoin } from 'tailwind-merge'
import UIcon from '../elements/Icon.vue'
import { useUI } from '../../composables/useUI'
import { useFormGroup } from '../../composables/useFormGroup'
import { mergeConfig } from '../../utils'
import type { ToggleSize, Strategy } from '../../types'
import type { ToggleSize, ToggleColor, Strategy } from '../../types'
// @ts-expect-error
import appConfig from '#build/app.config'
import { toggle } from '#ui/ui.config'
import colors from '#ui-colors'
import { useId } from '#imports'
const config = mergeConfig<typeof toggle>(appConfig.ui.strategy, appConfig.ui.toggle, toggle)
@@ -67,7 +67,7 @@ export default defineComponent({
default: () => config.default.offIcon
},
color: {
type: String as PropType<typeof colors[number]>,
type: String as PropType<ToggleColor>,
default: () => config.default.color,
validator (value: string) {
return appConfig.ui.colors.includes(value)
@@ -137,6 +137,8 @@ export default defineComponent({
)
})
provideUseId(() => useId())
return {
// eslint-disable-next-line vue/no-dupe-keys
ui,

View File

@@ -7,7 +7,7 @@
<div v-if="$slots.header" :class="[ui.header.base, ui.header.padding, ui.header.background]">
<slot name="header" />
</div>
<div :class="[ui.body.base, ui.body.padding, ui.body.background]">
<div v-if="$slots.default" :class="[ui.body.base, ui.body.padding, ui.body.background]">
<slot />
</div>
<div v-if="$slots.footer" :class="[ui.footer.base, ui.footer.padding, ui.footer.background]">

View File

@@ -26,7 +26,7 @@ import UIcon from '../elements/Icon.vue'
import UAvatar from '../elements/Avatar.vue'
import { useUI } from '../../composables/useUI'
import { mergeConfig } from '../../utils'
import type { Avatar, Strategy } from '../../types'
import type { Avatar, DividerSize, Strategy } from '../../types'
// @ts-expect-error
import appConfig from '#build/app.config'
import { divider } from '#ui/ui.config'
@@ -52,6 +52,13 @@ export default defineComponent({
type: Object as PropType<Avatar>,
default: null
},
size: {
type: String as PropType<DividerSize>,
default: () => config.default.size,
validator (value: string) {
return Object.keys(config.border.size.horizontal).includes(value) || Object.keys(config.border.size.vertical).includes(value)
}
},
orientation: {
type: String as PropType<'horizontal' | 'vertical'>,
default: 'horizontal',
@@ -92,7 +99,7 @@ export default defineComponent({
return twJoin(
ui.value.border.base,
ui.value.border[props.orientation],
ui.value.border.size[props.orientation],
ui.value.border.size[props.orientation][props.size],
ui.value.border.type[props.type]
)
})

View File

@@ -63,7 +63,7 @@
<script lang="ts">
import { ref, computed, watch, toRef, onMounted, defineComponent } from 'vue'
import { Combobox as HCombobox, ComboboxInput as HComboboxInput, ComboboxOptions as HComboboxOptions } from '@headlessui/vue'
import { Combobox as HCombobox, ComboboxInput as HComboboxInput, ComboboxOptions as HComboboxOptions, provideUseId } from '@headlessui/vue'
import type { ComputedRef, PropType, ComponentPublicInstance } from 'vue'
import { useDebounceFn } from '@vueuse/core'
import { useFuse } from '@vueuse/integrations/useFuse'
@@ -79,6 +79,7 @@ import type { Group, Command, Button, Strategy } from '../../types'
// @ts-expect-error
import appConfig from '#build/app.config'
import { commandPalette } from '#ui/ui.config'
import { useId } from '#imports'
const config = mergeConfig<typeof commandPalette>(appConfig.ui.strategy, appConfig.ui.commandPalette, commandPalette)
@@ -366,6 +367,8 @@ export default defineComponent({
results
})
provideUseId(() => useId())
return {
// eslint-disable-next-line vue/no-dupe-keys
ui,

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