Compare commits

..

5 Commits

Author SHA1 Message Date
Romain Hamel
3d62371af0 chore: up 2025-03-28 16:22:31 +01:00
Romain Hamel
f941df1541 chore: up 2025-03-28 08:58:21 +01:00
Romain Hamel
664e940098 chore: up 2025-03-26 13:45:17 +01:00
Romain Hamel
15fe0039f0 chore(playground): compodium setup 2025-03-26 11:40:23 +01:00
Romain Hamel
f68061975c chore: setup compodium in playground 2025-03-24 15:08:07 +01:00
757 changed files with 28240 additions and 47734 deletions

1
.github/CODEOWNERS vendored
View File

@@ -1 +0,0 @@
* @benjamincanac

View File

@@ -5,12 +5,12 @@ body:
- type: markdown - type: markdown
attributes: attributes:
value: | value: |
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). 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).
- type: textarea - type: textarea
id: env id: env
attributes: attributes:
label: Environment label: Environment
description: You can use `npx nuxt info` to fill this section description: You can use `npx nuxi info` to fill this section
placeholder: | placeholder: |
- Operating System: `Darwin` - Operating System: `Darwin`
- Node Version: `v18.16.0` - Node Version: `v18.16.0`
@@ -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, it will be closed automatically after a while. 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.
placeholder: https://github.com/my/reproduction placeholder: https://github.com/my/reproduction
validations: validations:
required: true required: true

View File

@@ -10,7 +10,7 @@ body:
id: env id: env
attributes: attributes:
label: Environment label: Environment
description: You can use `npx nuxt info` to fill this section description: You can use `npx nuxi info` to fill this section
placeholder: | placeholder: |
- Operating System: `Darwin` - Operating System: `Darwin`
- Node Version: `v18.16.0` - Node Version: `v18.16.0`

View File

@@ -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 [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 [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).
- type: textarea - type: textarea
id: description id: description
attributes: attributes:

View File

@@ -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 [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 [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).
- type: textarea - type: textarea
id: description id: description
attributes: attributes:

View File

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

View File

@@ -6,6 +6,10 @@ jobs:
deploy: deploy:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
environment:
name: ${{ github.ref == 'refs/heads/v3' && 'production' || 'preview' }}
url: ${{ steps.deploy.outputs.deployment-url }}
permissions: permissions:
contents: read contents: read
id-token: write id-token: write
@@ -36,10 +40,12 @@ jobs:
- name: Prepare build - name: Prepare build
run: pnpm run dev:prepare run: pnpm run dev:prepare
- name: Build application
run: pnpm run docs:build
- name: Deploy to NuxtHub - name: Deploy to NuxtHub
uses: nuxt-hub/action@v2 uses: nuxt-hub/action@v1
env: id: deploy
NODE_OPTIONS: '--max-old-space-size=8192'
with: with:
project-key: ui-7eg3 project-key: ui-7eg3
directory: docs directory: docs/dist

View File

@@ -18,7 +18,7 @@ jobs:
strategy: strategy:
matrix: matrix:
os: ${{ github.event_name == 'pull_request' && fromJSON('["ubuntu-latest"]') || fromJSON('["ubuntu-latest", "windows-latest"]') }} # macos-latest os: [ubuntu-latest] # macos-latest, windows-latest
node: [22] node: [22]
env: env:
@@ -65,57 +65,8 @@ jobs:
run: pnpm run dev:vue:build run: pnpm run dev:vue:build
- name: Publish - name: Publish
# Only publish preview package on ubuntu during PRs
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.event.pull_request.head.sha || github.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 nuxt prepare
- name: Typecheck
run: pnpm run typecheck
starter-nuxt: starter-nuxt:
needs: build needs: build
@@ -138,7 +89,7 @@ jobs:
- name: Store commit SHA - name: Store commit SHA
run: | run: |
echo "COMMIT_SHA=$(echo ${{ github.event.pull_request.head.sha || github.sha }} | cut -c1-7)" >> $GITHUB_ENV echo "COMMIT_SHA=$(echo ${{ github.workflow_sha }} | cut -c1-7)" >> $GITHUB_ENV
- name: Install pnpm - name: Install pnpm
uses: pnpm/action-setup@v4 uses: pnpm/action-setup@v4
@@ -183,7 +134,7 @@ jobs:
- name: Store commit SHA - name: Store commit SHA
run: | run: |
echo "COMMIT_SHA=$(echo ${{ github.event.pull_request.head.sha || github.sha }} | cut -c1-7)" >> $GITHUB_ENV echo "COMMIT_SHA=$(echo ${{ github.workflow_sha }} | cut -c1-7)" >> $GITHUB_ENV
- name: Install pnpm - name: Install pnpm
uses: pnpm/action-setup@v4 uses: pnpm/action-setup@v4
@@ -209,9 +160,6 @@ jobs:
nuxt-ui-pro: nuxt-ui-pro:
needs: build needs: build
# Only run this job if not a fork PR (when push event or PR from same repo)
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
permissions: permissions:
@@ -235,7 +183,7 @@ jobs:
- name: Store commit SHA - name: Store commit SHA
run: | run: |
echo "COMMIT_SHA=$(echo ${{ github.event.pull_request.head.sha || github.sha }} | cut -c1-7)" >> $GITHUB_ENV echo "COMMIT_SHA=$(echo ${{ github.workflow_sha }} | cut -c1-7)" >> $GITHUB_ENV
- name: Install pnpm - name: Install pnpm
uses: pnpm/action-setup@v4 uses: pnpm/action-setup@v4

View File

@@ -9,6 +9,10 @@ jobs:
deploy: deploy:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
environment:
name: ${{ github.ref == 'refs/heads/v3' && 'production' || 'preview' }}
url: ${{ steps.deploy.outputs.deployment-url }}
permissions: permissions:
contents: read contents: read
id-token: write id-token: write
@@ -36,10 +40,14 @@ jobs:
- name: Prepare build - name: Prepare build
run: pnpm run dev:prepare run: pnpm run dev:prepare
- name: Deploy to NuxtHub - name: Build application
uses: nuxt-hub/action@v2 run: pnpm run dev:build
env: env:
NODE_OPTIONS: '--max-old-space-size=8192' NITRO_PRESET: cloudflare-pages
- name: Deploy to NuxtHub
uses: nuxt-hub/action@v1
id: deploy
with: with:
project-key: ui3-playground-pb9b project-key: ui3-playground-pb9b
directory: playground directory: playground/dist

View File

@@ -1,54 +0,0 @@
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 }}

View File

@@ -1,27 +0,0 @@
name: reproduction
on:
workflow_dispatch:
schedule:
- cron: '30 1 * * *'
jobs:
reproduction:
runs-on: ubuntu-latest
permissions:
actions: write
issues: write
steps:
- uses: actions/stale@v9
with:
days-before-stale: -1 # Issues and PR will never be flagged stale automatically.
stale-issue-label: 'needs reproduction' # Label that flags an issue as stale.
only-labels: 'needs reproduction' # Only process these issues
days-before-issue-close: 7
ignore-updates: true
remove-stale-when-updated: false
close-issue-message: This issue was closed because it was open for 7 days without a reproduction.
close-issue-label: closed-by-bot
operations-per-run: 300 #default 30

View File

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

View File

@@ -1,7 +1,6 @@
name: stale name: stale
on: on:
workflow_dispatch:
schedule: schedule:
- cron: '30 1 * * *' - cron: '30 1 * * *'
@@ -10,28 +9,15 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
actions: write
issues: write issues: write
pull-requests: write
steps: steps:
- uses: actions/stale@4c023f01d613e60293d8004f251a18bfb9bbd71d - uses: actions/stale@v9
with: with:
days-before-pr-stale: -1 exempt-issue-labels: triage,v3
days-before-stale: 60 stale-issue-message: 'This issue is stale because it has been open for 30 days with no activity.'
days-before-close: 7 stale-issue-label: stale
stale-issue-label: 'stale' stale-pr-label: stale
close-issue-label: 'closed-by-bot' days-before-stale: 30
close-issue-message: | days-before-close: -1
Hi! 👋
This issue has been automatically **closed** due to prolonged inactivity.
We're a small team and can't address every report, but we appreciate your feedback and contributions.
If this issue is still relevant with the latest version of Nuxt UI, please feel free to reopen or create a new issue with updated details.
Thank you for your understanding and support!
— Nuxt UI Team
exempt-issue-labels: 'feature,announcement,release,reka-ui,upstream'
operations-per-run: 300

1
.npmrc
View File

@@ -1,3 +1,4 @@
shamefully-hoist=true shamefully-hoist=true
auto-install-peers=true auto-install-peers=true
ignore-workspace-root-check=true ignore-workspace-root-check=true
shell-emulator=true

View File

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

View File

@@ -3,9 +3,6 @@
"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}",

View File

