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
613 changed files with 22343 additions and 33133 deletions

View File

@@ -5,7 +5,7 @@ body:
- type: markdown - type: markdown
attributes: attributes:
value: | value: |
Before reporting a bug, please make sure that you have read through our [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:
@@ -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

@@ -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

@@ -42,8 +42,6 @@ jobs:
- name: Build application - name: Build application
run: pnpm run docs:build run: pnpm run docs:build
env:
NODE_OPTIONS: '--max-old-space-size=8192'
- name: Deploy to NuxtHub - name: Deploy to NuxtHub
uses: nuxt-hub/action@v1 uses: nuxt-hub/action@v1

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.workflow_sha }} | cut -c1-7)" >> $GITHUB_ENV
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Install node
uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- name: Install latest nuxt/ui
run: pnpm install https://pkg.pr.new/@nuxt/ui@${{ env.COMMIT_SHA }} --lockfile-only
- name: Install dependencies
run: pnpm install --ignore-workspace
- name: Prepare
run: pnpm nuxi prepare
- name: Typecheck
run: pnpm run typecheck
starter-nuxt: starter-nuxt:
needs: build needs: build
@@ -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:

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,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

@@ -10,16 +10,14 @@ jobs:
permissions: permissions:
issues: write issues: write
pull-requests: write
steps: steps:
- uses: actions/stale@v9 - uses: actions/stale@v9
with: with:
days-before-stale: -1 # Issues and PR will never be flagged stale automatically. exempt-issue-labels: triage,v3
stale-issue-label: 'needs reproduction' # Label that flags an issue as stale. stale-issue-message: 'This issue is stale because it has been open for 30 days with no activity.'
only-labels: 'needs reproduction' # Only process these issues stale-issue-label: stale
days-before-issue-close: 7 stale-pr-label: stale
ignore-updates: true days-before-stale: 30
remove-stale-when-updated: false days-before-close: -1
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

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

