mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-20 23:11:43 +01:00
Compare commits
130 Commits
chore/cont
...
fix/3952
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
266e870e67 | ||
|
|
d140acc608 | ||
|
|
cc20a26f07 | ||
|
|
983c6382d1 | ||
|
|
37eabc89bd | ||
|
|
a57844e416 | ||
|
|
2be60cddfe | ||
|
|
09b4699aea | ||
|
|
46c2987ebf | ||
|
|
f244d15b96 | ||
|
|
aaa60c0798 | ||
|
|
5467d71cc2 | ||
|
|
941a54e5e3 | ||
|
|
655f98ffed | ||
|
|
999a0f8467 | ||
|
|
2739939c46 | ||
|
|
2a241c87c3 | ||
|
|
e6e510b848 | ||
|
|
a655da1394 | ||
|
|
3a71256d59 | ||
|
|
404359a6ca | ||
|
|
1e4e9c4708 | ||
|
|
6f07f6bd6e | ||
|
|
4c1093bde4 | ||
|
|
322a6f467a | ||
|
|
7d51a9e479 | ||
|
|
04bdbcfc6e | ||
|
|
58aa296425 | ||
|
|
d3df3bb929 | ||
|
|
4863775e17 | ||
|
|
7551a85ad2 | ||
|
|
c2bcb8e264 | ||
|
|
0c1417b6b1 | ||
|
|
88124b85c5 | ||
|
|
9f539c9545 | ||
|
|
41d4ffe5b5 | ||
|
|
d8a0bbe710 | ||
|
|
06e5689da8 | ||
|
|
67d19f582a | ||
|
|
650bc40253 | ||
|
|
2f0f317a12 | ||
|
|
d2a26939ad | ||
|
|
b7a8146898 | ||
|
|
e81464a43e | ||
|
|
4b3d2a7b00 | ||
|
|
6f6fa6ae4a | ||
|
|
c7bb6d4c4b | ||
|
|
c23f85fe33 | ||
|
|
591d59fe89 | ||
|
|
caa3bf9c7e | ||
|
|
2731011bb7 | ||
|
|
aebf0b3dca | ||
|
|
6651987dc6 | ||
|
|
7c4329ded7 | ||
|
|
61aabd72e4 | ||
|
|
8dfdd63ce3 | ||
|
|
e6369a6746 | ||
|
|
a4f3f6d531 | ||
|
|
c5bdec0f64 | ||
|
|
87a7828931 | ||
|
|
501468960b | ||
|
|
13cd6be679 | ||
|
|
e421ea57ec | ||
|
|
799d7ae422 | ||
|
|
adf11f4326 | ||
|
|
be9b1f659a | ||
|
|
39e29fccf1 | ||
|
|
195773ec7d | ||
|
|
d7710795df | ||
|
|
53f8b34bef | ||
|
|
df83ab355e | ||
|
|
e5df026993 | ||
|
|
b507f69b87 | ||
|
|
6131871a0d | ||
|
|
9543bce787 | ||
|
|
5e345a8a73 | ||
|
|
8acf3c51db | ||
|
|
f3b8b17dc5 | ||
|
|
bc0a296f9d | ||
|
|
ac4c1946ec | ||
|
|
82b5f322eb | ||
|
|
67bcb496a9 | ||
|
|
4104cd993b | ||
|
|
e8d493a00e | ||
|
|
0f2d2e5d03 | ||
|
|
4d17989302 | ||
|
|
eb7607749d | ||
|
|
c0347f6e06 | ||
|
|
6366118709 | ||
|
|
9f7f5910ee | ||
|
|
996573f26d | ||
|
|
88ff542e63 | ||
|
|
1a119e6279 | ||
|
|
776aef6e7f | ||
|
|
fb6cf708a6 | ||
|
|
8ab1f75e47 | ||
|
|
8284d05529 | ||
|
|
713e943144 | ||
|
|
e9b19349ba | ||
|
|
b2b206e3f4 | ||
|
|
b779064129 | ||
|
|
94155258e2 | ||
|
|
505c1e502a | ||
|
|
c63a6dd133 | ||
|
|
79833035de | ||
|
|
79e7c7b729 | ||
|
|
bc06185282 | ||
|
|
6e27304d8c | ||
|
|
b4f8ac7ff7 | ||
|
|
a2fa1accaa | ||
|
|
f486423381 | ||
|
|
975331a7b1 | ||
|
|
75e4792f7f | ||
|
|
4a969a8b9e | ||
|
|
664a8c7524 | ||
|
|
fbcc3139a3 | ||
|
|
1a46394668 | ||
|
|
9ca213bd33 | ||
|
|
3484832822 | ||
|
|
80dfa88ea4 | ||
|
|
d710141a1b | ||
|
|
d3e2a3f33a | ||
|
|
9c3d53a02d | ||
|
|
22edfd708a | ||
|
|
e25aa78050 | ||
|
|
122e8ac8f4 | ||
|
|
5fc6312ab1 | ||
|
|
83f0a24704 | ||
|
|
13c299533f | ||
|
|
db11db6ff1 |
4
.github/ISSUE_TEMPLATE/bug-report-v3.yml
vendored
4
.github/ISSUE_TEMPLATE/bug-report-v3.yml
vendored
@@ -5,7 +5,7 @@ body:
|
|||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: |
|
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
|
- type: textarea
|
||||||
id: env
|
id: env
|
||||||
attributes:
|
attributes:
|
||||||
@@ -44,7 +44,7 @@ body:
|
|||||||
id: reproduction
|
id: reproduction
|
||||||
attributes:
|
attributes:
|
||||||
label: Reproduction
|
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
|
placeholder: https://github.com/my/reproduction
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ body:
|
|||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: |
|
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
|
- type: textarea
|
||||||
id: description
|
id: description
|
||||||
attributes:
|
attributes:
|
||||||
|
|||||||
2
.github/ISSUE_TEMPLATE/question-v3.yml
vendored
2
.github/ISSUE_TEMPLATE/question-v3.yml
vendored
@@ -5,7 +5,7 @@ body:
|
|||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: |
|
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
|
- type: textarea
|
||||||
id: description
|
id: description
|
||||||
attributes:
|
attributes:
|
||||||
|
|||||||
30
.github/reproduire/needs-reproduction.md
vendored
Normal file
30
.github/reproduire/needs-reproduction.md
vendored
Normal 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>
|
||||||
47
.github/workflows/module.yml
vendored
47
.github/workflows/module.yml
vendored
@@ -69,6 +69,53 @@ jobs:
|
|||||||
if: matrix.os == 'ubuntu-latest'
|
if: matrix.os == 'ubuntu-latest'
|
||||||
run: pnpx pkg-pr-new publish --compact --no-template --pnpm
|
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:
|
starter-nuxt:
|
||||||
needs: build
|
needs: build
|
||||||
|
|
||||||
|
|||||||
54
.github/workflows/release.yml
vendored
Normal file
54
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
name: release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v3*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish:
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os: [ubuntu-latest] # macos-latest, windows-latest
|
||||||
|
node: [22]
|
||||||
|
|
||||||
|
env:
|
||||||
|
NUXT_GITHUB_TOKEN: ${{ secrets.NUXT_GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install pnpm
|
||||||
|
uses: pnpm/action-setup@v4
|
||||||
|
|
||||||
|
- name: Install node
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node }}
|
||||||
|
cache: pnpm
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install
|
||||||
|
|
||||||
|
- name: Prepare
|
||||||
|
run: pnpm run dev:prepare
|
||||||
|
|
||||||
|
- name: Lint
|
||||||
|
run: pnpm run lint
|
||||||
|
|
||||||
|
- name: Typecheck
|
||||||
|
run: pnpm run typecheck
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: pnpm run test run
|
||||||
|
|
||||||
|
- name: Test (vue)
|
||||||
|
run: pnpm run test:vue run
|
||||||
|
|
||||||
|
- name: Publish
|
||||||
|
run: ./scripts/release.sh
|
||||||
|
env:
|
||||||
|
NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }}
|
||||||
17
.github/workflows/reproduire.yml
vendored
Normal file
17
.github/workflows/reproduire.yml
vendored
Normal 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
|
||||||
@@ -3,6 +3,9 @@
|
|||||||
"commitMessage": "chore(release): v${version}",
|
"commitMessage": "chore(release): v${version}",
|
||||||
"tagName": "v${version}"
|
"tagName": "v${version}"
|
||||||
},
|
},
|
||||||
|
"npm": {
|
||||||
|
"publish": false
|
||||||
|
},
|
||||||
"github": {
|
"github": {
|
||||||
"release": true,
|
"release": true,
|
||||||
"releaseName": "v${version}",
|
"releaseName": "v${version}",
|
||||||
|
|||||||
91
CHANGELOG.md
91
CHANGELOG.md
@@ -1,5 +1,96 @@
|
|||||||
# Changelog
|
# 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
|
||||||
|
|
||||||
|
* **OverlayProvider:** return an overlay instance from `.open()` (#3829)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **App:** add global `portal` prop ([#3688](https://github.com/nuxt/ui/issues/3688)) ([29fa462](https://github.com/nuxt/ui/commit/29fa46276d6bf69b5b87880c476c6f778c2820bf))
|
||||||
|
* **Carousel:** add `select` event ([#3678](https://github.com/nuxt/ui/issues/3678)) ([22edfd7](https://github.com/nuxt/ui/commit/22edfd708ae3eeadbd4ff6c830cdfd5632948286))
|
||||||
|
* **CheckboxGroup:** new component ([#3862](https://github.com/nuxt/ui/issues/3862)) ([9c3d53a](https://github.com/nuxt/ui/commit/9c3d53a02d6254f6b5c90e5fed826b8aefcdb042))
|
||||||
|
* **components:** add new `content-top` and `content-bottom` slots ([#3886](https://github.com/nuxt/ui/issues/3886)) ([1a46394](https://github.com/nuxt/ui/commit/1a463946681e152aa18372118d0fef4a7d8055a5))
|
||||||
|
* **Form:** add `attach` prop to opt-out of nested form attachement ([#3939](https://github.com/nuxt/ui/issues/3939)) ([1a0d7a3](https://github.com/nuxt/ui/commit/1a0d7a3103cf7591b019ef3ad685e2f3786ef6f2))
|
||||||
|
* **Form:** export loading state ([#3861](https://github.com/nuxt/ui/issues/3861)) ([fdee252](https://github.com/nuxt/ui/commit/fdee2522bb9d8361ff3e9fdd4aa2350be8e49b05))
|
||||||
|
* **InputMenu/SelectMenu:** handle `resetSearchTermOnSelect` ([cea881a](https://github.com/nuxt/ui/commit/cea881abdc139b39df89b503cf2ab872f4246c8f)), closes [#3782](https://github.com/nuxt/ui/issues/3782)
|
||||||
|
* **InputNumber:** add support for `stepSnapping` & `disableWheelChange` props ([#3731](https://github.com/nuxt/ui/issues/3731)) ([f5e6284](https://github.com/nuxt/ui/commit/f5e62849c9313063396ab0e3a9b7d22d98ef69bc))
|
||||||
|
* **locale:** add Bulgarian language ([#3783](https://github.com/nuxt/ui/issues/3783)) ([a0c9731](https://github.com/nuxt/ui/commit/a0c9731f634020e76aa98a9a68d673591d35e8c9))
|
||||||
|
* **locale:** add Kazakh language ([#3875](https://github.com/nuxt/ui/issues/3875)) ([43153c4](https://github.com/nuxt/ui/commit/43153c4e91034b728059e7a9bed05888e48f8890))
|
||||||
|
* **locale:** add Tajik language ([#3850](https://github.com/nuxt/ui/issues/3850)) ([f42a79b](https://github.com/nuxt/ui/commit/f42a79b5efe8dc65430a83799ebb0ee737773820))
|
||||||
|
* **locale:** add Uyghur language ([#3878](https://github.com/nuxt/ui/issues/3878)) ([b7fc69b](https://github.com/nuxt/ui/commit/b7fc69baa718ff65b3988d0fa9f143306fa8fac4))
|
||||||
|
* **Modal/Popover/Slideover:** add `close:prevent` event ([#3958](https://github.com/nuxt/ui/issues/3958)) ([f486423](https://github.com/nuxt/ui/commit/f4864233812eac0ed37e0a2d076a95c285a22c01))
|
||||||
|
* **module:** define default color shades ([#3916](https://github.com/nuxt/ui/issues/3916)) ([7ac7aa9](https://github.com/nuxt/ui/commit/7ac7aa9ba73b6aca1bc29b0de2e95c60b2700135))
|
||||||
|
* **module:** define neutral utilities ([#3629](https://github.com/nuxt/ui/issues/3629)) ([d49e0da](https://github.com/nuxt/ui/commit/d49e0dadeea2a58e05e60b2c461b29ce1d334d2b))
|
||||||
|
* **module:** dynamic `rounded-*` utilities ([#3906](https://github.com/nuxt/ui/issues/3906)) ([f9737c8](https://github.com/nuxt/ui/commit/f9737c8f401bf8bc5307674fad6defe2aeeeb907))
|
||||||
|
* **OverlayProvider:** return an overlay instance from `.open()` ([#3829](https://github.com/nuxt/ui/issues/3829)) ([f3098df](https://github.com/nuxt/ui/commit/f3098df84a3b7f58f7ccc1233bc8b45eab99ee10))
|
||||||
|
* **PinInput:** add `autofocus` / `autofocus-delay` props ([0456670](https://github.com/nuxt/ui/commit/0456670dac1153340220603c8c116e3b71f72ae7)), closes [#3717](https://github.com/nuxt/ui/issues/3717)
|
||||||
|
* **RadioGroup:** add `card` and `table` variants ([#3178](https://github.com/nuxt/ui/issues/3178)) ([4d138ad](https://github.com/nuxt/ui/commit/4d138ad6719a074f5f994006d12745ca05bec9c4))
|
||||||
|
* **Select:** handle `onSelect` field in items ([8640831](https://github.com/nuxt/ui/commit/864083156a79dfb5d0be868658b7f9fc77570178))
|
||||||
|
* **Table:** conditionally apply classes to `tr` and `td` ([#3866](https://github.com/nuxt/ui/issues/3866)) ([80dfa88](https://github.com/nuxt/ui/commit/80dfa88ea442571ee1dc673317cc7baa8cacd8a3))
|
||||||
|
* **Tabs:** add `list-leading` and `list-trailing` slots ([#3837](https://github.com/nuxt/ui/issues/3837)) ([3447a06](https://github.com/nuxt/ui/commit/3447a062b636a469089d6e9bdcfcb3dce9063ee5))
|
||||||
|
* **Textarea:** add `autoresize-delay` prop ([06414d3](https://github.com/nuxt/ui/commit/06414d344b151ad6e1a3225a9f5f1f76d58d319c)), closes [#3730](https://github.com/nuxt/ui/issues/3730)
|
||||||
|
* **Textarea:** add `icon`, `loading`, etc. props to match Input ([cb193f1](https://github.com/nuxt/ui/commit/cb193f1d25b5c73ca03dcf10864800350dd1c290))
|
||||||
|
* **Textarea:** add `resize-none` class with `autoresize` prop ([ffafd81](https://github.com/nuxt/ui/commit/ffafd81e1ed25074430668c792e5e1c6afc22bd0))
|
||||||
|
* **unplugin:** routing support for inertia ([#3845](https://github.com/nuxt/ui/issues/3845)) ([d059efc](https://github.com/nuxt/ui/commit/d059efca258da7ae5116e829189a492824ac1d87))
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Accordion:** use `div` instead of `h3` for header tag ([75e4792](https://github.com/nuxt/ui/commit/75e4792f7f00c55229253289c4f806f2b6fc9854)), closes [#3963](https://github.com/nuxt/ui/issues/3963)
|
||||||
|
* **Alert/Toast:** display actions when using slots ([5086363](https://github.com/nuxt/ui/commit/50863635d653c8083772046ddc5b828fba7047d0)), closes [#3950](https://github.com/nuxt/ui/issues/3950)
|
||||||
|
* **Carousel:** move arrows inside container on mobile ([d339dcb](https://github.com/nuxt/ui/commit/d339dcbfb8fe244bd198d247d8448e3ef856dfef)), closes [#3813](https://github.com/nuxt/ui/issues/3813)
|
||||||
|
* **CheckboxGroup:** proxy slots & `ui` prop ([bc06185](https://github.com/nuxt/ui/commit/bc061852822edd2dfb832a46dd6388123ec5771e))
|
||||||
|
* **CommandPalette:** consistent alignement with other components ([d25265c](https://github.com/nuxt/ui/commit/d25265c8b7d34e01af8827d9af5eccb98bf30e9e))
|
||||||
|
* **CommandPalette:** increase input font size to avoid zoom ([d227a10](https://github.com/nuxt/ui/commit/d227a105d8d409ea0753153afaecf639ddb80fed))
|
||||||
|
* **CommandPalette:** prevent hover background on disabled items ([ba534f1](https://github.com/nuxt/ui/commit/ba534f18b94383c97b2654d892ee4b8b024b3fab))
|
||||||
|
* **components:** refactor types after `@nuxt/module-builder` upgrade ([#3855](https://github.com/nuxt/ui/issues/3855)) ([39c861a](https://github.com/nuxt/ui/commit/39c861a64bbd452256ebd1a14a257b94c35855d4))
|
||||||
|
* **components:** respect `transform-origin` in popper content ([#3919](https://github.com/nuxt/ui/issues/3919)) ([01d8dc7](https://github.com/nuxt/ui/commit/01d8dc72adb0b32ad68bb4a98bf24b17f435a89c))
|
||||||
|
* **ContextMenu/DropdownMenu:** handle RTL mode ([#3744](https://github.com/nuxt/ui/issues/3744)) ([1ae5cc0](https://github.com/nuxt/ui/commit/1ae5cc09cb2eca6b6f53eb04db9dcc731b696cae))
|
||||||
|
* **ContextMenuContent/DropdownMenuContent:** remove unwanted `any` ([#3741](https://github.com/nuxt/ui/issues/3741)) ([97274f1](https://github.com/nuxt/ui/commit/97274f15b8bfe457e7e206f81b32e3febf0f875d))
|
||||||
|
* **Form:** input and output type inference ([#3938](https://github.com/nuxt/ui/issues/3938)) ([f429498](https://github.com/nuxt/ui/commit/f42949820be9be9fca41abc653dc12c033e1eeec))
|
||||||
|
* **Form:** loses focus on submit ([#3796](https://github.com/nuxt/ui/issues/3796)) ([8e78eb1](https://github.com/nuxt/ui/commit/8e78eb15c85beef1c814206c4a192d4eb00a7e86))
|
||||||
|
* **InputMenu/Select/SelectMenu:** add `min-w-fit` to `content` slot ([#3922](https://github.com/nuxt/ui/issues/3922)) ([f6b3761](https://github.com/nuxt/ui/commit/f6b376110c8bee2c41ae3137bb972aad402ebff1))
|
||||||
|
* **InputMenu/SelectMenu:** correctly call `onSelect` events ([#3735](https://github.com/nuxt/ui/issues/3735)) ([f25fed5](https://github.com/nuxt/ui/commit/f25fed58e988b304e79cdb536d544d257395cf89))
|
||||||
|
* **InputMenu/SelectMenu:** prevent `disabled` items to be selected ([8435a0f](https://github.com/nuxt/ui/commit/8435a0fe1622eb5b6863b6e4751c9d2d1be36db9)), closes [#3474](https://github.com/nuxt/ui/issues/3474)
|
||||||
|
* **InputMenu/SelectMenu:** remove `valueKey` string case ([9ca213b](https://github.com/nuxt/ui/commit/9ca213bd3340492d7503a34bd142e1f79a697050)), closes [#3949](https://github.com/nuxt/ui/issues/3949) [#3331](https://github.com/nuxt/ui/issues/3331)
|
||||||
|
* **InputMenu/SelectMenu:** support arbitrary `value` ([#3779](https://github.com/nuxt/ui/issues/3779)) ([52a97e2](https://github.com/nuxt/ui/commit/52a97e2df7903f91e3134931eb0d6bd4c528f71f))
|
||||||
|
* **InputMenu:** emit `change` on multiple item removal ([9d2fed1](https://github.com/nuxt/ui/commit/9d2fed125013e3bbfbf9435678729cd05254a5e8)), closes [#3756](https://github.com/nuxt/ui/issues/3756)
|
||||||
|
* **Link:** proxy `download` property ([#3879](https://github.com/nuxt/ui/issues/3879)) ([47cdc2e](https://github.com/nuxt/ui/commit/47cdc2e1d8cd9803ebc954ccae110d62b9a08779))
|
||||||
|
* **NavigationMenu:** add `sm:w-auto` content slot ([abe0859](https://github.com/nuxt/ui/commit/abe0859691e06564f68335bd82dcd121e976408e)), closes [#3788](https://github.com/nuxt/ui/issues/3788)
|
||||||
|
* **Skeleton:** improve accessibility ([#3613](https://github.com/nuxt/ui/issues/3613)) ([3484832](https://github.com/nuxt/ui/commit/3484832822015a224ce6fbeae5132018875557e6))
|
||||||
|
* **Stepper:** ui prop override on `icon` and `content` slots ([1d45980](https://github.com/nuxt/ui/commit/1d459803dc052a16b8966ee89c71646bf6ef1c16)), closes [#3785](https://github.com/nuxt/ui/issues/3785)
|
||||||
|
* **Table:** improve `data` reactivity ([#3967](https://github.com/nuxt/ui/issues/3967)) ([6e27304](https://github.com/nuxt/ui/commit/6e27304d8ca459a04667bac404084264a8cf58fd))
|
||||||
|
* **Table:** pass header `colspan` to `th` ([#3926](https://github.com/nuxt/ui/issues/3926)) ([122e8ac](https://github.com/nuxt/ui/commit/122e8ac8f41ba093cd350c3ce642263263f77296))
|
||||||
|
* **Tree:** simplify reusable template types ([#3836](https://github.com/nuxt/ui/issues/3836)) ([3deed4c](https://github.com/nuxt/ui/commit/3deed4c271cad4adc2a4c47d5dd02e95a14ce11a))
|
||||||
|
* **types:** allow color identifiers with dashes ([#3896](https://github.com/nuxt/ui/issues/3896)) ([e5a1e26](https://github.com/nuxt/ui/commit/e5a1e26f9db763b54caed4ca313f44d1b5fe269d))
|
||||||
|
* **types:** handle `ClassValue` in `ui` prop ([eea1415](https://github.com/nuxt/ui/commit/eea14155aa612649bc969d806ec5df4295945c70)), closes [#3860](https://github.com/nuxt/ui/issues/3860)
|
||||||
|
* **types:** improve dynamic slots ([#3857](https://github.com/nuxt/ui/issues/3857)) ([8dd9d08](https://github.com/nuxt/ui/commit/8dd9d08209e47a7d9a5654db4fb936b4cbcfc021))
|
||||||
|
* **usePortal:** adjust portal target resolution logic ([#3954](https://github.com/nuxt/ui/issues/3954)) ([db11db6](https://github.com/nuxt/ui/commit/db11db6ff1ce4b27a66aaa03f07870ba36426181))
|
||||||
|
* **vite:** vitest skipping nuxt imports transformations ([#3925](https://github.com/nuxt/ui/issues/3925)) ([c31bffa](https://github.com/nuxt/ui/commit/c31bffad1b8afeda584bca8c73bb7f790eb12a9f))
|
||||||
|
|
||||||
## [3.0.2](https://github.com/nuxt/ui/compare/v3.0.1...v3.0.2) (2025-03-28)
|
## [3.0.2](https://github.com/nuxt/ui/compare/v3.0.1...v3.0.2) (2025-03-28)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|||||||
@@ -16,6 +16,10 @@ Nuxt UI harnesses the combined strengths of [Reka UI](https://reka-ui.com/), [Ta
|
|||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> You are on the `v3` development branch, check out the [v2 branch](https://github.com/nuxt/ui/tree/v2) for Nuxt UI v2.
|
> 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
|
## Documentation
|
||||||
|
|
||||||
Visit https://ui.nuxt.com to explore the documentation.
|
Visit https://ui.nuxt.com to explore the documentation.
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export default defineBuildConfig({
|
|||||||
delimiters: ['', ''],
|
delimiters: ['', ''],
|
||||||
values: {
|
values: {
|
||||||
// Used in development to import directly from theme
|
// Used in development to import directly from theme
|
||||||
'const isUiDev = true': 'const isUiDev = false'
|
'process.argv.includes(\'--uiDev\')': 'false'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.${camelName}
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<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 />
|
<slot />
|
||||||
</Primitive>
|
</Primitive>
|
||||||
</template>
|
</template>
|
||||||
@@ -109,7 +109,7 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.${camelName}
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<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>
|
</template>
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ watch(framework, () => {
|
|||||||
color="neutral"
|
color="neutral"
|
||||||
:ui="{
|
:ui="{
|
||||||
indicator: 'bg-default',
|
indicator: 'bg-default',
|
||||||
trigger: 'px-1 data-[state=active]:text-highlighted'
|
trigger: 'px-1 data-[state=active]:text-highlighted w-full'
|
||||||
}"
|
}"
|
||||||
size="xs"
|
size="xs"
|
||||||
@update:model-value="(framework = $event as string)"
|
@update:model-value="(framework = $event as string)"
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ watch(module, () => {
|
|||||||
color="neutral"
|
color="neutral"
|
||||||
:ui="{
|
:ui="{
|
||||||
indicator: 'bg-default',
|
indicator: 'bg-default',
|
||||||
trigger: 'px-1 data-[state=active]:text-highlighted'
|
trigger: 'px-1 data-[state=active]:text-highlighted w-full'
|
||||||
}"
|
}"
|
||||||
size="xs"
|
size="xs"
|
||||||
@update:model-value="(module = $event as string)"
|
@update:model-value="(module = $event as string)"
|
||||||
|
|||||||
@@ -153,7 +153,8 @@ const options = computed(() => {
|
|||||||
const items = propItems.length
|
const items = propItems.length
|
||||||
? propItems.map((item: any) => ({
|
? propItems.map((item: any) => ({
|
||||||
value: item,
|
value: item,
|
||||||
label: String(item)
|
label: String(item),
|
||||||
|
chip: key.toLowerCase().endsWith('color') ? { color: item } : undefined
|
||||||
}))
|
}))
|
||||||
: prop?.type === 'boolean' || prop?.type === 'boolean | undefined'
|
: prop?.type === 'boolean' || prop?.type === 'boolean | undefined'
|
||||||
? [{ value: true, label: 'true' }, { value: false, label: 'false' }]
|
? [{ value: true, label: 'true' }, { value: false, label: 'false' }]
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { CalendarDate } from '@internationalized/date'
|
||||||
|
|
||||||
|
const date = shallowRef(new CalendarDate(2025, 4, 2))
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="flex flex-col gap-4">
|
||||||
|
<UCalendar v-model="date" :month-controls="false" :year-controls="false" />
|
||||||
|
|
||||||
|
<div class="flex justify-between gap-4">
|
||||||
|
<UButton color="neutral" variant="outline" @click="date = date.subtract({ months: 1 })">
|
||||||
|
Prev
|
||||||
|
</UButton>
|
||||||
|
|
||||||
|
<UButton color="neutral" variant="outline" @click="date = date.add({ months: 1 })">
|
||||||
|
Next
|
||||||
|
</UButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
const items = [
|
||||||
|
'https://picsum.photos/640/640?random=1',
|
||||||
|
'https://picsum.photos/640/640?random=2',
|
||||||
|
'https://picsum.photos/640/640?random=3',
|
||||||
|
'https://picsum.photos/640/640?random=4',
|
||||||
|
'https://picsum.photos/640/640?random=5',
|
||||||
|
'https://picsum.photos/640/640?random=6'
|
||||||
|
]
|
||||||
|
|
||||||
|
const carousel = useTemplateRef('carousel')
|
||||||
|
const activeIndex = ref(0)
|
||||||
|
|
||||||
|
function onClickPrev() {
|
||||||
|
activeIndex.value--
|
||||||
|
}
|
||||||
|
function onClickNext() {
|
||||||
|
activeIndex.value++
|
||||||
|
}
|
||||||
|
function onSelect(index: number) {
|
||||||
|
activeIndex.value = index
|
||||||
|
}
|
||||||
|
|
||||||
|
function select(index: number) {
|
||||||
|
activeIndex.value = index
|
||||||
|
|
||||||
|
carousel.value?.emblaApi?.scrollTo(index)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="flex-1 w-full">
|
||||||
|
<UCarousel
|
||||||
|
ref="carousel"
|
||||||
|
v-slot="{ item }"
|
||||||
|
arrows
|
||||||
|
:items="items"
|
||||||
|
: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>
|
||||||
|
|
||||||
|
<div class="flex gap-1 justify-between pt-4 max-w-xs mx-auto">
|
||||||
|
<div
|
||||||
|
v-for="(item, index) in items"
|
||||||
|
:key="index"
|
||||||
|
class="size-11 opacity-25 hover:opacity-100 transition-opacity"
|
||||||
|
:class="{ 'opacity-100': activeIndex === index }"
|
||||||
|
@click="select(index)"
|
||||||
|
>
|
||||||
|
<img :src="item" width="44" height="44" class="rounded-lg">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { refDebounced } from '@vueuse/core'
|
||||||
|
|
||||||
const searchTerm = ref('')
|
const searchTerm = ref('')
|
||||||
const searchTermDebounced = refDebounced(searchTerm, 200)
|
const searchTermDebounced = refDebounced(searchTerm, 200)
|
||||||
|
|
||||||
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
|
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
|
||||||
key: 'command-palette-users',
|
|
||||||
params: { q: searchTermDebounced },
|
params: { q: searchTermDebounced },
|
||||||
transform: (data: { id: number, name: string, email: string }[]) => {
|
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}` } })) || []
|
return data?.map(user => ({ id: user.id, label: user.name, suffix: user.email, avatar: { src: `https://i.pravatar.cc/120?img=${user.id}` } })) || []
|
||||||
|
|||||||
@@ -30,6 +30,9 @@ const schema = z.object({
|
|||||||
radioGroup: z.string().refine(value => value === 'option-2', {
|
radioGroup: z.string().refine(value => value === 'option-2', {
|
||||||
message: 'Select Option 2'
|
message: 'Select Option 2'
|
||||||
}),
|
}),
|
||||||
|
checkboxGroup: z.any().refine(values => !!values?.find((option: any) => option === 'option-2'), {
|
||||||
|
message: 'Include Option 2'
|
||||||
|
}),
|
||||||
slider: z.number().max(20, { message: 'Must be less than 20' }),
|
slider: z.number().max(20, { message: 'Must be less than 20' }),
|
||||||
pin: z.string().regex(/^\d$/).array().length(5)
|
pin: z.string().regex(/^\d$/).array().length(5)
|
||||||
})
|
})
|
||||||
@@ -101,11 +104,14 @@ async function onSubmit(event: FormSubmitEvent<Schema>) {
|
|||||||
<UFormField label="Textarea" name="textarea">
|
<UFormField label="Textarea" name="textarea">
|
||||||
<UTextarea v-model="state.textarea" class="w-full" />
|
<UTextarea v-model="state.textarea" class="w-full" />
|
||||||
</UFormField>
|
</UFormField>
|
||||||
|
<div class="flex gap-4">
|
||||||
<UFormField name="radioGroup">
|
<UFormField name="radioGroup">
|
||||||
<URadioGroup v-model="state.radioGroup" legend="Radio group" :items="items" />
|
<URadioGroup v-model="state.radioGroup" legend="Radio group" :items="items" />
|
||||||
</UFormField>
|
</UFormField>
|
||||||
|
<UFormField name="checkboxGroup">
|
||||||
|
<UCheckboxGroup v-model="state.checkboxGroup" legend="Checkbox group" :items="items" />
|
||||||
|
</UFormField>
|
||||||
|
</div>
|
||||||
<UFormField name="pin" label="Pin Input" :error-pattern="/(pin)\..*/">
|
<UFormField name="pin" label="Pin Input" :error-pattern="/(pin)\..*/">
|
||||||
<UPinInput v-model="state.pin" />
|
<UPinInput v-model="state.pin" />
|
||||||
</UFormField>
|
</UFormField>
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { refDebounced } from '@vueuse/core'
|
||||||
import type { AvatarProps } from '@nuxt/ui'
|
import type { AvatarProps } from '@nuxt/ui'
|
||||||
|
|
||||||
const searchTerm = ref('')
|
const searchTerm = ref('')
|
||||||
const searchTermDebounced = refDebounced(searchTerm, 200)
|
const searchTermDebounced = refDebounced(searchTerm, 200)
|
||||||
|
|
||||||
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
|
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
|
||||||
key: 'typicode-users',
|
|
||||||
params: { q: searchTermDebounced },
|
params: { q: searchTermDebounced },
|
||||||
transform: (data: { id: number, name: string }[]) => {
|
transform: (data: { id: number, name: string }[]) => {
|
||||||
return data?.map(user => ({
|
return data?.map(user => ({
|
||||||
|
|||||||
@@ -13,7 +13,9 @@ const modal = overlay.create(LazyModalExample, {
|
|||||||
})
|
})
|
||||||
|
|
||||||
async function open() {
|
async function open() {
|
||||||
const shouldIncrement = await modal.open()
|
const instance = modal.open()
|
||||||
|
|
||||||
|
const shouldIncrement = await instance.result
|
||||||
|
|
||||||
if (shouldIncrement) {
|
if (shouldIncrement) {
|
||||||
count.value++
|
count.value++
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ const items = [
|
|||||||
class="w-full justify-center"
|
class="w-full justify-center"
|
||||||
:ui="{
|
:ui="{
|
||||||
viewport: 'sm:w-(--reka-navigation-menu-viewport-width)',
|
viewport: 'sm:w-(--reka-navigation-menu-viewport-width)',
|
||||||
|
content: 'sm:w-auto',
|
||||||
childList: 'sm:w-96',
|
childList: 'sm:w-96',
|
||||||
childLinkDescription: 'text-balance line-clamp-2'
|
childLinkDescription: 'text-balance line-clamp-2'
|
||||||
}"
|
}"
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { refDebounced } from '@vueuse/core'
|
||||||
import type { AvatarProps } from '@nuxt/ui'
|
import type { AvatarProps } from '@nuxt/ui'
|
||||||
|
|
||||||
const searchTerm = ref('')
|
const searchTerm = ref('')
|
||||||
const searchTermDebounced = refDebounced(searchTerm, 200)
|
const searchTermDebounced = refDebounced(searchTerm, 200)
|
||||||
|
|
||||||
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
|
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
|
||||||
key: 'typicode-users',
|
|
||||||
params: { q: searchTermDebounced },
|
params: { q: searchTermDebounced },
|
||||||
transform: (data: { id: number, name: string }[]) => {
|
transform: (data: { id: number, name: string }[]) => {
|
||||||
return data?.map(user => ({
|
return data?.map(user => ({
|
||||||
|
|||||||
@@ -13,7 +13,9 @@ const slideover = overlay.create(LazySlideoverExample, {
|
|||||||
})
|
})
|
||||||
|
|
||||||
async function open() {
|
async function open() {
|
||||||
const shouldIncrement = await slideover.open()
|
const instance = slideover.open()
|
||||||
|
|
||||||
|
const shouldIncrement = await instance.result
|
||||||
|
|
||||||
if (shouldIncrement) {
|
if (shouldIncrement) {
|
||||||
count.value++
|
count.value++
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { TableColumn } from '@nuxt/ui'
|
||||||
|
import { useSortable } from '@vueuse/integrations/useSortable.mjs'
|
||||||
|
|
||||||
|
type Payment = {
|
||||||
|
id: string
|
||||||
|
date: string
|
||||||
|
email: string
|
||||||
|
amount: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = ref<Payment[]>([{
|
||||||
|
id: '4600',
|
||||||
|
date: '2024-03-11T15:30:00',
|
||||||
|
email: 'james.anderson@example.com',
|
||||||
|
amount: 594
|
||||||
|
}, {
|
||||||
|
id: '4599',
|
||||||
|
date: '2024-03-11T10:10:00',
|
||||||
|
email: 'mia.white@example.com',
|
||||||
|
amount: 276
|
||||||
|
}, {
|
||||||
|
id: '4598',
|
||||||
|
date: '2024-03-11T08:50:00',
|
||||||
|
email: 'william.brown@example.com',
|
||||||
|
amount: 315
|
||||||
|
}, {
|
||||||
|
id: '4597',
|
||||||
|
date: '2024-03-10T19:45:00',
|
||||||
|
email: 'emma.davis@example.com',
|
||||||
|
amount: 529
|
||||||
|
}])
|
||||||
|
|
||||||
|
const columns: TableColumn<Payment>[] = [{
|
||||||
|
accessorKey: 'id',
|
||||||
|
header: '#',
|
||||||
|
cell: ({ row }) => `#${row.getValue('id')}`
|
||||||
|
}, {
|
||||||
|
accessorKey: 'date',
|
||||||
|
header: 'Date',
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return new Date(row.getValue('date')).toLocaleString('en-US', {
|
||||||
|
day: 'numeric',
|
||||||
|
month: 'short',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
hour12: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
accessorKey: 'email',
|
||||||
|
header: 'Email'
|
||||||
|
}, {
|
||||||
|
accessorKey: 'amount',
|
||||||
|
header: () => h('div', { class: 'text-right' }, 'Amount'),
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const amount = Number.parseFloat(row.getValue('amount'))
|
||||||
|
const formatted = new Intl.NumberFormat('en-US', {
|
||||||
|
style: 'currency',
|
||||||
|
currency: 'EUR'
|
||||||
|
}).format(amount)
|
||||||
|
return h('div', { class: 'text-right font-medium' }, formatted)
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
|
||||||
|
useSortable('.my-table-tbody', data, {
|
||||||
|
animation: 150
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="w-full">
|
||||||
|
<UTable
|
||||||
|
ref="table"
|
||||||
|
:data="data"
|
||||||
|
:columns="columns"
|
||||||
|
:ui="{
|
||||||
|
tbody: 'my-table-tbody'
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -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>
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { TableColumn } from '@nuxt/ui'
|
||||||
|
import { useInfiniteScroll } from '@vueuse/core'
|
||||||
|
|
||||||
|
const UAvatar = resolveComponent('UAvatar')
|
||||||
|
|
||||||
|
type User = {
|
||||||
|
id: number
|
||||||
|
firstName: string
|
||||||
|
username: string
|
||||||
|
email: string
|
||||||
|
image: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserResponse = {
|
||||||
|
users: User[]
|
||||||
|
total: number
|
||||||
|
skip: number
|
||||||
|
limit: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const skip = ref(0)
|
||||||
|
|
||||||
|
const { data, status, execute } = await useFetch('https://dummyjson.com/users?limit=10&select=firstName,username,email,image', {
|
||||||
|
key: 'table-users-infinite-scroll',
|
||||||
|
params: { skip },
|
||||||
|
transform: (data?: UserResponse) => {
|
||||||
|
return data?.users
|
||||||
|
},
|
||||||
|
lazy: true,
|
||||||
|
immediate: false
|
||||||
|
})
|
||||||
|
|
||||||
|
const columns: TableColumn<User>[] = [{
|
||||||
|
accessorKey: 'id',
|
||||||
|
header: 'ID'
|
||||||
|
}, {
|
||||||
|
accessorKey: 'image',
|
||||||
|
header: 'Avatar',
|
||||||
|
cell: ({ row }) => h(UAvatar, { src: row.original.image })
|
||||||
|
}, {
|
||||||
|
accessorKey: 'firstName',
|
||||||
|
header: 'First name'
|
||||||
|
}, {
|
||||||
|
accessorKey: 'email',
|
||||||
|
header: 'Email'
|
||||||
|
}, {
|
||||||
|
accessorKey: 'username',
|
||||||
|
header: 'Username'
|
||||||
|
}]
|
||||||
|
|
||||||
|
const users = ref<User[]>([])
|
||||||
|
|
||||||
|
watch(data, () => {
|
||||||
|
users.value = [
|
||||||
|
...users.value,
|
||||||
|
...(data.value || [])
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
execute()
|
||||||
|
|
||||||
|
const table = useTemplateRef<ComponentPublicInstance>('table')
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
useInfiniteScroll(table.value?.$el, () => {
|
||||||
|
skip.value += 10
|
||||||
|
}, {
|
||||||
|
distance: 200,
|
||||||
|
canLoadMore: () => {
|
||||||
|
return status.value !== 'pending'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="w-full">
|
||||||
|
<UTable
|
||||||
|
ref="table"
|
||||||
|
:data="users"
|
||||||
|
:columns="columns"
|
||||||
|
:loading="status === 'pending'"
|
||||||
|
sticky
|
||||||
|
class="flex-1 h-80"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -26,7 +26,7 @@ const state = reactive({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<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 }">
|
<template #account="{ item }">
|
||||||
<p class="text-muted mb-4">
|
<p class="text-muted mb-4">
|
||||||
{{ item.description }}
|
{{ item.description }}
|
||||||
|
|||||||
@@ -1,22 +1,32 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { TabsItem } from '@nuxt/ui'
|
import type { TabsItem } from '@nuxt/ui'
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
const items: TabsItem[] = [
|
const items: TabsItem[] = [
|
||||||
{
|
{
|
||||||
label: 'Account'
|
label: 'Account',
|
||||||
|
value: 'account'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Password'
|
label: 'Password',
|
||||||
|
value: 'password'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
const active = ref('0')
|
const active = computed({
|
||||||
|
get() {
|
||||||
// Note: This is for demonstration purposes only. Don't do this at home.
|
return (route.query.tab as string) || 'account'
|
||||||
onMounted(() => {
|
},
|
||||||
setInterval(() => {
|
set(tab) {
|
||||||
active.value = String((Number(active.value) + 1) % items.length)
|
// Hash is specified here to prevent the page from scrolling to the top
|
||||||
}, 2000)
|
router.push({
|
||||||
|
path: '/components/tabs',
|
||||||
|
query: { tab },
|
||||||
|
hash: '#control-active-item'
|
||||||
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ export function useLinks() {
|
|||||||
label: 'Raycast Extension',
|
label: 'Raycast Extension',
|
||||||
description: 'Access Nuxt UI components without leaving your editor.',
|
description: 'Access Nuxt UI components without leaving your editor.',
|
||||||
icon: 'i-simple-icons-raycast',
|
icon: 'i-simple-icons-raycast',
|
||||||
to: 'https://www.raycast.com/HugoRCD/nuxt-ui',
|
to: 'https://www.raycast.com/HugoRCD/nuxt',
|
||||||
target: '_blank'
|
target: '_blank'
|
||||||
}, {
|
}, {
|
||||||
label: 'Figma to Code',
|
label: 'Figma to Code',
|
||||||
|
|||||||
@@ -239,7 +239,7 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
|
|||||||
<li>
|
<li>
|
||||||
<NuxtLink to="https://github.com/nuxt/ui/graphs/contributors" target="_blank" class="min-w-0">
|
<NuxtLink to="https://github.com/nuxt/ui/graphs/contributors" target="_blank" class="min-w-0">
|
||||||
<p class="text-4xl font-semibold text-highlighted truncate">
|
<p class="text-4xl font-semibold text-highlighted truncate">
|
||||||
175+
|
200+
|
||||||
</p>
|
</p>
|
||||||
<p class="text-muted text-sm truncate">Contributors</p>
|
<p class="text-muted text-sm truncate">Contributors</p>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
|
|||||||
@@ -16,6 +16,32 @@ links:
|
|||||||
variant: outline
|
variant: outline
|
||||||
trailingIcon: i-lucide-arrow-right
|
trailingIcon: i-lucide-arrow-right
|
||||||
templates:
|
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'
|
- 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)."
|
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
|
icon: i-lucide-message-circle
|
||||||
|
|||||||
@@ -48,13 +48,14 @@ const icons = {
|
|||||||
</UPageHero>
|
</UPageHero>
|
||||||
|
|
||||||
<UPageSection :ui="{ container: '!pt-0' }">
|
<UPageSection :ui="{ container: '!pt-0' }">
|
||||||
<UPageGrid class="xl:grid-cols-4">
|
<UPageGrid class="xl:grid-cols-5">
|
||||||
<UPageCard
|
<UPageCard
|
||||||
v-for="(user, index) in module?.team"
|
v-for="(user, index) in module?.team"
|
||||||
:key="index"
|
:key="index"
|
||||||
:title="user.name"
|
:title="user.name"
|
||||||
:description="[user.pronouns, user.location].filter(Boolean).join(' ・ ')"
|
:description="[user.pronouns, user.location].filter(Boolean).join(' ・ ')"
|
||||||
:ui="{
|
:ui="{
|
||||||
|
wrapper: 'items-center',
|
||||||
container: 'gap-y-4 lg:p-8',
|
container: 'gap-y-4 lg:p-8',
|
||||||
leading: 'flex justify-center',
|
leading: 'flex justify-center',
|
||||||
title: 'text-center',
|
title: 'text-center',
|
||||||
@@ -123,6 +124,7 @@ const icons = {
|
|||||||
:key="contributor.username"
|
:key="contributor.username"
|
||||||
:title="contributor.username"
|
:title="contributor.username"
|
||||||
:ui="{
|
:ui="{
|
||||||
|
wrapper: 'items-center',
|
||||||
container: 'gap-y-2',
|
container: 'gap-y-2',
|
||||||
leading: 'flex justify-center',
|
leading: 'flex justify-center',
|
||||||
title: 'text-center',
|
title: 'text-center',
|
||||||
|
|||||||
@@ -99,6 +99,10 @@ app.use(ui)
|
|||||||
app.mount('#app')
|
app.mount('#app')
|
||||||
```
|
```
|
||||||
|
|
||||||
|
::note{to="#inertia"}
|
||||||
|
If you're using [Inertia.js](https://inertiajs.com/), you can skip the `vue-router` setup as Inertia provides its own routing system.
|
||||||
|
::
|
||||||
|
|
||||||
#### Import Tailwind CSS and Nuxt UI in your CSS
|
#### Import Tailwind CSS and Nuxt UI in your CSS
|
||||||
|
|
||||||
```css [assets/main.css]
|
```css [assets/main.css]
|
||||||
@@ -313,6 +317,29 @@ export default defineConfig({
|
|||||||
This option adds the `transition-colors` class on components with hover or active states.
|
This option adds the `transition-colors` class on components with hover or active states.
|
||||||
::
|
::
|
||||||
|
|
||||||
|
### `inertia`
|
||||||
|
|
||||||
|
Use the `inertia` option to enable compatibility with [Inertia.js](https://inertiajs.com/).
|
||||||
|
|
||||||
|
```ts [vite.config.ts]
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
import ui from '@nuxt/ui/vite'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
vue(),
|
||||||
|
ui({
|
||||||
|
inertia: true
|
||||||
|
})
|
||||||
|
]
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
::note
|
||||||
|
When using this option, `vue-router` is not required as Inertia.js provides its own routing system. The components that would normally use `RouterLink` will automatically use Inertia's `InertiaLink` component instead.
|
||||||
|
::
|
||||||
|
|
||||||
## Continuous Releases
|
## Continuous Releases
|
||||||
|
|
||||||
Nuxt UI uses [pkg.pr.new](https://github.com/stackblitz-labs/pkg.pr.new) for continuous preview releases, providing developers with instant access to the latest features and bug fixes without waiting for official releases.
|
Nuxt UI uses [pkg.pr.new](https://github.com/stackblitz-labs/pkg.pr.new) for continuous preview releases, providing developers with instant access to the latest features and bug fixes without waiting for official releases.
|
||||||
|
|||||||
@@ -504,7 +504,7 @@ const count = ref(0)
|
|||||||
</script>
|
</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
|
```diff
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@@ -523,10 +523,12 @@ import { ModalExampleComponent } from '#components'
|
|||||||
- })
|
- })
|
||||||
- }
|
- }
|
||||||
+ async function openModal() {
|
+ async function openModal() {
|
||||||
+ const result = await modal.open(ModalExampleComponent, {
|
+ const instance = modal.open(ModalExampleComponent, {
|
||||||
+ count: count.value
|
+ count: count.value
|
||||||
+ })
|
+ })
|
||||||
+
|
+
|
||||||
|
+ const result = await instance.result
|
||||||
|
+
|
||||||
+ if (result) {
|
+ if (result) {
|
||||||
+ toast.add({ title: 'Success!' })
|
+ toast.add({ title: 'Success!' })
|
||||||
+ }
|
+ }
|
||||||
|
|||||||
@@ -727,15 +727,22 @@ This is how the `@theme` is generated for each design token:
|
|||||||
--border-color-muted: var(--ui-border-muted);
|
--border-color-muted: var(--ui-border-muted);
|
||||||
--border-color-accented: var(--ui-border-accented);
|
--border-color-accented: var(--ui-border-accented);
|
||||||
--border-color-inverted: var(--ui-border-inverted);
|
--border-color-inverted: var(--ui-border-inverted);
|
||||||
|
--border-color-bg: var(--ui-bg);
|
||||||
--ring-color-default: var(--ui-border);
|
--ring-color-default: var(--ui-border);
|
||||||
--ring-color-muted: var(--ui-border-muted);
|
--ring-color-muted: var(--ui-border-muted);
|
||||||
--ring-color-accented: var(--ui-border-accented);
|
--ring-color-accented: var(--ui-border-accented);
|
||||||
--ring-color-inverted: var(--ui-border-inverted);
|
--ring-color-inverted: var(--ui-border-inverted);
|
||||||
--ring-color-bg: var(--ui-bg);
|
--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-default: var(--ui-border);
|
||||||
--divide-color-muted: var(--ui-border-muted);
|
--divide-color-muted: var(--ui-border-muted);
|
||||||
--divide-color-accented: var(--ui-border-accented);
|
--divide-color-accented: var(--ui-border-accented);
|
||||||
--divide-color-inverted: var(--ui-border-inverted);
|
--divide-color-inverted: var(--ui-border-inverted);
|
||||||
|
--divide-color-bg: var(--ui-bg);
|
||||||
--outline-color-default: var(--ui-border);
|
--outline-color-default: var(--ui-border);
|
||||||
--outline-color-inverted: var(--ui-border-inverted);
|
--outline-color-inverted: var(--ui-border-inverted);
|
||||||
--stroke-color-default: var(--ui-border);
|
--stroke-color-default: var(--ui-border);
|
||||||
@@ -966,7 +973,7 @@ export default {
|
|||||||
|
|
||||||
```vue [src/runtime/components/Card.vue]
|
```vue [src/runtime/components/Card.vue]
|
||||||
<template>
|
<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 })">
|
<div :class="ui.header({ class: props.ui?.header })">
|
||||||
<slot name="header" />
|
<slot name="header" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ Here's an overview of the key directories and files in the Nuxt UI project struc
|
|||||||
|
|
||||||
### Documentation
|
### 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
|
```bash
|
||||||
├── app/
|
├── app/
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ defineShortcuts({
|
|||||||
</script>
|
</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.
|
- 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.
|
- 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
|
#### 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
|
- `ctrl`: Represents `Ctrl` on all platforms
|
||||||
- `shift`: Used for alphabetic keys when Shift is required
|
- `shift`: Used for alphabetic keys when Shift is required
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
- The `useOverlay` composable is created using `createSharedComposable`, ensuring that the same overlay state is shared across your entire application.
|
||||||
|
|
||||||
::note
|
::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
|
## API
|
||||||
|
|
||||||
### `create(component: T, options: OverlayOptions): OverlayInstance`
|
### `create(component: T, options: OverlayOptions): OverlayInstance`
|
||||||
|
|
||||||
Creates an overlay, and returns its instance
|
Creates an overlay, and returns a factory instance
|
||||||
|
|
||||||
- Parameters:
|
- Parameters:
|
||||||
- `component`: The overlay component
|
- `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.
|
- `props?: ComponentProps`: An optional object of props to pass to the rendered component.
|
||||||
- `destroyOnClose?: boolean` Removes the overlay from memory when closed `default: false`
|
- `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`
|
Opens the overlay using its `id`
|
||||||
|
|
||||||
@@ -62,10 +62,17 @@ Update an overlay using its `id`
|
|||||||
- `id`: The identifier of the overlay
|
- `id`: The identifier of the overlay
|
||||||
- `props`: An object of props to update on the rendered component.
|
- `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`
|
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:
|
- Parameters:
|
||||||
- `id`: The identifier of the overlay
|
- `id`: The identifier of the overlay
|
||||||
|
|
||||||
@@ -75,7 +82,7 @@ In-memory list of overlays that were created
|
|||||||
|
|
||||||
## Overlay Instance API
|
## Overlay Instance API
|
||||||
|
|
||||||
### `open(props?: ComponentProps<T>): Promise<any>`
|
### `open(props?: ComponentProps<T>): Promise<OpenedOverlay<T>>`
|
||||||
|
|
||||||
Opens the overlay
|
Opens the overlay
|
||||||
|
|
||||||
@@ -138,7 +145,7 @@ const overlay = useOverlay()
|
|||||||
|
|
||||||
// Create with default props
|
// Create with default props
|
||||||
const modalA = overlay.create(ModalA, { title: 'Welcome' })
|
const modalA = overlay.create(ModalA, { title: 'Welcome' })
|
||||||
const modalB = overlay.create(modalB)
|
const modalB = overlay.create(ModalB)
|
||||||
|
|
||||||
const slideoverA = overlay.create(SlideoverA)
|
const slideoverA = overlay.create(SlideoverA)
|
||||||
|
|
||||||
@@ -149,7 +156,9 @@ const openModalA = () => {
|
|||||||
|
|
||||||
const openModalB = async () => {
|
const openModalB = async () => {
|
||||||
// Open modalB, and wait for its result
|
// 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.
|
// Pass the result from modalB to the slideover, and open it.
|
||||||
slideoverA.open({ input })
|
slideoverA.open({ input })
|
||||||
|
|||||||
@@ -247,6 +247,16 @@ You can also pass the `value` of one of the items if provided.
|
|||||||
When `type="multiple"`, ensure to pass an array to the `default-value` prop or the `v-model` directive.
|
When `type="multiple"`, ensure to pass an array to the `default-value` prop or the `v-model` directive.
|
||||||
::
|
::
|
||||||
|
|
||||||
|
### With drag and drop
|
||||||
|
|
||||||
|
Use the [`useSortable`](https://vueuse.org/integrations/useSortable/) composable from [`@vueuse/integrations`](https://vueuse.org/integrations/README.html) to enable drag and drop functionality on the Accordion. This integration wraps [Sortable.js](https://sortablejs.github.io/Sortable/) to provide a seamless drag and drop experience.
|
||||||
|
|
||||||
|
::component-example
|
||||||
|
---
|
||||||
|
name: 'accordion-drag-and-drop-example'
|
||||||
|
---
|
||||||
|
::
|
||||||
|
|
||||||
### With body slot
|
### With body slot
|
||||||
|
|
||||||
Use the `#body` slot to customize the body of each item.
|
Use the `#body` slot to customize the body of each item.
|
||||||
@@ -292,18 +302,6 @@ props:
|
|||||||
---
|
---
|
||||||
::
|
::
|
||||||
|
|
||||||
### With drag and drop
|
|
||||||
|
|
||||||
Use the [`useSortable`](https://vueuse.org/integrations/useSortable/) composable from [`@vueuse/integrations`](https://vueuse.org/integrations/README.html) to enable drag and drop functionality on the accordion. This integration wraps [Sortable.js](https://sortablejs.github.io/Sortable/) to provide a seamless drag and drop experience.
|
|
||||||
|
|
||||||
The `useSortable` composable accepts various options, see the [Usage](https://vueuse.org/integrations/useSortable/#usage) for more examples.
|
|
||||||
|
|
||||||
::component-example
|
|
||||||
---
|
|
||||||
name: 'accordion-drag-and-drop-example'
|
|
||||||
---
|
|
||||||
::
|
|
||||||
|
|
||||||
## API
|
## API
|
||||||
|
|
||||||
### Props
|
### Props
|
||||||
|
|||||||
@@ -223,6 +223,16 @@ name: 'calendar-other-system-example'
|
|||||||
You can check all the available calendars on `@internationalized/date` docs.
|
You can check all the available calendars on `@internationalized/date` docs.
|
||||||
::
|
::
|
||||||
|
|
||||||
|
### With external controls
|
||||||
|
|
||||||
|
You can control the calendar with external controls by manipulating the date passed in the `v-model`.
|
||||||
|
|
||||||
|
::component-example
|
||||||
|
---
|
||||||
|
name: 'calendar-external-controls-example'
|
||||||
|
---
|
||||||
|
::
|
||||||
|
|
||||||
### As a DatePicker
|
### As a DatePicker
|
||||||
|
|
||||||
Use a [Button](/components/button) and a [Popover](/components/popover) component to create a date picker.
|
Use a [Button](/components/button) and a [Popover](/components/popover) component to create a date picker.
|
||||||
|
|||||||
@@ -229,6 +229,19 @@ class: 'p-8 px-16'
|
|||||||
---
|
---
|
||||||
::
|
::
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### With thumbnails
|
||||||
|
|
||||||
|
You can use the [`emblaApi`](#expose) function [scrollTo](https://www.embla-carousel.com/api/methods/#scrollto) to display thumbnails under the carousel that allows you to navigate to a specific slide.
|
||||||
|
|
||||||
|
::component-example
|
||||||
|
---
|
||||||
|
name: 'carousel-thumbnails-example'
|
||||||
|
class: 'p-8 px-16'
|
||||||
|
---
|
||||||
|
::
|
||||||
|
|
||||||
## API
|
## API
|
||||||
|
|
||||||
### Props
|
### Props
|
||||||
@@ -239,6 +252,10 @@ class: 'p-8 px-16'
|
|||||||
|
|
||||||
:component-slots
|
:component-slots
|
||||||
|
|
||||||
|
### Emits
|
||||||
|
|
||||||
|
:component-emits
|
||||||
|
|
||||||
### Expose
|
### Expose
|
||||||
|
|
||||||
You can access the typed component instance using [`useTemplateRef`](https://vuejs.org/api/composition-api-helpers.html#usetemplateref).
|
You can access the typed component instance using [`useTemplateRef`](https://vuejs.org/api/composition-api-helpers.html#usetemplateref).
|
||||||
|
|||||||
349
docs/content/3.components/checkbox-group.md
Normal file
349
docs/content/3.components/checkbox-group.md
Normal file
@@ -0,0 +1,349 @@
|
|||||||
|
---
|
||||||
|
title: CheckboxGroup
|
||||||
|
description: A set of checklist buttons to select multiple option from a list.
|
||||||
|
category: form
|
||||||
|
links:
|
||||||
|
- label: CheckboxGroup
|
||||||
|
icon: i-custom-reka-ui
|
||||||
|
to: https://reka-ui.com/docs/components/checkbox#group-root
|
||||||
|
- label: GitHub
|
||||||
|
icon: i-simple-icons-github
|
||||||
|
to: https://github.com/nuxt/ui/tree/v3/src/runtime/components/CheckboxGroup.vue
|
||||||
|
navigation.badge: New
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Use the `v-model` directive to control the value of the CheckboxGroup or the `default-value` prop to set the initial value when you do not need to control its state.
|
||||||
|
|
||||||
|
### Items
|
||||||
|
|
||||||
|
Use the `items` prop as an array of strings or numbers:
|
||||||
|
|
||||||
|
::component-code
|
||||||
|
---
|
||||||
|
prettier: true
|
||||||
|
ignore:
|
||||||
|
- modelValue
|
||||||
|
- items
|
||||||
|
external:
|
||||||
|
- items
|
||||||
|
- modelValue
|
||||||
|
externalTypes:
|
||||||
|
- CheckboxGroupItem[]
|
||||||
|
- CheckboxGroupValue[]
|
||||||
|
props:
|
||||||
|
modelValue:
|
||||||
|
- 'System'
|
||||||
|
items:
|
||||||
|
- 'System'
|
||||||
|
- 'Light'
|
||||||
|
- 'Dark'
|
||||||
|
---
|
||||||
|
::
|
||||||
|
|
||||||
|
You can also pass an array of objects with the following properties:
|
||||||
|
|
||||||
|
- `label?: string`{lang="ts-type"}
|
||||||
|
- `description?: string`{lang="ts-type"}
|
||||||
|
- [`value?: string`{lang="ts-type"}](#value-key)
|
||||||
|
- `disabled?: boolean`{lang="ts-type"}
|
||||||
|
|
||||||
|
::component-code
|
||||||
|
---
|
||||||
|
ignore:
|
||||||
|
- modelValue
|
||||||
|
- items
|
||||||
|
external:
|
||||||
|
- items
|
||||||
|
- modelValue
|
||||||
|
externalTypes:
|
||||||
|
- CheckboxGroupItem[]
|
||||||
|
- CheckboxGroupValue[]
|
||||||
|
props:
|
||||||
|
modelValue:
|
||||||
|
- 'system'
|
||||||
|
items:
|
||||||
|
- label: 'System'
|
||||||
|
description: 'This is the first option.'
|
||||||
|
value: 'system'
|
||||||
|
- label: 'Light'
|
||||||
|
description: 'This is the second option.'
|
||||||
|
value: 'light'
|
||||||
|
- label: 'Dark'
|
||||||
|
description: 'This is the third option.'
|
||||||
|
value: 'dark'
|
||||||
|
---
|
||||||
|
::
|
||||||
|
|
||||||
|
::caution
|
||||||
|
When using objects, you need to reference the `value` property of the object in the `v-model` directive or the `default-value` prop.
|
||||||
|
::
|
||||||
|
|
||||||
|
### Value Key
|
||||||
|
|
||||||
|
You can change the property that is used to set the value by using the `value-key` prop. Defaults to `value`.
|
||||||
|
|
||||||
|
::component-code
|
||||||
|
---
|
||||||
|
ignore:
|
||||||
|
- modelValue
|
||||||
|
- items
|
||||||
|
- valueKey
|
||||||
|
external:
|
||||||
|
- items
|
||||||
|
- modelValue
|
||||||
|
externalTypes:
|
||||||
|
- CheckboxGroupItem[]
|
||||||
|
- CheckboxGroupValue[]
|
||||||
|
props:
|
||||||
|
modelValue:
|
||||||
|
- 'light'
|
||||||
|
valueKey: 'id'
|
||||||
|
items:
|
||||||
|
- label: 'System'
|
||||||
|
description: 'This is the first option.'
|
||||||
|
id: 'system'
|
||||||
|
- label: 'Light'
|
||||||
|
description: 'This is the second option.'
|
||||||
|
id: 'light'
|
||||||
|
- label: 'Dark'
|
||||||
|
description: 'This is the third option.'
|
||||||
|
id: 'dark'
|
||||||
|
---
|
||||||
|
::
|
||||||
|
|
||||||
|
### Legend
|
||||||
|
|
||||||
|
Use the `legend` prop to set the legend of the CheckboxGroup.
|
||||||
|
|
||||||
|
::component-code
|
||||||
|
---
|
||||||
|
prettier: true
|
||||||
|
ignore:
|
||||||
|
- defaultValue
|
||||||
|
- items
|
||||||
|
external:
|
||||||
|
- items
|
||||||
|
externalTypes:
|
||||||
|
- CheckboxGroupItem[]
|
||||||
|
props:
|
||||||
|
legend: 'Theme'
|
||||||
|
defaultValue:
|
||||||
|
- 'System'
|
||||||
|
items:
|
||||||
|
- 'System'
|
||||||
|
- 'Light'
|
||||||
|
- 'Dark'
|
||||||
|
---
|
||||||
|
::
|
||||||
|
|
||||||
|
### Color
|
||||||
|
|
||||||
|
Use the `color` prop to change the color of the CheckboxGroup.
|
||||||
|
|
||||||
|
::component-code
|
||||||
|
---
|
||||||
|
prettier: true
|
||||||
|
ignore:
|
||||||
|
- defaultValue
|
||||||
|
- items
|
||||||
|
external:
|
||||||
|
- items
|
||||||
|
externalTypes:
|
||||||
|
- CheckboxGroupItem[]
|
||||||
|
items:
|
||||||
|
color:
|
||||||
|
- primary
|
||||||
|
- secondary
|
||||||
|
- success
|
||||||
|
- info
|
||||||
|
- warning
|
||||||
|
- error
|
||||||
|
- neutral
|
||||||
|
props:
|
||||||
|
color: neutral
|
||||||
|
defaultValue:
|
||||||
|
- 'System'
|
||||||
|
items:
|
||||||
|
- 'System'
|
||||||
|
- 'Light'
|
||||||
|
- 'Dark'
|
||||||
|
---
|
||||||
|
::
|
||||||
|
|
||||||
|
### Variant
|
||||||
|
|
||||||
|
Use the `variant` prop to change the variant of the CheckboxGroup.
|
||||||
|
|
||||||
|
::component-code
|
||||||
|
---
|
||||||
|
prettier: true
|
||||||
|
ignore:
|
||||||
|
- defaultValue
|
||||||
|
- items
|
||||||
|
external:
|
||||||
|
- items
|
||||||
|
externalTypes:
|
||||||
|
- CheckboxGroupItem[]
|
||||||
|
items:
|
||||||
|
color:
|
||||||
|
- primary
|
||||||
|
- secondary
|
||||||
|
- success
|
||||||
|
- info
|
||||||
|
- warning
|
||||||
|
- error
|
||||||
|
- neutral
|
||||||
|
variant:
|
||||||
|
- list
|
||||||
|
- card
|
||||||
|
props:
|
||||||
|
color: 'primary'
|
||||||
|
variant: 'card'
|
||||||
|
defaultValue:
|
||||||
|
- 'System'
|
||||||
|
items:
|
||||||
|
- 'System'
|
||||||
|
- 'Light'
|
||||||
|
- 'Dark'
|
||||||
|
---
|
||||||
|
::
|
||||||
|
|
||||||
|
### Size
|
||||||
|
|
||||||
|
Use the `size` prop to change the size of the CheckboxGroup.
|
||||||
|
|
||||||
|
::component-code
|
||||||
|
---
|
||||||
|
prettier: true
|
||||||
|
ignore:
|
||||||
|
- defaultValue
|
||||||
|
- items
|
||||||
|
external:
|
||||||
|
- items
|
||||||
|
externalTypes:
|
||||||
|
- CheckboxGroupItem[]
|
||||||
|
items:
|
||||||
|
variant:
|
||||||
|
- list
|
||||||
|
- card
|
||||||
|
props:
|
||||||
|
size: 'xl'
|
||||||
|
variant: 'list'
|
||||||
|
defaultValue:
|
||||||
|
- 'System'
|
||||||
|
items:
|
||||||
|
- 'System'
|
||||||
|
- 'Light'
|
||||||
|
- 'Dark'
|
||||||
|
---
|
||||||
|
::
|
||||||
|
|
||||||
|
### Orientation
|
||||||
|
|
||||||
|
Use the `orientation` prop to change the orientation of the CheckboxGroup. Defaults to `vertical`.
|
||||||
|
|
||||||
|
::component-code
|
||||||
|
---
|
||||||
|
prettier: true
|
||||||
|
ignore:
|
||||||
|
- defaultValue
|
||||||
|
- items
|
||||||
|
external:
|
||||||
|
- items
|
||||||
|
externalTypes:
|
||||||
|
- CheckboxGroupItem[]
|
||||||
|
items:
|
||||||
|
variant:
|
||||||
|
- list
|
||||||
|
- card
|
||||||
|
props:
|
||||||
|
orientation: 'horizontal'
|
||||||
|
variant: 'list'
|
||||||
|
defaultValue:
|
||||||
|
- 'System'
|
||||||
|
items:
|
||||||
|
- 'System'
|
||||||
|
- 'Light'
|
||||||
|
- 'Dark'
|
||||||
|
---
|
||||||
|
::
|
||||||
|
|
||||||
|
### Indicator
|
||||||
|
|
||||||
|
Use the `indicator` prop to change the position or hide the indicator. Defaults to `start`.
|
||||||
|
|
||||||
|
::component-code
|
||||||
|
---
|
||||||
|
prettier: true
|
||||||
|
ignore:
|
||||||
|
- defaultValue
|
||||||
|
- items
|
||||||
|
external:
|
||||||
|
- items
|
||||||
|
externalTypes:
|
||||||
|
- CheckboxGroupItem[]
|
||||||
|
items:
|
||||||
|
indicator:
|
||||||
|
- start
|
||||||
|
- end
|
||||||
|
- hidden
|
||||||
|
variant:
|
||||||
|
- list
|
||||||
|
- card
|
||||||
|
props:
|
||||||
|
indicator: 'end'
|
||||||
|
variant: 'card'
|
||||||
|
defaultValue:
|
||||||
|
- 'System'
|
||||||
|
items:
|
||||||
|
- 'System'
|
||||||
|
- 'Light'
|
||||||
|
- 'Dark'
|
||||||
|
---
|
||||||
|
::
|
||||||
|
|
||||||
|
### Disabled
|
||||||
|
|
||||||
|
Use the `disabled` prop to disable the CheckboxGroup.
|
||||||
|
|
||||||
|
::component-code
|
||||||
|
---
|
||||||
|
prettier: true
|
||||||
|
ignore:
|
||||||
|
- defaultValue
|
||||||
|
- items
|
||||||
|
external:
|
||||||
|
- items
|
||||||
|
externalTypes:
|
||||||
|
- CheckboxGroupItem[]
|
||||||
|
props:
|
||||||
|
disabled: true
|
||||||
|
defaultValue:
|
||||||
|
- 'System'
|
||||||
|
items:
|
||||||
|
- 'System'
|
||||||
|
- 'Light'
|
||||||
|
- 'Dark'
|
||||||
|
---
|
||||||
|
::
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
### Props
|
||||||
|
|
||||||
|
:component-props
|
||||||
|
|
||||||
|
### Slots
|
||||||
|
|
||||||
|
:component-slots
|
||||||
|
|
||||||
|
### Emits
|
||||||
|
|
||||||
|
:component-emits
|
||||||
|
|
||||||
|
## Theme
|
||||||
|
|
||||||
|
:component-theme
|
||||||
@@ -156,6 +156,23 @@ props:
|
|||||||
---
|
---
|
||||||
::
|
::
|
||||||
|
|
||||||
|
### Variant :badge{label="New" class="align-text-top"}
|
||||||
|
|
||||||
|
Use the `variant` prop to change the variant of the Checkbox.
|
||||||
|
|
||||||
|
::component-code
|
||||||
|
---
|
||||||
|
ignore:
|
||||||
|
- label
|
||||||
|
- defaultValue
|
||||||
|
props:
|
||||||
|
color: 'primary'
|
||||||
|
variant: 'card'
|
||||||
|
defaultValue: true
|
||||||
|
label: Check me
|
||||||
|
---
|
||||||
|
::
|
||||||
|
|
||||||
### Size
|
### Size
|
||||||
|
|
||||||
Use the `size` prop to change the size of the Checkbox.
|
Use the `size` prop to change the size of the Checkbox.
|
||||||
@@ -167,6 +184,24 @@ ignore:
|
|||||||
- defaultValue
|
- defaultValue
|
||||||
props:
|
props:
|
||||||
size: xl
|
size: xl
|
||||||
|
variant: list
|
||||||
|
defaultValue: true
|
||||||
|
label: Check me
|
||||||
|
---
|
||||||
|
::
|
||||||
|
|
||||||
|
### Indicator :badge{label="New" class="align-text-top"}
|
||||||
|
|
||||||
|
Use the `indicator` prop to change the position or hide the indicator. Defaults to `start`.
|
||||||
|
|
||||||
|
::component-code
|
||||||
|
---
|
||||||
|
ignore:
|
||||||
|
- label
|
||||||
|
- defaultValue
|
||||||
|
props:
|
||||||
|
indicator: 'end'
|
||||||
|
variant: 'card'
|
||||||
defaultValue: true
|
defaultValue: true
|
||||||
label: Check me
|
label: Check me
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ links:
|
|||||||
icon: i-custom-fuse-js
|
icon: i-custom-fuse-js
|
||||||
to: https://fusejs.io/
|
to: https://fusejs.io/
|
||||||
target: _blank
|
target: _blank
|
||||||
- label: Combobox
|
- label: Listbox
|
||||||
icon: i-custom-reka-ui
|
icon: i-custom-reka-ui
|
||||||
to: https://reka-ui.com/docs/components/combobox
|
to: https://reka-ui.com/docs/components/listbox
|
||||||
- label: GitHub
|
- label: GitHub
|
||||||
icon: i-simple-icons-github
|
icon: i-simple-icons-github
|
||||||
to: https://github.com/nuxt/ui/tree/v3/src/runtime/components/CommandPalette.vue
|
to: https://github.com/nuxt/ui/tree/v3/src/runtime/components/CommandPalette.vue
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ slots:
|
|||||||
The label `for` attribute and the form control are associated with a unique `id` if not provided.
|
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
|
::component-code
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ It requires two props:
|
|||||||
**No validation library is included** by default, ensure you **install the one you need**.
|
**No validation library is included** by default, ensure you **install the one you need**.
|
||||||
::
|
::
|
||||||
|
|
||||||
::tabs
|
::tabs{class="gap-0"}
|
||||||
::component-example{label="Valibot"}
|
::component-example{label="Valibot"}
|
||||||
---
|
---
|
||||||
name: 'form-example-valibot'
|
name: 'form-example-valibot'
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ title: InputNumber
|
|||||||
description: Input numerical values with a customizable range.
|
description: Input numerical values with a customizable range.
|
||||||
category: form
|
category: form
|
||||||
links:
|
links:
|
||||||
- label: Number Field
|
- label: NumberField
|
||||||
icon: i-custom-reka-ui
|
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
|
- label: GitHub
|
||||||
icon: i-simple-icons-github
|
icon: i-simple-icons-github
|
||||||
to: https://github.com/nuxt/ui/tree/v3/src/runtime/components/InputNumber.vue
|
to: https://github.com/nuxt/ui/tree/v3/src/runtime/components/InputNumber.vue
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
---
|
---
|
||||||
title: Keyboard Key
|
title: Kbd
|
||||||
description: A kbd element to display a keyboard key.
|
description: A kbd element to display a keyboard key.
|
||||||
category: element
|
category: element
|
||||||
links:
|
links:
|
||||||
- label: GitHub
|
- label: GitHub
|
||||||
icon: i-simple-icons-github
|
icon: i-simple-icons-github
|
||||||
to: https://github.com/nuxt/ui/tree/v3/src/runtime/components/Kbd.vue
|
to: https://github.com/nuxt/ui/tree/v3/src/runtime/components/Kbd.vue
|
||||||
navigation:
|
|
||||||
title: Kbd
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Usage
|
## 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
|
::component-code
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -276,7 +276,7 @@ This allows you to move the trigger outside of the Modal or remove it entirely.
|
|||||||
|
|
||||||
### Prevent closing
|
### Prevent closing
|
||||||
|
|
||||||
Set the `dismissible` prop to `false` to prevent the Modal from being closed when clicking outside of it or pressing escape.
|
Set the `dismissible` prop to `false` to prevent the Modal from being closed when clicking outside of it or pressing escape. A `close:prevent` event will be emitted when the user tries to close it.
|
||||||
|
|
||||||
::component-code
|
::component-code
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -21,8 +21,12 @@ Use the `items` prop as an array of objects with the following properties:
|
|||||||
- `icon?: string`{lang="ts-type"}
|
- `icon?: string`{lang="ts-type"}
|
||||||
- `avatar?: AvatarProps`{lang="ts-type"}
|
- `avatar?: AvatarProps`{lang="ts-type"}
|
||||||
- `badge?: string | number | BadgeProps`{lang="ts-type"}
|
- `badge?: string | number | BadgeProps`{lang="ts-type"}
|
||||||
|
- `tooltip?: TooltipProps`{lang="ts-type"}
|
||||||
- `trailingIcon?: string`{lang="ts-type"}
|
- `trailingIcon?: string`{lang="ts-type"}
|
||||||
- `type?: 'label' | 'link'`{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"}
|
- `value?: string`{lang="ts-type"}
|
||||||
- `disabled?: boolean`{lang="ts-type"}
|
- `disabled?: boolean`{lang="ts-type"}
|
||||||
- `class?: any`{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.
|
Use the `orientation` prop to change the orientation of the NavigationMenu.
|
||||||
|
|
||||||
::note
|
::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
|
::component-code
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ title: PinInput
|
|||||||
description: An input element to enter a pin.
|
description: An input element to enter a pin.
|
||||||
category: form
|
category: form
|
||||||
links:
|
links:
|
||||||
|
- label: PinInput
|
||||||
|
icon: i-custom-reka-ui
|
||||||
|
to: https://reka-ui.com/docs/components/pin-input
|
||||||
- label: GitHub
|
- label: GitHub
|
||||||
icon: i-simple-icons-github
|
icon: i-simple-icons-github
|
||||||
to: https://github.com/nuxt/ui/tree/v3/src/runtime/components/PinInput.vue
|
to: https://github.com/nuxt/ui/tree/v3/src/runtime/components/PinInput.vue
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ In this example, leveraging [`defineShortcuts`](/composables/define-shortcuts),
|
|||||||
|
|
||||||
### Prevent closing
|
### Prevent closing
|
||||||
|
|
||||||
Set the `dismissible` prop to `false` to prevent the Popover from being closed when clicking outside of it or pressing escape.
|
Set the `dismissible` prop to `false` to prevent the Popover from being closed when clicking outside of it or pressing escape. A `close:prevent` event will be emitted when the user tries to close it.
|
||||||
|
|
||||||
::component-example
|
::component-example
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -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.
|
Use the `variant` prop to change the variant of the RadioGroup.
|
||||||
|
|
||||||
@@ -169,6 +169,8 @@ ignore:
|
|||||||
- items
|
- items
|
||||||
external:
|
external:
|
||||||
- items
|
- items
|
||||||
|
externalTypes:
|
||||||
|
- RadioGroupItem[]
|
||||||
props:
|
props:
|
||||||
color: 'primary'
|
color: 'primary'
|
||||||
variant: 'table'
|
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`.
|
Use the `indicator` prop to change the position or hide the indicator. Defaults to `start`.
|
||||||
|
|
||||||
|
|||||||
@@ -275,7 +275,7 @@ This allows you to move the trigger outside of the Slideover or remove it entire
|
|||||||
|
|
||||||
### Prevent closing
|
### Prevent closing
|
||||||
|
|
||||||
Set the `dismissible` prop to `false` to prevent the Slideover from being closed when clicking outside of it or pressing escape.
|
Set the `dismissible` prop to `false` to prevent the Slideover from being closed when clicking outside of it or pressing escape. A `close:prevent` event will be emitted when the user tries to close it.
|
||||||
|
|
||||||
::component-code
|
::component-code
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -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
|
### Disabled
|
||||||
|
|
||||||
Use the `disabled` prop to disable the Slider.
|
Use the `disabled` prop to disable the Slider.
|
||||||
|
|||||||
@@ -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
|
::component-code
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -105,6 +105,13 @@ highlights:
|
|||||||
When rendering components with `h`, you can either use the `resolveComponent` function or import from `#components`.
|
When rendering components with `h`, you can either use the `resolveComponent` function or import from `#components`.
|
||||||
::
|
::
|
||||||
|
|
||||||
|
### Meta
|
||||||
|
|
||||||
|
Use the `meta` prop as an object ([TableMeta](https://tanstack.com/table/latest/docs/api/core/table#meta)) to pass properties like:
|
||||||
|
|
||||||
|
- `class`:
|
||||||
|
- `tr`: [The classes to apply to the `tr` element.]{class="text-muted"}
|
||||||
|
|
||||||
### Loading
|
### Loading
|
||||||
|
|
||||||
Use the `loading` prop to display a loading state, the `loading-color` prop to change its color and the `loading-animation` prop to change its animation.
|
Use the `loading` prop to display a loading state, the `loading-color` prop to change its color and the `loading-animation` prop to change its animation.
|
||||||
@@ -253,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.
|
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
|
### 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).
|
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).
|
||||||
@@ -444,6 +475,37 @@ class: '!p-0'
|
|||||||
---
|
---
|
||||||
::
|
::
|
||||||
|
|
||||||
|
### With infinite scroll
|
||||||
|
|
||||||
|
If you use server-side pagination, you can use the [`useInfiniteScroll`](https://vueuse.org/core/useInfiniteScroll/#useinfinitescroll) composable to load more data when scrolling.
|
||||||
|
|
||||||
|
::component-example
|
||||||
|
---
|
||||||
|
prettier: true
|
||||||
|
collapse: true
|
||||||
|
overflowHidden: true
|
||||||
|
name: 'table-infinite-scroll-example'
|
||||||
|
class: '!p-0'
|
||||||
|
---
|
||||||
|
::
|
||||||
|
|
||||||
|
### With drag and drop
|
||||||
|
|
||||||
|
Use the [`useSortable`](https://vueuse.org/integrations/useSortable/) composable from [`@vueuse/integrations`](https://vueuse.org/integrations/README.html) to enable drag and drop functionality on the Table. This integration wraps [Sortable.js](https://sortablejs.github.io/Sortable/) to provide a seamless drag and drop experience.
|
||||||
|
|
||||||
|
::note
|
||||||
|
Since the table ref doesn't expose the tbody element, add a unique class to it via the `:ui` prop to target it with `useSortable` (e.g. `:ui="{ tbody: 'my-table-tbody' }"`).
|
||||||
|
::
|
||||||
|
|
||||||
|
::component-example
|
||||||
|
---
|
||||||
|
prettier: true
|
||||||
|
collapse: true
|
||||||
|
name: 'table-drag-and-drop-example'
|
||||||
|
class: '!p-0'
|
||||||
|
---
|
||||||
|
::
|
||||||
|
|
||||||
### With slots
|
### With slots
|
||||||
|
|
||||||
You can use slots to customize the header and data cells of the table.
|
You can use slots to customize the header and data cells of the table.
|
||||||
@@ -489,6 +551,7 @@ This will give you access to the following:
|
|||||||
|
|
||||||
| Name | Type |
|
| Name | Type |
|
||||||
| ---- | ---- |
|
| ---- | ---- |
|
||||||
|
| `tableRef`{lang="ts-type"} | `Ref<HTMLTableElement \| null>`{lang="ts-type"} |
|
||||||
| `tableApi`{lang="ts-type"} | [`Ref<Table \| null>`{lang="ts-type"}](https://tanstack.com/table/latest/docs/api/core/table#table-api) |
|
| `tableApi`{lang="ts-type"} | [`Ref<Table \| null>`{lang="ts-type"}](https://tanstack.com/table/latest/docs/api/core/table#table-api) |
|
||||||
|
|
||||||
## Theme
|
## Theme
|
||||||
|
|||||||
@@ -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"}
|
:component-example{name="tabs-model-value-example"}
|
||||||
|
|
||||||
::tip
|
|
||||||
You can also pass the `value` of one of the items if provided.
|
|
||||||
::
|
|
||||||
|
|
||||||
### With content slot
|
### With content slot
|
||||||
|
|
||||||
Use the `#content` slot to customize the content of each item.
|
Use the `#content` slot to customize the content of each item.
|
||||||
|
|||||||
@@ -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.
|
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.
|
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.
|
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`.
|
Use the `loading-icon` prop to customize the loading icon. Defaults to `i-lucide-refresh-cw`.
|
||||||
|
|
||||||
|
|||||||
@@ -188,7 +188,7 @@ name: 'toast-example'
|
|||||||
:toaster-position-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.
|
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
|
: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.
|
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
|
: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.
|
In this example, we use the `AppConfig` to configure the `expand` prop of the `Toaster` component globally.
|
||||||
::
|
::
|
||||||
|
|
||||||
|
|||||||
@@ -3,40 +3,40 @@
|
|||||||
"name": "@nuxt/ui-docs",
|
"name": "@nuxt/ui-docs",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/vue": "^1.2.8",
|
"@ai-sdk/vue": "^1.2.11",
|
||||||
"@iconify-json/logos": "^1.2.4",
|
"@iconify-json/logos": "^1.2.4",
|
||||||
"@iconify-json/lucide": "^1.2.37",
|
"@iconify-json/lucide": "^1.2.41",
|
||||||
"@iconify-json/simple-icons": "^1.2.32",
|
"@iconify-json/simple-icons": "^1.2.33",
|
||||||
"@iconify-json/vscode-icons": "^1.2.19",
|
"@iconify-json/vscode-icons": "^1.2.20",
|
||||||
"@nuxt/content": "https://pkg.pr.new/@nuxt/content@754e480",
|
"@nuxt/content": "^3.5.1",
|
||||||
"@nuxt/image": "^1.10.0",
|
"@nuxt/image": "^1.10.0",
|
||||||
"@nuxt/ui": "latest",
|
"@nuxt/ui": "latest",
|
||||||
"@nuxt/ui-pro": "https://pkg.pr.new/@nuxt/ui-pro@5eebbb9",
|
"@nuxt/ui-pro": "https://pkg.pr.new/@nuxt/ui-pro@a30de4d",
|
||||||
"@nuxthub/core": "^0.8.24",
|
"@nuxthub/core": "^0.8.27",
|
||||||
"@nuxtjs/plausible": "^1.2.0",
|
"@nuxtjs/plausible": "^1.2.0",
|
||||||
"@octokit/rest": "^21.1.1",
|
"@octokit/rest": "^21.1.1",
|
||||||
"@rollup/plugin-yaml": "^4.1.2",
|
"@rollup/plugin-yaml": "^4.1.2",
|
||||||
"@vueuse/integrations": "^13.1.0",
|
"@vueuse/integrations": "^13.1.0",
|
||||||
"@vueuse/nuxt": "^13.1.0",
|
"@vueuse/nuxt": "^13.1.0",
|
||||||
"ai": "^4.3.9",
|
"ai": "^4.3.15",
|
||||||
"capture-website": "^4.2.0",
|
"capture-website": "^4.2.0",
|
||||||
"joi": "^17.13.3",
|
"joi": "^17.13.3",
|
||||||
"motion-v": "0.13.1",
|
"motion-v": "^1.0.2",
|
||||||
"nuxt": "^3.16.2",
|
"nuxt": "^3.17.2",
|
||||||
"nuxt-component-meta": "^0.10.1",
|
"nuxt-component-meta": "^0.11.0",
|
||||||
"nuxt-llms": "^0.1.2",
|
"nuxt-llms": "^0.1.2",
|
||||||
"nuxt-og-image": "^5.1.2",
|
"nuxt-og-image": "^5.1.3",
|
||||||
"prettier": "^3.5.3",
|
"prettier": "^3.5.3",
|
||||||
"shiki-transformer-color-highlight": "^1.0.0",
|
"shiki-transformer-color-highlight": "^1.0.0",
|
||||||
"sortablejs": "^1.15.6",
|
"sortablejs": "^1.15.6",
|
||||||
"superstruct": "^2.0.2",
|
"superstruct": "^2.0.2",
|
||||||
"ufo": "^1.6.1",
|
"ufo": "^1.6.1",
|
||||||
"valibot": "^1.0.0",
|
"valibot": "^1.1.0",
|
||||||
"workers-ai-provider": "^0.3.0",
|
"workers-ai-provider": "^0.3.1",
|
||||||
"yup": "^1.6.1",
|
"yup": "^1.6.1",
|
||||||
"zod": "^3.24.3"
|
"zod": "^3.24.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"wrangler": "^4.12.0"
|
"wrangler": "^4.14.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
docs/public/components/dark/checkbox-group.png
Normal file
BIN
docs/public/components/dark/checkbox-group.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.0 KiB |
BIN
docs/public/components/light/checkbox-group.png
Normal file
BIN
docs/public/components/light/checkbox-group.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.0 KiB |
@@ -1,6 +1,8 @@
|
|||||||
import json5 from 'json5'
|
import json5 from 'json5'
|
||||||
import { camelCase, kebabCase } from 'scule'
|
import { camelCase, kebabCase } from 'scule'
|
||||||
import { visit } from '@nuxt/content/runtime'
|
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 theme from '../../.nuxt/ui'
|
||||||
import * as themePro from '../../.nuxt/ui-pro'
|
import * as themePro from '../../.nuxt/ui-pro'
|
||||||
import meta from '#nuxt-component-meta'
|
import meta from '#nuxt-component-meta'
|
||||||
@@ -44,11 +46,33 @@ const parseBoolean = (value?: string): boolean => value === 'true'
|
|||||||
|
|
||||||
function getComponentMeta(componentName: string) {
|
function getComponentMeta(componentName: string) {
|
||||||
const pascalCaseName = componentName.charAt(0).toUpperCase() + componentName.slice(1)
|
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 {
|
return {
|
||||||
pascalCaseName,
|
pascalCaseName,
|
||||||
metaComponentName,
|
metaComponentName: finalMetaComponentName,
|
||||||
componentMeta: (meta as Record<string, any>)[metaComponentName]?.meta
|
componentMeta
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,6 +190,7 @@ function emitItemHandler(event: any): string {
|
|||||||
const generateThemeConfig = ({ pro, prose, componentName }: ThemeConfig) => {
|
const generateThemeConfig = ({ pro, prose, componentName }: ThemeConfig) => {
|
||||||
const computedTheme = pro ? (prose ? themePro.prose : themePro) : theme
|
const computedTheme = pro ? (prose ? themePro.prose : themePro) : theme
|
||||||
const componentTheme = computedTheme[componentName as keyof typeof computedTheme]
|
const componentTheme = computedTheme[componentName as keyof typeof computedTheme]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
[pro ? 'uiPro' : 'ui']: prose
|
[pro ? 'uiPro' : 'ui']: prose
|
||||||
? { prose: { [componentName]: componentTheme } }
|
? { prose: { [componentName]: componentTheme } }
|
||||||
@@ -278,14 +303,18 @@ const generateComponentCode = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default defineNitroPlugin((nitroApp) => {
|
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)
|
const componentName = camelCase(doc.title)
|
||||||
|
|
||||||
visitAndReplace(doc, 'component-theme', (node) => {
|
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 pro = parseBoolean(attributes[':pro'])
|
||||||
const prose = parseBoolean(attributes[':prose'])
|
const prose = parseBoolean(attributes[':prose'])
|
||||||
const appConfig = generateThemeConfig({ pro, prose, componentName })
|
const appConfig = generateThemeConfig({ pro, prose, componentName: finalComponentName })
|
||||||
|
|
||||||
replaceNodeWithPre(
|
replaceNodeWithPre(
|
||||||
node,
|
node,
|
||||||
@@ -320,14 +349,23 @@ export default defineNitroPlugin((nitroApp) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
visitAndReplace(doc, 'component-props', (node) => {
|
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
|
if (!componentMeta?.props) return
|
||||||
|
|
||||||
|
const interfaceName = isProse ? `Prose${pascalCaseName}Props` : `${pascalCaseName}Props`
|
||||||
|
|
||||||
const interfaceCode = generateTSInterface(
|
const interfaceCode = generateTSInterface(
|
||||||
`${pascalCaseName}Props`,
|
interfaceName,
|
||||||
Object.values(componentMeta.props),
|
Object.values(componentMeta.props),
|
||||||
propItemHandler,
|
propItemHandler,
|
||||||
`Props for the ${pascalCaseName} component`
|
`Props for the ${isProse ? 'Prose' : ''}${pascalCaseName} component`
|
||||||
)
|
)
|
||||||
replaceNodeWithPre(node, 'ts', interfaceCode)
|
replaceNodeWithPre(node, 'ts', interfaceCode)
|
||||||
})
|
})
|
||||||
|
|||||||
48
package.json
48
package.json
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "@nuxt/ui",
|
"name": "@nuxt/ui",
|
||||||
"description": "A UI Library for Modern Web Apps, powered by Vue & Tailwind CSS.",
|
"description": "A UI Library for Modern Web Apps, powered by Vue & Tailwind CSS.",
|
||||||
"version": "3.0.2",
|
"version": "3.1.1",
|
||||||
"packageManager": "pnpm@10.9.0",
|
"packageManager": "pnpm@10.10.0",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/nuxt/ui.git"
|
"url": "git+https://github.com/nuxt/ui.git"
|
||||||
@@ -96,12 +96,12 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "nuxt-module-build build",
|
"build": "nuxt-module-build build",
|
||||||
"prepack": "pnpm build",
|
"prepack": "pnpm build",
|
||||||
"dev": "nuxi dev playground",
|
"dev": "nuxi dev playground --uiDev",
|
||||||
"dev:build": "nuxi build playground",
|
"dev:build": "nuxi build playground",
|
||||||
"dev:vue": "vite playground-vue",
|
"dev:vue": "vite playground-vue -- --uiDev",
|
||||||
"dev:vue:build": "vite build playground-vue",
|
"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",
|
"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:build": "nuxi build docs",
|
||||||
"docs:prepare": "nuxt-component-meta docs",
|
"docs:prepare": "nuxt-component-meta docs",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
@@ -115,14 +115,14 @@
|
|||||||
"@iconify/vue": "^4.3.0",
|
"@iconify/vue": "^4.3.0",
|
||||||
"@internationalized/date": "^3.8.0",
|
"@internationalized/date": "^3.8.0",
|
||||||
"@internationalized/number": "^3.6.1",
|
"@internationalized/number": "^3.6.1",
|
||||||
"@nuxt/fonts": "^0.11.1",
|
"@nuxt/fonts": "^0.11.2",
|
||||||
"@nuxt/icon": "^1.12.0",
|
"@nuxt/icon": "^1.12.0",
|
||||||
"@nuxt/kit": "^3.16.2",
|
"@nuxt/kit": "^3.17.2",
|
||||||
"@nuxt/schema": "^3.16.2",
|
"@nuxt/schema": "^3.17.2",
|
||||||
"@nuxtjs/color-mode": "^3.5.2",
|
"@nuxtjs/color-mode": "^3.5.2",
|
||||||
"@standard-schema/spec": "^1.0.0",
|
"@standard-schema/spec": "^1.0.0",
|
||||||
"@tailwindcss/postcss": "^4.1.4",
|
"@tailwindcss/postcss": "^4.1.5",
|
||||||
"@tailwindcss/vite": "^4.1.4",
|
"@tailwindcss/vite": "^4.1.5",
|
||||||
"@tanstack/vue-table": "^8.21.3",
|
"@tanstack/vue-table": "^8.21.3",
|
||||||
"@unhead/vue": "^2.0.8",
|
"@unhead/vue": "^2.0.8",
|
||||||
"@vueuse/core": "^13.1.0",
|
"@vueuse/core": "^13.1.0",
|
||||||
@@ -144,30 +144,31 @@
|
|||||||
"mlly": "^1.7.4",
|
"mlly": "^1.7.4",
|
||||||
"ohash": "^2.0.11",
|
"ohash": "^2.0.11",
|
||||||
"pathe": "^2.0.3",
|
"pathe": "^2.0.3",
|
||||||
"reka-ui": "^2.2.0",
|
"reka-ui": "^2.2.1",
|
||||||
"scule": "^1.3.0",
|
"scule": "^1.3.0",
|
||||||
"tailwind-variants": "^1.0.0",
|
"tailwind-variants": "^1.0.0",
|
||||||
"tailwindcss": "^4.1.4",
|
"tailwindcss": "^4.1.5",
|
||||||
"tinyglobby": "^0.2.13",
|
"tinyglobby": "^0.2.13",
|
||||||
"unplugin": "^2.3.2",
|
"unplugin": "^2.3.2",
|
||||||
"unplugin-auto-import": "^19.1.2",
|
"unplugin-auto-import": "^19.2.0",
|
||||||
"unplugin-vue-components": "^28.5.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": {
|
"devDependencies": {
|
||||||
"@nuxt/eslint-config": "^1.3.0",
|
"@nuxt/eslint-config": "^1.3.0",
|
||||||
"@nuxt/module-builder": "^1.0.1",
|
"@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",
|
"@release-it/conventional-changelog": "^10.0.1",
|
||||||
"@vue/test-utils": "^2.4.6",
|
"@vue/test-utils": "^2.4.6",
|
||||||
"embla-carousel": "^8.6.0",
|
"embla-carousel": "^8.6.0",
|
||||||
"eslint": "^9.25.0",
|
"eslint": "^9.26.0",
|
||||||
"happy-dom": "^17.4.4",
|
"happy-dom": "^17.4.6",
|
||||||
"nuxt": "^3.16.2",
|
"nuxt": "^3.17.2",
|
||||||
"release-it": "^19.0.1",
|
"release-it": "^19.0.2",
|
||||||
"vitest": "^3.1.1",
|
"vitest": "^3.1.3",
|
||||||
"vitest-environment-nuxt": "^1.0.1",
|
"vitest-environment-nuxt": "^1.0.1",
|
||||||
"vue-tsc": "^2.2.0"
|
"vue-tsc": "^2.2.10"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@inertiajs/vue3": "^2.0.7",
|
"@inertiajs/vue3": "^2.0.7",
|
||||||
@@ -207,12 +208,13 @@
|
|||||||
"chokidar": "3.6.0",
|
"chokidar": "3.6.0",
|
||||||
"debug": "4.3.7",
|
"debug": "4.3.7",
|
||||||
"rollup": "4.34.9",
|
"rollup": "4.34.9",
|
||||||
"unplugin": "^2.3.2",
|
"unimport": "4.1.1",
|
||||||
"vue-tsc": "2.2.0"
|
"unplugin": "^2.3.2"
|
||||||
},
|
},
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
"onlyBuiltDependencies": [
|
"onlyBuiltDependencies": [
|
||||||
"better-sqlite3",
|
"better-sqlite3",
|
||||||
|
"puppeteer",
|
||||||
"sharp"
|
"sharp"
|
||||||
],
|
],
|
||||||
"ignoredBuiltDependencies": [
|
"ignoredBuiltDependencies": [
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/logo.svg" />
|
<link rel="icon" type="image/svg+xml" href="/logo.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<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>
|
<title>Nuxt UI - Vue Playground</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -12,13 +12,13 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nuxt/ui": "latest",
|
"@nuxt/ui": "latest",
|
||||||
"vue": "^3.5.13",
|
"vue": "^3.5.13",
|
||||||
"vue-router": "^4.5.0",
|
"vue-router": "^4.5.1",
|
||||||
"zod": "^3.24.3"
|
"zod": "^3.24.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "^5.2.3",
|
"@vitejs/plugin-vue": "^5.2.3",
|
||||||
"typescript": "^5.8.3",
|
"typescript": "^5.8.3",
|
||||||
"vite": "^6.3.2",
|
"vite": "^6.3.5",
|
||||||
"vue-tsc": "^2.2.0"
|
"vue-tsc": "^2.2.10"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ const components = [
|
|||||||
'calendar',
|
'calendar',
|
||||||
'carousel',
|
'carousel',
|
||||||
'checkbox',
|
'checkbox',
|
||||||
|
'checkbox-group',
|
||||||
'chip',
|
'chip',
|
||||||
'collapsible',
|
'collapsible',
|
||||||
'color-picker',
|
'color-picker',
|
||||||
|
|||||||
@@ -1,15 +1,9 @@
|
|||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import vue from '@vitejs/plugin-vue'
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
import ui from '@nuxt/ui/vite'
|
||||||
import ui from '../src/vite'
|
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
server: {
|
|
||||||
fs: {
|
|
||||||
allow: ['..']
|
|
||||||
}
|
|
||||||
},
|
|
||||||
plugins: [
|
plugins: [
|
||||||
vue(),
|
vue(),
|
||||||
ui({
|
ui({
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ const components = [
|
|||||||
'calendar',
|
'calendar',
|
||||||
'carousel',
|
'carousel',
|
||||||
'checkbox',
|
'checkbox',
|
||||||
|
'checkbox-group',
|
||||||
'chip',
|
'chip',
|
||||||
'collapsible',
|
'collapsible',
|
||||||
'color-picker',
|
'color-picker',
|
||||||
|
|||||||
@@ -28,7 +28,12 @@ const bind = computed(() => ({
|
|||||||
dots: dots.value
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -60,23 +65,23 @@ const items = Array.from({ length: 6 }).map((_, index) => index)
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template v-if="classNames">
|
<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">
|
<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="`https://picsum.photos/600/350?v=${index}`" class="rounded-lg">
|
<img :src="item.src" class="rounded-lg">
|
||||||
</UCarousel>
|
</UCarousel>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="autoHeight">
|
<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">
|
<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="`https://picsum.photos/600/${index % 2 === 0 ? 350 : 450}?v=${index}`" :class="index % 2 === 0 ? 'h-[350px]' : 'h-[450px]'" class="rounded-lg">
|
<img :src="item.src" class="rounded-lg">
|
||||||
</UCarousel>
|
</UCarousel>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<UCarousel v-slot="{ index }" v-bind="bind" :items="items" class="w-[320px] mx-auto" :ui="{ container: 'h-[336px]' }">
|
<UCarousel v-slot="{ item }" 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">
|
<img :src="item.src" class="rounded-lg">
|
||||||
</UCarousel>
|
</UCarousel>
|
||||||
|
|
||||||
<template v-if="orientation === 'horizontal'">
|
<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">
|
<UCarousel v-slot="{ item }" 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">
|
<img :src="item.src" class="rounded-lg">
|
||||||
</UCarousel>
|
</UCarousel>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
73
playground/app/pages/components/checkbox-group.vue
Normal file
73
playground/app/pages/components/checkbox-group.vue
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import themeCheckbox from '#build/ui/checkbox'
|
||||||
|
import theme from '#build/ui/checkbox-group'
|
||||||
|
|
||||||
|
const sizes = Object.keys(theme.variants.size) as Array<keyof typeof theme.variants.size>
|
||||||
|
const variants = Object.keys(themeCheckbox.variants.variant)
|
||||||
|
const variant = ref('list' as const)
|
||||||
|
|
||||||
|
const literalOptions = [
|
||||||
|
'Option 1',
|
||||||
|
'Option 2',
|
||||||
|
'Option 3'
|
||||||
|
]
|
||||||
|
const items = [
|
||||||
|
{ value: '1', label: 'Option 1' },
|
||||||
|
{ value: '2', label: 'Option 2' },
|
||||||
|
{ value: '3', label: 'Option 3' }
|
||||||
|
]
|
||||||
|
|
||||||
|
const itemsWithDescription = [
|
||||||
|
{ value: '1', label: 'Option 1', description: 'Description 1' },
|
||||||
|
{ value: '2', label: 'Option 2', description: 'Description 2' },
|
||||||
|
{ value: '3', label: 'Option 3', description: 'Description 3' }
|
||||||
|
]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="flex flex-col items-center gap-4">
|
||||||
|
<USelect v-model="variant" :items="variants" />
|
||||||
|
|
||||||
|
<div class="flex flex-wrap gap-4 ms-[100px]">
|
||||||
|
<UCheckboxGroup :variant="variant" :items="items" :default-value="['1']" />
|
||||||
|
<UCheckboxGroup :variant="variant" :items="items" color="neutral" :default-value="['1']" />
|
||||||
|
<UCheckboxGroup :variant="variant" :items="items" color="error" :default-value="['2']" />
|
||||||
|
<UCheckboxGroup :variant="variant" :items="literalOptions" />
|
||||||
|
<UCheckboxGroup :variant="variant" :items="items" disabled />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-wrap gap-4 ms-[100px]">
|
||||||
|
<UCheckboxGroup :variant="variant" :items="items" :default-value="['3']" indicator="start" />
|
||||||
|
<UCheckboxGroup :variant="variant" :items="items" :default-value="['3']" indicator="end" />
|
||||||
|
<UCheckboxGroup :variant="variant" :items="items" :default-value="['3']" indicator="hidden" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<UCheckboxGroup :variant="variant" :items="items" orientation="horizontal" class="ms-[95px]" />
|
||||||
|
|
||||||
|
<div class="flex items-center gap-4 ms-[34px]">
|
||||||
|
<UCheckboxGroup v-for="size in sizes" :key="size" :size="size" :variant="variant" :items="items" />
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-4 ms-[74px]">
|
||||||
|
<UCheckboxGroup v-for="size in sizes" :key="size" :size="size" :variant="variant" :items="itemsWithDescription" />
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<UCheckboxGroup :variant="variant" :items="items" legend="Legend" />
|
||||||
|
<UCheckboxGroup :variant="variant" :items="items" legend="Legend" required />
|
||||||
|
<UCheckboxGroup :variant="variant" :items="items">
|
||||||
|
<template #legend>
|
||||||
|
<span class="italic font-bold">
|
||||||
|
With slots
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<template #label="{ item }">
|
||||||
|
<span class="italic">
|
||||||
|
{{ item.label }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</UCheckboxGroup>
|
||||||
|
</div>
|
||||||
|
<UCheckboxGroup :variant="variant" :items="items" legend="Legend" orientation="horizontal" required />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -2,27 +2,51 @@
|
|||||||
import theme from '#build/ui/checkbox'
|
import theme from '#build/ui/checkbox'
|
||||||
|
|
||||||
const sizes = Object.keys(theme.variants.size) as Array<keyof typeof theme.variants.size>
|
const sizes = Object.keys(theme.variants.size) as Array<keyof typeof theme.variants.size>
|
||||||
|
const variants = Object.keys(theme.variants.variant)
|
||||||
|
const variant = ref('list' as const)
|
||||||
|
const indicators = Object.keys(theme.variants.indicator) as Array<keyof typeof theme.variants.indicator>
|
||||||
|
const indicator = ref('start' as const)
|
||||||
|
|
||||||
const checked = ref(true)
|
const checked = ref(true)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col items-center gap-4">
|
<div class="flex flex-col items-center gap-4">
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<USelect v-model="variant" :items="variants" />
|
||||||
|
<USelect v-model="indicator" :items="indicators" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
<UCheckbox v-model="checked" label="Primary" />
|
<UCheckbox v-model="checked" label="Primary" :variant="variant" :indicator="indicator" orientation="horizontal" />
|
||||||
<UCheckbox label="Neutral" color="neutral" :default-value="true" />
|
<UCheckbox label="Neutral" color="neutral" :variant="variant" :indicator="indicator" :default-value="true" />
|
||||||
<UCheckbox label="Error" color="error" :model-value="true" />
|
<UCheckbox label="Error" color="error" :variant="variant" :indicator="indicator" :model-value="true" />
|
||||||
<UCheckbox label="Icon" icon="i-lucide-heart" :model-value="true" />
|
<UCheckbox label="Icon" icon="i-lucide-heart" :variant="variant" :indicator="indicator" :model-value="true" />
|
||||||
<UCheckbox label="Default value" :default-value="true" />
|
<UCheckbox label="Default value" :variant="variant" :indicator="indicator" :default-value="true" />
|
||||||
<UCheckbox label="Indeterminate" default-value="indeterminate" />
|
<UCheckbox label="Indeterminate" :variant="variant" :indicator="indicator" default-value="indeterminate" />
|
||||||
<UCheckbox label="Required" required />
|
<UCheckbox label="Required" :variant="variant" :indicator="indicator" required />
|
||||||
<UCheckbox label="Disabled" disabled />
|
<UCheckbox label="Disabled" :variant="variant" :indicator="indicator" disabled />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-4 me-[-11px]">
|
<div class="flex items-center gap-4 me-[-11px]">
|
||||||
<UCheckbox v-for="size in sizes" :key="size" label="Check me" :size="size" />
|
<UCheckbox
|
||||||
|
v-for="size in sizes"
|
||||||
|
:key="size"
|
||||||
|
label="Check me"
|
||||||
|
:size="size"
|
||||||
|
:variant="variant"
|
||||||
|
:indicator="indicator"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-4 me-[-96px]">
|
<div class="flex items-center gap-4 me-[-96px]">
|
||||||
<UCheckbox v-for="size in sizes" :key="size" label="Check me" description="This is a description" :size="size" />
|
<UCheckbox
|
||||||
|
v-for="size in sizes"
|
||||||
|
:key="size"
|
||||||
|
label="Check me"
|
||||||
|
description="This is a description"
|
||||||
|
:size="size"
|
||||||
|
:variant="variant"
|
||||||
|
:indicator="indicator"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -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>
|
<template>
|
||||||
<div class="w-full flex flex-col gap-4">
|
<div class="w-full flex flex-col gap-4">
|
||||||
<UCard class="flex-1">
|
<UCard class="flex-1">
|
||||||
@@ -43,38 +80,3 @@
|
|||||||
</UCard>
|
</UCard>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</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>
|
|
||||||
|
|||||||
@@ -143,6 +143,8 @@ const data = ref<Payment[]>([{
|
|||||||
amount: 567
|
amount: 567
|
||||||
}])
|
}])
|
||||||
|
|
||||||
|
const currentID = ref(4601)
|
||||||
|
|
||||||
const columns: TableColumn<Payment>[] = [{
|
const columns: TableColumn<Payment>[] = [{
|
||||||
id: 'select',
|
id: 'select',
|
||||||
header: ({ table }) => h(UCheckbox, {
|
header: ({ table }) => h(UCheckbox, {
|
||||||
@@ -277,8 +279,19 @@ const pagination = ref({
|
|||||||
pageSize: 10
|
pageSize: 10
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function addElement() {
|
||||||
|
data.value.unshift({
|
||||||
|
id: currentID.value.toString(),
|
||||||
|
date: new Date().toISOString(),
|
||||||
|
status: 'paid',
|
||||||
|
email: 'new@example.com',
|
||||||
|
amount: Math.random() * 1000
|
||||||
|
})
|
||||||
|
currentID.value++
|
||||||
|
}
|
||||||
|
|
||||||
function randomize() {
|
function randomize() {
|
||||||
data.value = [...data.value].sort(() => Math.random() - 0.5)
|
data.value = data.value.sort(() => Math.random() - 0.5)
|
||||||
}
|
}
|
||||||
|
|
||||||
function onSelect(row: TableRow<Payment>) {
|
function onSelect(row: TableRow<Payment>) {
|
||||||
@@ -303,6 +316,7 @@ onMounted(() => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<UButton color="neutral" label="Randomize" @click="randomize" />
|
<UButton color="neutral" label="Randomize" @click="randomize" />
|
||||||
|
<UButton color="neutral" label="Add element" @click="addElement" />
|
||||||
|
|
||||||
<UDropdownMenu
|
<UDropdownMenu
|
||||||
:items="table?.tableApi?.getAllColumns().filter(column => column.getCanHide()).map(column => ({
|
:items="table?.tableApi?.getAllColumns().filter(column => column.getCanHide()).map(column => ({
|
||||||
|
|||||||
@@ -59,14 +59,6 @@ const items = [{
|
|||||||
<template #custom="{ item }">
|
<template #custom="{ item }">
|
||||||
<span class="text-muted">Custom: {{ item.content }}</span>
|
<span class="text-muted">Custom: {{ item.content }}</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #list-trailing>
|
|
||||||
<UButton
|
|
||||||
icon="lucide:refresh-cw"
|
|
||||||
variant="soft"
|
|
||||||
class="ml-2"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</UTabs>
|
</UTabs>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
export default defineNuxtConfig({
|
export default defineNuxtConfig({
|
||||||
modules: [
|
modules: [
|
||||||
'../src/module',
|
'@nuxt/ui',
|
||||||
'@nuxthub/core'
|
'@nuxthub/core'
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|||||||
@@ -5,14 +5,22 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "nuxi dev",
|
"dev": "nuxi dev",
|
||||||
"build": "nuxi build",
|
"build": "nuxi build",
|
||||||
"generate": "nuxi generate"
|
"generate": "nuxi generate",
|
||||||
|
"typecheck": "nuxt typecheck"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@iconify-json/lucide": "^1.2.37",
|
"@iconify-json/lucide": "^1.2.41",
|
||||||
"@iconify-json/simple-icons": "^1.2.32",
|
"@iconify-json/simple-icons": "^1.2.33",
|
||||||
"@nuxt/ui": "latest",
|
"@nuxt/ui": "latest",
|
||||||
"@nuxthub/core": "^0.8.24",
|
"@nuxthub/core": "^0.8.27",
|
||||||
"nuxt": "^3.16.2",
|
"nuxt": "^3.17.2",
|
||||||
"zod": "^3.24.3"
|
"zod": "^3.24.4"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"typescript": "^5.8.3",
|
||||||
|
"vue-tsc": "^2.2.10"
|
||||||
|
},
|
||||||
|
"resolutions": {
|
||||||
|
"unimport": "4.1.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
4450
pnpm-lock.yaml
generated
4450
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -5,9 +5,6 @@
|
|||||||
"lockFileMaintenance": {
|
"lockFileMaintenance": {
|
||||||
"enabled": true
|
"enabled": true
|
||||||
},
|
},
|
||||||
"ignoreDeps": [
|
|
||||||
"vue-tsc"
|
|
||||||
],
|
|
||||||
"baseBranches": ["v2", "v3"],
|
"baseBranches": ["v2", "v3"],
|
||||||
"packageRules": [{
|
"packageRules": [{
|
||||||
"matchBaseBranches": ["v3"],
|
"matchBaseBranches": ["v3"],
|
||||||
@@ -19,6 +16,11 @@
|
|||||||
"@tailwindcss/postcss",
|
"@tailwindcss/postcss",
|
||||||
"@tailwindcss/vite"
|
"@tailwindcss/vite"
|
||||||
]
|
]
|
||||||
|
}, {
|
||||||
|
"groupName": "reka-ui",
|
||||||
|
"matchPackageNames": [
|
||||||
|
"reka-ui"
|
||||||
|
]
|
||||||
}, {
|
}, {
|
||||||
"matchDepTypes": ["peerDependencies"],
|
"matchDepTypes": ["peerDependencies"],
|
||||||
"enabled": false
|
"enabled": false
|
||||||
|
|||||||
16
scripts/release.sh
Executable file
16
scripts/release.sh
Executable file
@@ -0,0 +1,16 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Restore all git changes
|
||||||
|
git restore -s@ -SW -- .
|
||||||
|
|
||||||
|
# Update token
|
||||||
|
if [[ ! -z ${NODE_AUTH_TOKEN} ]] ; then
|
||||||
|
echo "//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}" >> ~/.npmrc
|
||||||
|
echo "registry=https://registry.npmjs.org/" >> ~/.npmrc
|
||||||
|
echo "always-auth=true" >> ~/.npmrc
|
||||||
|
npm whoami
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Release package
|
||||||
|
echo "Publishing @nuxt/ui"
|
||||||
|
npm publish --access public
|
||||||
@@ -93,7 +93,7 @@ export default defineNuxtModule<ModuleOptions>({
|
|||||||
|
|
||||||
await registerModule('@nuxt/icon', 'icon', { cssLayer: 'components' })
|
await registerModule('@nuxt/icon', 'icon', { cssLayer: 'components' })
|
||||||
if (options.fonts) {
|
if (options.fonts) {
|
||||||
await registerModule('@nuxt/fonts', 'fonts', { experimental: { processCSSVariables: true } })
|
await registerModule('@nuxt/fonts', 'fonts', {})
|
||||||
}
|
}
|
||||||
if (options.colorMode) {
|
if (options.colorMode) {
|
||||||
await registerModule('@nuxtjs/color-mode', 'colorMode', { classSuffix: '', disableTransition: true })
|
await registerModule('@nuxtjs/color-mode', 'colorMode', { classSuffix: '', disableTransition: true })
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export default function PluginsPlugin(options: NuxtUIOptions) {
|
|||||||
const plugins = globSync(['**/*', '!*.d.ts'], { cwd: join(runtimeDir, 'plugins'), absolute: true })
|
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.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) {
|
if (options.colorMode) {
|
||||||
plugins.push(resolvePathSync('../runtime/vue/plugins/color-mode', { extensions: ['.ts', '.mjs', '.js'], url: import.meta.url }))
|
plugins.push(resolvePathSync('../runtime/vue/plugins/color-mode', { extensions: ['.ts', '.mjs', '.js'], url: import.meta.url }))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.accordion ||
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<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
|
<AccordionItem
|
||||||
v-for="(item, index) in props.items"
|
v-for="(item, index) in props.items"
|
||||||
v-slot="{ open }"
|
v-slot="{ open }"
|
||||||
@@ -98,7 +98,7 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.accordion ||
|
|||||||
:disabled="item.disabled"
|
:disabled="item.disabled"
|
||||||
:class="ui.item({ class: props.ui?.item })"
|
:class="ui.item({ class: props.ui?.item })"
|
||||||
>
|
>
|
||||||
<AccordionHeader :class="ui.header({ class: props.ui?.header })">
|
<AccordionHeader as="div" :class="ui.header({ class: props.ui?.header })">
|
||||||
<AccordionTrigger :class="ui.trigger({ class: props.ui?.trigger, disabled: item.disabled })">
|
<AccordionTrigger :class="ui.trigger({ class: props.ui?.trigger, disabled: item.disabled })">
|
||||||
<slot name="leading" :item="item" :index="index" :open="open">
|
<slot name="leading" :item="item" :index="index" :open="open">
|
||||||
<UIcon v-if="item.icon" :name="item.icon" :class="ui.leadingIcon({ class: props.ui?.leadingIcon })" />
|
<UIcon v-if="item.icon" :name="item.icon" :class="ui.leadingIcon({ class: props.ui?.leadingIcon })" />
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.alert || {})
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<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">
|
<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 })" />
|
<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 })" />
|
<UIcon v-else-if="icon" :name="icon" :class="ui.icon({ class: props.ui?.icon })" />
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ function onError() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<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
|
<component
|
||||||
:is="ImageComponent"
|
:is="ImageComponent"
|
||||||
v-if="src && !error"
|
v-if="src && !error"
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ provide(avatarGroupInjectionKey, computed(() => ({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<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 })" />
|
<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 })" />
|
<component :is="avatar" v-for="(avatar, count) in visibleAvatars" :key="count" :class="ui.base({ class: props.ui?.base })" />
|
||||||
</Primitive>
|
</Primitive>
|
||||||
|
|||||||
@@ -65,14 +65,14 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.badge || {})
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<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">
|
<slot name="leading">
|
||||||
<UIcon v-if="isLeading && leadingIconName" :name="leadingIconName" :class="ui.leadingIcon({ class: props.ui?.leadingIcon })" />
|
<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 })" />
|
<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>
|
||||||
|
|
||||||
<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 }}
|
{{ label }}
|
||||||
</span>
|
</span>
|
||||||
</slot>
|
</slot>
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.breadcrumb |
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<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 })">
|
<ol :class="ui.list({ class: props.ui?.list })">
|
||||||
<template v-for="(item, index) in items" :key="index">
|
<template v-for="(item, index) in items" :key="index">
|
||||||
<li :class="ui.item({ class: props.ui?.item })">
|
<li :class="ui.item({ class: props.ui?.item })">
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ const ui = computed(() => tv({
|
|||||||
v-slot="{ active, ...slotProps }"
|
v-slot="{ active, ...slotProps }"
|
||||||
:type="type"
|
:type="type"
|
||||||
:disabled="disabled || isLoading"
|
: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'])"
|
v-bind="omit(linkProps, ['type', 'disabled', 'onClick'])"
|
||||||
custom
|
custom
|
||||||
>
|
>
|
||||||
@@ -143,7 +143,7 @@ const ui = computed(() => tv({
|
|||||||
</slot>
|
</slot>
|
||||||
|
|
||||||
<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 }}
|
{{ label }}
|
||||||
</span>
|
</span>
|
||||||
</slot>
|
</slot>
|
||||||
|
|||||||
@@ -157,7 +157,7 @@ const Calendar = computed(() => props.range ? RangeCalendar : SingleCalendar)
|
|||||||
:default-value="defaultValue"
|
:default-value="defaultValue"
|
||||||
:locale="locale"
|
:locale="locale"
|
||||||
:dir="dir"
|
: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.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>
|
<Calendar.Prev v-if="props.yearControls" :prev-page="(date: DateValue) => paginateYear(date, -1)" :aria-label="t('calendar.prevYear')" as-child>
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.card || {})
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<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 })">
|
<div v-if="!!slots.header" :class="ui.header({ class: props.ui?.header })">
|
||||||
<slot name="header" />
|
<slot name="header" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -99,6 +99,13 @@ export type CarouselSlots<T extends CarouselItem = CarouselItem> = {
|
|||||||
default(props: { item: T, index: number }): any
|
default(props: { item: T, index: number }): any
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CarouselEmits {
|
||||||
|
/**
|
||||||
|
* Emitted when the selected slide changes
|
||||||
|
* @param selectedIndex The index of the selected slide
|
||||||
|
*/
|
||||||
|
select: [selectedIndex: number]
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script setup lang="ts" generic="T extends CarouselItem">
|
<script setup lang="ts" generic="T extends CarouselItem">
|
||||||
@@ -141,6 +148,7 @@ const props = withDefaults(defineProps<CarouselProps<T>>(), {
|
|||||||
wheelGestures: false
|
wheelGestures: false
|
||||||
})
|
})
|
||||||
defineSlots<CarouselSlots<T>>()
|
defineSlots<CarouselSlots<T>>()
|
||||||
|
const emits = defineEmits<CarouselEmits>()
|
||||||
|
|
||||||
const { dir, t } = useLocale()
|
const { dir, t } = useLocale()
|
||||||
const appConfig = useAppConfig() as Carousel['AppConfig']
|
const appConfig = useAppConfig() as Carousel['AppConfig']
|
||||||
@@ -242,6 +250,8 @@ function onSelect(api: EmblaCarouselType) {
|
|||||||
canScrollNext.value = api?.canScrollNext() || false
|
canScrollNext.value = api?.canScrollNext() || false
|
||||||
canScrollPrev.value = api?.canScrollPrev() || false
|
canScrollPrev.value = api?.canScrollPrev() || false
|
||||||
selectedIndex.value = api?.selectedScrollSnap() || 0
|
selectedIndex.value = api?.selectedScrollSnap() || 0
|
||||||
|
|
||||||
|
emits('select', selectedIndex.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@@ -268,7 +278,7 @@ defineExpose({
|
|||||||
role="region"
|
role="region"
|
||||||
aria-roledescription="carousel"
|
aria-roledescription="carousel"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
:class="ui.root({ class: [props.class, props.ui?.root] })"
|
:class="ui.root({ class: [props.ui?.root, props.class] })"
|
||||||
@keydown="onKeyDown"
|
@keydown="onKeyDown"
|
||||||
>
|
>
|
||||||
<div ref="emblaRef" :class="ui.viewport({ class: props.ui?.viewport })">
|
<div ref="emblaRef" :class="ui.viewport({ class: props.ui?.viewport })">
|
||||||
|
|||||||
@@ -18,10 +18,19 @@ export interface CheckboxProps extends Pick<CheckboxRootProps, 'disabled' | 'req
|
|||||||
* @defaultValue 'primary'
|
* @defaultValue 'primary'
|
||||||
*/
|
*/
|
||||||
color?: Checkbox['variants']['color']
|
color?: Checkbox['variants']['color']
|
||||||
|
/**
|
||||||
|
* @defaultValue 'list'
|
||||||
|
*/
|
||||||
|
variant?: Checkbox['variants']['variant']
|
||||||
/**
|
/**
|
||||||
* @defaultValue 'md'
|
* @defaultValue 'md'
|
||||||
*/
|
*/
|
||||||
size?: Checkbox['variants']['size']
|
size?: Checkbox['variants']['size']
|
||||||
|
/**
|
||||||
|
* Position of the indicator.
|
||||||
|
* @defaultValue 'start'
|
||||||
|
*/
|
||||||
|
indicator?: Checkbox['variants']['indicator']
|
||||||
/**
|
/**
|
||||||
* The icon displayed when checked.
|
* The icon displayed when checked.
|
||||||
* @defaultValue appConfig.ui.icons.check
|
* @defaultValue appConfig.ui.icons.check
|
||||||
@@ -75,9 +84,10 @@ const id = _id.value ?? useId()
|
|||||||
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.checkbox || {}) })({
|
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.checkbox || {}) })({
|
||||||
size: size.value,
|
size: size.value,
|
||||||
color: color.value,
|
color: color.value,
|
||||||
|
variant: props.variant,
|
||||||
|
indicator: props.indicator,
|
||||||
required: props.required,
|
required: props.required,
|
||||||
disabled: disabled.value,
|
disabled: disabled.value
|
||||||
checked: Boolean(modelValue.value ?? props.defaultValue)
|
|
||||||
}))
|
}))
|
||||||
|
|
||||||
function onUpdate(value: any) {
|
function onUpdate(value: any) {
|
||||||
@@ -91,7 +101,7 @@ function onUpdate(value: any) {
|
|||||||
|
|
||||||
<!-- eslint-disable vue/no-template-shadow -->
|
<!-- eslint-disable vue/no-template-shadow -->
|
||||||
<template>
|
<template>
|
||||||
<Primitive :as="as" :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 })">
|
<div :class="ui.container({ class: props.ui?.container })">
|
||||||
<CheckboxRoot
|
<CheckboxRoot
|
||||||
:id="id"
|
:id="id"
|
||||||
@@ -103,7 +113,7 @@ function onUpdate(value: any) {
|
|||||||
@update:model-value="onUpdate"
|
@update:model-value="onUpdate"
|
||||||
>
|
>
|
||||||
<template #default="{ modelValue }">
|
<template #default="{ modelValue }">
|
||||||
<CheckboxIndicator as-child>
|
<CheckboxIndicator :class="ui.indicator({ class: props.ui?.indicator })">
|
||||||
<UIcon v-if="modelValue === 'indeterminate'" :name="indeterminateIcon || appConfig.ui.icons.minus" :class="ui.icon({ class: props.ui?.icon })" />
|
<UIcon v-if="modelValue === 'indeterminate'" :name="indeterminateIcon || appConfig.ui.icons.minus" :class="ui.icon({ class: props.ui?.icon })" />
|
||||||
<UIcon v-else :name="icon || appConfig.ui.icons.check" :class="ui.icon({ class: props.ui?.icon })" />
|
<UIcon v-else :name="icon || appConfig.ui.icons.check" :class="ui.icon({ class: props.ui?.icon })" />
|
||||||
</CheckboxIndicator>
|
</CheckboxIndicator>
|
||||||
@@ -112,11 +122,11 @@ function onUpdate(value: any) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="(label || !!slots.label) || (description || !!slots.description)" :class="ui.wrapper({ class: props.ui?.wrapper })">
|
<div v-if="(label || !!slots.label) || (description || !!slots.description)" :class="ui.wrapper({ class: props.ui?.wrapper })">
|
||||||
<Label v-if="label || !!slots.label" :for="id" :class="ui.label({ class: props.ui?.label })">
|
<component :is="variant === 'list' ? Label : 'p'" v-if="label || !!slots.label" :for="id" :class="ui.label({ class: props.ui?.label })">
|
||||||
<slot name="label" :label="label">
|
<slot name="label" :label="label">
|
||||||
{{ label }}
|
{{ label }}
|
||||||
</slot>
|
</slot>
|
||||||
</Label>
|
</component>
|
||||||
<p v-if="description || !!slots.description" :class="ui.description({ class: props.ui?.description })">
|
<p v-if="description || !!slots.description" :class="ui.description({ class: props.ui?.description })">
|
||||||
<slot name="description" :description="description">
|
<slot name="description" :description="description">
|
||||||
{{ description }}
|
{{ description }}
|
||||||
|
|||||||
183
src/runtime/components/CheckboxGroup.vue
Normal file
183
src/runtime/components/CheckboxGroup.vue
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { CheckboxGroupRootProps, CheckboxGroupRootEmits } from 'reka-ui'
|
||||||
|
import type { AppConfig } from '@nuxt/schema'
|
||||||
|
import theme from '#build/ui/checkbox-group'
|
||||||
|
import type { CheckboxProps } from '../types'
|
||||||
|
import type { AcceptableValue, ComponentConfig } from '../types/utils'
|
||||||
|
|
||||||
|
type CheckboxGroup = ComponentConfig<typeof theme, AppConfig, 'checkboxGroup'>
|
||||||
|
|
||||||
|
export type CheckboxGroupValue = AcceptableValue
|
||||||
|
|
||||||
|
export type CheckboxGroupItem = {
|
||||||
|
label?: string
|
||||||
|
description?: string
|
||||||
|
disabled?: boolean
|
||||||
|
value?: string
|
||||||
|
[key: string]: any
|
||||||
|
} | CheckboxGroupValue
|
||||||
|
|
||||||
|
export interface CheckboxGroupProps<T extends CheckboxGroupItem = CheckboxGroupItem> extends Pick<CheckboxGroupRootProps, 'defaultValue' | 'disabled' | 'loop' | 'modelValue' | 'name' | 'required'>, Pick<CheckboxProps, 'color' | 'variant' | 'indicator' | 'icon'> {
|
||||||
|
/**
|
||||||
|
* The element or component this component should render as.
|
||||||
|
* @defaultValue 'div'
|
||||||
|
*/
|
||||||
|
as?: any
|
||||||
|
legend?: string
|
||||||
|
/**
|
||||||
|
* When `items` is an array of objects, select the field to use as the value.
|
||||||
|
* @defaultValue 'value'
|
||||||
|
*/
|
||||||
|
valueKey?: string
|
||||||
|
/**
|
||||||
|
* When `items` is an array of objects, select the field to use as the label.
|
||||||
|
* @defaultValue 'label'
|
||||||
|
*/
|
||||||
|
labelKey?: string
|
||||||
|
/**
|
||||||
|
* When `items` is an array of objects, select the field to use as the description.
|
||||||
|
* @defaultValue 'description'
|
||||||
|
*/
|
||||||
|
descriptionKey?: string
|
||||||
|
items?: T[]
|
||||||
|
/**
|
||||||
|
* @defaultValue 'md'
|
||||||
|
*/
|
||||||
|
size?: CheckboxGroup['variants']['size']
|
||||||
|
/**
|
||||||
|
* The orientation the checkbox buttons are laid out.
|
||||||
|
* @defaultValue 'vertical'
|
||||||
|
*/
|
||||||
|
orientation?: CheckboxGroupRootProps['orientation']
|
||||||
|
class?: any
|
||||||
|
ui?: CheckboxGroup['slots'] & CheckboxProps['ui']
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CheckboxGroupEmits = CheckboxGroupRootEmits & {
|
||||||
|
change: [payload: Event]
|
||||||
|
}
|
||||||
|
|
||||||
|
type SlotProps<T extends CheckboxGroupItem> = (props: { item: T & { id: string } }) => any
|
||||||
|
|
||||||
|
export interface CheckboxGroupSlots<T extends CheckboxGroupItem = CheckboxGroupItem> {
|
||||||
|
legend(props?: {}): any
|
||||||
|
label: SlotProps<T>
|
||||||
|
description: SlotProps<T>
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script setup lang="ts" generic="T extends CheckboxGroupItem">
|
||||||
|
import { computed, useId } from 'vue'
|
||||||
|
import { CheckboxGroupRoot, useForwardProps, useForwardPropsEmits } from 'reka-ui'
|
||||||
|
import { reactivePick } from '@vueuse/core'
|
||||||
|
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',
|
||||||
|
labelKey: 'label',
|
||||||
|
descriptionKey: 'description',
|
||||||
|
orientation: 'vertical'
|
||||||
|
})
|
||||||
|
const emits = defineEmits<CheckboxGroupEmits>()
|
||||||
|
const slots = defineSlots<CheckboxGroupSlots<T>>()
|
||||||
|
|
||||||
|
const appConfig = useAppConfig() as CheckboxGroup['AppConfig']
|
||||||
|
|
||||||
|
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'modelValue', 'defaultValue', 'orientation', 'loop', 'required'), emits)
|
||||||
|
const checkboxProps = useForwardProps(reactivePick(props, 'variant', 'indicator', 'icon'))
|
||||||
|
const proxySlots = omit(slots, ['legend'])
|
||||||
|
|
||||||
|
const { emitFormChange, emitFormInput, color, name, size, id: _id, disabled, ariaAttrs } = useFormField<CheckboxGroupProps<T>>(props, { bind: false })
|
||||||
|
const id = _id.value ?? useId()
|
||||||
|
|
||||||
|
const ui = computed(() => tv({ extend: theme, ...(appConfig.ui?.checkboxGroup || {}) })({
|
||||||
|
size: size.value,
|
||||||
|
required: props.required,
|
||||||
|
orientation: props.orientation
|
||||||
|
}))
|
||||||
|
|
||||||
|
function normalizeItem(item: any) {
|
||||||
|
if (item === null) {
|
||||||
|
return {
|
||||||
|
id: `${id}:null`,
|
||||||
|
value: undefined,
|
||||||
|
label: undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof item === 'string' || typeof item === 'number') {
|
||||||
|
return {
|
||||||
|
id: `${id}:${item}`,
|
||||||
|
value: String(item),
|
||||||
|
label: String(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = get(item, props.valueKey as string)
|
||||||
|
const label = get(item, props.labelKey as string)
|
||||||
|
const description = get(item, props.descriptionKey as string)
|
||||||
|
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
value,
|
||||||
|
label,
|
||||||
|
description,
|
||||||
|
id: `${id}:${value}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizedItems = computed(() => {
|
||||||
|
if (!props.items) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return props.items.map(normalizeItem)
|
||||||
|
})
|
||||||
|
|
||||||
|
function onUpdate(value: any) {
|
||||||
|
// @ts-expect-error - 'target' does not exist in type 'EventInit'
|
||||||
|
const event = new Event('change', { target: { value } })
|
||||||
|
emits('change', event)
|
||||||
|
emitFormChange()
|
||||||
|
emitFormInput()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- eslint-disable vue/no-template-shadow -->
|
||||||
|
<template>
|
||||||
|
<CheckboxGroupRoot
|
||||||
|
:id="id"
|
||||||
|
v-bind="rootProps"
|
||||||
|
:name="name"
|
||||||
|
:disabled="disabled"
|
||||||
|
:class="ui.root({ class: [props.ui?.root, props.class] })"
|
||||||
|
@update:model-value="onUpdate"
|
||||||
|
>
|
||||||
|
<fieldset :class="ui.fieldset({ class: props.ui?.fieldset })" v-bind="ariaAttrs">
|
||||||
|
<legend v-if="legend || !!slots.legend" :class="ui.legend({ class: props.ui?.legend })">
|
||||||
|
<slot name="legend">
|
||||||
|
{{ legend }}
|
||||||
|
</slot>
|
||||||
|
</legend>
|
||||||
|
|
||||||
|
<UCheckbox
|
||||||
|
v-for="item in normalizedItems"
|
||||||
|
:key="item.value"
|
||||||
|
v-bind="{ ...item, ...checkboxProps }"
|
||||||
|
:color="color"
|
||||||
|
:size="size"
|
||||||
|
:name="name"
|
||||||
|
:disabled="item.disabled || disabled"
|
||||||
|
:ui="props.ui ? omit(props.ui, ['root']) : undefined"
|
||||||
|
:class="ui.item({ class: props.ui?.item })"
|
||||||
|
>
|
||||||
|
<template v-for="(_, name) in proxySlots" #[name]>
|
||||||
|
<slot :name="(name as keyof CheckboxGroupSlots<T>)" :item="item" />
|
||||||
|
</template>
|
||||||
|
</UCheckbox>
|
||||||
|
</fieldset>
|
||||||
|
</CheckboxGroupRoot>
|
||||||
|
</template>
|
||||||
@@ -74,7 +74,7 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.chip || {})
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<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 v-bind="$attrs">
|
||||||
<slot />
|
<slot />
|
||||||
</Slot>
|
</Slot>
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.collapsible
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<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>
|
<CollapsibleTrigger v-if="!!slots.default" as-child>
|
||||||
<slot :open="open" />
|
<slot :open="open" />
|
||||||
</CollapsibleTrigger>
|
</CollapsibleTrigger>
|
||||||
|
|||||||
@@ -263,7 +263,7 @@ const trackThumbStyle = computed(() => ({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<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 :class="ui.picker({ class: props.ui?.picker })">
|
||||||
<div
|
<div
|
||||||
ref="selectorRef"
|
ref="selectorRef"
|
||||||
|
|||||||
@@ -249,7 +249,7 @@ const groups = computed(() => {
|
|||||||
|
|
||||||
<!-- eslint-disable vue/no-v-html -->
|
<!-- eslint-disable vue/no-v-html -->
|
||||||
<template>
|
<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>
|
<ListboxFilter v-model="searchTerm" as-child>
|
||||||
<UInput
|
<UInput
|
||||||
:placeholder="placeholder || t('commandPalette.placeholder')"
|
:placeholder="placeholder || t('commandPalette.placeholder')"
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user