Compare commits

...

104 Commits

Author SHA1 Message Date
HugoRCD
266e870e67 Merge remote-tracking branch 'origin/v3' into fix/3952 2025-05-11 19:21:02 +02:00
Benjamin Canac
d140acc608 feat(Slider): handle tooltip around thumbs
Resolves #1469
2025-05-10 21:53:12 +02:00
Mateusz Kalinowski
cc20a26f07 fix(ColorPicker): make thumb touch draggable (#4101)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2025-05-10 20:03:56 +02:00
Hugo Richard
983c6382d1 docs(llms): handle prose props in llms-full.txt (#4111) 2025-05-10 19:52:01 +02:00
Benjamin Canac
37eabc89bd docs(tabs): improve active item example
Resolves #4092
2025-05-10 18:11:33 +02:00
Benjamin Canac
a57844e416 fix(Stepper): use div tag for title & description
Resolves #4096
2025-05-10 17:59:27 +02:00
Benjamin Canac
2be60cddfe feat(NavigationMenu): add collapsible field in items
Resolves #3353, resolves #3911

Reverts 07e1b4f1f4
2025-05-10 17:51:49 +02:00
Norbiros
09b4699aea fix(Select): support more primitive types in value field (#4105) 2025-05-10 15:06:20 +02:00
Benjamin Canac
46c2987ebf feat(NavigationMenu): handle tooltip in items
Resolves #4050
2025-05-10 13:09:21 +02:00
Malik
f244d15b96 fix(Badge/Button): handle zero value in label correctly (#4108) 2025-05-09 19:27:00 +02:00
Benjamin Canac
aaa60c0798 chore(github): update reproduire workflow 2025-05-09 15:08:21 +02:00
Hugo Richard
5467d71cc2 docs(llms): some ::component-code not parsed in llms-full.txt (#4099)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2025-05-09 13:49:06 +02:00
renovate[bot]
941a54e5e3 chore(deps): update all non-major dependencies (v3) (#4078)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-09 10:40:46 +02:00
Benjamin Canac
655f98ffed docs(Header): update raycast extension link 2025-05-08 18:46:09 +02:00
Benjamin Canac
999a0f8467 fix(Tabs): set focus:outline-none with link variant 2025-05-07 21:20:36 +02:00
Benjamin Canac
2739939c46 chore(deps): update @nuxt/ui-pro 2025-05-07 19:37:21 +02:00
Benjamin Canac
2a241c87c3 chore(github): update reproduire workflow 2025-05-07 17:29:40 +02:00
Benjamin Canac
e6e510b848 fix(components): class should have priority over ui prop 2025-05-07 17:23:27 +02:00
Benjamin Canac
a655da1394 chore(github): add reproduire workflow 2025-05-07 15:58:36 +02:00
Benjamin Canac
3a71256d59 docs(contributions): update content url 2025-05-07 15:58:36 +02:00
Benjamin Canac
404359a6ca chore(github): improve v3 issue templates 2025-05-07 15:58:36 +02:00
Igor G
1e4e9c4708 docs(table): add grouped rows example (#4074)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2025-05-07 15:25:59 +02:00
Benjamin Canac
6f07f6bd6e docs(examples): remove key from useFetch with params watch 2025-05-07 10:52:06 +02:00
Benjamin Canac
4c1093bde4 docs(examples): add missing @vueuse/core imports 2025-05-07 10:51:46 +02:00
HugoRCD
322a6f467a Merge remote-tracking branch 'origin/v3' into fix/3952 2025-05-07 10:38:04 +02:00
Benjamin Canac
7d51a9e479 fix(Calendar): wrong color for today date with neutral color
Resolves #4084

Possible regression of #3629
2025-05-06 17:36:46 +02:00
Benjamin Canac
04bdbcfc6e chore(module): remove processCSSVariables option
Not needed since https://github.com/nuxt/fonts/releases/tag/v0.11.0
2025-05-06 17:02:12 +02:00
Benjamin Canac
58aa296425 playground(vue): missing fonts 2025-05-06 16:49:36 +02:00
Benjamin Canac
d3df3bb929 fix(templates): dont write unused variants in theme files 2025-05-06 16:03:28 +02:00
Benjamin Canac
4863775e17 docs(app): fix tabs trigger width
Regression of 06e5689da8
2025-05-06 15:22:52 +02:00
Benjamin Canac
7551a85ad2 fix(CheckboxGroup): relative UCheckbox import
Resolves #4090
2025-05-06 12:15:54 +02:00
Benjamin Canac
c2bcb8e264 docs(kbd): update title to fix llms-full.txt 2025-05-06 10:45:31 +02:00
HugoRCD
0c1417b6b1 Merge remote-tracking branch 'origin/v3' into fix/3952 2025-05-05 21:59:21 +02:00
renovate[bot]
88124b85c5 chore(deps): update dependency reka-ui to ^2.2.1 (v3) (#4082)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2025-05-05 18:46:26 +02:00
Benjamin Canac
9f539c9545 chore(renovate): remove vue-tsc ignore 2025-05-05 18:25:34 +02:00
Benjamin Canac
41d4ffe5b5 chore(renovate): add group for reka-ui 2025-05-05 18:20:11 +02:00
renovate[bot]
d8a0bbe710 chore(deps): update nuxt framework to ^3.17.2 (v3) (#4079)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-05 18:09:35 +02:00
Benjamin Canac
06e5689da8 fix(Tabs): prevent trigger truncate without parent width
Resolves #4056
2025-05-05 15:47:27 +02:00
Benjamin Canac
67d19f582a docs(team): center cards 2025-05-05 15:01:24 +02:00
Benjamin Canac
650bc40253 docs(index): update contributors count 2025-05-05 15:01:24 +02:00
renovate[bot]
2f0f317a12 chore(deps): update devdependency vite to ^6.3.5 (v3) (#4071)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-05 14:55:16 +02:00
renovate[bot]
d2a26939ad chore(deps): update all non-major dependencies (v3) (#4066)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-05 10:14:30 +02:00
renovate[bot]
b7a8146898 chore(deps): update tailwindcss to ^4.1.5 (v3) (#4067)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-05 10:14:12 +02:00
Victor Bianchi
e81464a43e fix(inertia): link always render as anchor tag (#3989)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
Co-authored-by: Eugen Istoc <eugenistoc@gmail.com>
Co-authored-by: Romain Hamel <rom.hml@gmail.com>
2025-05-04 17:36:45 +02:00
Benjamin Canac
4b3d2a7b00 chore(deps): update @nuxt/ui-pro 2025-05-02 19:20:03 +02:00
Benjamin Canac
6f6fa6ae4a chore(release): v3.1.1 2025-05-02 18:28:20 +02:00
Jez McKean
c7bb6d4c4b docs(toast): fix toaster example links (#4044) 2025-05-02 18:20:10 +02:00
Daniel Jimenez Gutierrez
c23f85fe33 docs(use-overlay): typo on unMount method (#4047) 2025-05-02 18:18:47 +02:00
Benjamin Canac
591d59fe89 fix(theme): improve app config types for ui object
Resolves #3579
2025-05-02 17:06:20 +02:00
Benjamin Canac
caa3bf9c7e docs(carousel): improve thumbnails example
Resolves #4032
2025-05-02 11:58:55 +02:00
Benjamin Canac
2731011bb7 docs(nuxt.config): put back local module import 2025-05-02 11:52:43 +02:00
Benjamin Canac
aebf0b3dca fix(NavigationMenu): remove sm:w-auto from content slot
Resolves #3987

Reverts abe0859691
2025-05-02 11:48:34 +02:00
renovate[bot]
6651987dc6 chore(deps): update nuxt framework to ^3.17.1 (v3) (#4003)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-02 10:21:32 +02:00
HugoRCD
7c4329ded7 Merge branch 'v3' into fix/3952 2025-05-02 10:16:44 +02:00
Eugen Istoc
61aabd72e4 chore(deps): add missing vue-component-type-helpers dependency (#4043) 2025-05-01 22:57:50 +02:00
Vitor Camarotto
8dfdd63ce3 fix(Calendar): add place-items-center to grid row (#4034) 2025-05-01 19:02:06 +02:00
renovate[bot]
e6369a6746 chore(deps): update devdependency vite to v6.3.4 [security] (v3) (#4037)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-01 18:49:10 +02:00
Eugen Istoc
a4f3f6d531 feat(useOverlay): add isOpen method to check overlay state (#4041) 2025-05-01 18:48:41 +02:00
Sandro Circi
c5bdec0f64 fix(templates): put back args to watch in dev (#4033) 2025-05-01 18:47:03 +02:00
renovate[bot]
87a7828931 chore(deps): update dependency motion-v to v1 (v3) (#4004)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-30 15:48:32 +02:00
Hugo Richard
501468960b docs(templates): add portfolio template (#4031) 2025-04-30 15:33:06 +02:00
Benjamin Canac
13cd6be679 docs(command-palette): wrong reka-ui link 2025-04-29 22:46:59 +02:00
Benjamin Canac
e421ea57ec docs(pin-input): missing reka-ui link 2025-04-29 22:46:59 +02:00
Malik
799d7ae422 docs(input-number): broken reka-ui link (#4028)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2025-04-29 22:44:53 +02:00
HugoRCD
adf11f4326 Merge remote-tracking branch 'origin/v3' into fix/3952 2025-04-29 21:25:30 +02:00
Benjamin Canac
be9b1f659a docs(team): update grid 2025-04-29 18:16:59 +02:00
Eugen Istoc
39e29fccf1 fix(useOverlay): improve types and docs (#4012)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2025-04-29 18:09:39 +02:00
reslear
195773ec7d fix(RadioGroup): improve items value field type (#3995) 2025-04-29 18:02:38 +02:00
Benjamin Canac
d7710795df chore(github): add playground job in module workflow (#4021) 2025-04-29 17:48:09 +02:00
Benjamin Canac
53f8b34bef chore: use module workspace 2025-04-29 17:21:43 +02:00
Benjamin Canac
df83ab355e chore(deps): update vue-tsc (#4023) 2025-04-29 17:07:24 +02:00
Hugo Richard
e5df026993 fix(theme): add missing ring-offset-* utilities (#3992)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2025-04-29 16:10:53 +02:00
Hugo Richard
b507f69b87 Merge branch 'v3' into fix/3952 2025-04-29 15:27:11 +02:00
Benjamin Canac
6131871a0d fix(theme): use @theme inline to properly reference css variables
Resolves #4018

https://tailwindcss.com/docs/theme#referencing-other-variables
2025-04-29 14:39:08 +02:00
Alain Limoges
9543bce787 docs(form-field/switch): fix typo (#4015) 2025-04-29 14:36:40 +02:00
renovate[bot]
5e345a8a73 chore(deps): update all non-major dependencies (v3) (#4002)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-29 14:36:40 +02:00
Benjamin Canac
8acf3c51db fix(theme): define default shades for named tailwindcss colors
Resolves #3977
2025-04-29 14:36:31 +02:00
Benjamin Canac
f3b8b17dc5 fix(defineShortcuts): bring back meta to ctrl convert on non macos platforms
Resolves #3869, resolves #3318

Co-Authored-By: Sylvain Marroufin <marroufin.sylvain@gmail.com>
2025-04-29 14:36:31 +02:00
Daniel Roe
bc0a296f9d fix(module): support nuxt-nightly (#3996) 2025-04-29 14:36:31 +02:00
Eugen Istoc
ac4c1946ec feat(useOverlay): add closeAll method (#3984) 2025-04-29 14:36:31 +02:00
Benjamin Canac
82b5f322eb fix(theme): add missing border-bg / divide-bg utilities 2025-04-29 14:36:19 +02:00
HugoRCD
67bcb496a9 Merge remote-tracking branch 'origin/v3' into fix/3952 2025-04-29 14:34:56 +02:00
Benjamin Canac
4104cd993b Revert "fix(InputMenu/Select/SelectMenu): add min-w-fit to content slot (#4010)"
This reverts commit 0f2d2e5d03.
2025-04-29 12:59:43 +02:00
Alain Limoges
e8d493a00e docs(form-field/switch): fix typo (#4015) 2025-04-29 12:18:45 +02:00
Hannes Küttner
0f2d2e5d03 fix(InputMenu/Select/SelectMenu): add min-w-fit to content slot (#4010) 2025-04-28 15:13:02 +02:00
renovate[bot]
4d17989302 chore(deps): update all non-major dependencies (v3) (#4002)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-28 15:02:25 +02:00
Benjamin Canac
eb7607749d fix(module): define default shades for named tailwindcss colors
Resolves #3977
2025-04-28 12:40:03 +02:00
Benjamin Canac
c0347f6e06 fix(defineShortcuts): bring back meta to ctrl convert on non macos platforms
Resolves #3869, resolves #3318

Co-Authored-By: Sylvain Marroufin <marroufin.sylvain@gmail.com>
2025-04-28 12:40:03 +02:00
Daniel Roe
6366118709 fix(module): support nuxt-nightly (#3996) 2025-04-28 10:43:22 +02:00
Eugen Istoc
9f7f5910ee feat(useOverlay): add closeAll method (#3984) 2025-04-25 15:04:03 +02:00
Hugo Richard
996573f26d Merge branch 'v3' into fix/3952 2025-04-25 12:15:53 +02:00
Benjamin Canac
88ff542e63 fix(templates): add missing border-bg / divide-bg utilities 2025-04-25 11:52:42 +02:00
HugoRCD
1a119e6279 Merge remote-tracking branch 'origin/v3' into fix/3952 2025-04-25 10:45:30 +02:00
HugoRCD
776aef6e7f improve code reusability 2025-04-25 10:45:26 +02:00
Benjamin Canac
fb6cf708a6 chore(deps): update @nuxt/content 2025-04-24 21:35:59 +02:00
Benjamin Canac
8ab1f75e47 chore(github): revert docs trigger 2025-04-24 17:44:30 +02:00
HugoRCD
8284d05529 Merge remote-tracking branch 'origin/v3' into fix/3952 2025-04-24 16:53:59 +02:00
HugoRCD
713e943144 remove log 2025-04-24 16:30:46 +02:00
Benjamin Canac
e9b19349ba chore(deps): update @nuxt/ui-pro 2025-04-24 16:16:57 +02:00
Benjamin Canac
b2b206e3f4 docs: update badges 2025-04-24 16:16:29 +02:00
Benjamin Canac
b779064129 docs(radio-group): add missing types on variant 2025-04-24 16:16:29 +02:00
Benjamin Canac
94155258e2 chore(github): prevent docs workflow to run on tags 2025-04-24 16:16:29 +02:00
HugoRCD
505c1e502a fix(vue): make theme reactive 2025-04-24 16:09:38 +02:00
Maxime Pauvert
c63a6dd133 chore(readme): add pro link (#3970) 2025-04-24 15:43:12 +02:00
134 changed files with 5207 additions and 2930 deletions

View File

@@ -5,7 +5,7 @@ body:
- type: markdown
attributes:
value: |
Before reporting a bug, please make sure that you have read through our [v3 documentation](https://ui.nuxt.com/) and existing [issues](https://github.com/nuxt/ui/issues?q=is%3Aissue%20is%3Aopen%20sort%3Aupdated-desc%20label%3Av3).
Before reporting a bug, please make sure that you have read through our [documentation](https://ui.nuxt.com/) and existing [issues](https://github.com/nuxt/ui/issues?q=is%3Aissue%20is%3Aopen%20sort%3Aupdated-desc%20label%3Av3).
- type: textarea
id: env
attributes:
@@ -44,7 +44,7 @@ body:
id: reproduction
attributes:
label: Reproduction
description: Please provide a reproduction link using the Nuxt template https://codesandbox.io/p/devbox/nuxt-ui3-n3sxks or the Vue template https://codesandbox.io/p/devbox/nuxt-ui3-vue-4h5gqn. A minimal [reproduction is required](https://antfu.me/posts/why-reproductions-are-required) unless you are absolutely sure that the issue is obvious and the provided information is enough to understand the problem. If a report is vague (e.g. just a generic error message) and has no reproduction, it will receive a "needs reproduction" label. If no reproduction is provided we might close it.
description: Please provide a reproduction link using the Nuxt template https://codesandbox.io/p/devbox/nuxt-ui3-n3sxks or the Vue template https://codesandbox.io/p/devbox/nuxt-ui3-vue-4h5gqn. A minimal [reproduction is required](https://antfu.me/posts/why-reproductions-are-required) unless you are absolutely sure that the issue is obvious and the provided information is enough to understand the problem. If a report is vague (e.g. just a generic error message) and has no reproduction, it will receive a "needs reproduction" label. If no reproduction is provided, it will be closed automatically after a while.
placeholder: https://github.com/my/reproduction
validations:
required: true

View File

@@ -5,7 +5,7 @@ body:
- type: markdown
attributes:
value: |
Before requesting a feature, please make sure that you have read through our [v3 documentation](https://ui.nuxt.com/) and existing [issues](https://github.com/nuxt/ui/issues?q=is%3Aissue%20is%3Aopen%20sort%3Aupdated-desc%20label%3Av3).
Before requesting a feature, please make sure that you have read through our [documentation](https://ui.nuxt.com/) and existing [issues](https://github.com/nuxt/ui/issues?q=is%3Aissue%20is%3Aopen%20sort%3Aupdated-desc%20label%3Av3).
- type: textarea
id: description
attributes:

View File

@@ -5,7 +5,7 @@ body:
- type: markdown
attributes:
value: |
Before asking a question, please make sure that you have read through our [v3 documentation](https://ui.nuxt.com/) and existing [issues](https://github.com/nuxt/ui/issues?q=is%3Aissue%20is%3Aopen%20sort%3Aupdated-desc%20label%3Av3).
Before asking a question, please make sure that you have read through our [documentation](https://ui.nuxt.com/) and existing [issues](https://github.com/nuxt/ui/issues?q=is%3Aissue%20is%3Aopen%20sort%3Aupdated-desc%20label%3Av3).
- type: textarea
id: description
attributes:

View File

@@ -0,0 +1,30 @@
Would you be able to provide a [reproduction](https://nuxt.com/docs/community/reporting-bugs/#create-a-minimal-reproduction)? 🙏
<details>
<summary>More info</summary>
### Why do I need to provide a reproduction?
Reproductions make it possible for us to triage and fix issues quickly with a relatively small team. It helps us discover the source of the problem, and also can reveal assumptions you or we might be making.
### What will happen?
If you've provided a reproduction, we'll remove the label and try to reproduce the issue. If we can, we'll mark it as a bug and prioritise it based on its severity and how many people we think it might affect.
If `needs reproduction` labeled issues don't receive any substantial activity (e.g., new comments featuring a reproduction link), they will be closed automatically after a while. That's not because we don't care! At any point, feel free to comment with a reproduction and we'll reopen it.
### How can I create a reproduction?
We have templates to create a minimal reproduction:
* **Nuxt**: https://codesandbox.io/p/devbox/nuxt-ui3-n3sxks
* **Vue**: https://codesandbox.io/p/devbox/nuxt-ui3-vue-4h5gqn
Please ensure that the reproduction is as **minimal** as possible. See more details [in our guide](https://nuxt.com/docs/community/reporting-bugs/#create-a-minimal-reproduction).
You might also find these other articles interesting and/or helpful:
- [The Importance of Reproductions](https://antfu.me/posts/why-reproductions-are-required)
- [How to Generate a Minimal, Complete, and Verifiable Example](https://stackoverflow.com/help/mcve)
</details>

View File

@@ -69,6 +69,53 @@ jobs:
if: matrix.os == 'ubuntu-latest'
run: pnpx pkg-pr-new publish --compact --no-template --pnpm
playground:
needs: build
runs-on: ${{ matrix.os }}
defaults:
run:
working-directory: ./playground
permissions:
contents: read
pull-requests: read
strategy:
matrix:
os: [ubuntu-latest] # macos-latest, windows-latest
node: [22]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Store commit SHA
run: |
echo "COMMIT_SHA=$(echo ${{ github.workflow_sha }} | cut -c1-7)" >> $GITHUB_ENV
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Install node
uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- name: Install latest nuxt/ui
run: pnpm install https://pkg.pr.new/@nuxt/ui@${{ env.COMMIT_SHA }} --lockfile-only
- name: Install dependencies
run: pnpm install --ignore-workspace
- name: Prepare
run: pnpm nuxi prepare
- name: Typecheck
run: pnpm run typecheck
starter-nuxt:
needs: build

17
.github/workflows/reproduire.yml vendored Normal file
View File

@@ -0,0 +1,17 @@
name: reproduire
on:
issues:
types: [labeled]
permissions:
issues: write
jobs:
reproduire:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: Hebilicious/reproduire@4b686ae9cbb72dad60f001d278b6e3b2ce40a9ac # v0.0.9-mp
with:
label: needs reproduction

View File

@@ -1,5 +1,27 @@
# Changelog
## [3.1.1](https://github.com/nuxt/ui/compare/v3.1.0...v3.1.1) (2025-05-02)
### Features
* **useOverlay:** add `closeAll` method ([#3984](https://github.com/nuxt/ui/issues/3984)) ([ac4c194](https://github.com/nuxt/ui/commit/ac4c1946ec399aec59b4bce9d538e3ff67868abf))
* **useOverlay:** add `isOpen` method to check overlay state ([#4041](https://github.com/nuxt/ui/issues/4041)) ([a4f3f6d](https://github.com/nuxt/ui/commit/a4f3f6d531f9c0281f99085a6688d296f8f13f2f))
### Bug Fixes
* **Calendar:** add `place-items-center` to grid row ([#4034](https://github.com/nuxt/ui/issues/4034)) ([8dfdd63](https://github.com/nuxt/ui/commit/8dfdd63ce3b3a0e904f7c013c774cf9aaf46b240))
* **defineShortcuts:** bring back `meta` to `ctrl` convert on non macos platforms ([f3b8b17](https://github.com/nuxt/ui/commit/f3b8b17dc5f43936ef7ffb11c1ed7f9a5f94d0bb)), closes [#3869](https://github.com/nuxt/ui/issues/3869) [#3318](https://github.com/nuxt/ui/issues/3318)
* **module:** support `nuxt-nightly` ([#3996](https://github.com/nuxt/ui/issues/3996)) ([bc0a296](https://github.com/nuxt/ui/commit/bc0a296f9d68ca72cd991b11cd3489b63c7b13db))
* **NavigationMenu:** remove `sm:w-auto` from content slot ([aebf0b3](https://github.com/nuxt/ui/commit/aebf0b3dca50c51c093cb6abf16c4fd995fc1b39)), closes [#3987](https://github.com/nuxt/ui/issues/3987)
* **RadioGroup:** improve items `value` field type ([#3995](https://github.com/nuxt/ui/issues/3995)) ([195773e](https://github.com/nuxt/ui/commit/195773ec7dac12ccc3a0a67867751e8ca634cc04))
* **templates:** put back args to watch in dev ([#4033](https://github.com/nuxt/ui/issues/4033)) ([c5bdec0](https://github.com/nuxt/ui/commit/c5bdec0f64963ef602975270a09a1ee795cdacf9))
* **theme:** add missing `border-bg` / `divide-bg` utilities ([82b5f32](https://github.com/nuxt/ui/commit/82b5f322ebd8a08e63588122bd4ef567dcb8ba8c))
* **theme:** add missing `ring-offset-*` utilities ([#3992](https://github.com/nuxt/ui/issues/3992)) ([e5df026](https://github.com/nuxt/ui/commit/e5df0269935be59df759fe0e1378acb2b0d9014a))
* **theme:** define default shades for named tailwindcss colors ([8acf3c5](https://github.com/nuxt/ui/commit/8acf3c51db6c2f9443d04be6ba7d9f062c5cf8ab)), closes [#3977](https://github.com/nuxt/ui/issues/3977)
* **theme:** improve app config types for `ui` object ([591d59f](https://github.com/nuxt/ui/commit/591d59fe89f1d9bf016c121bf9160f73fe0a290d)), closes [#3579](https://github.com/nuxt/ui/issues/3579)
* **theme:** use `[@theme](https://github.com/theme) inline` to properly reference css variables ([6131871](https://github.com/nuxt/ui/commit/6131871a0d124c5942d60dc5dff20981e8542e51)), closes [#4018](https://github.com/nuxt/ui/issues/4018)
* **useOverlay:** improve types and docs ([#4012](https://github.com/nuxt/ui/issues/4012)) ([39e29fc](https://github.com/nuxt/ui/commit/39e29fccf1840c723a13237d65002501b2829b70))
## [3.1.0](https://github.com/nuxt/ui/compare/v3.0.2...v3.1.0) (2025-04-24)
### ⚠ BREAKING CHANGES

View File

@@ -16,6 +16,10 @@ Nuxt UI harnesses the combined strengths of [Reka UI](https://reka-ui.com/), [Ta
> [!NOTE]
> You are on the `v3` development branch, check out the [v2 branch](https://github.com/nuxt/ui/tree/v2) for Nuxt UI v2.
> [!TIP]
> **Looking for more components ?**
> Check out [Nuxt UI Pro](https://ui.nuxt.com/pro), a collection of premium Vue components, composables, and utilities built on top of Nuxt UI for faster and more powerful app development.
## Documentation
Visit https://ui.nuxt.com to explore the documentation.

View File

@@ -11,7 +11,7 @@ export default defineBuildConfig({
delimiters: ['', ''],
values: {
// Used in development to import directly from theme
'const isUiDev = true': 'const isUiDev = false'
'process.argv.includes(\'--uiDev\')': 'false'
}
}
},

View File

@@ -66,7 +66,7 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.${camelName}
</script>
<template>
<Primitive :as="as" :class="ui.root({ class: [props.class, props.ui?.root] })">
<Primitive :as="as" :class="ui.root({ class: [props.ui?.root, props.class] })">
<slot />
</Primitive>
</template>
@@ -109,7 +109,7 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.${camelName}
</script>
<template>
<${upperName}Root v-bind="rootProps" :class="ui.root({ class: [props.class, props.ui?.root] })" />
<${upperName}Root v-bind="rootProps" :class="ui.root({ class: [props.ui?.root, props.class] })" />
</template>
`
}

View File

@@ -20,7 +20,7 @@ watch(framework, () => {
color="neutral"
:ui="{
indicator: 'bg-default',
trigger: 'px-1 data-[state=active]:text-highlighted'
trigger: 'px-1 data-[state=active]:text-highlighted w-full'
}"
size="xs"
@update:model-value="(framework = $event as string)"

View File

@@ -20,7 +20,7 @@ watch(module, () => {
color="neutral"
:ui="{
indicator: 'bg-default',
trigger: 'px-1 data-[state=active]:text-highlighted'
trigger: 'px-1 data-[state=active]:text-highlighted w-full'
}"
size="xs"
@update:model-value="(module = $event as string)"

View File

@@ -17,9 +17,12 @@ function onClickPrev() {
function onClickNext() {
activeIndex.value++
}
function onSelect(index: number) {
activeIndex.value = index
}
function select(index: number) {
activeIndex.value = index
carousel.value?.emblaApi?.scrollTo(index)
}
@@ -35,6 +38,7 @@ function onSelect(index: number) {
:prev="{ onClick: onClickPrev }"
:next="{ onClick: onClickNext }"
class="w-full max-w-xs mx-auto"
@select="onSelect"
>
<img :src="item" width="320" height="320" class="rounded-lg">
</UCarousel>
@@ -45,7 +49,7 @@ function onSelect(index: number) {
:key="index"
class="size-11 opacity-25 hover:opacity-100 transition-opacity"
:class="{ 'opacity-100': activeIndex === index }"
@click="onSelect(index)"
@click="select(index)"
>
<img :src="item" width="44" height="44" class="rounded-lg">
</div>

View File

@@ -1,9 +1,10 @@
<script setup lang="ts">
import { refDebounced } from '@vueuse/core'
const searchTerm = ref('')
const searchTermDebounced = refDebounced(searchTerm, 200)
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
key: 'command-palette-users',
params: { q: searchTermDebounced },
transform: (data: { id: number, name: string, email: string }[]) => {
return data?.map(user => ({ id: user.id, label: user.name, suffix: user.email, avatar: { src: `https://i.pravatar.cc/120?img=${user.id}` } })) || []

View File

@@ -1,11 +1,11 @@
<script setup lang="ts">
import { refDebounced } from '@vueuse/core'
import type { AvatarProps } from '@nuxt/ui'
const searchTerm = ref('')
const searchTermDebounced = refDebounced(searchTerm, 200)
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
key: 'typicode-users',
params: { q: searchTermDebounced },
transform: (data: { id: number, name: string }[]) => {
return data?.map(user => ({

View File

@@ -13,7 +13,9 @@ const modal = overlay.create(LazyModalExample, {
})
async function open() {
const shouldIncrement = await modal.open()
const instance = modal.open()
const shouldIncrement = await instance.result
if (shouldIncrement) {
count.value++

View File

@@ -65,6 +65,7 @@ const items = [
class="w-full justify-center"
:ui="{
viewport: 'sm:w-(--reka-navigation-menu-viewport-width)',
content: 'sm:w-auto',
childList: 'sm:w-96',
childLinkDescription: 'text-balance line-clamp-2'
}"

View File

@@ -1,11 +1,11 @@
<script setup lang="ts">
import { refDebounced } from '@vueuse/core'
import type { AvatarProps } from '@nuxt/ui'
const searchTerm = ref('')
const searchTermDebounced = refDebounced(searchTerm, 200)
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
key: 'typicode-users',
params: { q: searchTermDebounced },
transform: (data: { id: number, name: string }[]) => {
return data?.map(user => ({

View File

@@ -13,7 +13,9 @@ const slideover = overlay.create(LazySlideoverExample, {
})
async function open() {
const shouldIncrement = await slideover.open()
const instance = slideover.open()
const shouldIncrement = await instance.result
if (shouldIncrement) {
count.value++

View File

@@ -0,0 +1,203 @@
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { TableColumn } from '@nuxt/ui'
import { getGroupedRowModel, type GroupingOptions } from '@tanstack/vue-table'
const UBadge = resolveComponent('UBadge')
type Account = {
id: string
name: string
}
type PaymentStatus = 'paid' | 'failed' | 'refunded'
type Payment = {
id: string
date: string
status: PaymentStatus
email: string
amount: number
account: Account
}
const getColorByStatus = (status: PaymentStatus) => {
return {
paid: 'success',
failed: 'error',
refunded: 'neutral'
}[status]
}
const data = ref<Payment[]>([
{
id: '4600',
date: '2024-03-11T15:30:00',
status: 'paid',
email: 'james.anderson@example.com',
amount: 594,
account: {
id: '1',
name: 'Account 1'
}
},
{
id: '4599',
date: '2024-03-11T10:10:00',
status: 'failed',
email: 'mia.white@example.com',
amount: 276,
account: {
id: '2',
name: 'Account 2'
}
},
{
id: '4598',
date: '2024-03-11T08:50:00',
status: 'refunded',
email: 'william.brown@example.com',
amount: 315,
account: {
id: '1',
name: 'Account 1'
}
},
{
id: '4597',
date: '2024-03-10T19:45:00',
status: 'paid',
email: 'emma.davis@example.com',
amount: 529,
account: {
id: '2',
name: 'Account 2'
}
},
{
id: '4596',
date: '2024-03-10T15:55:00',
status: 'paid',
email: 'ethan.harris@example.com',
amount: 639,
account: {
id: '1',
name: 'Account 1'
}
}
])
const columns: TableColumn<Payment>[] = [
{
id: 'title',
header: 'Item'
},
{
id: 'account_id',
accessorKey: 'account.id'
},
{
accessorKey: 'id',
header: '#',
cell: ({ row }) =>
row.getIsGrouped()
? `${row.getValue('id')} records`
: `#${row.getValue('id')}`,
aggregationFn: 'count'
},
{
accessorKey: 'date',
header: 'Date',
cell: ({ row }) => {
return new Date(row.getValue('date')).toLocaleString('en-US', {
day: 'numeric',
month: 'short',
hour: '2-digit',
minute: '2-digit',
hour12: false
})
},
aggregationFn: 'max'
},
{
accessorKey: 'status',
header: 'Status'
},
{
accessorKey: 'email',
header: 'Email',
meta: {
class: {
td: 'w-full'
}
},
cell: ({ row }) =>
row.getIsGrouped()
? `${row.getValue('email')} customers`
: row.getValue('email'),
aggregationFn: 'uniqueCount'
},
{
accessorKey: 'amount',
header: () => h('div', { class: 'text-right' }, 'Amount'),
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
const formatted = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
return h('div', { class: 'text-right font-medium' }, formatted)
},
aggregationFn: 'sum'
}
]
const grouping_options = ref<GroupingOptions>({
groupedColumnMode: 'remove',
getGroupedRowModel: getGroupedRowModel()
})
</script>
<template>
<UTable
:data="data"
:columns="columns"
:grouping="['account_id', 'status']"
:grouping-options="grouping_options"
:ui="{
root: 'min-w-full',
td: 'empty:p-0' // helps with the colspaned row added for expand slot
}"
>
<template #title-cell="{ row }">
<div v-if="row.getIsGrouped()" class="flex items-center">
<span
class="inline-block"
:style="{ width: `calc(${row.depth} * 1rem)` }"
/>
<UButton
variant="outline"
color="neutral"
class="mr-2"
size="xs"
:icon="row.getIsExpanded() ? 'i-lucide-minus' : 'i-lucide-plus'"
@click="row.toggleExpanded()"
/>
<strong v-if="row.groupingColumnId === 'account_id'">{{
row.original.account.name
}}</strong>
<UBadge
v-else-if="row.groupingColumnId === 'status'"
:color="getColorByStatus(row.original.status)"
class="capitalize"
variant="subtle"
>
{{ row.original.status }}
</UBadge>
</div>
</template>
</UTable>
</template>

View File

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

View File

@@ -1,22 +1,32 @@
<script setup lang="ts">
import type { TabsItem } from '@nuxt/ui'
const route = useRoute()
const router = useRouter()
const items: TabsItem[] = [
{
label: 'Account'
label: 'Account',
value: 'account'
},
{
label: 'Password'
label: 'Password',
value: 'password'
}
]
const active = ref('0')
// Note: This is for demonstration purposes only. Don't do this at home.
onMounted(() => {
setInterval(() => {
active.value = String((Number(active.value) + 1) % items.length)
}, 2000)
const active = computed({
get() {
return (route.query.tab as string) || 'account'
},
set(tab) {
// Hash is specified here to prevent the page from scrolling to the top
router.push({
path: '/components/tabs',
query: { tab },
hash: '#control-active-item'
})
}
})
</script>

View File

@@ -98,7 +98,7 @@ export function useLinks() {
label: 'Raycast Extension',
description: 'Access Nuxt UI components without leaving your editor.',
icon: 'i-simple-icons-raycast',
to: 'https://www.raycast.com/HugoRCD/nuxt-ui',
to: 'https://www.raycast.com/HugoRCD/nuxt',
target: '_blank'
}, {
label: 'Figma to Code',

View File

@@ -239,7 +239,7 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
<li>
<NuxtLink to="https://github.com/nuxt/ui/graphs/contributors" target="_blank" class="min-w-0">
<p class="text-4xl font-semibold text-highlighted truncate">
175+
200+
</p>
<p class="text-muted text-sm truncate">Contributors</p>
</NuxtLink>

View File

@@ -16,6 +16,32 @@ links:
variant: outline
trailingIcon: i-lucide-arrow-right
templates:
- title: 'Portfolio'
description: "A sleek, modern portfolio template to showcase your work, skills, blog posts, speaking engagements, and provide contact information. Which can customized easily from the `content/` directory."
icon: i-lucide-user
thumbnail:
dark: https://assets.hub.nuxt.com/eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1cmwiOiJodHRwczovL3BvcnRmb2xpby10ZW1wbGF0ZS5udXh0LmRldiIsImlhdCI6MTc0NTkzNDczMX0.XDWnQoyVy3XVtKQD6PLQ8RFUwr4yr1QnVwPxRrjCrro.jpg?theme=dark
light: https://assets.hub.nuxt.com/eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1cmwiOiJodHRwczovL3BvcnRmb2xpby10ZW1wbGF0ZS5udXh0LmRldiIsImlhdCI6MTc0NTkzNDczMX0.XDWnQoyVy3XVtKQD6PLQ8RFUwr4yr1QnVwPxRrjCrro.jpg?theme=light
features:
- title: Sections for Projects, Blog, Speaking & About
icon: i-lucide-layout-list
- title: Easily editable content via Markdown & YAML
icon: i-simple-icons-markdown
- title: Fully responsive design
icon: i-lucide-smartphone
links:
- label: Preview
to: https://portfolio-template.nuxt.dev
target: _blank
leadingIcon: i-logos-nuxt-icon
trailingIcon: i-lucide-arrow-up-right
color: neutral
- label: Nuxt Template
to: https://github.com/nuxt-ui-pro/portfolio
target: _blank
icon: i-simple-icons-github
color: neutral
variant: outline
- title: 'Chat'
description: "An AI chatbot template designed to help you build your own chatbot with Nuxt UI Pro components and deployed on [NuxtHub](https://hub.nuxt.com)."
icon: i-lucide-message-circle

View File

@@ -48,13 +48,14 @@ const icons = {
</UPageHero>
<UPageSection :ui="{ container: '!pt-0' }">
<UPageGrid class="xl:grid-cols-4">
<UPageGrid class="xl:grid-cols-5">
<UPageCard
v-for="(user, index) in module?.team"
:key="index"
:title="user.name"
:description="[user.pronouns, user.location].filter(Boolean).join(' ・ ')"
:ui="{
wrapper: 'items-center',
container: 'gap-y-4 lg:p-8',
leading: 'flex justify-center',
title: 'text-center',
@@ -123,6 +124,7 @@ const icons = {
:key="contributor.username"
:title="contributor.username"
:ui="{
wrapper: 'items-center',
container: 'gap-y-2',
leading: 'flex justify-center',
title: 'text-center',

View File

@@ -504,7 +504,7 @@ const count = ref(0)
</script>
```
Closing a modal is now done through the `close` event. The `modal.open` method now returns a promise that resolves to the result of the modal whenever the modal is close:
Closing a modal is now done through the `close` event. The `modal.open` method now returns an instance that can be used to await for the result of the modal whenever the modal is closed:
```diff
<script setup lang="ts">
@@ -523,10 +523,12 @@ import { ModalExampleComponent } from '#components'
- })
- }
+ async function openModal() {
+ const result = await modal.open(ModalExampleComponent, {
+ const instance = modal.open(ModalExampleComponent, {
+ count: count.value
+ })
+
+ const result = await instance.result
+
+ if (result) {
+ toast.add({ title: 'Success!' })
+ }

View File

@@ -727,15 +727,22 @@ This is how the `@theme` is generated for each design token:
--border-color-muted: var(--ui-border-muted);
--border-color-accented: var(--ui-border-accented);
--border-color-inverted: var(--ui-border-inverted);
--border-color-bg: var(--ui-bg);
--ring-color-default: var(--ui-border);
--ring-color-muted: var(--ui-border-muted);
--ring-color-accented: var(--ui-border-accented);
--ring-color-inverted: var(--ui-border-inverted);
--ring-color-bg: var(--ui-bg);
--ring-offset-color-default: var(--ui-border);
--ring-offset-color-muted: var(--ui-border-muted);
--ring-offset-color-accented: var(--ui-border-accented);
--ring-offset-color-inverted: var(--ui-border-inverted);
--ring-offset-color-bg: var(--ui-bg);
--divide-color-default: var(--ui-border);
--divide-color-muted: var(--ui-border-muted);
--divide-color-accented: var(--ui-border-accented);
--divide-color-inverted: var(--ui-border-inverted);
--divide-color-bg: var(--ui-bg);
--outline-color-default: var(--ui-border);
--outline-color-inverted: var(--ui-border-inverted);
--stroke-color-default: var(--ui-border);
@@ -966,7 +973,7 @@ export default {
```vue [src/runtime/components/Card.vue]
<template>
<div :class="ui.root({ class: [props.class, props.ui?.root] })">
<div :class="ui.root({ class: [props.ui?.root, props.class] })">
<div :class="ui.header({ class: props.ui?.header })">
<slot name="header" />
</div>

View File

@@ -16,7 +16,7 @@ Here's an overview of the key directories and files in the Nuxt UI project struc
### Documentation
The documentation lives in the `docs` folder as a Nuxt app using `@nuxt/content` v3 to generate pages from Markdown files. See the [Content v3 Docs](https://content3.nuxt.dev/docs/getting-started) for details on how it works. Here's a breakdown of its structure:
The documentation lives in the `docs` folder as a Nuxt app using `@nuxt/content` v3 to generate pages from Markdown files. See the [Nuxt Content documentation](https://content.nuxt.com/docs/getting-started) for details on how it works. Here's a breakdown of its structure:
```bash
├── app/

View File

@@ -19,6 +19,7 @@ defineShortcuts({
</script>
```
- Shortcuts are automatically adjusted for non-macOS platforms, converting `meta` to `ctrl`.
- The composable uses VueUse's [`useEventListener`](https://vueuse.org/core/useEventListener/) to handle keydown events.
- For a complete list of available shortcut keys, refer to the [`KeyboardEvent.key`](https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values) API documentation. Note that the key should be written in lowercase.
@@ -46,7 +47,7 @@ Shortcuts are defined using the following format:
#### Modifiers
- `meta`: Represents `⌘ Command` on macOS and `⊞ Windows` on Windows
- `meta`: Represents `⌘ Command` on macOS and `Ctrl` on other platforms
- `ctrl`: Represents `Ctrl` on all platforms
- `shift`: Used for alphabetic keys when Shift is required

View File

@@ -22,14 +22,14 @@ async function openModal() {
- The `useOverlay` composable is created using `createSharedComposable`, ensuring that the same overlay state is shared across your entire application.
::note
In order to return a value from the overlay, the `overlay.open()` can be awaited. In order for this to work, however, the **overlay component must emit a `close` event**. See example below for details.
In order to return a value from the overlay, the `overlay.open().instance.result` can be awaited. In order for this to work, however, the **overlay component must emit a `close` event**. See example below for details.
::
## API
### `create(component: T, options: OverlayOptions): OverlayInstance`
Creates an overlay, and returns its instance
Creates an overlay, and returns a factory instance
- Parameters:
- `component`: The overlay component
@@ -38,7 +38,7 @@ Creates an overlay, and returns its instance
- `props?: ComponentProps`: An optional object of props to pass to the rendered component.
- `destroyOnClose?: boolean` Removes the overlay from memory when closed `default: false`
### `open(id: symbol, props?: ComponentProps<T>): Promise<any>`
### `open(id: symbol, props?: ComponentProps<T>): OpenedOverlay<T>`
Opens the overlay using its `id`
@@ -62,10 +62,17 @@ Update an overlay using its `id`
- `id`: The identifier of the overlay
- `props`: An object of props to update on the rendered component.
### `unmount(id: symbol): void`
### `unMount(id: symbol): void`
Removes the overlay from the DOM using its `id`
- Parameters:
- `id`: The identifier of the overlay
### `isOpen(id: symbol): boolean`
Checks if an overlay its open using its `id`
- Parameters:
- `id`: The identifier of the overlay
@@ -75,7 +82,7 @@ In-memory list of overlays that were created
## Overlay Instance API
### `open(props?: ComponentProps<T>): Promise<any>`
### `open(props?: ComponentProps<T>): Promise<OpenedOverlay<T>>`
Opens the overlay
@@ -138,7 +145,7 @@ const overlay = useOverlay()
// Create with default props
const modalA = overlay.create(ModalA, { title: 'Welcome' })
const modalB = overlay.create(modalB)
const modalB = overlay.create(ModalB)
const slideoverA = overlay.create(SlideoverA)
@@ -149,7 +156,9 @@ const openModalA = () => {
const openModalB = async () => {
// Open modalB, and wait for its result
const input = await modalB.open()
const modalBInstance = modalB.open()
const input = await modalBInstance.result
// Pass the result from modalB to the slideover, and open it.
slideoverA.open({ input })

View File

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

View File

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

View File

@@ -7,9 +7,9 @@ links:
icon: i-custom-fuse-js
to: https://fusejs.io/
target: _blank
- label: Combobox
- label: Listbox
icon: i-custom-reka-ui
to: https://reka-ui.com/docs/components/combobox
to: https://reka-ui.com/docs/components/listbox
- label: GitHub
icon: i-simple-icons-github
to: https://github.com/nuxt/ui/tree/v3/src/runtime/components/CommandPalette.vue

View File

@@ -34,7 +34,7 @@ slots:
The label `for` attribute and the form control are associated with a unique `id` if not provided.
::
When using the `required` prop, an asterisk is be added next to the label.
When using the `required` prop, an asterisk is added next to the label.
::component-code
---

View File

@@ -24,7 +24,7 @@ It requires two props:
**No validation library is included** by default, ensure you **install the one you need**.
::
::tabs
::tabs{class="gap-0"}
::component-example{label="Valibot"}
---
name: 'form-example-valibot'

View File

@@ -3,9 +3,9 @@ title: InputNumber
description: Input numerical values with a customizable range.
category: form
links:
- label: Number Field
- label: NumberField
icon: i-custom-reka-ui
to: https://www.reka-ui.com/components/input-number
to: https://www.reka-ui.com/docs/components/number-field
- label: GitHub
icon: i-simple-icons-github
to: https://github.com/nuxt/ui/tree/v3/src/runtime/components/InputNumber.vue

View File

@@ -1,13 +1,11 @@
---
title: Keyboard Key
title: Kbd
description: A kbd element to display a keyboard key.
category: element
links:
- label: GitHub
icon: i-simple-icons-github
to: https://github.com/nuxt/ui/tree/v3/src/runtime/components/Kbd.vue
navigation:
title: Kbd
---
## Usage
@@ -32,7 +30,7 @@ props:
---
::
You can pass special keys to the `value` prop that goes through the [`useKbd`](https://github.com/nuxt/ui/blob/v3/src/runtime/composables/useKbd.ts) composable. For example, the `meta` key displays as `⌘` on macOS and `` on other platforms.
You can pass special keys to the `value` prop that goes through the [`useKbd`](https://github.com/nuxt/ui/blob/v3/src/runtime/composables/useKbd.ts) composable. For example, the `meta` key displays as `⌘` on macOS and `Ctrl` on other platforms.
::component-code
---

View File

@@ -21,8 +21,12 @@ Use the `items` prop as an array of objects with the following properties:
- `icon?: string`{lang="ts-type"}
- `avatar?: AvatarProps`{lang="ts-type"}
- `badge?: string | number | BadgeProps`{lang="ts-type"}
- `tooltip?: TooltipProps`{lang="ts-type"}
- `trailingIcon?: string`{lang="ts-type"}
- `type?: 'label' | 'link'`{lang="ts-type"}
- `collapsible?: boolean`{lang="ts-type"}
- `defaultOpen?: boolean`{lang="ts-type"}
- `open?: boolean`{lang="ts-type"}
- `value?: string`{lang="ts-type"}
- `disabled?: boolean`{lang="ts-type"}
- `class?: any`{lang="ts-type"}
@@ -140,7 +144,7 @@ Each item can take a `children` array of objects with the following properties t
Use the `orientation` prop to change the orientation of the NavigationMenu.
::note
When orientation is `vertical`, a [Collapsible](/components/collapsible) component is used to display children. You can control the open state of each item using the `open` and `defaultOpen` properties.
When orientation is `vertical`, a [Collapsible](/components/collapsible) component is used to display children. You can control the open state of each item using the `open` and `defaultOpen` properties. You can also use the `collapsible` property to control if the item is collapsible.
::
::component-code

View File

@@ -3,6 +3,9 @@ title: PinInput
description: An input element to enter a pin.
category: form
links:
- label: PinInput
icon: i-custom-reka-ui
to: https://reka-ui.com/docs/components/pin-input
- label: GitHub
icon: i-simple-icons-github
to: https://github.com/nuxt/ui/tree/v3/src/runtime/components/PinInput.vue

View File

@@ -157,7 +157,7 @@ props:
---
::
### Variant :badge{label="Not released" class="align-text-top"}
### Variant :badge{label="New" class="align-text-top"}
Use the `variant` prop to change the variant of the RadioGroup.
@@ -169,6 +169,8 @@ ignore:
- items
external:
- items
externalTypes:
- RadioGroupItem[]
props:
color: 'primary'
variant: 'table'
@@ -236,7 +238,7 @@ props:
---
::
### Indicator :badge{label="Not released" class="align-text-top"}
### Indicator :badge{label="New" class="align-text-top"}
Use the `indicator` prop to change the position or hide the indicator. Defaults to `start`.

View File

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

View File

@@ -47,7 +47,7 @@ props:
---
::
When using the `required` prop, an asterisk is be added next to the label.
When using the `required` prop, an asterisk is added next to the label.
::component-code
---

View File

@@ -260,6 +260,30 @@ You can use the `expanded` prop to control the expandable state of the rows (can
You could also add this action to the [`DropdownMenu`](/components/dropdown-menu) component inside the `actions` column.
::
### With grouped rows
You can group rows based on a given column value and show/hide sub rows via some button added to the cell using the TanStack Table [Grouping APIs](https://tanstack.com/table/latest/docs/api/features/grouping).
#### Important parts:
* Add prop `grouping` to `UTable` component with an array of column ids you want to group by.
* Add prop `grouping-options` to `UTable`. It must include `getGroupedRowModel`, you can import it from `@tanstack/vue-table` or implement your own.
* Expand rows via `row.toggleExpanded()` method on any cell of the row. Keep in mind, it also toggles `#expanded` slot.
* Use `aggregateFn` on column definition to define how to aggregate the rows.
* `agregatedCell` renderer on column definition only works if there is no `cell` renderer.
::component-example
---
prettier: true
collapse: true
name: 'table-grouped-rows-example'
highlights:
- 159
- 169
class: '!p-0'
---
::
### With row selection
You can add a new column that renders a [Checkbox](/components/checkbox) component inside the `header` and `cell` to select rows using the TanStack Table [Row Selection APIs](https://tanstack.com/table/latest/docs/api/features/row-selection).

View File

@@ -210,10 +210,6 @@ You can control the active item by using the `default-value` prop or the `v-mode
:component-example{name="tabs-model-value-example"}
::tip
You can also pass the `value` of one of the items if provided.
::
### With content slot
Use the `#content` slot to customize the content of each item.

View File

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

View File

@@ -188,7 +188,7 @@ name: 'toast-example'
:toaster-position-example
::
::note{to="https://github.com/nuxt/ui/blob/v3/docs/app/app.vue#L77"}
::note{to="https://github.com/nuxt/ui/blob/v3/docs/app/app.config.ts#L3"}
In this example, we use the `AppConfig` to configure the `position` prop of the `Toaster` component globally.
::
@@ -206,7 +206,7 @@ name: 'toast-example'
:toaster-duration-example
::
::note{to="https://github.com/nuxt/ui/blob/v3/docs/app/app.vue#L77"}
::note{to="https://github.com/nuxt/ui/blob/v3/docs/app/app.config.ts#L5"}
In this example, we use the `AppConfig` to configure the `duration` prop of the `Toaster` component globally.
::
@@ -228,7 +228,7 @@ name: 'toast-example'
:toaster-expand-example
::
::note{to="https://github.com/nuxt/ui/blob/v3/docs/app/app.vue#L77"}
::note{to="https://github.com/nuxt/ui/blob/v3/docs/app/app.config.ts#L4"}
In this example, we use the `AppConfig` to configure the `expand` prop of the `Toaster` component globally.
::

View File

@@ -3,40 +3,40 @@
"name": "@nuxt/ui-docs",
"type": "module",
"dependencies": {
"@ai-sdk/vue": "^1.2.8",
"@ai-sdk/vue": "^1.2.11",
"@iconify-json/logos": "^1.2.4",
"@iconify-json/lucide": "^1.2.38",
"@iconify-json/lucide": "^1.2.41",
"@iconify-json/simple-icons": "^1.2.33",
"@iconify-json/vscode-icons": "^1.2.19",
"@nuxt/content": "https://pkg.pr.new/@nuxt/content@754e480",
"@iconify-json/vscode-icons": "^1.2.20",
"@nuxt/content": "^3.5.1",
"@nuxt/image": "^1.10.0",
"@nuxt/ui": "latest",
"@nuxt/ui-pro": "https://pkg.pr.new/@nuxt/ui-pro@fa25c12",
"@nuxthub/core": "^0.8.25",
"@nuxt/ui-pro": "https://pkg.pr.new/@nuxt/ui-pro@a30de4d",
"@nuxthub/core": "^0.8.27",
"@nuxtjs/plausible": "^1.2.0",
"@octokit/rest": "^21.1.1",
"@rollup/plugin-yaml": "^4.1.2",
"@vueuse/integrations": "^13.1.0",
"@vueuse/nuxt": "^13.1.0",
"ai": "^4.3.9",
"ai": "^4.3.15",
"capture-website": "^4.2.0",
"joi": "^17.13.3",
"motion-v": "^0.13.1",
"nuxt": "^3.16.2",
"motion-v": "^1.0.2",
"nuxt": "^3.17.2",
"nuxt-component-meta": "^0.11.0",
"nuxt-llms": "^0.1.2",
"nuxt-og-image": "^5.1.2",
"nuxt-og-image": "^5.1.3",
"prettier": "^3.5.3",
"shiki-transformer-color-highlight": "^1.0.0",
"sortablejs": "^1.15.6",
"superstruct": "^2.0.2",
"ufo": "^1.6.1",
"valibot": "^1.0.0",
"workers-ai-provider": "^0.3.0",
"valibot": "^1.1.0",
"workers-ai-provider": "^0.3.1",
"yup": "^1.6.1",
"zod": "^3.24.3"
"zod": "^3.24.4"
},
"devDependencies": {
"wrangler": "^4.13.0"
"wrangler": "^4.14.4"
}
}

View File

@@ -1,6 +1,8 @@
import json5 from 'json5'
import { camelCase, kebabCase } from 'scule'
import { visit } from '@nuxt/content/runtime'
import type { H3Event } from 'h3'
import type { PageCollectionItemBase } from '@nuxt/content'
import * as theme from '../../.nuxt/ui'
import * as themePro from '../../.nuxt/ui-pro'
import meta from '#nuxt-component-meta'
@@ -44,11 +46,33 @@ const parseBoolean = (value?: string): boolean => value === 'true'
function getComponentMeta(componentName: string) {
const pascalCaseName = componentName.charAt(0).toUpperCase() + componentName.slice(1)
const metaComponentName = `U${pascalCaseName}`
const strategies = [
`U${pascalCaseName}`,
`Prose${pascalCaseName}`,
pascalCaseName
]
let componentMeta: any
let finalMetaComponentName: string = pascalCaseName
for (const nameToTry of strategies) {
finalMetaComponentName = nameToTry
const metaAttempt = (meta as Record<string, any>)[nameToTry]?.meta
if (metaAttempt) {
componentMeta = metaAttempt
break
}
}
if (!componentMeta) {
console.warn(`[getComponentMeta] Metadata not found for ${pascalCaseName} using strategies: U, Prose, or no prefix. Last tried: ${finalMetaComponentName}`)
}
return {
pascalCaseName,
metaComponentName,
componentMeta: (meta as Record<string, any>)[metaComponentName]?.meta
metaComponentName: finalMetaComponentName,
componentMeta
}
}
@@ -166,6 +190,7 @@ function emitItemHandler(event: any): string {
const generateThemeConfig = ({ pro, prose, componentName }: ThemeConfig) => {
const computedTheme = pro ? (prose ? themePro.prose : themePro) : theme
const componentTheme = computedTheme[componentName as keyof typeof computedTheme]
return {
[pro ? 'uiPro' : 'ui']: prose
? { prose: { [componentName]: componentTheme } }
@@ -278,14 +303,18 @@ const generateComponentCode = ({
}
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('content:llms:generate:document' as any, async (doc: Document) => {
nitroApp.hooks.hook('content:llms:generate:document', async (_: H3Event, doc: PageCollectionItemBase) => {
const componentName = camelCase(doc.title)
visitAndReplace(doc, 'component-theme', (node) => {
const attributes = node[1] as ComponentAttributes
const attributes = node[1] as Record<string, string>
const mdcSpecificName = attributes?.slug
const finalComponentName = mdcSpecificName ? camelCase(mdcSpecificName) : componentName
const pro = parseBoolean(attributes[':pro'])
const prose = parseBoolean(attributes[':prose'])
const appConfig = generateThemeConfig({ pro, prose, componentName })
const appConfig = generateThemeConfig({ pro, prose, componentName: finalComponentName })
replaceNodeWithPre(
node,
@@ -320,14 +349,23 @@ export default defineNitroPlugin((nitroApp) => {
})
visitAndReplace(doc, 'component-props', (node) => {
const { pascalCaseName, componentMeta } = getComponentMeta(componentName)
const attributes = node[1] as Record<string, string>
const mdcSpecificName = attributes?.name
const isProse = parseBoolean(attributes[':prose'])
const finalComponentName = mdcSpecificName ? camelCase(mdcSpecificName) : componentName
const { pascalCaseName, componentMeta } = getComponentMeta(finalComponentName)
if (!componentMeta?.props) return
const interfaceName = isProse ? `Prose${pascalCaseName}Props` : `${pascalCaseName}Props`
const interfaceCode = generateTSInterface(
`${pascalCaseName}Props`,
interfaceName,
Object.values(componentMeta.props),
propItemHandler,
`Props for the ${pascalCaseName} component`
`Props for the ${isProse ? 'Prose' : ''}${pascalCaseName} component`
)
replaceNodeWithPre(node, 'ts', interfaceCode)
})

View File

@@ -1,8 +1,8 @@
{
"name": "@nuxt/ui",
"description": "A UI Library for Modern Web Apps, powered by Vue & Tailwind CSS.",
"version": "3.1.0",
"packageManager": "pnpm@10.9.0",
"version": "3.1.1",
"packageManager": "pnpm@10.10.0",
"repository": {
"type": "git",
"url": "git+https://github.com/nuxt/ui.git"
@@ -96,12 +96,12 @@
"scripts": {
"build": "nuxt-module-build build",
"prepack": "pnpm build",
"dev": "nuxi dev playground",
"dev": "nuxi dev playground --uiDev",
"dev:build": "nuxi build playground",
"dev:vue": "vite playground-vue",
"dev:vue": "vite playground-vue -- --uiDev",
"dev:vue:build": "vite build playground-vue",
"dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground && nuxi prepare docs && vite build playground-vue",
"docs": "nuxi dev docs",
"docs": "nuxi dev docs --uiDev",
"docs:build": "nuxi build docs",
"docs:prepare": "nuxt-component-meta docs",
"lint": "eslint .",
@@ -115,14 +115,14 @@
"@iconify/vue": "^4.3.0",
"@internationalized/date": "^3.8.0",
"@internationalized/number": "^3.6.1",
"@nuxt/fonts": "^0.11.1",
"@nuxt/fonts": "^0.11.2",
"@nuxt/icon": "^1.12.0",
"@nuxt/kit": "^3.16.2",
"@nuxt/schema": "^3.16.2",
"@nuxt/kit": "^3.17.2",
"@nuxt/schema": "^3.17.2",
"@nuxtjs/color-mode": "^3.5.2",
"@standard-schema/spec": "^1.0.0",
"@tailwindcss/postcss": "^4.1.4",
"@tailwindcss/vite": "^4.1.4",
"@tailwindcss/postcss": "^4.1.5",
"@tailwindcss/vite": "^4.1.5",
"@tanstack/vue-table": "^8.21.3",
"@unhead/vue": "^2.0.8",
"@vueuse/core": "^13.1.0",
@@ -144,30 +144,31 @@
"mlly": "^1.7.4",
"ohash": "^2.0.11",
"pathe": "^2.0.3",
"reka-ui": "^2.2.0",
"reka-ui": "^2.2.1",
"scule": "^1.3.0",
"tailwind-variants": "^1.0.0",
"tailwindcss": "^4.1.4",
"tailwindcss": "^4.1.5",
"tinyglobby": "^0.2.13",
"unplugin": "^2.3.2",
"unplugin-auto-import": "^19.1.2",
"unplugin-auto-import": "^19.2.0",
"unplugin-vue-components": "^28.5.0",
"vaul-vue": "^0.4.1"
"vaul-vue": "^0.4.1",
"vue-component-type-helpers": "^2.2.10"
},
"devDependencies": {
"@nuxt/eslint-config": "^1.3.0",
"@nuxt/module-builder": "^1.0.1",
"@nuxt/test-utils": "^3.17.2",
"@nuxt/test-utils": "^3.18.0",
"@release-it/conventional-changelog": "^10.0.1",
"@vue/test-utils": "^2.4.6",
"embla-carousel": "^8.6.0",
"eslint": "^9.25.1",
"happy-dom": "^17.4.4",
"nuxt": "^3.16.2",
"release-it": "^19.0.1",
"vitest": "^3.1.2",
"eslint": "^9.26.0",
"happy-dom": "^17.4.6",
"nuxt": "^3.17.2",
"release-it": "^19.0.2",
"vitest": "^3.1.3",
"vitest-environment-nuxt": "^1.0.1",
"vue-tsc": "^2.2.0"
"vue-tsc": "^2.2.10"
},
"peerDependencies": {
"@inertiajs/vue3": "^2.0.7",
@@ -207,8 +208,8 @@
"chokidar": "3.6.0",
"debug": "4.3.7",
"rollup": "4.34.9",
"unplugin": "^2.3.2",
"vue-tsc": "2.2.0"
"unimport": "4.1.1",
"unplugin": "^2.3.2"
},
"pnpm": {
"onlyBuiltDependencies": [

View File

@@ -4,6 +4,8 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/logo.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="preconnect" href="https://fonts.bunny.net">
<link href="https://fonts.bunny.net/css?family=public-sans:400,500,600,700" rel="stylesheet" />
<title>Nuxt UI - Vue Playground</title>
</head>
<body>

View File

@@ -12,13 +12,13 @@
"dependencies": {
"@nuxt/ui": "latest",
"vue": "^3.5.13",
"vue-router": "^4.5.0",
"zod": "^3.24.3"
"vue-router": "^4.5.1",
"zod": "^3.24.4"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.2.3",
"typescript": "^5.8.3",
"vite": "^6.3.3",
"vue-tsc": "^2.2.0"
"vite": "^6.3.5",
"vue-tsc": "^2.2.10"
}
}

View File

@@ -1,15 +1,9 @@
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ui from '../src/vite'
import ui from '@nuxt/ui/vite'
// https://vitejs.dev/config/
export default defineConfig({
server: {
fs: {
allow: ['..']
}
},
plugins: [
vue(),
ui({

View File

@@ -28,7 +28,12 @@ const bind = computed(() => ({
dots: dots.value
}))
const items = Array.from({ length: 6 }).map((_, index) => index)
const items = Array.from({ length: 6 }).map((_, index) => ({
id: index,
title: `Item ${index + 1}`,
description: `Description for item ${index + 1}`,
src: `https://picsum.photos/640/640?v=${index}`
}))
</script>
<template>
@@ -60,23 +65,23 @@ const items = Array.from({ length: 6 }).map((_, index) => index)
</div>
<template v-if="classNames">
<UCarousel v-slot="{ index }" v-bind="bind" :items="items" :ui="{ item: 'basis-[70%] transition-opacity ease-in-out [&:not(.is-snapped)]:opacity-10', container: 'h-[352px]' }" class="w-full max-w-xl mx-auto">
<img :src="`https://picsum.photos/600/350?v=${index}`" class="rounded-lg">
<UCarousel v-slot="{ item }" v-bind="bind" :items="items" :ui="{ item: 'basis-[70%] transition-opacity ease-in-out [&:not(.is-snapped)]:opacity-10', container: 'h-[352px]' }" class="w-full max-w-xl mx-auto">
<img :src="item.src" class="rounded-lg">
</UCarousel>
</template>
<template v-else-if="autoHeight">
<UCarousel v-slot="{ index }" v-bind="bind" :items="items" :ui="{ container: 'transition-[height] duration-200' }" class="w-full max-w-md mx-auto">
<img :src="`https://picsum.photos/600/${index % 2 === 0 ? 350 : 450}?v=${index}`" :class="index % 2 === 0 ? 'h-[350px]' : 'h-[450px]'" class="rounded-lg">
<UCarousel v-slot="{ item }" v-bind="bind" :items="items" :ui="{ container: 'transition-[height] duration-200' }" class="w-full max-w-md mx-auto">
<img :src="item.src" class="rounded-lg">
</UCarousel>
</template>
<template v-else>
<UCarousel v-slot="{ index }" v-bind="bind" :items="items" class="w-[320px] mx-auto" :ui="{ container: 'h-[336px]' }">
<img :src="`https://picsum.photos/640/640?v=${index}`" class="rounded-lg">
<UCarousel v-slot="{ item }" v-bind="bind" :items="items" class="w-[320px] mx-auto" :ui="{ container: 'h-[336px]' }">
<img :src="item.src" class="rounded-lg">
</UCarousel>
<template v-if="orientation === 'horizontal'">
<UCarousel v-slot="{ index }" v-bind="bind" :items="items" :ui="{ item: 'basis-1/3' }" class="w-full max-w-xs mx-auto">
<img :src="`https://picsum.photos/320/320?v=${index}`" class="rounded-lg">
<UCarousel v-slot="{ item }" v-bind="bind" :items="items" :ui="{ item: 'basis-1/3' }" class="w-full max-w-xs mx-auto">
<img :src="item.src" class="rounded-lg">
</UCarousel>
</template>
</template>

View File

@@ -1,3 +1,40 @@
<script setup lang="ts">
import type { ShortcutsConfig } from '@nuxt/ui/composables/defineShortcuts.js'
const logs = ref<string[]>([])
const shortcutsState = ref({
'a': {
label: 'A',
disabled: false,
usingInput: false
},
'shift_i': {
label: 'Shift+I',
disabled: false,
usingInput: false
},
'g-i': {
label: 'G->I',
disabled: false,
usingInput: false
}
})
const shortcuts = computed(() => {
return Object.entries(shortcutsState.value).reduce<ShortcutsConfig>((acc, [key, { label, disabled, usingInput }]) => {
if (disabled) {
return acc
}
acc[key] = {
handler: () => { logs.value.unshift(`"${label}" triggered`) },
usingInput
}
return acc
}, {})
})
defineShortcuts(shortcuts)
</script>
<template>
<div class="w-full flex flex-col gap-4">
<UCard class="flex-1">
@@ -43,38 +80,3 @@
</UCard>
</div>
</template>
<script setup lang="ts">
const logs = ref<string[]>([])
const shortcutsState = ref({
'a': {
label: 'A',
disabled: false,
usingInput: false
},
'shift_i': {
label: 'Shift+I',
disabled: false,
usingInput: false
},
'g-i': {
label: 'G->I',
disabled: false,
usingInput: false
}
})
const shortcuts = computed(() => {
return Object.entries(shortcutsState.value).reduce<ShortcutsConfig>((acc, [key, { label, disabled, usingInput }]) => {
if (disabled) {
return acc
}
acc[key] = {
handler: () => { logs.value.unshift(`"${label}" triggered`) },
usingInput
}
return acc
}, {})
})
defineShortcuts(shortcuts)
</script>

View File

@@ -1,6 +1,6 @@
export default defineNuxtConfig({
modules: [
'../src/module',
'@nuxt/ui',
'@nuxthub/core'
],

View File

@@ -5,14 +5,22 @@
"scripts": {
"dev": "nuxi dev",
"build": "nuxi build",
"generate": "nuxi generate"
"generate": "nuxi generate",
"typecheck": "nuxt typecheck"
},
"dependencies": {
"@iconify-json/lucide": "^1.2.38",
"@iconify-json/lucide": "^1.2.41",
"@iconify-json/simple-icons": "^1.2.33",
"@nuxt/ui": "latest",
"@nuxthub/core": "^0.8.25",
"nuxt": "^3.16.2",
"zod": "^3.24.3"
"@nuxthub/core": "^0.8.27",
"nuxt": "^3.17.2",
"zod": "^3.24.4"
},
"devDependencies": {
"typescript": "^5.8.3",
"vue-tsc": "^2.2.10"
},
"resolutions": {
"unimport": "4.1.1"
}
}

4415
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,9 +5,6 @@
"lockFileMaintenance": {
"enabled": true
},
"ignoreDeps": [
"vue-tsc"
],
"baseBranches": ["v2", "v3"],
"packageRules": [{
"matchBaseBranches": ["v3"],
@@ -19,6 +16,11 @@
"@tailwindcss/postcss",
"@tailwindcss/vite"
]
}, {
"groupName": "reka-ui",
"matchPackageNames": [
"reka-ui"
]
}, {
"matchDepTypes": ["peerDependencies"],
"enabled": false

View File

@@ -93,7 +93,7 @@ export default defineNuxtModule<ModuleOptions>({
await registerModule('@nuxt/icon', 'icon', { cssLayer: 'components' })
if (options.fonts) {
await registerModule('@nuxt/fonts', 'fonts', { experimental: { processCSSVariables: true } })
await registerModule('@nuxt/fonts', 'fonts', {})
}
if (options.colorMode) {
await registerModule('@nuxtjs/color-mode', 'colorMode', { classSuffix: '', disableTransition: true })

View File

@@ -16,6 +16,7 @@ export default function PluginsPlugin(options: NuxtUIOptions) {
const plugins = globSync(['**/*', '!*.d.ts'], { cwd: join(runtimeDir, 'plugins'), absolute: true })
plugins.unshift(resolvePathSync('../runtime/vue/plugins/head', { extensions: ['.ts', '.mjs', '.js'], url: import.meta.url }))
plugins.push(resolvePathSync('../runtime/vue/plugins/colors', { extensions: ['.ts', '.mjs', '.js'], url: import.meta.url }))
if (options.colorMode) {
plugins.push(resolvePathSync('../runtime/vue/plugins/color-mode', { extensions: ['.ts', '.mjs', '.js'], url: import.meta.url }))
}

View File

@@ -89,7 +89,7 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.accordion ||
</script>
<template>
<AccordionRoot v-bind="rootProps" :class="ui.root({ class: [props.class, props.ui?.root] })">
<AccordionRoot v-bind="rootProps" :class="ui.root({ class: [props.ui?.root, props.class] })">
<AccordionItem
v-for="(item, index) in props.items"
v-slot="{ open }"

View File

@@ -97,7 +97,7 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.alert || {})
</script>
<template>
<Primitive :as="as" :data-orientation="orientation" :class="ui.root({ class: [props.class, props.ui?.root] })">
<Primitive :as="as" :data-orientation="orientation" :class="ui.root({ class: [props.ui?.root, props.class] })">
<slot name="leading">
<UAvatar v-if="avatar" :size="((props.ui?.avatarSize || ui.avatarSize()) as AvatarProps['size'])" v-bind="avatar" :class="ui.avatar({ class: props.ui?.avatar })" />
<UIcon v-else-if="icon" :name="icon" :class="ui.icon({ class: props.ui?.icon })" />

View File

@@ -81,7 +81,7 @@ function onError() {
</script>
<template>
<Primitive :as="as" :class="ui.root({ class: [props.class, props.ui?.root] })" :style="props.style">
<Primitive :as="as" :class="ui.root({ class: [props.ui?.root, props.class] })" :style="props.style">
<component
:is="ImageComponent"
v-if="src && !error"

View File

@@ -93,7 +93,7 @@ provide(avatarGroupInjectionKey, computed(() => ({
</script>
<template>
<Primitive :as="as" :class="ui.root({ class: [props.class, props.ui?.root] })">
<Primitive :as="as" :class="ui.root({ class: [props.ui?.root, props.class] })">
<UAvatar v-if="hiddenCount > 0" :text="`+${hiddenCount}`" :class="ui.base({ class: props.ui?.base })" />
<component :is="avatar" v-for="(avatar, count) in visibleAvatars" :key="count" :class="ui.base({ class: props.ui?.base })" />
</Primitive>

View File

@@ -65,14 +65,14 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.badge || {})
</script>
<template>
<Primitive :as="as" :class="ui.base({ class: [props.class, props.ui?.base] })">
<Primitive :as="as" :class="ui.base({ class: [props.ui?.base, props.class] })">
<slot name="leading">
<UIcon v-if="isLeading && leadingIconName" :name="leadingIconName" :class="ui.leadingIcon({ class: props.ui?.leadingIcon })" />
<UAvatar v-else-if="!!avatar" :size="((props.ui?.leadingAvatarSize || ui.leadingAvatarSize()) as AvatarProps['size'])" v-bind="avatar" :class="ui.leadingAvatar({ class: props.ui?.leadingAvatar })" />
</slot>
<slot>
<span v-if="label" :class="ui.label({ class: props.ui?.label })">
<span v-if="label !== undefined && label !== null" :class="ui.label({ class: props.ui?.label })">
{{ label }}
</span>
</slot>

View File

@@ -81,7 +81,7 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.breadcrumb |
</script>
<template>
<Primitive :as="as" aria-label="breadcrumb" :class="ui.root({ class: [props.class, props.ui?.root] })">
<Primitive :as="as" aria-label="breadcrumb" :class="ui.root({ class: [props.ui?.root, props.class] })">
<ol :class="ui.list({ class: props.ui?.list })">
<template v-for="(item, index) in items" :key="index">
<li :class="ui.item({ class: props.ui?.item })">

View File

@@ -123,7 +123,7 @@ const ui = computed(() => tv({
v-slot="{ active, ...slotProps }"
:type="type"
:disabled="disabled || isLoading"
:class="ui.base({ class: [props.class, props.ui?.base] })"
:class="ui.base({ class: [props.ui?.base, props.class] })"
v-bind="omit(linkProps, ['type', 'disabled', 'onClick'])"
custom
>
@@ -143,7 +143,7 @@ const ui = computed(() => tv({
</slot>
<slot>
<span v-if="label" :class="ui.label({ class: props.ui?.label, active })">
<span v-if="label !== undefined && label !== null" :class="ui.label({ class: props.ui?.label, active })">
{{ label }}
</span>
</slot>

View File

@@ -157,7 +157,7 @@ const Calendar = computed(() => props.range ? RangeCalendar : SingleCalendar)
:default-value="defaultValue"
:locale="locale"
:dir="dir"
:class="ui.root({ class: [props.class, props.ui?.root] })"
:class="ui.root({ class: [props.ui?.root, props.class] })"
>
<Calendar.Header :class="ui.header({ class: props.ui?.header })">
<Calendar.Prev v-if="props.yearControls" :prev-page="(date: DateValue) => paginateYear(date, -1)" :aria-label="t('calendar.prevYear')" as-child>

View File

@@ -43,7 +43,7 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.card || {})
</script>
<template>
<Primitive :as="as" :class="ui.root({ class: [props.class, props.ui?.root] })">
<Primitive :as="as" :class="ui.root({ class: [props.ui?.root, props.class] })">
<div v-if="!!slots.header" :class="ui.header({ class: props.ui?.header })">
<slot name="header" />
</div>

View File

@@ -278,7 +278,7 @@ defineExpose({
role="region"
aria-roledescription="carousel"
tabindex="0"
:class="ui.root({ class: [props.class, props.ui?.root] })"
:class="ui.root({ class: [props.ui?.root, props.class] })"
@keydown="onKeyDown"
>
<div ref="emblaRef" :class="ui.viewport({ class: props.ui?.viewport })">

View File

@@ -101,7 +101,7 @@ function onUpdate(value: any) {
<!-- eslint-disable vue/no-template-shadow -->
<template>
<Primitive :as="variant === 'list' ? as : Label" :class="ui.root({ class: [props.class, props.ui?.root] })">
<Primitive :as="variant === 'list' ? as : Label" :class="ui.root({ class: [props.ui?.root, props.class] })">
<div :class="ui.container({ class: props.ui?.container })">
<CheckboxRoot
:id="id"

View File

@@ -74,6 +74,7 @@ import { useAppConfig } from '#imports'
import { useFormField } from '../composables/useFormField'
import { get, omit } from '../utils'
import { tv } from '../utils/tv'
import UCheckbox from './Checkbox.vue'
const props = withDefaults(defineProps<CheckboxGroupProps<T>>(), {
valueKey: 'value',
@@ -152,7 +153,7 @@ function onUpdate(value: any) {
v-bind="rootProps"
:name="name"
:disabled="disabled"
:class="ui.root({ class: [props.class, props.ui?.root] })"
:class="ui.root({ class: [props.ui?.root, props.class] })"
@update:model-value="onUpdate"
>
<fieldset :class="ui.fieldset({ class: props.ui?.fieldset })" v-bind="ariaAttrs">

View File

@@ -74,7 +74,7 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.chip || {})
</script>
<template>
<Primitive :as="as" :class="ui.root({ class: [props.class, props.ui?.root] })">
<Primitive :as="as" :class="ui.root({ class: [props.ui?.root, props.class] })">
<Slot v-bind="$attrs">
<slot />
</Slot>

View File

@@ -46,7 +46,7 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.collapsible
</script>
<template>
<CollapsibleRoot v-slot="{ open }" v-bind="rootProps" :class="ui.root({ class: [props.class, props.ui?.root] })">
<CollapsibleRoot v-slot="{ open }" v-bind="rootProps" :class="ui.root({ class: [props.ui?.root, props.class] })">
<CollapsibleTrigger v-if="!!slots.default" as-child>
<slot :open="open" />
</CollapsibleTrigger>

View File

@@ -263,7 +263,7 @@ const trackThumbStyle = computed(() => ({
</script>
<template>
<Primitive :as="as" :class="ui.root({ class: [props.class, props.ui?.root] })" :data-disabled="disabled ? true : undefined">
<Primitive :as="as" :class="ui.root({ class: [props.ui?.root, props.class] })" :data-disabled="disabled ? true : undefined">
<div :class="ui.picker({ class: props.ui?.picker })">
<div
ref="selectorRef"

View File

@@ -249,7 +249,7 @@ const groups = computed(() => {
<!-- eslint-disable vue/no-v-html -->
<template>
<ListboxRoot v-bind="rootProps" :class="ui.root({ class: [props.class, props.ui?.root] })">
<ListboxRoot v-bind="rootProps" :class="ui.root({ class: [props.ui?.root, props.class] })">
<ListboxFilter v-model="searchTerm" as-child>
<UInput
:placeholder="placeholder || t('commandPalette.placeholder')"

View File

@@ -90,7 +90,7 @@ provide(formFieldInjectionKey, computed(() => ({
</script>
<template>
<Primitive :as="as" :class="ui.root({ class: [props.class, props.ui?.root] })">
<Primitive :as="as" :class="ui.root({ class: [props.ui?.root, props.class] })">
<div :class="ui.wrapper({ class: props.ui?.wrapper })">
<div v-if="label || !!slots.label" :class="ui.labelWrapper({ class: props.ui?.labelWrapper })">
<Label :for="id" :class="ui.label({ class: props.ui?.label })">

View File

@@ -163,7 +163,7 @@ defineExpose({
</script>
<template>
<Primitive :as="as" :class="ui.root({ class: [props.class, props.ui?.root] })">
<Primitive :as="as" :class="ui.root({ class: [props.ui?.root, props.class] })">
<input
:id="id"
ref="inputRef"

View File

@@ -406,7 +406,7 @@ defineExpose({
v-bind="rootProps"
:name="name"
:disabled="disabled"
:class="ui.root({ class: [props.class, props.ui?.root] })"
:class="ui.root({ class: [props.ui?.root, props.class] })"
:as-child="!!multiple"
ignore-filter
@update:model-value="onUpdate"

View File

@@ -145,7 +145,7 @@ defineExpose({
<NumberFieldRoot
v-bind="rootProps"
:id="id"
:class="ui.root({ class: [props.class, props.ui?.root] })"
:class="ui.root({ class: [props.ui?.root, props.class] })"
:name="name"
:disabled="disabled"
:locale="locale"

View File

@@ -3,7 +3,7 @@
import type { NavigationMenuRootProps, NavigationMenuRootEmits, NavigationMenuContentProps, NavigationMenuContentEmits, CollapsibleRootProps } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema'
import theme from '#build/ui/navigation-menu'
import type { AvatarProps, BadgeProps, LinkProps } from '../types'
import type { AvatarProps, BadgeProps, LinkProps, TooltipProps } from '../types'
import type { ArrayOrNested, DynamicSlots, MergeTypes, NestedItem, EmitsToProps, ComponentConfig } from '../types/utils'
type NavigationMenu = ComponentConfig<typeof theme, AppConfig, 'navigationMenu'>
@@ -26,6 +26,12 @@ export interface NavigationMenuItem extends Omit<LinkProps, 'type' | 'raw' | 'cu
* `{ size: 'sm', color: 'neutral', variant: 'outline' }`{lang="ts-type"}
*/
badge?: string | number | BadgeProps
/**
* Display a tooltip on the item.
* Only works when `type` is `link`.
* `{ content: { side: 'right' } }`{lang="ts-type"}
*/
tooltip?: TooltipProps
/**
* @IconifyIcon
*/
@@ -38,6 +44,12 @@ export interface NavigationMenuItem extends Omit<LinkProps, 'type' | 'raw' | 'cu
type?: 'label' | 'link'
slot?: string
value?: string
/**
* Make the item collapsible.
* Only works when `orientation` is `vertical`.
* @defaultValue true
*/
collapsible?: boolean
children?: NavigationMenuChildItem[]
onSelect?(e: Event): void
[key: string]: any
@@ -143,6 +155,7 @@ import UAvatar from './Avatar.vue'
import UIcon from './Icon.vue'
import UBadge from './Badge.vue'
import UCollapsible from './Collapsible.vue'
import UTooltip from './Tooltip.vue'
const props = withDefaults(defineProps<NavigationMenuProps<T>>(), {
orientation: 'horizontal',
@@ -229,7 +242,7 @@ const lists = computed<NavigationMenuItem[][]>(() =>
:class="ui.linkTrailingBadge({ class: props.ui?.linkTrailingBadge })"
/>
<UIcon v-if="(orientation === 'horizontal' && (item.children?.length || !!slots[(item.slot ? `${item.slot}-content` : 'item-content') as keyof NavigationMenuSlots<T>])) || (orientation === 'vertical' && item.children?.length)" :name="item.trailingIcon || trailingIcon || appConfig.ui.icons.chevronDown" :class="ui.linkTrailingIcon({ class: props.ui?.linkTrailingIcon, active })" />
<UIcon v-if="(orientation === 'horizontal' && (item.children?.length || !!slots[(item.slot ? `${item.slot}-content` : 'item-content') as keyof NavigationMenuSlots<T>])) || (orientation === 'vertical' && item.children?.length && item.collapsible !== false)" :name="item.trailingIcon || trailingIcon || appConfig.ui.icons.chevronDown" :class="ui.linkTrailingIcon({ class: props.ui?.linkTrailingIcon, active })" />
<UIcon v-else-if="item.trailingIcon" :name="item.trailingIcon" :class="ui.linkTrailingIcon({ class: props.ui?.linkTrailingIcon, active })" />
</slot>
</span>
@@ -238,17 +251,18 @@ const lists = computed<NavigationMenuItem[][]>(() =>
<DefineItemTemplate v-slot="{ item, index, level = 0 }">
<component
:is="(orientation === 'vertical' && item.children?.length && !collapsed) ? UCollapsible : NavigationMenuItem"
:is="(orientation === 'vertical' && item.children?.length) ? UCollapsible : NavigationMenuItem"
as="li"
:value="item.value || String(index)"
:default-open="item.defaultOpen"
:unmount-on-hide="(orientation === 'vertical' && item.children?.length && !collapsed) ? unmountOnHide : undefined"
:disabled="(orientation === 'vertical' && item.children?.length) ? item.collapsible === false : undefined"
:unmount-on-hide="(orientation === 'vertical' && item.children?.length) ? unmountOnHide : undefined"
:open="item.open"
>
<div v-if="orientation === 'vertical' && item.type === 'label'" :class="ui.label({ class: props.ui?.label })">
<ReuseLinkTemplate :item="item" :index="index" />
</div>
<ULink v-else-if="item.type !== 'label'" v-slot="{ active, ...slotProps }" v-bind="(orientation === 'vertical' && item.children?.length && !collapsed) ? {} : pickLinkProps(item as Omit<NavigationMenuItem, 'type'>)" custom>
<ULink v-else-if="item.type !== 'label'" v-slot="{ active, ...slotProps }" v-bind="(orientation === 'vertical' && item.children?.length && item.collapsible !== false) ? {} : pickLinkProps(item as Omit<NavigationMenuItem, 'type'>)" custom>
<component
:is="(orientation === 'horizontal' && (item.children?.length || !!slots[(item.slot ? `${item.slot}-content` : 'item-content') as keyof NavigationMenuSlots<T>])) ? NavigationMenuTrigger : NavigationMenuLink"
as-child
@@ -256,7 +270,12 @@ const lists = computed<NavigationMenuItem[][]>(() =>
:disabled="item.disabled"
@select="item.onSelect"
>
<ULinkBase v-bind="slotProps" :class="ui.link({ class: [props.ui?.link, item.class], active: active || item.active, disabled: !!item.disabled, level: orientation === 'horizontal' || level > 0 })">
<UTooltip v-if="!!item.tooltip" :content="{ side: 'right' }" v-bind="item.tooltip">
<ULinkBase v-bind="slotProps" :class="ui.link({ class: [props.ui?.link, item.class], active: active || item.active, disabled: !!item.disabled, level: orientation === 'horizontal' || level > 0 })">
<ReuseLinkTemplate :item="item" :active="active || item.active" :index="index" />
</ULinkBase>
</UTooltip>
<ULinkBase v-else v-bind="slotProps" :class="ui.link({ class: [props.ui?.link, item.class], active: active || item.active, disabled: !!item.disabled, level: orientation === 'horizontal' || level > 0 })">
<ReuseLinkTemplate :item="item" :active="active || item.active" :index="index" />
</ULinkBase>
</component>
@@ -289,7 +308,7 @@ const lists = computed<NavigationMenuItem[][]>(() =>
</NavigationMenuContent>
</ULink>
<template v-if="orientation === 'vertical' && item.children?.length && !collapsed" #content>
<template v-if="orientation === 'vertical' && item.children?.length " #content>
<ul :class="ui.childList({ class: props.ui?.childList })">
<ReuseItemTemplate
v-for="(childItem, childIndex) in item.children"
@@ -304,7 +323,7 @@ const lists = computed<NavigationMenuItem[][]>(() =>
</component>
</DefineItemTemplate>
<NavigationMenuRoot v-bind="rootProps" :data-collapsed="collapsed" :class="ui.root({ class: [props.class, props.ui?.root] })">
<NavigationMenuRoot v-bind="rootProps" :data-collapsed="collapsed" :class="ui.root({ class: [props.ui?.root, props.class] })">
<slot name="list-leading" />
<template v-for="(list, listIndex) in lists" :key="`list-${listIndex}`">

View File

@@ -140,7 +140,7 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.pagination |
</script>
<template>
<PaginationRoot v-slot="{ page, pageCount }" v-bind="rootProps" :class="ui.root({ class: [props.class, props.ui?.root] })">
<PaginationRoot v-slot="{ page, pageCount }" v-bind="rootProps" :class="ui.root({ class: [props.ui?.root, props.class] })">
<PaginationList v-slot="{ items }" :class="ui.list({ class: props.ui?.list })">
<PaginationFirst v-if="showControls || !!slots.first" as-child :class="ui.first({ class: props.ui?.first })">
<slot name="first">

View File

@@ -113,7 +113,7 @@ defineExpose({
v-bind="{ ...rootProps, ...ariaAttrs }"
:id="id"
:name="name"
:class="ui.root({ class: [props.class, props.ui?.root] })"
:class="ui.root({ class: [props.ui?.root, props.class] })"
@update:model-value="emitFormInput()"
@complete="onComplete"
>

View File

@@ -167,7 +167,7 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.progress ||
</script>
<template>
<Primitive :as="as" :class="ui.root({ class: [props.class, props.ui?.root] })">
<Primitive :as="as" :class="ui.root({ class: [props.ui?.root, props.class] })">
<div v-if="!isIndeterminate && (status || !!slots.status)" :class="ui.status({ class: props.ui?.status })" :style="statusStyle">
<slot name="status" :percent="percent">
{{ percent }}%

View File

@@ -11,7 +11,7 @@ export type RadioGroupItem = {
label?: string
description?: string
disabled?: boolean
value?: string
value?: RadioGroupValue
[key: string]: any
} | RadioGroupValue
@@ -166,7 +166,7 @@ function onUpdate(value: any) {
v-bind="rootProps"
:name="name"
:disabled="disabled"
:class="ui.root({ class: [props.class, props.ui?.root] })"
:class="ui.root({ class: [props.ui?.root, props.class] })"
@update:model-value="onUpdate"
>
<fieldset :class="ui.fieldset({ class: props.ui?.fieldset })" v-bind="ariaAttrs">

View File

@@ -21,7 +21,7 @@ interface SelectItemBase {
* @defaultValue 'item'
*/
type?: 'label' | 'separator' | 'item'
value?: string | number
value?: AcceptableValue | boolean
disabled?: boolean
onSelect?(e?: Event): void
[key: string]: any
@@ -238,7 +238,7 @@ function isSelectItem(item: SelectItem): item is SelectItemBase {
@update:model-value="onUpdate"
@update:open="onUpdateOpen"
>
<SelectTrigger :id="id" :class="ui.base({ class: [props.class, props.ui?.base] })" v-bind="{ ...$attrs, ...ariaAttrs }">
<SelectTrigger :id="id" :class="ui.base({ class: [props.ui?.base, props.class] })" v-bind="{ ...$attrs, ...ariaAttrs }">
<span v-if="isLeading || !!avatar || !!slots.leading" :class="ui.leading({ class: props.ui?.leading })">
<slot name="leading" :model-value="(modelValue as GetModelValue<T, VK, M>)" :open="open" :ui="ui">
<UIcon v-if="isLeading && leadingIconName" :name="leadingIconName" :class="ui.leadingIcon({ class: props.ui?.leadingIcon })" />

View File

@@ -373,7 +373,7 @@ function isSelectItem(item: SelectMenuItem): item is _SelectMenuItem {
@update:open="onUpdateOpen"
>
<ComboboxAnchor as-child>
<ComboboxTrigger :class="ui.base({ class: [props.class, props.ui?.base] })" tabindex="0">
<ComboboxTrigger :class="ui.base({ class: [props.ui?.base, props.class] })" tabindex="0">
<span v-if="isLeading || !!avatar || !!slots.leading" :class="ui.leading({ class: props.ui?.leading })">
<slot name="leading" :model-value="(modelValue as GetModelValue<T, VK, M>)" :open="open" :ui="ui">
<UIcon v-if="isLeading && leadingIconName" :name="leadingIconName" :class="ui.leadingIcon({ class: props.ui?.leadingIcon })" />

View File

@@ -75,7 +75,7 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.separator ||
</script>
<template>
<Separator v-bind="rootProps" :class="ui.root({ class: [props.class, props.ui?.root] })">
<Separator v-bind="rootProps" :class="ui.root({ class: [props.ui?.root, props.class] })">
<div :class="ui.border({ class: props.ui?.border })" />
<template v-if="label || icon || avatar || !!slots.default">

View File

@@ -2,6 +2,7 @@
import type { SliderRootProps } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema'
import theme from '#build/ui/slider'
import type { TooltipProps } from '../types'
import type { ComponentConfig } from '../types/utils'
type Slider = ComponentConfig<typeof theme, AppConfig, 'slider'>
@@ -25,6 +26,12 @@ export interface SliderProps extends Pick<SliderRootProps, 'name' | 'disabled' |
* @defaultValue 'horizontal'
*/
orientation?: SliderRootProps['orientation']
/**
* Display a tooltip around the slider thumbs with the current value.
* `{ disableClosingTrigger: true }`{lang="ts-type"}
* @defaultValue false
*/
tooltip?: boolean | TooltipProps
/** The value of the slider when initially rendered. Use when you do not need to control the state of the slider. */
defaultValue?: number | number[]
class?: any
@@ -44,6 +51,7 @@ import { reactivePick } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { useFormField } from '../composables/useFormField'
import { tv } from '../utils/tv'
import UTooltip from './Tooltip.vue'
const props = withDefaults(defineProps<SliderProps>(), {
min: 0,
@@ -80,7 +88,7 @@ const sliderValue = computed({
}
})
const thumbsCount = computed(() => sliderValue.value?.length ?? 1)
const thumbs = computed(() => sliderValue.value?.length ?? 1)
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.slider || {}) })({
disabled: disabled.value,
@@ -104,7 +112,7 @@ function onChange(value: any) {
v-model="sliderValue"
:name="name"
:disabled="disabled"
:class="ui.root({ class: [props.class, props.ui?.root] })"
:class="ui.root({ class: [props.ui?.root, props.class] })"
:default-value="defaultSliderValue"
@update:model-value="emitFormInput()"
@value-commit="onChange"
@@ -113,6 +121,16 @@ function onChange(value: any) {
<SliderRange :class="ui.range({ class: props.ui?.range })" />
</SliderTrack>
<SliderThumb v-for="count in thumbsCount" :key="count" :class="ui.thumb({ class: props.ui?.thumb })" />
<template v-for="thumb in thumbs" :key="thumb">
<UTooltip
v-if="!!tooltip"
:text="thumbs > 1 ? String(sliderValue?.[thumb - 1]) : String(sliderValue)"
disable-closing-trigger
v-bind="(typeof tooltip === 'object' ? tooltip : {})"
>
<SliderThumb :class="ui.thumb({ class: props.ui?.thumb })" />
</UTooltip>
<SliderThumb v-else :class="ui.thumb({ class: props.ui?.thumb })" />
</template>
</SliderRoot>
</template>

View File

@@ -129,7 +129,7 @@ defineExpose({
</script>
<template>
<StepperRoot v-bind="rootProps" v-model="currentStepIndex" :class="ui.root({ class: [props.class, props.ui?.root] })">
<StepperRoot v-bind="rootProps" v-model="currentStepIndex" :class="ui.root({ class: [props.ui?.root, props.class] })">
<div :class="ui.header({ class: props.ui?.header })">
<StepperItem
v-for="(item, count) in items"
@@ -157,12 +157,12 @@ defineExpose({
</div>
<div :class="ui.wrapper({ class: props.ui?.wrapper })">
<StepperTitle :class="ui.title({ class: props.ui?.title })">
<StepperTitle as="div" :class="ui.title({ class: props.ui?.title })">
<slot name="title" :item="item">
{{ item.title }}
</slot>
</StepperTitle>
<StepperDescription :class="ui.description({ class: props.ui?.description })">
<StepperDescription as="div" :class="ui.description({ class: props.ui?.description })">
<slot name="description" :item="item">
{{ item.description }}
</slot>

View File

@@ -96,7 +96,7 @@ function onUpdate(value: any) {
</script>
<template>
<Primitive :as="as" :class="ui.root({ class: [props.class, props.ui?.root] })">
<Primitive :as="as" :class="ui.root({ class: [props.ui?.root, props.class] })">
<div :class="ui.container({ class: props.ui?.container })">
<SwitchRoot
:id="id"

View File

@@ -338,7 +338,7 @@ defineExpose({
</script>
<template>
<Primitive :as="as" :class="ui.root({ class: [props.class, props.ui?.root] })">
<Primitive :as="as" :class="ui.root({ class: [props.ui?.root, props.class] })">
<table ref="tableRef" :class="ui.base({ class: [props.ui?.base] })">
<caption v-if="caption || !!slots.caption" :class="ui.caption({ class: [props.ui?.caption] })">
<slot name="caption">

View File

@@ -109,7 +109,7 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.tabs || {})
</script>
<template>
<TabsRoot v-bind="rootProps" :class="ui.root({ class: [props.class, props.ui?.root] })">
<TabsRoot v-bind="rootProps" :class="ui.root({ class: [props.ui?.root, props.class] })">
<TabsList :class="ui.list({ class: props.ui?.list })">
<TabsIndicator :class="ui.indicator({ class: props.ui?.indicator })" />

View File

@@ -190,7 +190,7 @@ defineExpose({
</script>
<template>
<Primitive :as="as" :class="ui.root({ class: [props.class, props.ui?.root] })">
<Primitive :as="as" :class="ui.root({ class: [props.ui?.root, props.class] })">
<textarea
:id="id"
ref="textareaRef"

View File

@@ -116,7 +116,7 @@ defineExpose({
v-slot="{ remaining, duration }"
v-bind="rootProps"
:data-orientation="orientation"
:class="ui.root({ class: [props.class, props.ui?.root] })"
:class="ui.root({ class: [props.ui?.root, props.class] })"
:style="{ '--height': height }"
>
<slot name="leading">

View File

@@ -131,7 +131,7 @@ function getOffset(index: number) {
<ToastPortal v-bind="portalProps">
<ToastViewport
:data-expanded="expanded"
:class="ui.viewport({ class: [props.class, props.ui?.viewport] })"
:class="ui.viewport({ class: [props.ui?.viewport, props.class] })"
:style="{
'--scale-factor': '0.05',
'--translate-factor': position?.startsWith('top') ? '1px' : '-1px',

View File

@@ -198,7 +198,7 @@ const defaultExpanded = computed(() =>
<TreeRoot
v-bind="(rootProps as unknown as TreeRootProps<NestedItem<T>>)"
:class="ui.root({ class: [props.class, props.ui?.root] })"
:class="ui.root({ class: [props.ui?.root, props.class] })"
:get-key="getItemValue"
:default-expanded="defaultExpanded"
:selection-behavior="selectionBehavior"

View File

@@ -3,6 +3,7 @@
import { ref, computed, toValue } from 'vue'
import type { MaybeRef } from 'vue'
import { useEventListener, useActiveElement, useDebounceFn } from '@vueuse/core'
import { useKbd } from './useKbd'
type Handler = (e?: any) => void
@@ -66,6 +67,7 @@ export function defineShortcuts(config: MaybeRef<ShortcutsConfig>, options: Shor
}
const debouncedClearChainedInput = useDebounceFn(clearChainedInput, options.chainDelay ?? 800)
const { macOS } = useKbd()
const activeElement = useActiveElement()
const onKeyDown = (e: KeyboardEvent) => {
@@ -178,6 +180,12 @@ export function defineShortcuts(config: MaybeRef<ShortcutsConfig>, options: Shor
}
shortcut.chained = chained
// Convert Meta to Ctrl for non-MacOS
if (!macOS.value && shortcut.metaKey && !shortcut.ctrlKey) {
shortcut.metaKey = false
shortcut.ctrlKey = true
}
// Retrieve handler function
if (typeof shortcutConfig === 'function') {
shortcut.handler = shortcutConfig

View File

@@ -14,6 +14,7 @@ export const kbdKeysMap = {
win: '⊞',
command: '⌘',
shift: '⇧',
control: '⌃',
option: '⌥',
enter: '↵',
delete: '⌦',
@@ -44,9 +45,9 @@ const _useKbd = () => {
})
onMounted(() => {
kbdKeysSpecificMap.meta = macOS.value ? kbdKeysMap.command : kbdKeysMap.win
kbdKeysSpecificMap.alt = macOS.value ? kbdKeysMap.option : 'alt'
kbdKeysSpecificMap.ctrl = macOS.value ? '⌃' : 'ctrl'
kbdKeysSpecificMap.meta = macOS.value ? kbdKeysMap.command : 'Ctrl'
kbdKeysSpecificMap.ctrl = macOS.value ? kbdKeysMap.control : 'Ctrl'
kbdKeysSpecificMap.alt = macOS.value ? kbdKeysMap.option : 'Alt'
})
function getKbdKey(value?: KbdKey | string) {

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