@@ -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,170 +1,5 @@
# Changelog # Changelog
## [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,10 +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'
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}'
import type { ComponentConfig } from '../types/utils' import { tv } from '${pro ? '#ui/utils/tv' : '../utils/tv'}'
type ${upperName} = ComponentConfig<typeof theme, AppConfig, ${upperName}${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 {
/** /**
@@ -43,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 {
@@ -52,37 +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.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'
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}'
import type { ComponentConfig } from '../types/utils' import { tv } from '${pro ? '#ui/utils/tv' : '../utils/tv'}'
type ${upperName} = ComponentConfig<typeof theme, AppConfig, ${upperName}${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 {}
@@ -91,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.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>
` `
} }

View File

@@ -22,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: [
@@ -39,8 +39,7 @@ useServerSeoMeta({
twitterCard: 'summary_large_image' twitterCard: 'summary_large_image'
}) })
useFaviconFromTheme() const { frameworks, modules } = useSharedData()
const { mappedNavigation, filteredNavigation } = useContentNavigation(navigation) const { mappedNavigation, filteredNavigation } = useContentNavigation(navigation)
provide('navigation', mappedNavigation) provide('navigation', mappedNavigation)
@@ -51,7 +50,7 @@ provide('navigation', mappedNavigation)
<NuxtLoadingIndicator color="var(--ui-primary)" :height="2" /> <NuxtLoadingIndicator color="var(--ui-primary)" :height="2" />
<template v-if="!route.path.startsWith('/examples')"> <template v-if="!route.path.startsWith('/examples')">
<!-- <Banner /> --> <Banner />
<Header :links="links" /> <Header :links="links" />
</template> </template>
@@ -63,11 +62,26 @@ provide('navigation', mappedNavigation)
<template v-if="!route.path.startsWith('/examples')"> <template v-if="!route.path.startsWith('/examples')">
<Footer /> <Footer />
<Search :files="files" :navigation="filteredNavigation" /> <ClientOnly>
<LazyUContentSearch
:files="files"
:groups="[{
id: 'framework',
label: 'Framework',
items: frameworks
}, {
id: 'module',
label: 'Module',
items: modules
}]"
:navigation="filteredNavigation"
:fuse="{ resultLimit: 100 }"
/>
</ClientOnly>
</template> </template>
</UApp> </UApp>
</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

@@ -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,26 +22,14 @@ onMounted(() => {
const navigation = inject<Ref<ContentNavigationItem[]>>('navigation') const navigation = inject<Ref<ContentNavigationItem[]>>('navigation')
const githubLink = computed(() => {
return `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'
}
])
</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>
@@ -63,7 +51,7 @@ const mobileLinks = 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(' ')
}" }"
@@ -85,7 +73,7 @@ const mobileLinks = 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"
@@ -108,7 +96,7 @@ const mobileLinks = 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,199 +0,0 @@
<script setup lang="ts">
import type { DefineComponent } from 'vue'
import type { ContentNavigationItem } from '@nuxt/content'
import { useChat } from '@ai-sdk/vue'
import ProseStreamPre from './prose/PreStream.vue'
const components = {
pre: ProseStreamPre as unknown as DefineComponent
}
interface ContentSearchFile {
id: string
title: string
titles: string[]
level: number
content: string
}
defineProps<{
files?: ContentSearchFile[]
navigation?: ContentNavigationItem[]
}>()
const { frameworks, modules } = useSharedData()
const { messages, input, handleSubmit, status, error, reload, setMessages } = useChat({
maxSteps: 2
})
const ai = ref(false)
const searchTerm = ref('')
const links = computed(() => [{
label: 'Ask AI',
icon: 'i-lucide-bot',
onSelect: (e: any) => {
e.preventDefault()
ai.value = true
}
}, {
label: 'Docs',
icon: 'i-lucide-square-play',
to: '/getting-started'
}, {
label: 'Components',
icon: 'i-lucide-square-code',
to: '/components'
}, {
icon: 'i-lucide-sparkles',
label: 'Pro > Features',
description: 'A collection of premium Vue components.',
to: '/pro'
}, {
icon: 'i-lucide-credit-card',
label: 'Pro > Pricing',
description: 'Free in development, buy when ready to launch.',
to: '/pro/pricing'
}, {
icon: 'i-lucide-panels-top-left',
label: 'Pro > Templates',
description: 'Official templates made with Nuxt UI Pro.',
to: '/pro/templates'
}, {
icon: 'i-lucide-circle-check',
label: 'Pro > Activate',
description: 'Enable Nuxt UI Pro in your production projects.',
to: '/pro/activate'
}, {
label: 'Figma',
icon: 'i-simple-icons-figma',
to: '/figma'
}, {
icon: 'i-lucide-presentation',
label: 'Community > Showcase',
description: 'Check out some of the amazing projects built with Nuxt UI.',
to: '/showcase'
}, {
label: 'Community > Contribution',
description: 'A comprehensive guide on contributing to Nuxt UI, including project structure, development workflow, and best practices.',
icon: 'i-lucide-git-pull-request-arrow',
to: '/getting-started/contribution'
}, {
label: 'Community > Roadmap',
description: 'Track our development progress in real-time.',
icon: 'i-lucide-map',
to: '/roadmap'
}, {
label: 'Community > Devtools',
description: 'Integrate Nuxt UI with Nuxt Devtools with Compodium.',
icon: 'i-lucide-code',
to: 'https://github.com/romhml/compodium',
target: '_blank'
}, {
label: 'Community > Team',
description: 'Meet the team behind Nuxt UI.',
icon: 'i-lucide-users',
to: '/team'
}, {
label: 'Releases',
icon: 'i-lucide-rocket',
to: 'https://github.com/nuxt/ui/releases',
target: '_blank'
}])
const groups = computed(() => [{
id: 'ai',
label: 'AI',
ignoreFilter: true,
items: [{
label: searchTerm.value ? `Ask Nuxt AI for “${searchTerm.value}` : 'Ask Nuxt AI',
icon: 'i-lucide-bot',
onSelect: (e: any) => {
e.preventDefault()
ai.value = true
if (searchTerm.value) {
setMessages([{
id: '1',
role: 'user',
content: searchTerm.value
}])
reload()
}
}
}]
}, {
id: 'framework',
label: 'Framework',
items: frameworks.value
}, {
id: 'module',
label: 'Module',
items: modules.value
}])
function onClose(e: Event) {
console.log('onClose')
e.preventDefault()
ai.value = false
}
</script>
<template>
<LazyUContentSearch
v-model:search-term="searchTerm"
:links="links"
:files="files"
:groups="groups"
:navigation="navigation"
:fuse="{ resultLimit: 100 }"
>
<template v-if="ai" #content>
<UChatPalette>
<UChatMessages
:messages="messages"
:status="status"
:user="{ side: 'left', variant: 'naked', icon: 'i-lucide-user' }"
:assistant="{ icon: 'i-lucide-bot' }"
>
<template #content="{ message }">
<MDCCached
v-if="message.toolInvocations?.[0]?.state === 'result'"
:value="message.toolInvocations?.[0]?.result"
:cache-key="message.id"
unwrap="p"
:components="components"
:parser-options="{ highlight: false }"
/>
<MDCCached
v-else-if="message.content.length > 0"
:value="message.content"
:cache-key="message.id"
unwrap="p"
:components="components"
:parser-options="{ highlight: false }"
/>
<span v-else class="italic font-light">
Searching documentation...
</span>
</template>
</UChatMessages>
<template #prompt>
<UChatPrompt
v-model="input"
icon="i-lucide-search"
variant="naked"
:error="error"
@submit="handleSubmit"
@close="onClose"
/>
</template>
</UChatPalette>
</template>
</LazyUContentSearch>
</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

@@ -36,7 +36,7 @@ const meta = await fetchComponentMeta(name as any)
<ProseTd> <ProseTd>
<HighlightInlineType v-if="slot.type" :type="slot.type" /> <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,25 +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
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

@@ -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>

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,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-refresh-cw" 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

@@ -7,7 +7,7 @@ const open = ref(false)
<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,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

@@ -30,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)
}) })
@@ -50,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)
} }
@@ -104,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

@@ -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,16 +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

@@ -15,7 +15,7 @@ const domain = ref(domains[0])
}" }"
> >
<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

@@ -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

@@ -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

@@ -13,9 +13,7 @@ const modal = overlay.create(LazyModalExample, {
}) })
async function open() { async function open() {
const instance = modal.open() const shouldIncrement = await modal.open()
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,7 +54,7 @@ const items = [
label: 'GitHub', label: 'GitHub',
icon: 'i-simple-icons-github' icon: 'i-simple-icons-github'
} }
] satisfies NavigationMenuItem[] ]
</script> </script>
<template> <template>
@@ -65,7 +63,6 @@ const items = [
class="w-full justify-center" class="w-full justify-center"
:ui="{ :ui="{
viewport: 'sm:w-(--reka-navigation-menu-viewport-width)', viewport: 'sm:w-(--reka-navigation-menu-viewport-width)',
content: 'sm:w-auto',
childList: 'sm:w-96', childList: 'sm:w-96',
childLinkDescription: 'text-balance line-clamp-2' childLinkDescription: 'text-balance line-clamp-2'
}" }"
@@ -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

@@ -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

@@ -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

@@ -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>

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>
<USelectMenu <USelectMenu
: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>
<USelectMenu <USelectMenu
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"
@@ -32,7 +30,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 { SelectMenuItem } from '@nuxt/ui'
const items = ref([ const items = ref([
{ {
label: 'benjamincanac', label: 'benjamincanac',
@@ -25,16 +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 SelectMenuItem[]) ])
const value = ref(items.value[0]) const value = ref(items.value[0])
</script> </script>

View File

@@ -1,29 +1,27 @@
<script setup lang="ts"> <script setup lang="ts">
import type { SelectMenuItem, 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 SelectMenuItem[]) ])
const value = ref(items.value[0]) const value = ref(items.value[0])
</script> </script>
@@ -35,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 { SelectMenuItem } from '@nuxt/ui'
const items = ref([ const items = ref([
{ {
label: 'Backlog', label: 'Backlog',
@@ -22,7 +20,7 @@ const items = ref([
value: 'done', value: 'done',
icon: 'i-lucide-circle-check' icon: 'i-lucide-circle-check'
} }
] satisfies SelectMenuItem[]) ])
const value = ref(items.value[0]) const value = ref(items.value[0])
</script> </script>

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
}) })
@@ -20,18 +18,17 @@ function getUserAvatar(value: string) {
<template> <template>
<USelect <USelect
:items="users" :items="users || []"
:loading="status === 'pending'" :loading="status === 'pending'"
icon="i-lucide-user" icon="i-lucide-user"
placeholder="Select user" placeholder="Select user"
class="w-48" class="w-48"
value-key="value"
> >
<template #leading="{ modelValue, ui }"> <template #leading="{ modelValue, ui }">
<UAvatar <UAvatar
v-if="modelValue" v-if="modelValue"
v-bind="getUserAvatar(modelValue)" v-bind="getUserAvatar(modelValue as string)"
: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 { SelectItem } from '@nuxt/ui'
const items = ref([ const items = ref([
{ {
label: 'benjamincanac', label: 'benjamincanac',
@@ -25,21 +23,13 @@ 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 SelectItem[]) ])
const value = ref(items.value[0]?.value) const value = ref(items.value[0]?.value)
const avatar = computed(() => items.value.find(item => item.value === value.value)?.avatar) const avatar = computed(() => items.value.find(item => item.value === value.value)?.avatar)
</script> </script>
<template> <template>
<USelect v-model="value" :items="items" value-key="value" :avatar="avatar" class="w-48" /> <USelect v-model="value" :avatar="avatar" :items="items" class="w-48" />
</template> </template>

View File

@@ -1,30 +1,27 @@
<script setup lang="ts"> <script setup lang="ts">
import type { SelectItem, 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 SelectItem[]) ])
const value = ref(items.value[0]?.value) const value = ref(items.value[0]?.value)
function getChip(value: string) { function getChip(value: string) {
@@ -33,14 +30,14 @@ function getChip(value: string) {
</script> </script>
<template> <template>
<USelect v-model="value" :items="items" value-key="value" class="w-48"> <USelect v-model="value" :items="items" class="w-48">
<template #leading="{ modelValue, ui }"> <template #leading="{ modelValue, ui }">
<UChip <UChip
v-if="modelValue" v-if="modelValue"
v-bind="getChip(modelValue)" v-bind="getChip(modelValue as string)"
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 { SelectItem } from '@nuxt/ui'
const items = ref([ const items = ref([
{ {
label: 'Backlog', label: 'Backlog',
@@ -22,12 +20,12 @@ const items = ref([
value: 'done', value: 'done',
icon: 'i-lucide-circle-check' icon: 'i-lucide-circle-check'
} }
] satisfies SelectItem[]) ])
const value = ref(items.value[0]?.value) const value = ref(items.value[0]?.value)
const icon = computed(() => items.value.find(item => item.value === value.value)?.icon) const icon = computed(() => items.value.find(item => item.value === value.value)?.icon)
</script> </script>
<template> <template>
<USelect v-model="value" :items="items" value-key="value" :icon="icon" class="w-48" /> <USelect v-model="value" :icon="icon" :items="items" class="w-48" />
</template> </template>

View File

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

View File

@@ -1,7 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import type { StepperItem } from '@nuxt/ui' const items = [
const items: StepperItem[] = [
{ {
title: 'Address', title: 'Address',
description: 'Add your address here', description: 'Add your address here',

View File

@@ -1,23 +1,21 @@
<script setup lang="ts"> <script setup lang="ts">
import type { StepperItem } from '@nuxt/ui'
const items = [ const items = [
{ {
slot: 'address' as const, slot: 'address',
title: 'Address', title: 'Address',
description: 'Add your address here', description: 'Add your address here',
icon: 'i-lucide-house' icon: 'i-lucide-house'
}, { }, {
slot: 'shipping' as const, slot: 'shipping',
title: 'Shipping', title: 'Shipping',
description: 'Set your preferred shipping method', description: 'Set your preferred shipping method',
icon: 'i-lucide-truck' icon: 'i-lucide-truck'
}, { }, {
slot: 'checkout' as const, slot: 'checkout',
title: 'Checkout', title: 'Checkout',
description: 'Confirm your order' description: 'Confirm your order'
} }
] satisfies StepperItem[] ]
</script> </script>
<template> <template>

View File

@@ -1,8 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import type { StepperItem } from '@nuxt/ui'
import { onMounted, ref } from 'vue' import { onMounted, ref } from 'vue'
const items: StepperItem[] = [ const items = [
{ {
title: 'Address', title: 'Address',
description: 'Add your address here', description: 'Add your address here',

View File

@@ -1,16 +1,17 @@
<script setup lang="ts"> <script setup lang="ts">
import type { StepperItem } from '@nuxt/ui' const items = [
const items: StepperItem[] = [
{ {
slot: 'address',
title: 'Address', title: 'Address',
description: 'Add your address here', description: 'Add your address here',
icon: 'i-lucide-house' icon: 'i-lucide-house'
}, { }, {
slot: 'shipping',
title: 'Shipping', title: 'Shipping',
description: 'Set your preferred shipping method', description: 'Set your preferred shipping method',
icon: 'i-lucide-truck' icon: 'i-lucide-truck'
}, { }, {
slot: 'checkout',
title: 'Checkout', title: 'Checkout',
description: 'Confirm your order' description: 'Confirm your order'
} }

View File

@@ -100,7 +100,7 @@ const columnFilters = ref([{
<template> <template>
<div class="flex flex-col flex-1 w-full"> <div class="flex flex-col flex-1 w-full">
<div class="flex px-4 py-3.5 border-b border-accented"> <div class="flex px-4 py-3.5 border-b border-(--ui-border-accented)">
<UInput <UInput
:model-value="(table?.tableApi?.getColumn('email')?.getFilterValue() as string)" :model-value="(table?.tableApi?.getColumn('email')?.getFilterValue() as string)"
class="max-w-sm" class="max-w-sm"

View File

@@ -97,11 +97,10 @@ function getHeader(column: Column<Payment>, label: string) {
const isSorted = column.getIsSorted() const isSorted = column.getIsSorted()
return h(UDropdownMenu, { return h(UDropdownMenu, {
'content': { content: {
align: 'start' align: 'start'
}, },
'aria-label': 'Actions dropdown', items: [{
'items': [{
label: 'Asc', label: 'Asc',
type: 'checkbox', type: 'checkbox',
icon: 'i-lucide-arrow-up-narrow-wide', icon: 'i-lucide-arrow-up-narrow-wide',
@@ -127,12 +126,11 @@ function getHeader(column: Column<Payment>, label: string) {
} }
}] }]
}, () => h(UButton, { }, () => h(UButton, {
'color': 'neutral', color: 'neutral',
'variant': 'ghost', variant: 'ghost',
label, label,
'icon': isSorted ? (isSorted === 'asc' ? 'i-lucide-arrow-up-narrow-wide' : 'i-lucide-arrow-down-wide-narrow') : 'i-lucide-arrow-up-down', icon: isSorted ? (isSorted === 'asc' ? 'i-lucide-arrow-up-narrow-wide' : 'i-lucide-arrow-down-wide-narrow') : 'i-lucide-arrow-up-down',
'class': '-mx-2.5 data-[state=open]:bg-elevated', class: '-mx-2.5 data-[state=open]:bg-(--ui-bg-elevated)'
'aria-label': `Sort by ${isSorted === 'asc' ? 'descending' : 'ascending'}`
})) }))
} }

View File

@@ -100,7 +100,7 @@ const columnVisibility = ref({
<template> <template>
<div class="flex flex-col flex-1 w-full"> <div class="flex flex-col flex-1 w-full">
<div class="flex justify-end px-4 py-3.5 border-b border-accented"> <div class="flex justify-end px-4 py-3.5 border-b border-(--ui-border-accented)">
<UDropdownMenu <UDropdownMenu
:items="table?.tableApi?.getAllColumns().filter(column => column.getCanHide()).map(column => ({ :items="table?.tableApi?.getAllColumns().filter(column => column.getCanHide()).map(column => ({
label: upperFirst(column.id), label: upperFirst(column.id),

View File

@@ -1,82 +0,0 @@
<script setup lang="ts">
import type { TableColumn } from '@nuxt/ui'
import { useSortable } from '@vueuse/integrations/useSortable.mjs'
type Payment = {
id: string
date: string
email: string
amount: number
}
const data = ref<Payment[]>([{
id: '4600',
date: '2024-03-11T15:30:00',
email: 'james.anderson@example.com',
amount: 594
}, {
id: '4599',
date: '2024-03-11T10:10:00',
email: 'mia.white@example.com',
amount: 276
}, {
id: '4598',
date: '2024-03-11T08:50:00',
email: 'william.brown@example.com',
amount: 315
}, {
id: '4597',
date: '2024-03-10T19:45:00',
email: 'emma.davis@example.com',
amount: 529
}])
const columns: TableColumn<Payment>[] = [{
accessorKey: 'id',
header: '#',
cell: ({ row }) => `#${row.getValue('id')}`
}, {
accessorKey: 'date',
header: 'Date',
cell: ({ row }) => {
return new Date(row.getValue('date')).toLocaleString('en-US', {
day: 'numeric',
month: 'short',
hour: '2-digit',
minute: '2-digit',
hour12: false
})
}
}, {
accessorKey: 'email',
header: 'Email'
}, {
accessorKey: 'amount',
header: () => h('div', { class: 'text-right' }, 'Amount'),
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
const formatted = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
return h('div', { class: 'text-right font-medium' }, formatted)
}
}]
useSortable('.my-table-tbody', data, {
animation: 150
})
</script>
<template>
<div class="w-full">
<UTable
ref="table"
:data="data"
:columns="columns"
:ui="{
tbody: 'my-table-tbody'
}"
/>
</div>
</template>

View File

@@ -145,12 +145,12 @@ const columns: TableColumn<Payment>[] = [{
header: ({ table }) => h(UCheckbox, { header: ({ table }) => h(UCheckbox, {
'modelValue': table.getIsSomePageRowsSelected() ? 'indeterminate' : table.getIsAllPageRowsSelected(), 'modelValue': table.getIsSomePageRowsSelected() ? 'indeterminate' : table.getIsAllPageRowsSelected(),
'onUpdate:modelValue': (value: boolean | 'indeterminate') => table.toggleAllPageRowsSelected(!!value), 'onUpdate:modelValue': (value: boolean | 'indeterminate') => table.toggleAllPageRowsSelected(!!value),
'aria-label': 'Select all' 'ariaLabel': 'Select all'
}), }),
cell: ({ row }) => h(UCheckbox, { cell: ({ row }) => h(UCheckbox, {
'modelValue': row.getIsSelected(), 'modelValue': row.getIsSelected(),
'onUpdate:modelValue': (value: boolean | 'indeterminate') => row.toggleSelected(!!value), 'onUpdate:modelValue': (value: boolean | 'indeterminate') => row.toggleSelected(!!value),
'aria-label': 'Select row' 'ariaLabel': 'Select row'
}), }),
enableSorting: false, enableSorting: false,
enableHiding: false enableHiding: false
@@ -242,17 +242,15 @@ const columns: TableColumn<Payment>[] = [{
}] }]
return h('div', { class: 'text-right' }, h(UDropdownMenu, { return h('div', { class: 'text-right' }, h(UDropdownMenu, {
'content': { content: {
align: 'end' align: 'end'
}, },
items, items
'aria-label': 'Actions dropdown'
}, () => h(UButton, { }, () => h(UButton, {
'icon': 'i-lucide-ellipsis-vertical', icon: 'i-lucide-ellipsis-vertical',
'color': 'neutral', color: 'neutral',
'variant': 'ghost', variant: 'ghost',
'class': 'ml-auto', class: 'ml-auto'
'aria-label': 'Actions dropdown'
}))) })))
} }
}] }]
@@ -265,7 +263,7 @@ function randomize() {
</script> </script>
<template> <template>
<div class="flex-1 divide-y divide-accented w-full"> <div class="flex-1 divide-y divide-(--ui-border-accented) w-full">
<div class="flex items-center gap-2 px-4 py-3.5 overflow-x-auto"> <div class="flex items-center gap-2 px-4 py-3.5 overflow-x-auto">
<UInput <UInput
:model-value="(table?.tableApi?.getColumn('email')?.getFilterValue() as string)" :model-value="(table?.tableApi?.getColumn('email')?.getFilterValue() as string)"
@@ -296,7 +294,6 @@ function randomize() {
variant="outline" variant="outline"
trailing-icon="i-lucide-chevron-down" trailing-icon="i-lucide-chevron-down"
class="ml-auto" class="ml-auto"
aria-label="Columns select dropdown"
/> />
</UDropdownMenu> </UDropdownMenu>
</div> </div>
@@ -313,7 +310,7 @@ function randomize() {
</template> </template>
</UTable> </UTable>
<div class="px-4 py-3.5 text-sm text-muted"> <div class="px-4 py-3.5 text-sm text-(--ui-text-muted)">
{{ table?.tableApi?.getFilteredSelectedRowModel().rows.length || 0 }} of {{ table?.tableApi?.getFilteredSelectedRowModel().rows.length || 0 }} of
{{ table?.tableApi?.getFilteredRowModel().rows.length || 0 }} row(s) selected. {{ table?.tableApi?.getFilteredRowModel().rows.length || 0 }} row(s) selected.
</div> </div>

View File

@@ -17,7 +17,7 @@ const { data, status } = await useFetch<User[]>('https://jsonplaceholder.typicod
transform: (data) => { transform: (data) => {
return data?.map(user => ({ return data?.map(user => ({
...user, ...user,
avatar: { src: `https://i.pravatar.cc/120?img=${user.id}`, alt: `${user.name} avatar` } avatar: { src: `https://i.pravatar.cc/120?img=${user.id}` }
})) || [] })) || []
}, },
lazy: true lazy: true
@@ -36,7 +36,7 @@ const columns: TableColumn<User>[] = [{
size: 'lg' size: 'lg'
}), }),
h('div', undefined, [ h('div', undefined, [
h('p', { class: 'font-medium text-highlighted' }, row.original.name), h('p', { class: 'font-medium text-(--ui-text-highlighted)' }, row.original.name),
h('p', { class: '' }, `@${row.original.username}`) h('p', { class: '' }, `@${row.original.username}`)
]) ])
]) ])

View File

@@ -95,7 +95,7 @@ const globalFilter = ref('45')
<template> <template>
<div class="flex flex-col flex-1 w-full"> <div class="flex flex-col flex-1 w-full">
<div class="flex px-4 py-3.5 border-b border-accented"> <div class="flex px-4 py-3.5 border-b border-(--ui-border-accented)">
<UInput <UInput
v-model="globalFilter" v-model="globalFilter"
class="max-w-sm" class="max-w-sm"

View File

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

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