@@ -1,251 +1,5 @@
# Changelog # Changelog
## [3.2.0](https://github.com/nuxt/ui/compare/v3.1.3...v3.2.0) (2025-06-25)
### ⚠ BREAKING CHANGES
* **useOverlay:** correct spelling of `unmount` function (#4051)
### Features
* **Avatar:** add `chip` prop ([#4224](https://github.com/nuxt/ui/issues/4224)) ([03ac395](https://github.com/nuxt/ui/commit/03ac395164c02c964361c68743268b1bc90aae59))
* **Carousel:** allow customization of active dot color ([#4229](https://github.com/nuxt/ui/issues/4229)) ([2ee1c5a](https://github.com/nuxt/ui/commit/2ee1c5ac2e20ab9ce2f4037a8e8c64e561b0428b))
* **CommandPalette:** handle `children` in items ([#4226](https://github.com/nuxt/ui/issues/4226)) ([59c26ec](https://github.com/nuxt/ui/commit/59c26ec1230375a24fbaf8a630a696ae854700c7))
* **extendLocale:** new composable ([0f558fc](https://github.com/nuxt/ui/commit/0f558fc0d014d51549222accfc50286d1770d1aa)), closes [#3729](https://github.com/nuxt/ui/issues/3729)
* **Form:** expose loading state to default slot ([#4247](https://github.com/nuxt/ui/issues/4247)) ([ea0c459](https://github.com/nuxt/ui/commit/ea0c459306be585bacaaf5b433114d072550c824))
* **InputTags:** new component ([#4261](https://github.com/nuxt/ui/issues/4261)) ([54bb228](https://github.com/nuxt/ui/commit/54bb2282c58d3bf5a7dde4cdee687c68efd934a0))
* **locale:** add Luxembourgish language ([#4264](https://github.com/nuxt/ui/issues/4264)) ([43cbb94](https://github.com/nuxt/ui/commit/43cbb94ee25106b414fc8fe979fa65ebaa9ccc76))
* **Modal/Slideover:** add `actions` slot ([#4358](https://github.com/nuxt/ui/issues/4358)) ([8156971](https://github.com/nuxt/ui/commit/81569713e9da9d5531ecdf4614660b84c686fa81))
* **Modal/Slideover:** add `close` method in slots ([#4219](https://github.com/nuxt/ui/issues/4219)) ([5835eb5](https://github.com/nuxt/ui/commit/5835eb5f0f835b5f03646dec78f85b2f556a109b))
* **Select/SelectMenu/Tabs:** expose trigger refs ([7a2bd4e](https://github.com/nuxt/ui/commit/7a2bd4e6179373902ba6f285903ea896fd1d378f)), closes [#4292](https://github.com/nuxt/ui/issues/4292)
* **Select/SelectMenu:** handle dynamic `autofocus` ([1a4de49](https://github.com/nuxt/ui/commit/1a4de49c1665c9ef65279315be0393d6272447b9)), closes [#4324](https://github.com/nuxt/ui/issues/4324)
* **Table:** add `body-top` / `body-bottom` slots ([#4354](https://github.com/nuxt/ui/issues/4354)) ([595fc64](https://github.com/nuxt/ui/commit/595fc64515613fe82c3a56fc5518f2e3fcce6e19))
* **Timeline:** add `reverse` prop ([#4316](https://github.com/nuxt/ui/issues/4316)) ([5170cfd](https://github.com/nuxt/ui/commit/5170cfd7eb44a25c64673cf12979f9ca1049695f))
* **Timeline:** new component ([#4215](https://github.com/nuxt/ui/issues/4215)) ([8017767](https://github.com/nuxt/ui/commit/80177679f2aa0d7f0e39e639a02d527a06e6172c))
### Bug Fixes
* **Card/Drawer/Modal:** prevent scrollbars overflow ([#4368](https://github.com/nuxt/ui/issues/4368)) ([c3adc38](https://github.com/nuxt/ui/commit/c3adc381c90dad7152e27fc303ee678efc7c4c94))
* **components:** remove default `md` size on buttons ([#4357](https://github.com/nuxt/ui/issues/4357)) ([be41aed](https://github.com/nuxt/ui/commit/be41aed1f3d3476801e1840dbb8766926bc93c05))
* **defineShortcuts:** allow `meta_-` shortcut ([#4321](https://github.com/nuxt/ui/issues/4321)) ([4e7c1c9](https://github.com/nuxt/ui/commit/4e7c1c9c305b45dd76d4c238e70a6aeedae78c8b))
* **Form:** conditionally type form data via `transform` prop ([#4188](https://github.com/nuxt/ui/issues/4188)) ([37abcc6](https://github.com/nuxt/ui/commit/37abcc6a5b0a678be626673af5067956657a50d6))
* **Form:** expose reactive fields ([#4386](https://github.com/nuxt/ui/issues/4386)) ([1a8feb7](https://github.com/nuxt/ui/commit/1a8feb751e6827c414ef82fe9fb259ba7dcc7e08))
* **InputMenu/SelectMenu:** dynamic `empty` size ([ba3c6e8](https://github.com/nuxt/ui/commit/ba3c6e8788ed75d86d4406749797da52d7816b84)), closes [#4377](https://github.com/nuxt/ui/issues/4377)
* **InputTags:** extend emits interface ([8781a07](https://github.com/nuxt/ui/commit/8781a079096def0d3bae5b8d896db0df6ce37e23))
* **Modal/Slideover:** don't emit `close:prevent` on `closeAutoFocus` ([150b334](https://github.com/nuxt/ui/commit/150b334b1d242c6dc132193e23359c03e6f35666))
* **NavigationMenu:** nested accordion context at every level ([#4363](https://github.com/nuxt/ui/issues/4363)) ([2fa8db6](https://github.com/nuxt/ui/commit/2fa8db64ddf4c92a19e73774143518d87d001b72))
* **NavigationMenu:** set content `max-height` in `horizontal` orientation ([62bc7b2](https://github.com/nuxt/ui/commit/62bc7b25a2d205d8dffb47a109196f91ff3e823a)), closes [#4208](https://github.com/nuxt/ui/issues/4208)
* **Pagination:** match default button `size` ([#4350](https://github.com/nuxt/ui/issues/4350)) ([4dd56c8](https://github.com/nuxt/ui/commit/4dd56c8111e5a224105b82d541b7742b46abb34a))
* **Select/SelectMenu:** display falsy values ([7df7ee3](https://github.com/nuxt/ui/commit/7df7ee336a925d7ee07f866551dad9350785c9fc))
* **Select/SelectMenu:** prevent empty string display when multiple ([483e473](https://github.com/nuxt/ui/commit/483e473e3f5681cc97c3766ea47283dc95f76345))
* **SelectMenu:** dynamic input size ([b0364b9](https://github.com/nuxt/ui/commit/b0364b96b73b9e543781a35962c03b5a983352c4))
* **Table:** use `tr` as separator ([#4083](https://github.com/nuxt/ui/issues/4083)) ([edca3bc](https://github.com/nuxt/ui/commit/edca3bcb743c7eb63e6abbaa801d3858342a8777))
* **Toast:** calc height on next tick ([3bf5acb](https://github.com/nuxt/ui/commit/3bf5acb683f0ad09735b2417d265d6fcfd901b11)), closes [#4265](https://github.com/nuxt/ui/issues/4265)
* **Toaster:** smoother visibility transition for stacked toasts ([#4367](https://github.com/nuxt/ui/issues/4367)) ([abfd0ed](https://github.com/nuxt/ui/commit/abfd0ede036fa2953f9abc841d77ac71bbd3bba9))
* **useOverlay:** correct spelling of `unmount` function ([#4051](https://github.com/nuxt/ui/issues/4051)) ([546df57](https://github.com/nuxt/ui/commit/546df572fca60325315bed17c9be3367052fb7a9))
* **useOverlay:** set props to original props when `defaultOpen` is set ([#4308](https://github.com/nuxt/ui/issues/4308)) ([66355ba](https://github.com/nuxt/ui/commit/66355ba301d569b9f44527bafc5f8f09bcda63c0))
* **useOverlay:** use original props when not provided to `open` ([#4269](https://github.com/nuxt/ui/issues/4269)) ([bf56e15](https://github.com/nuxt/ui/commit/bf56e15a2eed7d51199d5641649a822e91ca41ba))
## [3.1.3](https://github.com/nuxt/ui/compare/v3.1.2...v3.1.3) (2025-05-26)
### ⚠ BREAKING CHANGES
* **NavigationMenu:** revert new `collapsible` field
### Features
* **locale:** add Kyrgyz language ([#4189](https://github.com/nuxt/ui/issues/4189)) ([4053047](https://github.com/nuxt/ui/commit/405304775e4b2b4e8b37a2364f3e5ee34b46036e))
* **locale:** add Lithuanian language ([#4171](https://github.com/nuxt/ui/issues/4171)) ([d86956e](https://github.com/nuxt/ui/commit/d86956e1d57482b3e98eef2d34bff13544284b0b))
* **locale:** add Malay language ([#4160](https://github.com/nuxt/ui/issues/4160)) ([c00f6e8](https://github.com/nuxt/ui/commit/c00f6e8cdfd88eeba58812b78d94a2326c13f164))
* **locale:** add Mongolian language ([#4214](https://github.com/nuxt/ui/issues/4214)) ([44ea02c](https://github.com/nuxt/ui/commit/44ea02c0d64322ef0cfda63b234369c00d3d0180))
* **Modal/Slideover:** add `after:enter` event ([#4187](https://github.com/nuxt/ui/issues/4187)) ([d9e9fea](https://github.com/nuxt/ui/commit/d9e9fea35e4b22d68324c9e85b3aa221a7987d0f))
* **NavigationMenu:** add `tooltip` and `popover` props ([f2682fd](https://github.com/nuxt/ui/commit/f2682fd2ae8abb7807977727fc22ef34cb5752e5)), closes [#4186](https://github.com/nuxt/ui/issues/4186)
* **NavigationMenu:** add `trigger` type in items ([9cf9f25](https://github.com/nuxt/ui/commit/9cf9f25f4424447691e03e9034155d1541badd43))
* **NavigationMenu:** handle `vertical` orientation with Accordion instead of Collapsible ([1e2a10b](https://github.com/nuxt/ui/commit/1e2a10b4bdebaef12316ac60f98a956dad21c1ec)), closes [#4072](https://github.com/nuxt/ui/issues/4072) [#3911](https://github.com/nuxt/ui/issues/3911)
* **Popover:** add `anchor` slot ([#4119](https://github.com/nuxt/ui/issues/4119)) ([473513c](https://github.com/nuxt/ui/commit/473513c2460d4329d7d2e0a0ea69bf1310a072d1))
### Bug Fixes
* **CheckboxGroup/RadioGroup:** variant `table` borders in RTL mode ([#4192](https://github.com/nuxt/ui/issues/4192)) ([43d281f](https://github.com/nuxt/ui/commit/43d281f6d1d8b0017ed61d929c5e311fb5b03447))
* **CommandPalette:** add `presentation` role to viewport ([2ba94db](https://github.com/nuxt/ui/commit/2ba94db09e1ba86020d5d289f1ca1e24ef706299))
* **ContextMenu/DropdownMenu:** wrap groups in a viewport ([dcf34a7](https://github.com/nuxt/ui/commit/dcf34a7ac236b96b1302ec2eae155b8f2d3784ef)), closes [#3315](https://github.com/nuxt/ui/issues/3315)
* **Drawer:** improve title & description accessibility ([41087d4](https://github.com/nuxt/ui/commit/41087d4c9569eb00c04bd748e055cd151c2f762c)), closes [#4199](https://github.com/nuxt/ui/issues/4199)
* **icons:** update `loading` icon ([#4163](https://github.com/nuxt/ui/issues/4163)) ([fe4e1f8](https://github.com/nuxt/ui/commit/fe4e1f859d42aa3c32bb7b75302e84a280abe525))
* **Input/Textarea:** define model modifiers types ([#4195](https://github.com/nuxt/ui/issues/4195)) ([3243fb8](https://github.com/nuxt/ui/commit/3243fb88f71c5475824bfdc4d7c4f303b2d6790b))
* **InputMenu/Select/SelectMenu:** manual viewport to display scrollbars ([f95abf8](https://github.com/nuxt/ui/commit/f95abf8d1d7b9149e400d7dc6f96f93f5154da7a)), closes [#4069](https://github.com/nuxt/ui/issues/4069)
* **NavigationMenu:** incorrect hover when disabled and active ([d0be599](https://github.com/nuxt/ui/commit/d0be59946bfe30c79a6f75476385ab8538aa51b8))
* **NavigationMenu:** only display `tooltip` when collapsed ([44f536f](https://github.com/nuxt/ui/commit/44f536fd0034facb3550d910fae71d4f9442ed19))
* **NavigationMenu:** remove `font-medium` in popover children ([0236399](https://github.com/nuxt/ui/commit/02363994d66d3c2d11b9913f31167fa25f5c5de2))
* **NavigationMenu:** revert new `collapsible` field ([3c78e2f](https://github.com/nuxt/ui/commit/3c78e2fd983f19b5cec65b4a94a8a8b14e548e5e))
* **Textarea:** missing imports ([#4207](https://github.com/nuxt/ui/issues/4207)) ([6aab62e](https://github.com/nuxt/ui/commit/6aab62ec30e266c5f0da0cd24aefbb7c53f447ac))
* **theme:** define `old-neutral` color as static ([#4193](https://github.com/nuxt/ui/issues/4193)) ([dae9f0b](https://github.com/nuxt/ui/commit/dae9f0b8631b3b9fb60ef47753f7aded0c36c4a2))
* **Tooltip:** increase padding for consistency ([0634a75](https://github.com/nuxt/ui/commit/0634a756a496f5131841abafd218ae7e4aaa61e5))
## [3.1.2](https://github.com/nuxt/ui/compare/v3.1.1...v3.1.2) (2025-05-15)
### Features
* **Badge:** add `square` prop ([#4008](https://github.com/nuxt/ui/issues/4008)) ([894e8a6](https://github.com/nuxt/ui/commit/894e8a61b6fea3618fc863bd77678385e9d021c2))
* **CheckboxGroup:** add `table` variant ([#3997](https://github.com/nuxt/ui/issues/3997)) ([1b6ab27](https://github.com/nuxt/ui/commit/1b6ab271ea3875a7c77ffe9367c7c341083dd53c))
* **components:** add `ui` field in items ([#4060](https://github.com/nuxt/ui/issues/4060)) ([b9adc83](https://github.com/nuxt/ui/commit/b9adc83e787db02507e6e7bb1aabc684eccc197b))
* **InputNumber:** add `increment-disabled` / `decrement-disabled` props ([#4141](https://github.com/nuxt/ui/issues/4141)) ([c7fba2e](https://github.com/nuxt/ui/commit/c7fba2e0ebfb7153f3bfb727165d653bbd3dbe54))
* **locale:** add Slovenian language ([#4140](https://github.com/nuxt/ui/issues/4140)) ([e86dc79](https://github.com/nuxt/ui/commit/e86dc79e51b2773a77ada5f12d4f0964fbc83354))
* **NavigationMenu:** add `collapsible` field in items ([2be60cd](https://github.com/nuxt/ui/commit/2be60cddfe10fd1e2466900fd53e21ee0c877227)), closes [#3353](https://github.com/nuxt/ui/issues/3353) [#3911](https://github.com/nuxt/ui/issues/3911)
* **NavigationMenu:** handle `tooltip` in items ([46c2987](https://github.com/nuxt/ui/commit/46c2987ebfd30b2b071a96a745b7270e852e96de)), closes [#4050](https://github.com/nuxt/ui/issues/4050)
* **Slider:** handle `tooltip` around thumbs ([d140acc](https://github.com/nuxt/ui/commit/d140acc608c6ae11c0a0531fe443588776ea7807)), closes [#1469](https://github.com/nuxt/ui/issues/1469)
* **Toast:** add `progress` prop to hide progress bar ([#4125](https://github.com/nuxt/ui/issues/4125)) ([92632e9](https://github.com/nuxt/ui/commit/92632e969eaa11521a166e50e346753929b7f523))
### Bug Fixes
* **Badge/Button:** handle zero value in label correctly ([#4108](https://github.com/nuxt/ui/issues/4108)) ([f244d15](https://github.com/nuxt/ui/commit/f244d15b96d97cd8ba34ba9c18f23965e17e3cef))
* **ButtonGroup:** add `z-index` on focused element ([204953b](https://github.com/nuxt/ui/commit/204953b780bde08dbfde230fc8887674449227b7))
* **Calendar:** wrong color for today date with `neutral` color ([7d51a9e](https://github.com/nuxt/ui/commit/7d51a9e479cb6105ea37759c5cd67ff9f7702c49)), closes [#4084](https://github.com/nuxt/ui/issues/4084) [#3629](https://github.com/nuxt/ui/issues/3629)
* **Checkbox/RadioGroup:** render correct element without `variant` ([f2fd778](https://github.com/nuxt/ui/commit/f2fd778c0a604f2d65aec9f3fe2d54b6d4e8c3a2)), closes [#3998](https://github.com/nuxt/ui/issues/3998)
* **CheckboxGroup:** relative `UCheckbox` import ([7551a85](https://github.com/nuxt/ui/commit/7551a85ad2d92b59e2909396affb862403d5b27a)), closes [#4090](https://github.com/nuxt/ui/issues/4090)
* **ColorPicker:** make thumb touch draggable ([#4101](https://github.com/nuxt/ui/issues/4101)) ([cc20a26](https://github.com/nuxt/ui/commit/cc20a26f07268d19119ab4c7c254033143bb63f4))
* **components:** `class` should have priority over `ui` prop ([e6e510b](https://github.com/nuxt/ui/commit/e6e510b848d995a286a51d50a120d67483e11232))
* **FormField:** block form field injection after use ([#4150](https://github.com/nuxt/ui/issues/4150)) ([d79da9d](https://github.com/nuxt/ui/commit/d79da9d7b60c9972af64acd8e6eef4ae7d6bc3eb))
* **FormField:** use `div` for `error` and `help` slots ([459a041](https://github.com/nuxt/ui/commit/459a0410ab729fde60865e84632b36903465f57e))
* **inertia:** link always render as anchor tag ([#3989](https://github.com/nuxt/ui/issues/3989)) ([e81464a](https://github.com/nuxt/ui/commit/e81464a43ede4e63ce3dc92429bbfef48614f731))
* **inertia:** make `useAppConfig` reactive ([12303a8](https://github.com/nuxt/ui/commit/12303a87be62dae84ef774e3a9795deb0ac90cc7))
* **Input/Textarea:** handle generic types ([3c8d6cd](https://github.com/nuxt/ui/commit/3c8d6cd01dfafed5844c376f52adbdda0c814420)), closes [nuxt/ui-pro#887](https://github.com/nuxt/ui-pro/issues/887)
* **InputNumber:** handle inside button group ([2e4c308](https://github.com/nuxt/ui/commit/2e4c3082a1e66fa597086dc3431fec37fa29ef62)), closes [#4155](https://github.com/nuxt/ui/issues/4155)
* **Link:** consistent behavior between nuxt, vue and inertia ([#4134](https://github.com/nuxt/ui/issues/4134)) ([67da90a](https://github.com/nuxt/ui/commit/67da90a2f638124f640c4271d3376c5ff3fab6a1))
* **module:** configure `@nuxt/fonts` with default weights ([276268d](https://github.com/nuxt/ui/commit/276268d311f57715cec47bc600a0ccc3d3885682))
* **NavigationMenu:** arrow position conflict ([#4137](https://github.com/nuxt/ui/issues/4137)) ([0dc4678](https://github.com/nuxt/ui/commit/0dc4678c68e4b500be49c38336dc75b73843e38d))
* **Select:** support more primitive types in `value` field ([#4105](https://github.com/nuxt/ui/issues/4105)) ([09b4699](https://github.com/nuxt/ui/commit/09b4699aeadaa195ea081509f8e237bb2c346238))
* **Slider:** handle generic types ([d7a4d02](https://github.com/nuxt/ui/commit/d7a4d029b77d2dfa0b8efcd2755d482fa5e31fd3))
* **Stepper:** use `div` tag for `title` & `description` ([a57844e](https://github.com/nuxt/ui/commit/a57844e41676c13ed1af861424961b88cee7b4da)), closes [#4096](https://github.com/nuxt/ui/issues/4096)
* **Tabs:** prevent trigger truncate without parent width ([06e5689](https://github.com/nuxt/ui/commit/06e5689da80b36205d0548d5d6b58510938e4a6e)), closes [#4056](https://github.com/nuxt/ui/issues/4056)
* **Tabs:** set `focus:outline-none` with `link` variant ([999a0f8](https://github.com/nuxt/ui/commit/999a0f84671fad20fa3dc50c6774af2e0200b32e))
* **templates:** dont write unused variants in theme files ([d3df3bb](https://github.com/nuxt/ui/commit/d3df3bb929fe6732f27b182d1664213884a662ec))
* **Toaster:** allow `base` slot override ([c63d2f3](https://github.com/nuxt/ui/commit/c63d2f380aac16f1d1e812516df3dca7fa7c8034))
* **vue:** make `useAppConfig` reactive ([869c070](https://github.com/nuxt/ui/commit/869c0708bd351c7be44e5e430c348b19dd316db9)), closes [#3952](https://github.com/nuxt/ui/issues/3952)
## [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)
### Features
* **Calendar:** allow year and month buttons styling ([#3672](https://github.com/nuxt/ui/issues/3672)) ([4a2b77d](https://github.com/nuxt/ui/commit/4a2b77d86c28806234002340eda39de4dc78cce0))
* **locale:** add Armenian language ([#3664](https://github.com/nuxt/ui/issues/3664)) ([c76f590](https://github.com/nuxt/ui/commit/c76f5900970e3f5c451192b1207ccea04771e8b3))
* **Table:** add `empty` prop ([afff54f](https://github.com/nuxt/ui/commit/afff54fecd31497238461e0a44abd8668ed734c3))
### Bug Fixes
* **Avatar:** proxy `$attrs` to default slot ([#3712](https://github.com/nuxt/ui/issues/3712)) ([88f349d](https://github.com/nuxt/ui/commit/88f349d0d74eb1c2ce5066818731759c25a9e83e))
* **Button:** use `focus:outline-none` instead of `focus:outline-hidden` ([c231fe5](https://github.com/nuxt/ui/commit/c231fe5f26ca7614df46a7ec8a5ce7f4ec8884e7)), closes [#3658](https://github.com/nuxt/ui/issues/3658)
* **CommandPalette:** use `group.id` as key ([bc61d29](https://github.com/nuxt/ui/commit/bc61d29cce531715a6279444845f02a002a22af7))
* **components:** improve generic types ([#3331](https://github.com/nuxt/ui/issues/3331)) ([b998354](https://github.com/nuxt/ui/commit/b9983549a4b743724ea3ef99cc4a243f5ca41e53))
* **Container:** add `w-full` class ([df00149](https://github.com/nuxt/ui/commit/df001495980647cab1e67fd16154f1bc778de5e2))
* **defineLocale/defineShortcuts:** remove `@__NO_SIDE_EFFECTS__` ([82e2665](https://github.com/nuxt/ui/commit/82e26655a40782555299516f32a76046fa0dbd3a))
* **Drawer:** remove `fadeFromIndex` prop proxy ([f7604e5](https://github.com/nuxt/ui/commit/f7604e565f717001a4d4c2974cf23559a3f01c21))
* **Form:** clear dirty state after submit ([#3692](https://github.com/nuxt/ui/issues/3692)) ([3dd88ba](https://github.com/nuxt/ui/commit/3dd88bacecb2945efba8cc3cb4fe59fcbc056e9a))
* **FormField:** add `help` to `aria-describedby` attribute ([#3691](https://github.com/nuxt/ui/issues/3691)) ([20c3392](https://github.com/nuxt/ui/commit/20c33920d005332db3c83f33a8c54c7c227ce0a0))
* **InputMenu/SelectMenu:** empty search results ([94b6e52](https://github.com/nuxt/ui/commit/94b6e520f5ccf011204e953421fcc5b44b637e51))
* **InputMenu:** reset `searchTerm` on `update:open` ([3074632](https://github.com/nuxt/ui/commit/3074632523e67fa6a0ad3d9a71e5692c285bdc3a)), closes [#3620](https://github.com/nuxt/ui/issues/3620)
* **Link:** handle `aria-current` like `NuxtLink` / `RouterLink` ([c531d02](https://github.com/nuxt/ui/commit/c531d0248be7863980a1f676643c2dea8301c009))
* **Link:** prevent `active="true"` binding on html ([d73768b](https://github.com/nuxt/ui/commit/d73768b70453d60dd4186a996c1cf808b0294bf6))
* **Link:** properly pick all `aria-*` & `data-*` attrs ([ade16b7](https://github.com/nuxt/ui/commit/ade16b76cf535924a8d0f402b4d5d65cb67a55eb))
* **Link:** proxy `onClick` ([370054b](https://github.com/nuxt/ui/commit/370054b20c0201c9dba84ddfcd1e916594619b93)), closes [#3631](https://github.com/nuxt/ui/issues/3631)
* **NavigationMenu:** add `z-index` on viewport ([0095d89](https://github.com/nuxt/ui/commit/0095d8916bf361c0c89972e2f86b79850510c6a9)), closes [#3654](https://github.com/nuxt/ui/issues/3654)
* **Switch:** prevent transition on focus outline ([68787b2](https://github.com/nuxt/ui/commit/68787b26fdf2bd5f9d9e812e5bfddb19abe45d1d))
* **Table:** wrong condition on `caption` slot ([4ebb94c](https://github.com/nuxt/ui/commit/4ebb94cd7ef909b3547bce0922f75fe3ff74de4c))
* **Tabs:** remove `focus:outline-hidden` class ([1769d5e](https://github.com/nuxt/ui/commit/1769d5ed6ea46b1f7eafdc48cb6456512229f98b))
* **types:** add missing export for ButtonGroup ([#3709](https://github.com/nuxt/ui/issues/3709)) ([e7e6745](https://github.com/nuxt/ui/commit/e7e674559981177ad08be42418746060d7737df9))
* **useOverlay:** refine `open` method type to infer close emit return type ([#3716](https://github.com/nuxt/ui/issues/3716)) ([bd99c2d](https://github.com/nuxt/ui/commit/bd99c2d850d57baccc51e049c0b578a6fc6ab431))
* **vue:** mock `nuxtApp.hooks` & `useRuntimeHook` ([23bfeb9](https://github.com/nuxt/ui/commit/23bfeb937004d619187a67fb43e4c76b13d00069))
## [3.0.1](https://github.com/nuxt/ui/compare/v3.0.0...v3.0.1) (2025-03-21) ## [3.0.1](https://github.com/nuxt/ui/compare/v3.0.0...v3.0.1) (2025-03-21)
### ⚠ BREAKING CHANGES ### ⚠ BREAKING CHANGES

View File

@@ -16,10 +16,6 @@ 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.
@@ -108,17 +104,6 @@ app.mount('#app')
Learn more in the [installation guide](https://ui.nuxt.com/getting-started/installation/vue). Learn more in the [installation guide](https://ui.nuxt.com/getting-started/installation/vue).
## Contribution
Thank you for considering contributing to Nuxt UI. Here are a few ways you can get involved:
- Reporting Bugs: If you come across any bugs or issues, please check out the reporting bugs guide to learn how to submit a bug report.
- Suggestions: Have any thoughts to enhance Nuxt UI? We'd love to hear them! Check out the [contribution guide](https://ui.nuxt.com/getting-started/contribution) to share your suggestions.
## Local Development
Follow the docs to [set up your local development environment](https://ui.nuxt.com/getting-started/contribution#local-development) and contribute.
## Credits ## Credits
- [nuxt/nuxt](https://github.com/nuxt/nuxt) - [nuxt/nuxt](https://github.com/nuxt/nuxt)

View File

@@ -7,13 +7,10 @@ export default defineBuildConfig({
'./src/vite' './src/vite'
], ],
rollup: { rollup: {
replace: { emitCJS: true
delimiters: ['', ''], },
values: { replace: {
// Used in development to import directly from theme 'process.env.DEV': 'false'
'process.argv.includes(\'--uiDev\')': 'false'
}
}
}, },
hooks: { hooks: {
'mkdist:entry:options'(ctx, entry, options) { 'mkdist:entry:options'(ctx, entry, options) {

View File

@@ -31,11 +31,13 @@ const component = ({ name, primitive, pro, prose, content }) => {
? ` ? `
<script lang="ts"> <script lang="ts">
import type { AppConfig } from '@nuxt/schema' import type { AppConfig } from '@nuxt/schema'
${pro ? `import type { ComponentConfig } from '@nuxt/ui'` : ''} import _appConfig from '#build/app.config'
import theme from '#build/${path}/${prose ? 'prose/' : ''}${content ? 'content/' : ''}${kebabName}' import theme from '#build/${path}/${prose ? 'prose/' : ''}${content ? 'content/' : ''}${kebabName}'
${!pro ? `import type { ComponentConfig } from '../types/utils'` : ''} import { tv } from '${pro ? '#ui/utils/tv' : '../utils/tv'}'
type ${upperName} = ComponentConfig<typeof theme, AppConfig, '${camelName}'${pro ? `, '${key}'` : ''}> const appConfig${camelName} = _appConfig as AppConfig & { ${key}: { ${prose ? 'prose: { ' : ''}${camelName}: Partial<typeof theme> } }${prose ? ' }' : ''}
const ${camelName} = tv({ extend: tv(theme), ...(appConfig${camelName}.${key}?.${prose ? 'prose?.' : ''}${camelName} || {}) })
export interface ${upperName}Props { export interface ${upperName}Props {
/** /**
@@ -44,7 +46,7 @@ export interface ${upperName}Props {
*/ */
as?: any as?: any
class?: any class?: any
ui?: ${upperName}['slots'] ui?: Partial<typeof ${camelName}.slots>
} }
export interface ${upperName}Slots { export interface ${upperName}Slots {
@@ -53,38 +55,38 @@ export interface ${upperName}Slots {
</script> </script>
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue'
import { Primitive } from 'reka-ui' import { Primitive } from 'reka-ui'
import { useAppConfig } from '#imports'
import { tv } from '../utils/tv'
const props = defineProps<${upperName}Props>() const props = defineProps<${upperName}Props>()
defineSlots<${upperName}Slots>() defineSlots<${upperName}Slots>()
const appConfig = useAppConfig() as ${upperName}['AppConfig'] const ui = ${camelName}()
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.${pro ? 'uiPro' : 'ui'}?.${camelName} || {}) })())
</script> </script>
<template> <template>
<Primitive :as="as" :class="ui.root({ class: [props.ui?.root, props.class] })"> <Primitive :as="as" :class="ui.root({ class: [props.class, props.ui?.root] })">
<slot /> <slot />
</Primitive> </Primitive>
</template> </template>
` `
: ` : `
<script lang="ts"> <script lang="ts">
import type { VariantProps } from 'tailwind-variants'
import type { ${upperName}RootProps, ${upperName}RootEmits } from 'reka-ui' import type { ${upperName}RootProps, ${upperName}RootEmits } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema' import type { AppConfig } from '@nuxt/schema'
${pro ? `import type { ComponentConfig } from '@nuxt/ui'` : ''} import _appConfig from '#build/app.config'
import theme from '#build/${path}/${prose ? 'prose/' : ''}${content ? 'content/' : ''}${kebabName}' import theme from '#build/${path}/${prose ? 'prose/' : ''}${content ? 'content/' : ''}${kebabName}'
${!pro ? `import type { ComponentConfig } from '../types/utils'` : ''} import { tv } from '${pro ? '#ui/utils/tv' : '../utils/tv'}'
type ${upperName} = ComponentConfig<typeof theme, AppConfig, '${camelName}'${pro ? `, '${key}'` : ''}> const appConfig${camelName} = _appConfig as AppConfig & { ${key}: { ${prose ? 'prose: { ' : ''}${camelName}: Partial<typeof theme> } }${prose ? ' }' : ''}
const ${camelName} = tv({ extend: tv(theme), ...(appConfig${camelName}.${key}?.${prose ? 'prose?.' : ''}${camelName} || {}) })
type ${upperName}Variants = VariantProps<typeof ${camelName}>
export interface ${upperName}Props extends Pick<${upperName}RootProps> { export interface ${upperName}Props extends Pick<${upperName}RootProps> {
class?: any class?: any
ui?: ${upperName}['slots'] ui?: Partial<typeof ${camelName}.slots>
} }
export interface ${upperName}Emits extends ${upperName}RootEmits {} export interface ${upperName}Emits extends ${upperName}RootEmits {}
@@ -93,25 +95,20 @@ export interface ${upperName}Slots {}
</script> </script>
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue'
import { ${upperName}Root, useForwardPropsEmits } from 'reka-ui' import { ${upperName}Root, useForwardPropsEmits } from 'reka-ui'
import { reactivePick } from '@vueuse/core' import { reactivePick } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { tv } from '../utils/tv'
const props = defineProps<${upperName}Props>() const props = defineProps<${upperName}Props>()
const emits = defineEmits<${upperName}Emits>() const emits = defineEmits<${upperName}Emits>()
const slots = defineSlots<${upperName}Slots>() const slots = defineSlots<${upperName}Slots>()
const appConfig = useAppConfig() as ${upperName}['AppConfig']
const rootProps = useForwardPropsEmits(reactivePick(props), emits) const rootProps = useForwardPropsEmits(reactivePick(props), emits)
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.${pro ? 'uiPro' : 'ui'}?.${camelName} || {}) })()) const ui = ${camelName}()
</script> </script>
<template> <template>
<${upperName}Root v-bind="rootProps" :class="ui.root({ class: [props.ui?.root, props.class] })" /> <${upperName}Root v-bind="rootProps" :class="ui.root({ class: [props.class, props.ui?.root] })" />
</template> </template>
` `
} }
@@ -147,8 +144,7 @@ const test = ({ name, prose, content }) => {
? undefined ? undefined
: ` : `
import { describe, it, expect } from 'vitest' import { describe, it, expect } from 'vitest'
import ${upperName} from '../../${content ? '../' : ''}src/runtime/components/${content ? 'content/' : ''}${upperName}.vue' import ${upperName}, { type ${upperName}Props, type ${upperName}Slots } from '../../${content ? '../' : ''}src/runtime/components/${content ? 'content/' : ''}${upperName}.vue'
import type { ${upperName}Props, ${upperName}Slots } from '../../${content ? '../' : ''}src/runtime/components/${content ? 'content/' : ''}${upperName}.vue'
import ComponentRender from '../${content ? '../' : ''}component-render' import ComponentRender from '../${content ? '../' : ''}component-render'
describe('${upperName}', () => { describe('${upperName}', () => {
@@ -189,7 +185,6 @@ links:${primitive
- label: GitHub - label: GitHub
icon: i-simple-icons-github icon: i-simple-icons-github
to: https://github.com/nuxt/${pro ? 'ui-pro' : 'ui'}/tree/v3/src/runtime/components/${upperName}.vue to: https://github.com/nuxt/${pro ? 'ui-pro' : 'ui'}/tree/v3/src/runtime/components/${upperName}.vue
navigation.badge: Soon
--- ---
## Usage ## Usage

View File

@@ -12,7 +12,6 @@ const { data: files } = useLazyAsyncData('search', () => queryCollectionSearchSe
}) })
const links = useLinks() const links = useLinks()
const searchLinks = useSearchLinks()
const color = computed(() => colorMode.value === 'dark' ? (colors as any)[appConfig.ui.colors.neutral][900] : 'white') const color = computed(() => colorMode.value === 'dark' ? (colors as any)[appConfig.ui.colors.neutral][900] : 'white')
const radius = computed(() => `:root { --ui-radius: ${appConfig.theme.radius}rem; }`) const radius = computed(() => `:root { --ui-radius: ${appConfig.theme.radius}rem; }`)
const blackAsPrimary = computed(() => appConfig.theme.blackAsPrimary ? `:root { --ui-primary: black; } .dark { --ui-primary: white; }` : ':root {}') const blackAsPrimary = computed(() => appConfig.theme.blackAsPrimary ? `:root { --ui-primary: black; } .dark { --ui-primary: white; }` : ':root {}')
@@ -23,7 +22,7 @@ useHead({
{ key: 'theme-color', name: 'theme-color', content: color } { key: 'theme-color', name: 'theme-color', content: color }
], ],
link: [ link: [
// { rel: 'icon', type: 'image/svg+xml', href: '/icon.svg' }, { rel: 'icon', type: 'image/svg+xml', href: '/icon.svg' },
{ rel: 'canonical', href: `https://ui.nuxt.com${withoutTrailingSlash(route.path)}` } { rel: 'canonical', href: `https://ui.nuxt.com${withoutTrailingSlash(route.path)}` }
], ],
style: [ style: [
@@ -40,8 +39,6 @@ useServerSeoMeta({
twitterCard: 'summary_large_image' twitterCard: 'summary_large_image'
}) })
useFaviconFromTheme()
const { frameworks, modules } = useSharedData() const { frameworks, modules } = useSharedData()
const { mappedNavigation, filteredNavigation } = useContentNavigation(navigation) const { mappedNavigation, filteredNavigation } = useContentNavigation(navigation)
@@ -67,7 +64,6 @@ provide('navigation', mappedNavigation)
<ClientOnly> <ClientOnly>
<LazyUContentSearch <LazyUContentSearch
:links="searchLinks"
:files="files" :files="files"
:groups="[{ :groups="[{
id: 'framework', id: 'framework',
@@ -87,5 +83,5 @@ provide('navigation', mappedNavigation)
</template> </template>
<style> <style>
/* Safelist (do not remove): [&>div]:*:my-0 [&>div]:*:w-full h-64 !px-0 !py-0 !pt-0 !pb-0 !p-0 !justify-start !justify-end !min-h-96 h-136 max-h-[341px] */ /* Safelist (do not remove): [&>div]:*:my-0 [&>div]:*:w-full h-64 !px-0 !py-0 !pt-0 !pb-0 !p-0 !justify-start !justify-end !min-h-96 h-136 */
</style> </style>

View File

@@ -1,7 +1,7 @@
@import "tailwindcss" theme(static) source("../../../.."); @import "tailwindcss" theme(static) source("../../../..");
@import "@nuxt/ui-pro"; @import "@nuxt/ui-pro";
@source "../../../content/**/*"; @source "../../../content";
@source "../../../node_modules/.c12"; @source "../../../node_modules/.c12";
@theme static { @theme static {

View File

@@ -23,27 +23,27 @@ onMounted(() => {
@reference "../assets/css/main.css"; @reference "../assets/css/main.css";
.carbon :deep(#carbonads) { .carbon :deep(#carbonads) {
@apply relative border border-default rounded-md hover:bg-elevated/50 w-full transition-colors min-h-[220px] p-2; @apply relative border border-(--ui-border) rounded-[calc(var(--ui-radius)*1.5)] hover:bg-(--ui-bg-elevated)/50 w-full transition-colors min-h-[220px] p-2;
.carbon-img { .carbon-img {
@apply flex justify-center w-full; @apply flex justify-center w-full;
& > img { & > img {
@apply !max-w-full w-full rounded-sm; @apply !max-w-full w-full rounded-(--ui-radius);
} }
} }
.carbon-text { .carbon-text {
@apply text-sm text-muted transition-colors text-center text-pretty flex pt-2; @apply text-sm text-(--ui-text-muted) transition-colors text-center text-pretty flex pt-2;
} }
.carbon-poweredby { .carbon-poweredby {
@apply block text-xs text-center text-muted pt-2; @apply block text-[10px] text-center text-(--ui-text-dimmed) pt-2;
} }
&:hover { &:hover {
.carbon-text { .carbon-text {
@apply text-default; @apply text-(--ui-text);
} }
} }
} }

View File

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

View File

@@ -2,8 +2,8 @@
const route = useRoute() const route = useRoute()
const links = [{ const links = [{
label: 'Team', label: 'Figma',
to: '/team' to: '/figma'
}, { }, {
label: 'Roadmap', label: 'Roadmap',
to: '/roadmap' to: '/roadmap'
@@ -22,8 +22,8 @@ const links = [{
<UFooter> <UFooter>
<template #left> <template #left>
<NuxtLink to="https://github.com/nuxt/ui" target="_blank" class="text-sm text-muted"> <NuxtLink to="https://github.com/nuxt/ui" target="_blank" class="text-sm text-(--ui-text-muted)">
Published under <span class="text-highlighted">MIT License</span> Published under <span class="text-(--ui-text-highlighted)">MIT License</span>
</NuxtLink> </NuxtLink>
</template> </template>

View File

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

View File

@@ -22,34 +22,14 @@ onMounted(() => {
const navigation = inject<Ref<ContentNavigationItem[]>>('navigation') const navigation = inject<Ref<ContentNavigationItem[]>>('navigation')
const githubLink = computed(() => `https://github.com/nuxt/${value.value}`)
const desktopLinks = computed(() => props.links.map(({ icon, ...link }) => link)) const desktopLinks = computed(() => props.links.map(({ icon, ...link }) => link))
const mobileLinks = computed(() => [ const mobileLinks = computed(() => props.links.map(link => ({ ...link, defaultOpen: link.children && route.path.startsWith(link.to as string) })))
...props.links.map(link => ({ ...link, defaultOpen: link.children && route.path.startsWith(link.to as string) })),
{
label: 'Open on GitHub',
to: githubLink.value,
icon: 'i-simple-icons-github',
target: '_blank'
}
])
const items = computed(() => {
const ui2 = { label: 'v2.22.0', to: 'https://ui2.nuxt.com' }
const uiPro1 = { label: 'v1.8.0', to: 'https://ui2.nuxt.com/pro' }
return [
{ label: `v${config.version}`, active: true, color: 'primary' as const, checked: true, type: 'checkbox' as const },
route.path === '/' ? ui2 : route.path.startsWith('/pro') ? uiPro1 : module.value === 'ui-pro' ? uiPro1 : ui2
]
})
</script> </script>
<template> <template>
<UHeader :ui="{ left: 'min-w-0' }" :menu="{ shouldScaleBackground: true }"> <UHeader :ui="{ left: 'min-w-0' }" :menu="{ shouldScaleBackground: true }">
<template #left> <template #left>
<NuxtLink to="/" class="flex items-end gap-2 font-bold text-xl text-highlighted min-w-0 focus-visible:outline-primary shrink-0" aria-label="Nuxt UI"> <NuxtLink to="/" class="flex items-end gap-2 font-bold text-xl text-(--ui-text-highlighted) min-w-0 focus-visible:outline-(--ui-primary) shrink-0" aria-label="Nuxt UI">
<Logo v-if="route.path === '/'" class="w-auto h-6 shrink-0" /> <Logo v-if="route.path === '/'" class="w-auto h-6 shrink-0" />
<LogoPro v-else-if="route.path.startsWith('/pro')" class="w-auto h-6 shrink-0" /> <LogoPro v-else-if="route.path.startsWith('/pro')" class="w-auto h-6 shrink-0" />
<template v-else> <template v-else>
@@ -61,7 +41,7 @@ const items = computed(() => {
<UDropdownMenu <UDropdownMenu
v-slot="{ open }" v-slot="{ open }"
:modal="false" :modal="false"
:items="items" :items="[{ label: `v${config.version}`, active: true, color: 'primary', checked: true, type: 'checkbox' }, { label: module === 'ui-pro' ? 'v1.7.1' : 'v2.21.1', to: module === 'ui-pro' ? 'https://ui2.nuxt.com/pro' : 'https://ui2.nuxt.com' }]"
:ui="{ content: 'w-(--reka-dropdown-menu-trigger-width) min-w-0' }" :ui="{ content: 'w-(--reka-dropdown-menu-trigger-width) min-w-0' }"
size="xs" size="xs"
> >
@@ -71,7 +51,7 @@ const items = computed(() => {
trailing-icon="i-lucide-chevron-down" trailing-icon="i-lucide-chevron-down"
size="xs" size="xs"
class="-mb-[6px] font-semibold rounded-full truncate" class="-mb-[6px] font-semibold rounded-full truncate"
:class="[open && 'bg-primary/15 ']" :class="[open && 'bg-(--ui-primary)/15 ']"
:ui="{ :ui="{
trailingIcon: ['transition-transform duration-200', open ? 'rotate-180' : undefined].filter(Boolean).join(' ') trailingIcon: ['transition-transform duration-200', open ? 'rotate-180' : undefined].filter(Boolean).join(' ')
}" }"
@@ -93,7 +73,7 @@ const items = computed(() => {
:key="value" :key="value"
color="neutral" color="neutral"
variant="ghost" variant="ghost"
:to="githubLink" :to="`https://github.com/nuxt/${value}`"
target="_blank" target="_blank"
icon="i-simple-icons-github" icon="i-simple-icons-github"
aria-label="GitHub" aria-label="GitHub"
@@ -116,7 +96,7 @@ const items = computed(() => {
<span class="inline-flex items-center gap-0.5"> <span class="inline-flex items-center gap-0.5">
{{ link.title }} {{ link.title }}
<sup v-if="link.module === 'ui-pro'" class="text-[8px] font-medium text-primary">PRO</sup> <sup v-if="link.module === 'ui-pro'" class="text-[8px] font-medium text-(--ui-primary)">PRO</sup>
</span> </span>
</template> </template>
</UContentNavigation> </UContentNavigation>

View File

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

View File

@@ -1,77 +0,0 @@
<script setup lang="ts">
const route = useRoute()
const toast = useToast()
const { copy, copied } = useClipboard()
const site = useSiteConfig()
const mdPath = computed(() => `${site.url}/raw${route.path}.md`)
const items = [
{
label: 'Copy Markdown link',
icon: 'i-lucide-link',
onSelect() {
copy(mdPath.value)
toast.add({
title: 'Copied to clipboard',
icon: 'i-lucide-check-circle'
})
}
},
{
label: 'View as Markdown',
icon: 'i-simple-icons:markdown',
target: '_blank',
to: `/raw${route.path}.md`
},
{
label: 'Open in ChatGPT',
icon: 'i-simple-icons:openai',
target: '_blank',
to: `https://chatgpt.com/?hints=search&q=${encodeURIComponent(`Read ${mdPath.value} so I can ask questions about it.`)}`
},
{
label: 'Open in Claude',
icon: 'i-simple-icons:anthropic',
target: '_blank',
to: `https://claude.ai/new?q=${encodeURIComponent(`Read ${mdPath.value} so I can ask questions about it.`)}`
}
]
async function copyPage() {
copy(await $fetch<string>(`/raw${route.path}.md`))
}
</script>
<template>
<UButtonGroup>
<UButton
label="Copy page"
:icon="copied ? 'i-lucide-copy-check' : 'i-lucide-copy'"
color="neutral"
variant="outline"
:ui="{
leadingIcon: [copied ? 'text-primary' : 'text-neutral', 'size-3.5']
}"
@click="copyPage"
/>
<UDropdownMenu
:items="items"
:content="{
align: 'end',
side: 'bottom',
sideOffset: 8
}"
:ui="{
content: 'w-48'
}"
>
<UButton
icon="i-lucide-chevron-down"
size="sm"
color="neutral"
variant="outline"
/>
</UDropdownMenu>
</UButtonGroup>
</template>

View File

@@ -14,7 +14,6 @@ const props = withDefaults(defineProps<{
color?: string color?: string
size?: { min: number, max: number } size?: { min: number, max: number }
speed?: 'slow' | 'normal' | 'fast' speed?: 'slow' | 'normal' | 'fast'
isIndex?: boolean
}>(), { }>(), {
starCount: 50, starCount: 50,
color: 'var(--ui-primary)', color: 'var(--ui-primary)',
@@ -22,8 +21,7 @@ const props = withDefaults(defineProps<{
min: 1, min: 1,
max: 3 max: 3
}), }),
speed: 'normal', speed: 'normal'
isIndex: false
}) })
const route = useRoute() const route = useRoute()
@@ -55,7 +53,7 @@ const twinkleDuration = computed(() => {
</script> </script>
<template> <template>
<div class="absolute pointer-events-none z-[-1] overflow-hidden" :class="isIndex ? 'inset-y-0 left-4 right-4 lg:right-[50%]' : 'inset-0'"> <div class="absolute pointer-events-none z-[-1] inset-y-0 left-4 right-4 lg:right-[50%] overflow-hidden">
<div <div
v-for="star in stars" v-for="star in stars"
:key="star.id" :key="star.id"

View File

@@ -1,6 +1,5 @@
<!-- eslint-disable no-useless-escape --> <!-- eslint-disable no-useless-escape -->
<script setup lang="ts"> <script setup lang="ts">
import type { ChipProps } from '@nuxt/ui'
import json5 from 'json5' import json5 from 'json5'
import { upperFirst, camelCase, kebabCase } from 'scule' import { upperFirst, camelCase, kebabCase } from 'scule'
import { hash } from 'ohash' import { hash } from 'ohash'
@@ -54,8 +53,6 @@ const props = defineProps<{
hide?: string[] hide?: string[]
/** List of props to externalize in script setup */ /** List of props to externalize in script setup */
external?: string[] external?: string[]
/** The types of the externalized props */
externalTypes?: string[]
/** List of props to use with `v-model` */ /** List of props to use with `v-model` */
model?: string[] model?: string[]
/** List of props to cast from code and selection */ /** List of props to cast from code and selection */
@@ -153,8 +150,7 @@ 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' }]
@@ -213,21 +209,11 @@ ${props.slots?.default}
code += ` code += `
<script setup lang="ts"> <script setup lang="ts">
` `
if (props.externalTypes?.length) { for (const key of props.external) {
const removeArrayBrackets = (type: string): string => type.endsWith('[]') ? removeArrayBrackets(type.slice(0, -2)) : type
const types = props.externalTypes.map(type => removeArrayBrackets(type))
code += `import type { ${types.join(', ')} } from '@nuxt/ui${props.pro ? '-pro' : ''}'
`
}
for (const [i, key] of props.external.entries()) {
const cast = props.cast?.[key] const cast = props.cast?.[key]
const value = cast ? castMap[cast]!.template(componentProps[key]) : json5.stringify(componentProps[key], null, 2)?.replace(/,([ |\t\n]+[}|\]])/g, '$1') const value = cast ? castMap[cast]!.template(componentProps[key]) : json5.stringify(componentProps[key], null, 2)?.replace(/,([ |\t\n]+[}|\]])/g, '$1')
const type = props.externalTypes?.[i] ? `<${props.externalTypes[i]}>` : ''
code += `const ${key === 'modelValue' ? 'value' : key} = ref${type}(${value}) code += `const ${key === 'modelValue' ? 'value' : key} = ref(${value})
` `
} }
code += `<\/script> code += `<\/script>
@@ -329,16 +315,16 @@ const { data: ast } = await useAsyncData(`component-code-${name}-${hash({ props:
<template> <template>
<div class="my-5"> <div class="my-5">
<div class="relative"> <div>
<div v-if="options.length" class="flex flex-wrap items-center gap-2.5 border border-muted border-b-0 relative rounded-t-md px-4 py-2.5 overflow-x-auto"> <div v-if="options.length" class="flex flex-wrap items-center gap-2.5 border border-(--ui-border-muted) border-b-0 relative rounded-t-[calc(var(--ui-radius)*1.5)] px-4 py-2.5 overflow-x-auto">
<template v-for="option in options" :key="option.name"> <template v-for="option in options" :key="option.name">
<UFormField <UFormField
:label="option.label" :label="option.label"
size="sm" size="sm"
class="inline-flex ring ring-accented rounded-sm" class="inline-flex ring ring-(--ui-border-accented) rounded-(--ui-radius)"
:ui="{ :ui="{
wrapper: 'bg-elevated/50 rounded-l-sm flex border-r border-accented', wrapper: 'bg-(--ui-bg-elevated)/50 rounded-l-(--ui-radius) flex border-r border-(--ui-border-accented)',
label: 'text-muted px-2 py-1.5', label: 'text-(--ui-text-muted) px-2 py-1.5',
container: 'mt-0' container: 'mt-0'
}" }"
> >
@@ -349,7 +335,7 @@ const { data: ast } = await useAsyncData(`component-code-${name}-${hash({ props:
value-key="value" value-key="value"
color="neutral" color="neutral"
variant="soft" variant="soft"
class="rounded-sm rounded-l-none min-w-12" class="rounded-(--ui-radius) rounded-l-none min-w-12"
:class="[option.name.toLowerCase().endsWith('color') && 'pl-6']" :class="[option.name.toLowerCase().endsWith('color') && 'pl-6']"
:ui="{ itemLeadingChip: 'size-2' }" :ui="{ itemLeadingChip: 'size-2' }"
@update:model-value="setComponentProp(option.name, $event)" @update:model-value="setComponentProp(option.name, $event)"
@@ -360,7 +346,7 @@ const { data: ast } = await useAsyncData(`component-code-${name}-${hash({ props:
inset inset
standalone standalone
:color="(modelValue as any)" :color="(modelValue as any)"
:size="(ui.itemLeadingChipSize() as ChipProps['size'])" :size="ui.itemLeadingChipSize()"
class="size-2" class="size-2"
/> />
</template> </template>
@@ -371,14 +357,14 @@ const { data: ast } = await useAsyncData(`component-code-${name}-${hash({ props:
:model-value="getComponentProp(option.name)" :model-value="getComponentProp(option.name)"
color="neutral" color="neutral"
variant="soft" variant="soft"
:ui="{ base: 'rounded-sm rounded-l-none min-w-12' }" :ui="{ base: 'rounded-(--ui-radius) rounded-l-none min-w-12' }"
@update:model-value="setComponentProp(option.name, $event)" @update:model-value="setComponentProp(option.name, $event)"
/> />
</UFormField> </UFormField>
</template> </template>
</div> </div>
<div v-if="component" class="flex justify-center border border-b-0 border-muted relative p-4 z-[1]" :class="[!options.length && 'rounded-t-md', props.class, { 'overflow-hidden': props.overflowHidden }]"> <div v-if="component" class="flex justify-center border border-b-0 border-(--ui-border-muted) relative p-4 z-[1]" :class="[!options.length && 'rounded-t-[calc(var(--ui-radius)*1.5)]', props.class, { 'overflow-hidden': props.overflowHidden }]">
<component :is="component" v-bind="{ ...componentProps, ...componentEvents }"> <component :is="component" v-bind="{ ...componentProps, ...componentEvents }">
<template v-for="slot in Object.keys(slots || {})" :key="slot" #[slot]> <template v-for="slot in Object.keys(slots || {})" :key="slot" #[slot]>
<slot :name="slot" mdc-unwrap="p"> <slot :name="slot" mdc-unwrap="p">

View File

@@ -1,5 +1,4 @@
<script setup lang="ts"> <script setup lang="ts">
import type { ChipProps } from '@nuxt/ui'
import { camelCase } from 'scule' import { camelCase } from 'scule'
import { useElementSize } from '@vueuse/core' import { useElementSize } from '@vueuse/core'
import { get, set } from '#ui/utils' import { get, set } from '#ui/utils'
@@ -150,8 +149,8 @@ const urlSearchParams = computed(() => {
<template> <template>
<div ref="el" class="my-5"> <div ref="el" class="my-5">
<template v-if="preview"> <template v-if="preview">
<div class="border border-muted relative z-[1]" :class="[{ 'border-b-0 rounded-t-md': props.source, 'rounded-md': !props.source, 'overflow-hidden': props.overflowHidden }]"> <div class="border border-(--ui-border-muted) relative z-[1]" :class="[{ 'border-b-0 rounded-t-[calc(var(--ui-radius)*1.5)]': props.source, 'rounded-[calc(var(--ui-radius)*1.5)]': !props.source, 'overflow-hidden': props.overflowHidden }]">
<div v-if="props.options?.length || !!slots.options" class="flex gap-4 p-4 border-b border-muted"> <div v-if="props.options?.length || !!slots.options" class="flex gap-4 p-4 border-b border-(--ui-border-muted)">
<slot name="options" /> <slot name="options" />
<UFormField <UFormField
@@ -160,10 +159,10 @@ const urlSearchParams = computed(() => {
:label="option.label" :label="option.label"
:name="option.name" :name="option.name"
size="sm" size="sm"
class="inline-flex ring ring-accented rounded-sm" class="inline-flex ring ring-(--ui-border-accented) rounded-(--ui-radius)"
:ui="{ :ui="{
wrapper: 'bg-elevated/50 rounded-l-sm flex border-r border-accented', wrapper: 'bg-(--ui-bg-elevated)/50 rounded-l-(--ui-radius) flex border-r border-(--ui-border-accented)',
label: 'text-muted px-2 py-1.5', label: 'text-(--ui-text-muted) px-2 py-1.5',
container: 'mt-0' container: 'mt-0'
}" }"
> >
@@ -175,7 +174,7 @@ const urlSearchParams = computed(() => {
:value-key="option.name.toLowerCase().endsWith('color') ? 'value' : undefined" :value-key="option.name.toLowerCase().endsWith('color') ? 'value' : undefined"
color="neutral" color="neutral"
variant="soft" variant="soft"
class="rounded-sm rounded-l-none min-w-12" class="rounded-(--ui-radius) rounded-l-none min-w-12"
:multiple="option.multiple" :multiple="option.multiple"
:class="[option.name.toLowerCase().endsWith('color') && 'pl-6']" :class="[option.name.toLowerCase().endsWith('color') && 'pl-6']"
:ui="{ itemLeadingChip: 'size-2' }" :ui="{ itemLeadingChip: 'size-2' }"
@@ -186,7 +185,7 @@ const urlSearchParams = computed(() => {
inset inset
standalone standalone
:color="(modelValue as any)" :color="(modelValue as any)"
:size="(ui.itemLeadingChipSize() as ChipProps['size'])" :size="ui.itemLeadingChipSize()"
class="size-2" class="size-2"
/> />
</template> </template>
@@ -196,7 +195,7 @@ const urlSearchParams = computed(() => {
:model-value="get(optionsValues, option.name)" :model-value="get(optionsValues, option.name)"
color="neutral" color="neutral"
variant="soft" variant="soft"
:ui="{ base: 'rounded-sm rounded-l-none min-w-12' }" :ui="{ base: 'rounded-(--ui-radius) rounded-l-none min-w-12' }"
@update:model-value="set(optionsValues, option.name, $event)" @update:model-value="set(optionsValues, option.name, $event)"
/> />
</UFormField> </UFormField>

View File

@@ -112,7 +112,7 @@ const metaProps: ComputedRef<ComponentMeta['props']> = computed(() => {
<ProseTd> <ProseTd>
<HighlightInlineType v-if="prop.type" :type="prop.type" /> <HighlightInlineType v-if="prop.type" :type="prop.type" />
<MDC v-if="prop.description" :value="prop.description" class="text-toned mt-1" :cache-key="`${kebabCase(route.path)}-${prop.name}-description`" /> <MDC v-if="prop.description" :value="prop.description" class="text-(--ui-text-toned) mt-1" :cache-key="`${kebabCase(route.path)}-${prop.name}-description`" />
<ComponentPropsLinks v-if="prop.tags?.length" :prop="prop" /> <ComponentPropsLinks v-if="prop.tags?.length" :prop="prop" />
<ComponentPropsSchema v-if="prop.schema" :prop="prop" :ignore="ignore" /> <ComponentPropsSchema v-if="prop.schema" :prop="prop" :ignore="ignore" />

View File

@@ -38,12 +38,12 @@ const schemaProps = computed(() => {
</script> </script>
<template> <template>
<ProseCollapsible v-if="schemaProps?.length" class="mt-1 mb-0"> <ProseCollapsible v-if="schemaProps?.length" class="mt-1">
<ProseUl> <ProseUl>
<ProseLi v-for="schemaProp in schemaProps" :key="schemaProp.name"> <ProseLi v-for="schemaProp in schemaProps" :key="schemaProp.name">
<HighlightInlineType :type="`${schemaProp.name}${schemaProp.required === false ? '?' : ''}: ${schemaProp.type}`" /> <HighlightInlineType :type="`${schemaProp.name}${schemaProp.required === false ? '?' : ''}: ${schemaProp.type}`" />
<MDC v-if="schemaProp.description" :value="schemaProp.description" class="text-muted my-1" :cache-key="`${kebabCase(route.path)}-${prop.name}-${schemaProp.name}-description`" /> <MDC v-if="schemaProp.description" :value="schemaProp.description" class="text-(--ui-text-muted) my-1" :cache-key="`${kebabCase(route.path)}-${prop.name}-${schemaProp.name}-description`" />
</ProseLi> </ProseLi>
</ProseUl> </ProseUl>
</ProseCollapsible> </ProseCollapsible>

View File

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

View File

@@ -1,6 +1,6 @@
<template> <template>
<div class="relative overflow-hidden rounded-sm border border-dashed border-accented opacity-75 px-4 flex items-center justify-center"> <div class="relative overflow-hidden rounded-(--ui-radius) border border-dashed border-(--ui-border-accented) opacity-75 px-4 flex items-center justify-center">
<svg class="absolute inset-0 h-full w-full stroke-inverted/10" fill="none"> <svg class="absolute inset-0 h-full w-full stroke-(--ui-border-inverted)/10" fill="none">
<defs> <defs>
<pattern <pattern
id="pattern-5c1e4f0e-62d5-498b-8ff0-cf77bb448c8e" id="pattern-5c1e4f0e-62d5-498b-8ff0-cf77bb448c8e"

View File

@@ -12,28 +12,23 @@ function getEmojiFlag(locale: string): string {
ar: 'sa', // Arabic -> Saudi Arabia ar: 'sa', // Arabic -> Saudi Arabia
bn: 'bd', // Bengali -> Bangladesh bn: 'bd', // Bengali -> Bangladesh
ca: 'es', // Catalan -> Spain ca: 'es', // Catalan -> Spain
ckb: 'iq', // Central Kurdish -> Iraq
cs: 'cz', // Czech -> Czech Republic (note: modern country code is actually 'cz') cs: 'cz', // Czech -> Czech Republic (note: modern country code is actually 'cz')
ckb: 'iq', // Central Kurdish -> Iraq
da: 'dk', // Danish -> Denmark da: 'dk', // Danish -> Denmark
el: 'gr', // Greek -> Greece el: 'gr', // Greek -> Greece
en: 'gb', // English -> Great Britain
et: 'ee', // Estonian -> Estonia et: 'ee', // Estonian -> Estonia
en: 'gb', // English -> Great Britain
he: 'il', // Hebrew -> Israel he: 'il', // Hebrew -> Israel
hi: 'in', // Hindi -> India hi: 'in', // Hindi -> India
hy: 'am', // Armenian -> Armenia
ja: 'jp', // Japanese -> Japan ja: 'jp', // Japanese -> Japan
kk: 'kz', // Kazakh -> Kazakhstan
km: 'kh', // Khmer -> Cambodia km: 'kh', // Khmer -> Cambodia
ko: 'kr', // Korean -> South Korea ko: 'kr', // Korean -> South Korea
ky: 'kg', // Kyrgyz -> Kyrgyzstan
lb: 'lu', // Luxembourgish -> Luxembourg
ms: 'my', // Malay -> Malaysia
nb: 'no', // Norwegian Bokmål -> Norway nb: 'no', // Norwegian Bokmål -> Norway
sl: 'si', // Slovenian -> Slovenia
sv: 'se', // Swedish -> Sweden sv: 'se', // Swedish -> Sweden
uk: 'ua', // Ukrainian -> Ukraine uk: 'ua', // Ukrainian -> Ukraine
ur: 'pk', // Urdu -> Pakistan ur: 'pk', // Urdu -> Pakistan
vi: 'vn' // Vietnamese -> Vietnam vi: 'vn', // Vietnamese -> Vietnam
hy: 'am' // Armenian -> Armenia
} }
const baseLanguage = locale.split('-')[0]?.toLowerCase() || locale const baseLanguage = locale.split('-')[0]?.toLowerCase() || locale

View File

@@ -1,7 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import type { AccordionItem } from '@nuxt/ui' const items = [
const items: AccordionItem[] = [
{ {
label: 'Icons', label: 'Icons',
icon: 'i-lucide-smile' icon: 'i-lucide-smile'

View File

@@ -1,7 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import type { AccordionItem } from '@nuxt/ui' const items = [
const items: AccordionItem[] = [
{ {
label: 'Icons', label: 'Icons',
icon: 'i-lucide-smile' icon: 'i-lucide-smile'
@@ -20,7 +18,7 @@ const items: AccordionItem[] = [
<template> <template>
<UAccordion :items="items"> <UAccordion :items="items">
<template #content="{ item }"> <template #content="{ item }">
<p class="pb-3.5 text-sm text-muted"> <p class="pb-3.5 text-sm text-(--ui-text-muted)">
This is the {{ item.label }} panel. This is the {{ item.label }} panel.
</p> </p>
</template> </template>

View File

@@ -1,6 +1,4 @@
<script setup lang="ts"> <script setup lang="ts">
import type { AccordionItem } from '@nuxt/ui'
const items = [ const items = [
{ {
label: 'Icons', label: 'Icons',
@@ -10,7 +8,7 @@ const items = [
{ {
label: 'Colors', label: 'Colors',
icon: 'i-lucide-swatch-book', icon: 'i-lucide-swatch-book',
slot: 'colors' as const, slot: 'colors',
content: 'Choose a primary and a neutral color from your Tailwind CSS theme.' content: 'Choose a primary and a neutral color from your Tailwind CSS theme.'
}, },
{ {
@@ -18,13 +16,13 @@ const items = [
icon: 'i-lucide-box', icon: 'i-lucide-box',
content: 'You can customize components by using the `class` / `ui` props or in your app.config.ts.' content: 'You can customize components by using the `class` / `ui` props or in your app.config.ts.'
} }
] satisfies AccordionItem[] ]
</script> </script>
<template> <template>
<UAccordion :items="items"> <UAccordion :items="items">
<template #colors="{ item }"> <template #colors="{ item }">
<p class="text-sm pb-3.5 text-primary"> <p class="text-sm pb-3.5 text-(--ui-primary)">
{{ item.content }} {{ item.content }}
</p> </p>
</template> </template>

View File

@@ -1,32 +0,0 @@
<script setup lang="ts">
import type { AccordionItem } from '@nuxt/ui'
import { useSortable } from '@vueuse/integrations/useSortable'
const items = shallowRef<AccordionItem[]>([
{
label: 'Icons',
icon: 'i-lucide-smile',
content: 'You have nothing to do, @nuxt/icon will handle it automatically.'
},
{
label: 'Colors',
icon: 'i-lucide-swatch-book',
content: 'Choose a primary and a neutral color from your Tailwind CSS theme.'
},
{
label: 'Components',
icon: 'i-lucide-box',
content: 'You can customize components by using the `class` / `ui` props or in your app.config.ts.'
}
])
const accordion = useTemplateRef<HTMLElement>('accordion')
useSortable(accordion, items, {
animation: 150
})
</script>
<template>
<UAccordion ref="accordion" :items="items" />
</template>

View File

@@ -1,7 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import type { AccordionItem } from '@nuxt/ui' const items = [
const items: AccordionItem[] = [
{ {
label: 'Icons', label: 'Icons',
icon: 'i-lucide-smile', icon: 'i-lucide-smile',

View File

@@ -3,7 +3,7 @@
<ULink <ULink
to="https://github.com/benjamincanac" to="https://github.com/benjamincanac"
target="_blank" target="_blank"
class="hover:ring-primary transition" class="hover:ring-(--ui-primary) transition"
raw raw
> >
<UAvatar <UAvatar
@@ -15,7 +15,7 @@
<ULink <ULink
to="https://github.com/romhml" to="https://github.com/romhml"
target="_blank" target="_blank"
class="hover:ring-primary transition" class="hover:ring-(--ui-primary) transition"
raw raw
> >
<UAvatar <UAvatar
@@ -27,7 +27,7 @@
<ULink <ULink
to="https://github.com/noook" to="https://github.com/noook"
target="_blank" target="_blank"
class="hover:ring-primary transition" class="hover:ring-(--ui-primary) transition"
raw raw
> >
<UAvatar <UAvatar

View File

@@ -1,35 +1,24 @@
<script setup lang="ts"> <script setup lang="ts">
import type { BreadcrumbItem } from '@nuxt/ui' const items = [{
label: 'Home',
const items = [ to: '/'
{ }, {
label: 'Home', slot: 'dropdown',
to: '/' icon: 'i-lucide-ellipsis',
}, children: [{
{ label: 'Documentation'
slot: 'dropdown' as const, }, {
icon: 'i-lucide-ellipsis', label: 'Themes'
children: [ }, {
{ label: 'GitHub'
label: 'Documentation' }]
}, }, {
{ label: 'Components',
label: 'Themes' to: '/components'
}, }, {
{ label: 'Breadcrumb',
label: 'GitHub' to: '/components/breadcrumb'
} }]
]
},
{
label: 'Components',
to: '/components'
},
{
label: 'Breadcrumb',
to: '/components/breadcrumb'
}
] satisfies BreadcrumbItem[]
</script> </script>
<template> <template>

View File

@@ -1,26 +1,20 @@
<script setup lang="ts"> <script setup lang="ts">
import type { BreadcrumbItem } from '@nuxt/ui' const items = [{
label: 'Home',
const items: BreadcrumbItem[] = [ to: '/'
{ }, {
label: 'Home', label: 'Components',
to: '/' to: '/components'
}, }, {
{ label: 'Breadcrumb',
label: 'Components', to: '/components/breadcrumb'
to: '/components' }]
},
{
label: 'Breadcrumb',
to: '/components/breadcrumb'
}
]
</script> </script>
<template> <template>
<UBreadcrumb :items="items"> <UBreadcrumb :items="items">
<template #separator> <template #separator>
<span class="mx-2 text-muted">/</span> <span class="mx-2 text-(--ui-text-muted)">/</span>
</template> </template>
</UBreadcrumb> </UBreadcrumb>
</template> </template>

View File

@@ -1,30 +1,21 @@
<script setup lang="ts"> <script setup lang="ts">
import type { DropdownMenuItem } from '@nuxt/ui' const items = [{
label: 'Team',
const items: DropdownMenuItem[] = [ icon: 'i-lucide-users'
{ }, {
label: 'Team', label: 'Invite users',
icon: 'i-lucide-users' icon: 'i-lucide-user-plus',
}, children: [{
{ label: 'Invite by email',
label: 'Invite users', icon: 'i-lucide-send-horizontal'
icon: 'i-lucide-user-plus', }, {
children: [ label: 'Invite by link',
{ icon: 'i-lucide-link'
label: 'Invite by email', }]
icon: 'i-lucide-send-horizontal' }, {
}, label: 'New team',
{ icon: 'i-lucide-plus'
label: 'Invite by link', }]
icon: 'i-lucide-link'
}
]
},
{
label: 'New team',
icon: 'i-lucide-plus'
}
]
</script> </script>
<template> <template>

View File

@@ -1,21 +0,0 @@
<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>

View File

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

View File

@@ -1,58 +0,0 @@
<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>

View File

@@ -1,79 +1,76 @@
<script setup lang="ts"> <script setup lang="ts">
const groups = [ const groups = [{
{ id: 'settings',
id: 'settings', items: [
items: [ {
{ label: 'Profile',
label: 'Profile', icon: 'i-lucide-user',
icon: 'i-lucide-user', kbds: ['meta', 'P']
kbds: ['meta', 'P'] },
}, {
{ label: 'Billing',
label: 'Billing', icon: 'i-lucide-credit-card',
icon: 'i-lucide-credit-card', kbds: ['meta', 'B'],
kbds: ['meta', 'B'], slot: 'billing'
slot: 'billing' as const },
}, {
{ label: 'Notifications',
label: 'Notifications', icon: 'i-lucide-bell'
icon: 'i-lucide-bell' },
}, {
{ label: 'Security',
label: 'Security', icon: 'i-lucide-lock'
icon: 'i-lucide-lock' }
} ]
] }, {
}, id: 'users',
{ label: 'Users',
id: 'users', slot: 'users',
label: 'Users', items: [
slot: 'users' as const, {
items: [ label: 'Benjamin Canac',
{ suffix: 'benjamincanac',
label: 'Benjamin Canac', to: 'https://github.com/benjamincanac',
suffix: 'benjamincanac', target: '_blank'
to: 'https://github.com/benjamincanac', },
target: '_blank' {
}, label: 'Sylvain Marroufin',
{ suffix: 'smarroufin',
label: 'Sylvain Marroufin', to: 'https://github.com/smarroufin',
suffix: 'smarroufin', target: '_blank'
to: 'https://github.com/smarroufin', },
target: '_blank' {
}, label: 'Sébastien Chopin',
{ suffix: 'atinux',
label: 'Sébastien Chopin', to: 'https://github.com/atinux',
suffix: 'atinux', target: '_blank'
to: 'https://github.com/atinux', },
target: '_blank' {
}, label: 'Romain Hamel',
{ suffix: 'romhml',
label: 'Romain Hamel', to: 'https://github.com/romhml',
suffix: 'romhml', target: '_blank'
to: 'https://github.com/romhml', },
target: '_blank' {
}, label: 'Haytham A. Salama',
{ suffix: 'Haythamasalama',
label: 'Haytham A. Salama', to: 'https://github.com/Haythamasalama',
suffix: 'Haythamasalama', target: '_blank'
to: 'https://github.com/Haythamasalama', },
target: '_blank' {
}, label: 'Daniel Roe',
{ suffix: 'danielroe',
label: 'Daniel Roe', to: 'https://github.com/danielroe',
suffix: 'danielroe', target: '_blank'
to: 'https://github.com/danielroe', },
target: '_blank' {
}, label: 'Neil Richter',
{ suffix: 'noook',
label: 'Neil Richter', to: 'https://github.com/noook',
suffix: 'noook', target: '_blank'
to: 'https://github.com/noook', }
target: '_blank' ]
} }]
]
}
]
</script> </script>
<template> <template>
@@ -83,7 +80,7 @@ const groups = [
</template> </template>
<template #billing-label="{ item }"> <template #billing-label="{ item }">
<span class="font-medium text-primary">{{ item.label }}</span> {{ item.label }}
<UBadge variant="subtle" size="sm"> <UBadge variant="subtle" size="sm">
50% off 50% off

View File

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

View File

@@ -1,10 +1,9 @@
<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}` } })) || []

View File

@@ -1,119 +0,0 @@
<script setup lang="ts">
const toast = useToast()
const groups = [{
id: 'actions',
label: 'Actions',
items: [{
label: 'Create new',
icon: 'i-lucide-plus',
children: [{
label: 'New file',
icon: 'i-lucide-file-plus',
suffix: 'Create a new file in the current directory',
onSelect(e: Event) {
e.preventDefault()
toast.add({ title: 'New file created!' })
},
kbds: ['meta', 'N']
}, {
label: 'New folder',
icon: 'i-lucide-folder-plus',
suffix: 'Create a new folder in the current directory',
onSelect(e: Event) {
e.preventDefault()
toast.add({ title: 'New folder created!' })
},
kbds: ['meta', 'F']
}, {
label: 'New project',
icon: 'i-lucide-folder-git',
suffix: 'Create a new project from a template',
onSelect(e: Event) {
e.preventDefault()
toast.add({ title: 'New project created!' })
},
kbds: ['meta', 'P']
}]
}, {
label: 'Share',
icon: 'i-lucide-share',
children: [{
label: 'Copy link',
icon: 'i-lucide-link',
suffix: 'Copy a link to the current item',
onSelect(e: Event) {
e.preventDefault()
toast.add({ title: 'Link copied to clipboard!' })
},
kbds: ['meta', 'L']
}, {
label: 'Share via email',
icon: 'i-lucide-mail',
suffix: 'Share the current item via email',
onSelect(e: Event) {
e.preventDefault()
toast.add({ title: 'Share via email dialog opened!' })
}
}, {
label: 'Share on social',
icon: 'i-lucide-share-2',
suffix: 'Share the current item on social media',
children: [{
label: 'Twitter',
icon: 'i-simple-icons-twitter',
onSelect(e: Event) {
e.preventDefault()
toast.add({ title: 'Shared on Twitter!' })
}
}, {
label: 'LinkedIn',
icon: 'i-simple-icons-linkedin',
onSelect(e: Event) {
e.preventDefault()
toast.add({ title: 'Shared on LinkedIn!' })
}
}, {
label: 'Facebook',
icon: 'i-simple-icons-facebook',
onSelect(e: Event) {
e.preventDefault()
toast.add({ title: 'Shared on Facebook!' })
}
}]
}]
}, {
label: 'Settings',
icon: 'i-lucide-settings',
children: [{
label: 'General',
icon: 'i-lucide-sliders',
suffix: 'Configure general settings',
onSelect(e: Event) {
e.preventDefault()
toast.add({ title: 'General settings opened!' })
}
}, {
label: 'Appearance',
icon: 'i-lucide-palette',
suffix: 'Customize the appearance',
onSelect(e: Event) {
e.preventDefault()
toast.add({ title: 'Appearance settings opened!' })
}
}, {
label: 'Security',
icon: 'i-lucide-shield',
suffix: 'Manage security settings',
onSelect(e: Event) {
e.preventDefault()
toast.add({ title: 'Security settings opened!' })
}
}]
}]
}]
</script>
<template>
<UCommandPalette :groups="groups" class="flex-1" />
</template>

View File

@@ -1,10 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import type { ContextMenuItem } from '@nuxt/ui'
const showSidebar = ref(true) const showSidebar = ref(true)
const showToolbar = ref(false) const showToolbar = ref(false)
const items = computed<ContextMenuItem[]>(() => [{ const items = computed(() => [{
label: 'View', label: 'View',
type: 'label' as const type: 'label' as const
}, { }, {
@@ -35,7 +33,7 @@ const items = computed<ContextMenuItem[]>(() => [{
<template> <template>
<UContextMenu :items="items" :ui="{ content: 'w-48' }"> <UContextMenu :items="items" :ui="{ content: 'w-48' }">
<div class="flex items-center justify-center rounded-md border border-dashed border-accented text-sm aspect-video w-72"> <div class="flex items-center justify-center rounded-md border border-dashed border-(--ui-border-accented) text-sm aspect-video w-72">
Right click here Right click here
</div> </div>
</UContextMenu> </UContextMenu>

View File

@@ -1,7 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import type { ContextMenuItem } from '@nuxt/ui' const items = [
const items: ContextMenuItem[][] = [
[ [
{ {
label: 'View', label: 'View',
@@ -28,7 +26,7 @@ const items: ContextMenuItem[][] = [
<template> <template>
<UContextMenu :items="items" :ui="{ content: 'w-48' }"> <UContextMenu :items="items" :ui="{ content: 'w-48' }">
<div class="flex items-center justify-center rounded-md border border-dashed border-accented text-sm aspect-video w-72"> <div class="flex items-center justify-center rounded-md border border-dashed border-(--ui-border-accented) text-sm aspect-video w-72">
Right click here Right click here
</div> </div>
</UContextMenu> </UContextMenu>

View File

@@ -1,25 +1,19 @@
<script setup lang="ts"> <script setup lang="ts">
import type { ContextMenuItem } from '@nuxt/ui'
const loading = ref(true) const loading = ref(true)
const items = [ const items = [{
{ label: 'Refresh the Page',
label: 'Refresh the Page', slot: 'refresh'
slot: 'refresh' as const }, {
}, label: 'Clear Cookies and Refresh'
{ }, {
label: 'Clear Cookies and Refresh' label: 'Clear Cache and Refresh'
}, }]
{
label: 'Clear Cache and Refresh'
}
] satisfies ContextMenuItem[]
</script> </script>
<template> <template>
<UContextMenu :items="items" :ui="{ content: 'w-48' }"> <UContextMenu :items="items" :ui="{ content: 'w-48' }">
<div class="flex items-center justify-center rounded-md border border-dashed border-accented text-sm aspect-video w-72"> <div class="flex items-center justify-center rounded-md border border-dashed border-(--ui-border-accented) text-sm aspect-video w-72">
Right click here Right click here
</div> </div>
@@ -28,7 +22,7 @@ const items = [
</template> </template>
<template #refresh-trailing> <template #refresh-trailing>
<UIcon v-if="loading" name="i-lucide-loader-circle" class="shrink-0 size-5 text-primary animate-spin" /> <UIcon v-if="loading" name="i-lucide-refresh-cw" class="shrink-0 size-5 text-(--ui-primary) animate-spin" />
</template> </template>
</UContextMenu> </UContextMenu>
</template> </template>

View File

@@ -3,11 +3,11 @@ const open = ref(false)
</script> </script>
<template> <template>
<UDrawer v-model:open="open" :dismissible="false" :handle="false" :ui="{ header: 'flex items-center justify-between' }"> <UDrawer v-model:open="open" :dismissible="false" :ui="{ header: 'flex items-center justify-between' }">
<UButton label="Open" color="neutral" variant="subtle" trailing-icon="i-lucide-chevron-up" /> <UButton label="Open" color="neutral" variant="subtle" trailing-icon="i-lucide-chevron-up" />
<template #header> <template #header>
<h2 class="text-highlighted font-semibold"> <h2 class="text-(--ui-text-highlighted) font-semibold">
Drawer non-dismissible Drawer non-dismissible
</h2> </h2>

View File

@@ -1,28 +0,0 @@
<script setup lang="ts">
const open = ref(false)
</script>
<template>
<UDrawer
v-model:open="open"
:dismissible="false"
:overlay="false"
:handle="false"
:modal="false"
:ui="{ header: 'flex items-center justify-between' }"
>
<UButton label="Open" color="neutral" variant="subtle" trailing-icon="i-lucide-chevron-up" />
<template #header>
<h2 class="text-highlighted font-semibold">
Drawer non-dismissible
</h2>
<UButton color="neutral" variant="ghost" icon="i-lucide-x" @click="open = false" />
</template>
<template #body>
<Placeholder class="h-48" />
</template>
</UDrawer>
</template>

View File

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

View File

@@ -1,43 +0,0 @@
<script lang="ts" setup>
import { createReusableTemplate, useMediaQuery } from '@vueuse/core'
const [DefineFormTemplate, ReuseFormTemplate] = createReusableTemplate()
const isDesktop = useMediaQuery('(min-width: 768px)')
const open = ref(false)
const state = reactive({
email: undefined
})
const title = 'Edit profile'
const description = 'Make changes to your profile here. Click save when you\'re done.'
</script>
<template>
<DefineFormTemplate>
<UForm :state="state" class="space-y-4">
<UFormField label="Email" name="email" required>
<UInput v-model="state.email" placeholder="shadcn@example.com" required />
</UFormField>
<UButton label="Save changes" type="submit" />
</UForm>
</DefineFormTemplate>
<UModal v-if="isDesktop" v-model:open="open" :title="title" :description="description">
<UButton label="Edit profile" color="neutral" variant="outline" />
<template #body>
<ReuseFormTemplate />
</template>
</UModal>
<UDrawer v-else v-model:open="open" :title="title" :description="description">
<UButton label="Edit profile" color="neutral" variant="outline" />
<template #body>
<ReuseFormTemplate />
</template>
</UDrawer>
</template>

View File

@@ -1,6 +1,4 @@
<script setup lang="ts"> <script setup lang="ts">
import type { DropdownMenuItem } from '@nuxt/ui'
const showBookmarks = ref(true) const showBookmarks = ref(true)
const showHistory = ref(false) const showHistory = ref(false)
const showDownloads = ref(false) const showDownloads = ref(false)
@@ -38,7 +36,7 @@ const items = computed(() => [{
onUpdateChecked(checked: boolean) { onUpdateChecked(checked: boolean) {
showDownloads.value = checked showDownloads.value = checked
} }
}] satisfies DropdownMenuItem[]) }])
</script> </script>
<template> <template>

View File

@@ -1,7 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import type { DropdownMenuItem } from '@nuxt/ui' const items = [
const items: DropdownMenuItem[][] = [
[ [
{ {
label: 'View', label: 'View',
@@ -19,7 +17,7 @@ const items: DropdownMenuItem[][] = [
[ [
{ {
label: 'Delete', label: 'Delete',
color: 'error', color: 'error' as const,
icon: 'i-lucide-trash' icon: 'i-lucide-trash'
} }
] ]
@@ -29,5 +27,9 @@ const items: DropdownMenuItem[][] = [
<template> <template>
<UDropdownMenu :items="items" :ui="{ content: 'w-48' }"> <UDropdownMenu :items="items" :ui="{ content: 'w-48' }">
<UButton label="Open" color="neutral" variant="outline" icon="i-lucide-menu" /> <UButton label="Open" color="neutral" variant="outline" icon="i-lucide-menu" />
<template #profile-trailing>
<UIcon name="i-lucide-badge-check" class="shrink-0 size-5 text-(--ui-primary)" />
</template>
</UDropdownMenu> </UDropdownMenu>
</template> </template>

View File

@@ -1,19 +1,15 @@
<script setup lang="ts"> <script setup lang="ts">
import type { DropdownMenuItem } from '@nuxt/ui' const items = [{
label: 'Profile',
const items = [ icon: 'i-lucide-user',
{ slot: 'profile'
label: 'Profile', }, {
icon: 'i-lucide-user', label: 'Billing',
slot: 'profile' as const icon: 'i-lucide-credit-card'
}, { }, {
label: 'Billing', label: 'Settings',
icon: 'i-lucide-credit-card' icon: 'i-lucide-cog'
}, { }]
label: 'Settings',
icon: 'i-lucide-cog'
}
] satisfies DropdownMenuItem[]
</script> </script>
<template> <template>
@@ -21,7 +17,7 @@ const items = [
<UButton label="Open" color="neutral" variant="outline" icon="i-lucide-menu" /> <UButton label="Open" color="neutral" variant="outline" icon="i-lucide-menu" />
<template #profile-trailing> <template #profile-trailing>
<UIcon name="i-lucide-badge-check" class="shrink-0 size-5 text-primary" /> <UIcon name="i-lucide-badge-check" class="shrink-0 size-5 text-(--ui-primary)" />
</template> </template>
</UDropdownMenu> </UDropdownMenu>
</template> </template>

View File

@@ -1,24 +1,20 @@
<script setup lang="ts"> <script setup lang="ts">
import type { DropdownMenuItem } from '@nuxt/ui'
const open = ref(false) const open = ref(false)
defineShortcuts({ defineShortcuts({
o: () => open.value = !open.value o: () => open.value = !open.value
}) })
const items: DropdownMenuItem[] = [ const items = [{
{ label: 'Profile',
label: 'Profile', icon: 'i-lucide-user'
icon: 'i-lucide-user' }, {
}, { label: 'Billing',
label: 'Billing', icon: 'i-lucide-credit-card'
icon: 'i-lucide-credit-card' }, {
}, { label: 'Settings',
label: 'Settings', icon: 'i-lucide-cog'
icon: 'i-lucide-cog' }]
}
]
</script> </script>
<template> <template>

View File

@@ -14,7 +14,7 @@ const validate = (state: any): FormError[] => {
} }
const toast = useToast() const toast = useToast()
async function onSubmit(event: FormSubmitEvent<typeof state>) { async function onSubmit(event: FormSubmitEvent<any>) {
toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'success' }) toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'success' })
console.log(event.data) console.log(event.data)
} }

View File

@@ -15,9 +15,6 @@ const schema = z.object({
select: z.string().refine(value => value === 'option-2', { select: z.string().refine(value => value === 'option-2', {
message: 'Select Option 2' message: 'Select Option 2'
}), }),
selectMultiple: z.array(z.string()).refine(values => values.includes('option-2'), {
message: 'Include Option 2'
}),
selectMenu: z.any().refine(option => option?.value === 'option-2', { selectMenu: z.any().refine(option => option?.value === 'option-2', {
message: 'Select Option 2' message: 'Select Option 2'
}), }),
@@ -33,9 +30,6 @@ 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)
}) })
@@ -53,7 +47,7 @@ const items = [
] ]
const toast = useToast() const toast = useToast()
async function onSubmit(event: FormSubmitEvent<Schema>) { async function onSubmit(event: FormSubmitEvent<any>) {
toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'success' }) toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'success' })
console.log(event.data) console.log(event.data)
} }
@@ -84,10 +78,6 @@ async function onSubmit(event: FormSubmitEvent<Schema>) {
<USelect v-model="state.select" :items="items" class="w-full" /> <USelect v-model="state.select" :items="items" class="w-full" />
</UFormField> </UFormField>
<UFormField name="selectMultiple" label="Select (Multiple)">
<USelect v-model="state.selectMultiple" multiple :items="items" class="w-full" />
</UFormField>
<UFormField name="selectMenu" label="Select Menu"> <UFormField name="selectMenu" label="Select Menu">
<USelectMenu v-model="state.selectMenu" :items="items" class="w-full" /> <USelectMenu v-model="state.selectMenu" :items="items" class="w-full" />
</UFormField> </UFormField>
@@ -111,14 +101,11 @@ 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>

View File

@@ -15,7 +15,7 @@ const state = reactive({
}) })
const toast = useToast() const toast = useToast()
async function onSubmit(event: FormSubmitEvent<typeof state>) { async function onSubmit(event: FormSubmitEvent<any>) {
toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'success' }) toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'success' })
console.log(event.data) console.log(event.data)
} }

View File

@@ -18,7 +18,7 @@ type NestedSchema = z.output<typeof nestedSchema>
const state = reactive<Partial<Schema & NestedSchema>>({ }) const state = reactive<Partial<Schema & NestedSchema>>({ })
const toast = useToast() const toast = useToast()
async function onSubmit(event: FormSubmitEvent<Schema>) { async function onSubmit(event: FormSubmitEvent<any>) {
toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'success' }) toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'success' })
console.log(event.data) console.log(event.data)
} }
@@ -39,7 +39,7 @@ async function onSubmit(event: FormSubmitEvent<Schema>) {
<UCheckbox v-model="state.news" name="news" label="Register to our newsletter" @update:model-value="state.email = undefined" /> <UCheckbox v-model="state.news" name="news" label="Register to our newsletter" @update:model-value="state.email = undefined" />
</div> </div>
<UForm v-if="state.news" :state="state" :schema="nestedSchema" attach> <UForm v-if="state.news" :state="state" :schema="nestedSchema">
<UFormField label="Email" name="email"> <UFormField label="Email" name="email">
<UInput v-model="state.email" placeholder="john@lennon.com" /> <UInput v-model="state.email" placeholder="john@lennon.com" />
</UFormField> </UFormField>

View File

@@ -34,7 +34,7 @@ function removeItem() {
const toast = useToast() const toast = useToast()
async function onSubmit(event: FormSubmitEvent<Schema>) { async function onSubmit(event: FormSubmitEvent<any>) {
toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'success' }) toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'success' })
console.log(event.data) console.log(event.data)
} }
@@ -51,14 +51,7 @@ async function onSubmit(event: FormSubmitEvent<Schema>) {
<UInput v-model="state.customer" placeholder="Wonka Industries" /> <UInput v-model="state.customer" placeholder="Wonka Industries" />
</UFormField> </UFormField>
<UForm <UForm v-for="item, count in state.items" :key="count" :state="item" :schema="itemSchema" class="flex gap-2">
v-for="item, count in state.items"
:key="count"
:state="item"
:schema="itemSchema"
attach
class="flex gap-2"
>
<UFormField :label="!count ? 'Description' : undefined" name="description"> <UFormField :label="!count ? 'Description' : undefined" name="description">
<UInput v-model="item.description" /> <UInput v-model="item.description" />
</UFormField> </UFormField>

View File

@@ -14,7 +14,7 @@ const validate = (state: any): FormError[] => {
} }
const toast = useToast() const toast = useToast()
async function onSubmit(event: FormSubmitEvent<typeof state>) { async function onSubmit(event: FormSubmitEvent<any>) {
toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'success' }) toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'success' })
console.log(event.data) console.log(event.data)
} }

View File

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

View File

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

View File

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

View File

@@ -16,7 +16,7 @@ function onOpen() {
<template> <template>
<UInputMenu <UInputMenu
:items="countries" :items="countries || []"
:loading="status === 'pending'" :loading="status === 'pending'"
label-key="name" label-key="name"
:search-input="{ icon: 'i-lucide-search' }" :search-input="{ icon: 'i-lucide-search' }"

View File

@@ -1,6 +1,4 @@
<script setup lang="ts"> <script setup lang="ts">
import type { AvatarProps } from '@nuxt/ui'
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', key: 'typicode-users',
transform: (data: { id: number, name: string }[]) => { transform: (data: { id: number, name: string }[]) => {
@@ -8,7 +6,7 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
label: user.name, label: user.name,
value: String(user.id), value: String(user.id),
avatar: { src: `https://i.pravatar.cc/120?img=${user.id}` } avatar: { src: `https://i.pravatar.cc/120?img=${user.id}` }
})) })) || []
}, },
lazy: true lazy: true
}) })
@@ -16,7 +14,7 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
<template> <template>
<UInputMenu <UInputMenu
:items="users" :items="users || []"
:loading="status === 'pending'" :loading="status === 'pending'"
icon="i-lucide-user" icon="i-lucide-user"
placeholder="Select user" placeholder="Select user"
@@ -25,7 +23,7 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
<UAvatar <UAvatar
v-if="modelValue" v-if="modelValue"
v-bind="modelValue.avatar" v-bind="modelValue.avatar"
:size="(ui.leadingAvatarSize() as AvatarProps['size'])" :size="ui.leadingAvatarSize()"
:class="ui.leadingAvatar()" :class="ui.leadingAvatar()"
/> />
</template> </template>

View File

@@ -1,6 +1,4 @@
<script setup lang="ts"> <script setup lang="ts">
import type { AvatarProps } from '@nuxt/ui'
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-email', key: 'typicode-users-email',
transform: (data: { id: number, name: string, email: string }[]) => { transform: (data: { id: number, name: string, email: string }[]) => {
@@ -9,7 +7,7 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
email: user.email, email: user.email,
value: String(user.id), value: String(user.id),
avatar: { src: `https://i.pravatar.cc/120?img=${user.id}` } avatar: { src: `https://i.pravatar.cc/120?img=${user.id}` }
})) })) || []
}, },
lazy: true lazy: true
}) })
@@ -17,7 +15,7 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
<template> <template>
<UInputMenu <UInputMenu
:items="users" :items="users || []"
:loading="status === 'pending'" :loading="status === 'pending'"
:filter-fields="['label', 'email']" :filter-fields="['label', 'email']"
icon="i-lucide-user" icon="i-lucide-user"
@@ -28,7 +26,7 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
<UAvatar <UAvatar
v-if="modelValue" v-if="modelValue"
v-bind="modelValue.avatar" v-bind="modelValue.avatar"
:size="(ui.leadingAvatarSize() as AvatarProps['size'])" :size="ui.leadingAvatarSize()"
:class="ui.leadingAvatar()" :class="ui.leadingAvatar()"
/> />
</template> </template>
@@ -36,7 +34,7 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
<template #item-label="{ item }"> <template #item-label="{ item }">
{{ item.label }} {{ item.label }}
<span class="text-muted"> <span class="text-(--ui-text-muted)">
{{ item.email }} {{ item.email }}
</span> </span>
</template> </template>

View File

@@ -1,18 +1,16 @@
<script setup lang="ts"> <script setup lang="ts">
import { refDebounced } from '@vueuse/core'
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 => ({
label: user.name, label: user.name,
value: String(user.id), value: String(user.id),
avatar: { src: `https://i.pravatar.cc/120?img=${user.id}` } avatar: { src: `https://i.pravatar.cc/120?img=${user.id}` }
})) })) || []
}, },
lazy: true lazy: true
}) })
@@ -21,7 +19,7 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
<template> <template>
<UInputMenu <UInputMenu
v-model:search-term="searchTerm" v-model:search-term="searchTerm"
:items="users" :items="users || []"
:loading="status === 'pending'" :loading="status === 'pending'"
ignore-filter ignore-filter
icon="i-lucide-user" icon="i-lucide-user"
@@ -31,7 +29,7 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
<UAvatar <UAvatar
v-if="modelValue" v-if="modelValue"
v-bind="modelValue.avatar" v-bind="modelValue.avatar"
:size="(ui.leadingAvatarSize() as AvatarProps['size'])" :size="ui.leadingAvatarSize()"
:class="ui.leadingAvatar()" :class="ui.leadingAvatar()"
/> />
</template> </template>

View File

@@ -1,6 +1,4 @@
<script setup lang="ts"> <script setup lang="ts">
import type { InputMenuItem } from '@nuxt/ui'
const items = ref([ const items = ref([
{ {
label: 'benjamincanac', label: 'benjamincanac',
@@ -25,17 +23,8 @@ const items = ref([
src: 'https://github.com/noook.png', src: 'https://github.com/noook.png',
alt: 'noook' alt: 'noook'
} }
},
{
label: 'sandros94',
value: 'sandros94',
avatar: {
src: 'https://github.com/sandros94.png',
alt: 'sandros94'
}
} }
] satisfies InputMenuItem[]) ])
const value = ref(items.value[0]) const value = ref(items.value[0])
</script> </script>

View File

@@ -1,30 +1,27 @@
<script setup lang="ts"> <script setup lang="ts">
import type { InputMenuItem, ChipProps } from '@nuxt/ui'
const items = ref([ const items = ref([
{ {
label: 'bug', label: 'bug',
value: 'bug', value: 'bug',
chip: { chip: {
color: 'error' color: 'error' as const
} }
}, },
{ {
label: 'feature', label: 'feature',
value: 'feature', value: 'feature',
chip: { chip: {
color: 'success' color: 'success' as const
} }
}, },
{ {
label: 'enhancement', label: 'enhancement',
value: 'enhancement', value: 'enhancement',
chip: { chip: {
color: 'info' color: 'info' as const
} }
} }
] satisfies InputMenuItem[]) ])
const value = ref(items.value[0]) const value = ref(items.value[0])
</script> </script>
@@ -36,7 +33,7 @@ const value = ref(items.value[0])
v-bind="modelValue.chip" v-bind="modelValue.chip"
inset inset
standalone standalone
:size="(ui.itemLeadingChipSize() as ChipProps['size'])" :size="ui.itemLeadingChipSize()"
:class="ui.itemLeadingChip()" :class="ui.itemLeadingChip()"
/> />
</template> </template>

View File

@@ -1,6 +1,4 @@
<script setup lang="ts"> <script setup lang="ts">
import type { InputMenuItem } from '@nuxt/ui'
const items = ref([ const items = ref([
{ {
label: 'Backlog', label: 'Backlog',
@@ -22,8 +20,7 @@ const items = ref([
value: 'done', value: 'done',
icon: 'i-lucide-circle-check' icon: 'i-lucide-circle-check'
} }
] satisfies InputMenuItem[]) ])
const value = ref(items.value[0]) const value = ref(items.value[0])
</script> </script>

View File

@@ -1,9 +0,0 @@
<script setup lang="ts">
const tags = ref(['Vue'])
</script>
<template>
<UFormField label="Tags" required>
<UInputTags v-model="tags" placeholder="Enter tags..." />
</UFormField>
</template>

View File

@@ -10,12 +10,12 @@ const domain = ref(domains[0])
v-model="value" v-model="value"
placeholder="nuxt" placeholder="nuxt"
:ui="{ :ui="{
base: 'pl-14.5', base: 'pl-[57px]',
leading: 'pointer-events-none' leading: 'pointer-events-none'
}" }"
> >
<template #leading> <template #leading>
<p class="text-sm text-muted"> <p class="text-sm text-(--ui-text-muted)">
https:// https://
</p> </p>
</template> </template>

View File

@@ -13,7 +13,7 @@ const maxLength = 15
<template #trailing> <template #trailing>
<div <div
id="character-count" id="character-count"
class="text-xs text-muted tabular-nums" class="text-xs text-(--ui-text-muted) tabular-nums"
aria-live="polite" aria-live="polite"
role="status" role="status"
> >

View File

@@ -1,9 +1,15 @@
<script setup lang="ts"> <script setup lang="ts">
import { useClipboard } from '@vueuse/core' const value = ref('npx nuxi module add ui')
const copied = ref(false)
const value = ref('npx nuxt module add ui') function copy() {
navigator.clipboard.writeText(value.value)
copied.value = true
const { copy, copied } = useClipboard() setTimeout(() => {
copied.value = false
}, 2000)
}
</script> </script>
<template> <template>
@@ -19,7 +25,7 @@ const { copy, copied } = useClipboard()
size="sm" size="sm"
:icon="copied ? 'i-lucide-copy-check' : 'i-lucide-copy'" :icon="copied ? 'i-lucide-copy-check' : 'i-lucide-copy'"
aria-label="Copy to clipboard" aria-label="Copy to clipboard"
@click="copy(value)" @click="copy"
/> />
</UTooltip> </UTooltip>
</template> </template>

View File

@@ -4,8 +4,8 @@ const value = ref('')
<template> <template>
<UInput v-model="value" placeholder="" :ui="{ base: 'peer' }"> <UInput v-model="value" placeholder="" :ui="{ base: 'peer' }">
<label class="pointer-events-none absolute left-0 -top-2.5 text-highlighted text-xs font-medium px-1.5 transition-all peer-focus:-top-2.5 peer-focus:text-highlighted peer-focus:text-xs peer-focus:font-medium peer-placeholder-shown:text-sm peer-placeholder-shown:text-dimmed peer-placeholder-shown:top-1.5 peer-placeholder-shown:font-normal"> <label class="pointer-events-none absolute left-0 -top-2.5 text-(--ui-text-highlighted) text-xs font-medium px-1.5 transition-all peer-focus:-top-2.5 peer-focus:text-(--ui-text-highlighted) peer-focus:text-xs peer-focus:font-medium peer-placeholder-shown:text-sm peer-placeholder-shown:text-(--ui-text-dimmed) peer-placeholder-shown:top-1.5 peer-placeholder-shown:font-normal">
<span class="inline-flex bg-default px-1">Email address</span> <span class="inline-flex bg-(--ui-bg) px-1">Email address</span>
</label> </label>
</UInput> </UInput>
</template> </template>

View File

@@ -1,14 +0,0 @@
<script setup lang="ts">
import { vMaska } from 'maska/vue'
</script>
<template>
<div class="flex flex-col gap-2">
<UInput v-maska="'#### #### #### ####'" placeholder="4242 4242 4242 4242" icon="i-lucide-credit-card" />
<div class="flex items-center gap-2">
<UInput v-maska="'##/##'" placeholder="MM/YY" icon="i-lucide-calendar" />
<UInput v-maska="'###'" placeholder="CVC" />
</div>
</div>
</template>

View File

@@ -40,9 +40,9 @@ const text = computed(() => {
placeholder="Password" placeholder="Password"
:color="color" :color="color"
:type="show ? 'text' : 'password'" :type="show ? 'text' : 'password'"
:ui="{ trailing: 'pe-1' }"
:aria-invalid="score < 4" :aria-invalid="score < 4"
aria-describedby="password-strength" aria-describedby="password-strength"
:ui="{ trailing: 'pe-1' }"
class="w-full" class="w-full"
> >
<template #trailing> <template #trailing>
@@ -77,7 +77,7 @@ const text = computed(() => {
v-for="(req, index) in strength" v-for="(req, index) in strength"
:key="index" :key="index"
class="flex items-center gap-0.5" class="flex items-center gap-0.5"
:class="req.met ? 'text-success' : 'text-muted'" :class="req.met ? 'text-(--ui-success)' : 'text-(--ui-text-muted)'"
> >
<UIcon :name="req.met ? 'i-lucide-circle-check' : 'i-lucide-circle-x'" class="size-4 shrink-0" /> <UIcon :name="req.met ? 'i-lucide-circle-check' : 'i-lucide-circle-x'" class="size-4 shrink-0" />

View File

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

View File

@@ -10,8 +10,8 @@ const open = ref(false)
<Placeholder class="h-48" /> <Placeholder class="h-48" />
</template> </template>
<template #footer="{ close }"> <template #footer>
<UButton label="Cancel" color="neutral" variant="outline" @click="close" /> <UButton label="Cancel" color="neutral" variant="outline" @click="open = false" />
<UButton label="Submit" color="neutral" /> <UButton label="Submit" color="neutral" />
</template> </template>
</UModal> </UModal>

View File

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

View File

@@ -1,11 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import type { NavigationMenuItem } from '@nuxt/ui'
const items = [ const items = [
{ {
label: 'Docs', label: 'Docs',
icon: 'i-lucide-book-open', icon: 'i-lucide-book-open',
slot: 'docs' as const, slot: 'docs',
children: [ children: [
{ {
label: 'Icons', label: 'Icons',
@@ -24,7 +22,7 @@ const items = [
{ {
label: 'Components', label: 'Components',
icon: 'i-lucide-box', icon: 'i-lucide-box',
slot: 'components' as const, slot: 'components',
children: [ children: [
{ {
label: 'Link', label: 'Link',
@@ -56,19 +54,18 @@ const items = [
label: 'GitHub', label: 'GitHub',
icon: 'i-simple-icons-github' icon: 'i-simple-icons-github'
} }
] satisfies NavigationMenuItem[] ]
</script> </script>
<template> <template>
<UNavigationMenu <UNavigationMenu
:items="items" :items="items"
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'
}" }"
class="w-full justify-center"
> >
<template #docs-content="{ item }"> <template #docs-content="{ item }">
<ul class="grid gap-2 p-4 lg:w-[500px] lg:grid-cols-[minmax(0,.75fr)_minmax(0,1fr)]"> <ul class="grid gap-2 p-4 lg:w-[500px] lg:grid-cols-[minmax(0,.75fr)_minmax(0,1fr)]">
@@ -77,11 +74,11 @@ const items = [
</li> </li>
<li v-for="child in item.children" :key="child.label"> <li v-for="child in item.children" :key="child.label">
<ULink class="text-sm text-left rounded-md p-3 transition-colors hover:bg-elevated/50"> <ULink class="text-sm text-left rounded-md p-3 transition-colors hover:bg-(--ui-bg-elevated)/50">
<p class="font-medium text-highlighted"> <p class="font-medium text-(--ui-text-highlighted)">
{{ child.label }} {{ child.label }}
</p> </p>
<p class="text-muted line-clamp-2"> <p class="text-(--ui-text-muted) line-clamp-2">
{{ child.description }} {{ child.description }}
</p> </p>
</ULink> </ULink>

View File

@@ -1,21 +1,21 @@
<script setup lang="ts"> <script setup lang="ts">
import type { NavigationMenuItem } from '@nuxt/ui'
const items = [ const items = [
{ {
label: 'Guide', label: 'Guide',
icon: 'i-lucide-book-open' icon: 'i-lucide-book-open'
}, },
{ {
label: 'Composables', label: 'Composables',
icon: 'i-lucide-database' icon: 'i-lucide-database'
}, },
{ {
label: 'Components', label: 'Components',
icon: 'i-lucide-box', icon: 'i-lucide-box',
slot: 'components' as const slot: 'components'
} }
] satisfies NavigationMenuItem[] ]
</script> </script>
<template> <template>

View File

@@ -1,7 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import type { NavigationMenuItem } from '@nuxt/ui' const items = [
const items: NavigationMenuItem[] = [
{ {
label: 'Guide', label: 'Guide',
icon: 'i-lucide-book-open', icon: 'i-lucide-book-open',

View File

@@ -1,19 +0,0 @@
<script lang="ts" setup>
const open = ref(false)
</script>
<template>
<UPopover
v-model:open="open"
:dismissible="false"
:ui="{ content: 'w-(--reka-popper-anchor-width) p-4' }"
>
<template #anchor>
<UInput placeholder="Focus to open" @focus="open = true" @blur="open = false" />
</template>
<template #content>
<Placeholder class="w-full aspect-square" />
</template>
</UPopover>
</template>

View File

@@ -40,7 +40,7 @@ const label = ref([])
multiple multiple
placeholder="Search labels..." placeholder="Search labels..."
:groups="[{ id: 'labels', items }]" :groups="[{ id: 'labels', items }]"
:ui="{ input: '[&>input]:h-8 [&>input]:text-sm' }" :ui="{ input: '[&>input]:h-8' }"
/> />
</template> </template>
</UPopover> </UPopover>

View File

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

View File

@@ -8,7 +8,7 @@ const open = ref(false)
<template #content> <template #content>
<div class="flex items-center gap-4 mb-4"> <div class="flex items-center gap-4 mb-4">
<h2 class="text-highlighted font-semibold"> <h2 class="text-(--ui-text-highlighted) font-semibold">
Popover non-dismissible Popover non-dismissible
</h2> </h2>

View File

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

View File

@@ -4,7 +4,8 @@ const { data: countries, status, execute } = await useLazyFetch<{
code: string code: string
emoji: string emoji: string
}[]>('/api/countries.json', { }[]>('/api/countries.json', {
immediate: false immediate: false,
default: () => []
}) })
function onOpen() { function onOpen() {

View File

@@ -1,6 +1,4 @@
<script setup lang="ts"> <script setup lang="ts">
import type { AvatarProps } from '@nuxt/ui'
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', key: 'typicode-users',
transform: (data: { id: number, name: string }[]) => { transform: (data: { id: number, name: string }[]) => {
@@ -8,7 +6,7 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
label: user.name, label: user.name,
value: String(user.id), value: String(user.id),
avatar: { src: `https://i.pravatar.cc/120?img=${user.id}` } avatar: { src: `https://i.pravatar.cc/120?img=${user.id}` }
})) })) || []
}, },
lazy: true lazy: true
}) })
@@ -16,7 +14,7 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
<template> <template>
<USelectMenu <USelectMenu
:items="users" :items="users || []"
:loading="status === 'pending'" :loading="status === 'pending'"
icon="i-lucide-user" icon="i-lucide-user"
placeholder="Select user" placeholder="Select user"
@@ -26,7 +24,7 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
<UAvatar <UAvatar
v-if="modelValue" v-if="modelValue"
v-bind="modelValue.avatar" v-bind="modelValue.avatar"
:size="(ui.leadingAvatarSize() as AvatarProps['size'])" :size="ui.leadingAvatarSize()"
:class="ui.leadingAvatar()" :class="ui.leadingAvatar()"
/> />
</template> </template>

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