Compare commits
156 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
268e29b041 | ||
|
|
b0364b96b7 | ||
|
|
ba3c6e8788 | ||
|
|
01da3cbf31 | ||
|
|
595fc64515 | ||
|
|
81569713e9 | ||
|
|
1a8feb751e | ||
|
|
1d281e915a | ||
|
|
c3adc381c9 | ||
|
|
edca3bcb74 | ||
|
|
8f32ee3d24 | ||
|
|
9172bb7dc2 | ||
|
|
32dae2e002 | ||
|
|
be41aed1f3 | ||
|
|
bf678412ca | ||
|
|
a999600e9f | ||
|
|
04f12adc5b | ||
|
|
abfd0ede03 | ||
|
|
2fa8db64dd | ||
|
|
52f1963833 | ||
|
|
9a83c9c7f4 | ||
|
|
f2510cb342 | ||
|
|
4dd56c8111 | ||
|
|
6e3ec6a077 | ||
|
|
59c26ec123 | ||
|
|
67ef866a40 | ||
|
|
5170cfd7eb | ||
|
|
9bcf1ad92f | ||
|
|
7a2bd4e617 | ||
|
|
8781a07909 | ||
|
|
2492526d7c | ||
|
|
54bb2282c5 | ||
|
|
2a2495a652 | ||
|
|
f17b15ed1e | ||
|
|
66355ba301 | ||
|
|
4873b3a043 | ||
|
|
0d4baf7851 | ||
|
|
04333cd8cb | ||
|
|
cb3522ed18 | ||
|
|
1a4de49c16 | ||
|
|
3eb7812f2d | ||
|
|
080aed7225 | ||
|
|
d77fd6102a | ||
|
|
0f558fc0d0 | ||
|
|
1841e13b32 | ||
|
|
4e7c1c9c30 | ||
|
|
4b3dd48778 | ||
|
|
a9e8ea9231 | ||
|
|
4dd9344ff9 | ||
|
|
180c150e0f | ||
|
|
145cae798c | ||
|
|
9aea54267a | ||
|
|
9400552491 | ||
|
|
89753fc337 | ||
|
|
f8a6bd3bf6 | ||
|
|
228d4c9835 | ||
|
|
150b334b1d | ||
|
|
bf56e15a2e | ||
|
|
09151df170 | ||
|
|
22b917a0f7 | ||
|
|
43cbb94ee2 | ||
|
|
3bf5acb683 | ||
|
|
d37315cc83 | ||
|
|
326bb9a31e | ||
|
|
4157260a02 | ||
|
|
03b20fdb26 | ||
|
|
004c93bfa2 | ||
|
|
18eb5e6b97 | ||
|
|
42f7f94521 | ||
|
|
ea0c459306 | ||
|
|
546df572fc | ||
|
|
37abcc6a5b | ||
|
|
accf69046c | ||
|
|
80177679f2 | ||
|
|
536b7afcc1 | ||
|
|
483e473e3f | ||
|
|
5835eb5f0f | ||
|
|
ca507c6a0d | ||
|
|
03ac395164 | ||
|
|
f761369888 | ||
|
|
7df7ee336a | ||
|
|
2ee1c5ac2e | ||
|
|
62bc7b25a2 | ||
|
|
66f6c7743c | ||
|
|
dec2b9fd6a | ||
|
|
4604da0f16 | ||
|
|
a9d693095b | ||
|
|
cddcb95ed4 | ||
|
|
967968e02e | ||
|
|
f8e560525f | ||
|
|
8216b59d4f | ||
|
|
44ea02c0d6 | ||
|
|
f95abf8d1d | ||
|
|
dcf34a7ac2 | ||
|
|
2ba94db09e | ||
|
|
d9e9fea35e | ||
|
|
dae9f0b863 | ||
|
|
0a72024361 | ||
|
|
41087d4c95 | ||
|
|
6aab62ec30 | ||
|
|
742a37201e | ||
|
|
473513c246 | ||
|
|
fe4e1f859d | ||
|
|
3243fb88f7 | ||
|
|
43d281f6d1 | ||
|
|
405304775e | ||
|
|
0559beb365 | ||
|
|
56fc757244 | ||
|
|
9cf9f25f44 | ||
|
|
02363994d6 | ||
|
|
f2682fd2ae | ||
|
|
0634a756a4 | ||
|
|
44f536fd00 | ||
|
|
d0be59946b | ||
|
|
1e2a10b4bd | ||
|
|
3c78e2fd98 | ||
|
|
6887e33aae | ||
|
|
28e869e8aa | ||
|
|
d86956e1d5 | ||
|
|
23e4f0ec4d | ||
|
|
c00f6e8cdf | ||
|
|
d29e1481f2 | ||
|
|
79aa161c6d | ||
|
|
94ea75f441 | ||
|
|
0c368c8ab8 | ||
|
|
c5796c4f82 | ||
|
|
204953b780 | ||
|
|
2e4c3082a1 | ||
|
|
f2fd778c0a | ||
|
|
d79da9d7b6 | ||
|
|
a4429eee09 | ||
|
|
0905b2b3d5 | ||
|
|
c7fba2e0eb | ||
|
|
4167f04205 | ||
|
|
276268d311 | ||
|
|
717e35f098 | ||
|
|
459a0410ab | ||
|
|
b9adc83e78 | ||
|
|
d7a4d029b7 | ||
|
|
3c8d6cd01d | ||
|
|
67da90a2f6 | ||
|
|
894e8a61b6 | ||
|
|
1b6ab271ea | ||
|
|
0dc4678c68 | ||
|
|
e86dc79e51 | ||
|
|
35997377a6 | ||
|
|
12303a87be | ||
|
|
f84ccddcd6 | ||
|
|
869c0708bd | ||
|
|
c63d2f380a | ||
|
|
92632e969e | ||
|
|
f6d7994a55 | ||
|
|
f738f68f76 | ||
|
|
17d6803329 | ||
|
|
732a67aa88 | ||
|
|
bdf129fc38 |
1
.github/CODEOWNERS
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
* @benjamincanac
|
||||||
2
.github/ISSUE_TEMPLATE/bug-report-v3.yml
vendored
@@ -10,7 +10,7 @@ body:
|
|||||||
id: env
|
id: env
|
||||||
attributes:
|
attributes:
|
||||||
label: Environment
|
label: Environment
|
||||||
description: You can use `npx nuxi info` to fill this section
|
description: You can use `npx nuxt info` to fill this section
|
||||||
placeholder: |
|
placeholder: |
|
||||||
- Operating System: `Darwin`
|
- Operating System: `Darwin`
|
||||||
- Node Version: `v18.16.0`
|
- Node Version: `v18.16.0`
|
||||||
|
|||||||
2
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
@@ -10,7 +10,7 @@ body:
|
|||||||
id: env
|
id: env
|
||||||
attributes:
|
attributes:
|
||||||
label: Environment
|
label: Environment
|
||||||
description: You can use `npx nuxi info` to fill this section
|
description: You can use `npx nuxt info` to fill this section
|
||||||
placeholder: |
|
placeholder: |
|
||||||
- Operating System: `Darwin`
|
- Operating System: `Darwin`
|
||||||
- Node Version: `v18.16.0`
|
- Node Version: `v18.16.0`
|
||||||
|
|||||||
14
.github/workflows/docs.yml
vendored
@@ -6,10 +6,6 @@ jobs:
|
|||||||
deploy:
|
deploy:
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
environment:
|
|
||||||
name: ${{ github.ref == 'refs/heads/v3' && 'production' || 'preview' }}
|
|
||||||
url: ${{ steps.deploy.outputs.deployment-url }}
|
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
id-token: write
|
id-token: write
|
||||||
@@ -40,14 +36,10 @@ jobs:
|
|||||||
- name: Prepare build
|
- name: Prepare build
|
||||||
run: pnpm run dev:prepare
|
run: pnpm run dev:prepare
|
||||||
|
|
||||||
- name: Build application
|
- name: Deploy to NuxtHub
|
||||||
run: pnpm run docs:build
|
uses: nuxt-hub/action@v2
|
||||||
env:
|
env:
|
||||||
NODE_OPTIONS: '--max-old-space-size=8192'
|
NODE_OPTIONS: '--max-old-space-size=8192'
|
||||||
|
|
||||||
- name: Deploy to NuxtHub
|
|
||||||
uses: nuxt-hub/action@v1
|
|
||||||
id: deploy
|
|
||||||
with:
|
with:
|
||||||
project-key: ui-7eg3
|
project-key: ui-7eg3
|
||||||
directory: docs/dist
|
directory: docs
|
||||||
|
|||||||
10
.github/workflows/module.yml
vendored
@@ -93,7 +93,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Store commit SHA
|
- name: Store commit SHA
|
||||||
run: |
|
run: |
|
||||||
echo "COMMIT_SHA=$(echo ${{ github.workflow_sha }} | cut -c1-7)" >> $GITHUB_ENV
|
echo "COMMIT_SHA=$(echo ${{ github.event.pull_request.head.sha || github.sha }} | cut -c1-7)" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
@@ -111,7 +111,7 @@ jobs:
|
|||||||
run: pnpm install --ignore-workspace
|
run: pnpm install --ignore-workspace
|
||||||
|
|
||||||
- name: Prepare
|
- name: Prepare
|
||||||
run: pnpm nuxi prepare
|
run: pnpm nuxt prepare
|
||||||
|
|
||||||
- name: Typecheck
|
- name: Typecheck
|
||||||
run: pnpm run typecheck
|
run: pnpm run typecheck
|
||||||
@@ -138,7 +138,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Store commit SHA
|
- name: Store commit SHA
|
||||||
run: |
|
run: |
|
||||||
echo "COMMIT_SHA=$(echo ${{ github.workflow_sha }} | cut -c1-7)" >> $GITHUB_ENV
|
echo "COMMIT_SHA=$(echo ${{ github.event.pull_request.head.sha || github.sha }} | cut -c1-7)" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
@@ -183,7 +183,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Store commit SHA
|
- name: Store commit SHA
|
||||||
run: |
|
run: |
|
||||||
echo "COMMIT_SHA=$(echo ${{ github.workflow_sha }} | cut -c1-7)" >> $GITHUB_ENV
|
echo "COMMIT_SHA=$(echo ${{ github.event.pull_request.head.sha || github.sha }} | cut -c1-7)" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
@@ -235,7 +235,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Store commit SHA
|
- name: Store commit SHA
|
||||||
run: |
|
run: |
|
||||||
echo "COMMIT_SHA=$(echo ${{ github.workflow_sha }} | cut -c1-7)" >> $GITHUB_ENV
|
echo "COMMIT_SHA=$(echo ${{ github.event.pull_request.head.sha || github.sha }} | cut -c1-7)" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
|
|||||||
16
.github/workflows/playground.yml
vendored
@@ -9,10 +9,6 @@ jobs:
|
|||||||
deploy:
|
deploy:
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
environment:
|
|
||||||
name: ${{ github.ref == 'refs/heads/v3' && 'production' || 'preview' }}
|
|
||||||
url: ${{ steps.deploy.outputs.deployment-url }}
|
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
id-token: write
|
id-token: write
|
||||||
@@ -40,14 +36,10 @@ jobs:
|
|||||||
- name: Prepare build
|
- name: Prepare build
|
||||||
run: pnpm run dev:prepare
|
run: pnpm run dev:prepare
|
||||||
|
|
||||||
- name: Build application
|
|
||||||
run: pnpm run dev:build
|
|
||||||
env:
|
|
||||||
NITRO_PRESET: cloudflare-pages
|
|
||||||
|
|
||||||
- name: Deploy to NuxtHub
|
- name: Deploy to NuxtHub
|
||||||
uses: nuxt-hub/action@v1
|
uses: nuxt-hub/action@v2
|
||||||
id: deploy
|
env:
|
||||||
|
NODE_OPTIONS: '--max-old-space-size=8192'
|
||||||
with:
|
with:
|
||||||
project-key: ui3-playground-pb9b
|
project-key: ui3-playground-pb9b
|
||||||
directory: playground/dist
|
directory: playground
|
||||||
|
|||||||
27
.github/workflows/reproduction.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
name: reproduction
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
schedule:
|
||||||
|
- cron: '30 1 * * *'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
reproduction:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
actions: write
|
||||||
|
issues: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/stale@v9
|
||||||
|
with:
|
||||||
|
days-before-stale: -1 # Issues and PR will never be flagged stale automatically.
|
||||||
|
stale-issue-label: 'needs reproduction' # Label that flags an issue as stale.
|
||||||
|
only-labels: 'needs reproduction' # Only process these issues
|
||||||
|
days-before-issue-close: 7
|
||||||
|
ignore-updates: true
|
||||||
|
remove-stale-when-updated: false
|
||||||
|
close-issue-message: This issue was closed because it was open for 7 days without a reproduction.
|
||||||
|
close-issue-label: closed-by-bot
|
||||||
|
operations-per-run: 300 #default 30
|
||||||
32
.github/workflows/stale.yml
vendored
@@ -1,6 +1,7 @@
|
|||||||
name: stale
|
name: stale
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '30 1 * * *'
|
- cron: '30 1 * * *'
|
||||||
|
|
||||||
@@ -9,17 +10,28 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
|
actions: write
|
||||||
issues: write
|
issues: write
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v9
|
- uses: actions/stale@4c023f01d613e60293d8004f251a18bfb9bbd71d
|
||||||
with:
|
with:
|
||||||
days-before-stale: -1 # Issues and PR will never be flagged stale automatically.
|
days-before-pr-stale: -1
|
||||||
stale-issue-label: 'needs reproduction' # Label that flags an issue as stale.
|
days-before-stale: 60
|
||||||
only-labels: 'needs reproduction' # Only process these issues
|
days-before-close: 7
|
||||||
days-before-issue-close: 7
|
stale-issue-label: 'stale'
|
||||||
ignore-updates: true
|
close-issue-label: 'closed-by-bot'
|
||||||
remove-stale-when-updated: false
|
close-issue-message: |
|
||||||
close-issue-message: This issue was closed because it was open for 7 days without a reproduction.
|
Hi! 👋
|
||||||
close-issue-label: closed-by-bot
|
|
||||||
operations-per-run: 300 #default 30
|
This issue has been automatically **closed** due to prolonged inactivity.
|
||||||
|
|
||||||
|
We're a small team and can't address every report, but we appreciate your feedback and contributions.
|
||||||
|
|
||||||
|
If this issue is still relevant with the latest version of Nuxt UI, please feel free to reopen or create a new issue with updated details.
|
||||||
|
|
||||||
|
Thank you for your understanding and support!
|
||||||
|
|
||||||
|
— Nuxt UI Team
|
||||||
|
exempt-issue-labels: 'feature,announcement'
|
||||||
|
operations-per-run: 300
|
||||||
122
CHANGELOG.md
@@ -1,5 +1,127 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [3.2.0](https://github.com/nuxt/ui/compare/v3.1.3...v3.2.0) (2025-06-25)
|
||||||
|
|
||||||
|
### ⚠ BREAKING CHANGES
|
||||||
|
|
||||||
|
* **useOverlay:** correct spelling of `unmount` function (#4051)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **Avatar:** add `chip` prop ([#4224](https://github.com/nuxt/ui/issues/4224)) ([03ac395](https://github.com/nuxt/ui/commit/03ac395164c02c964361c68743268b1bc90aae59))
|
||||||
|
* **Carousel:** allow customization of active dot color ([#4229](https://github.com/nuxt/ui/issues/4229)) ([2ee1c5a](https://github.com/nuxt/ui/commit/2ee1c5ac2e20ab9ce2f4037a8e8c64e561b0428b))
|
||||||
|
* **CommandPalette:** handle `children` in items ([#4226](https://github.com/nuxt/ui/issues/4226)) ([59c26ec](https://github.com/nuxt/ui/commit/59c26ec1230375a24fbaf8a630a696ae854700c7))
|
||||||
|
* **extendLocale:** new composable ([0f558fc](https://github.com/nuxt/ui/commit/0f558fc0d014d51549222accfc50286d1770d1aa)), closes [#3729](https://github.com/nuxt/ui/issues/3729)
|
||||||
|
* **Form:** expose loading state to default slot ([#4247](https://github.com/nuxt/ui/issues/4247)) ([ea0c459](https://github.com/nuxt/ui/commit/ea0c459306be585bacaaf5b433114d072550c824))
|
||||||
|
* **InputTags:** new component ([#4261](https://github.com/nuxt/ui/issues/4261)) ([54bb228](https://github.com/nuxt/ui/commit/54bb2282c58d3bf5a7dde4cdee687c68efd934a0))
|
||||||
|
* **locale:** add Luxembourgish language ([#4264](https://github.com/nuxt/ui/issues/4264)) ([43cbb94](https://github.com/nuxt/ui/commit/43cbb94ee25106b414fc8fe979fa65ebaa9ccc76))
|
||||||
|
* **Modal/Slideover:** add `actions` slot ([#4358](https://github.com/nuxt/ui/issues/4358)) ([8156971](https://github.com/nuxt/ui/commit/81569713e9da9d5531ecdf4614660b84c686fa81))
|
||||||
|
* **Modal/Slideover:** add `close` method in slots ([#4219](https://github.com/nuxt/ui/issues/4219)) ([5835eb5](https://github.com/nuxt/ui/commit/5835eb5f0f835b5f03646dec78f85b2f556a109b))
|
||||||
|
* **Select/SelectMenu/Tabs:** expose trigger refs ([7a2bd4e](https://github.com/nuxt/ui/commit/7a2bd4e6179373902ba6f285903ea896fd1d378f)), closes [#4292](https://github.com/nuxt/ui/issues/4292)
|
||||||
|
* **Select/SelectMenu:** handle dynamic `autofocus` ([1a4de49](https://github.com/nuxt/ui/commit/1a4de49c1665c9ef65279315be0393d6272447b9)), closes [#4324](https://github.com/nuxt/ui/issues/4324)
|
||||||
|
* **Table:** add `body-top` / `body-bottom` slots ([#4354](https://github.com/nuxt/ui/issues/4354)) ([595fc64](https://github.com/nuxt/ui/commit/595fc64515613fe82c3a56fc5518f2e3fcce6e19))
|
||||||
|
* **Timeline:** add `reverse` prop ([#4316](https://github.com/nuxt/ui/issues/4316)) ([5170cfd](https://github.com/nuxt/ui/commit/5170cfd7eb44a25c64673cf12979f9ca1049695f))
|
||||||
|
* **Timeline:** new component ([#4215](https://github.com/nuxt/ui/issues/4215)) ([8017767](https://github.com/nuxt/ui/commit/80177679f2aa0d7f0e39e639a02d527a06e6172c))
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Card/Drawer/Modal:** prevent scrollbars overflow ([#4368](https://github.com/nuxt/ui/issues/4368)) ([c3adc38](https://github.com/nuxt/ui/commit/c3adc381c90dad7152e27fc303ee678efc7c4c94))
|
||||||
|
* **components:** remove default `md` size on buttons ([#4357](https://github.com/nuxt/ui/issues/4357)) ([be41aed](https://github.com/nuxt/ui/commit/be41aed1f3d3476801e1840dbb8766926bc93c05))
|
||||||
|
* **defineShortcuts:** allow `meta_-` shortcut ([#4321](https://github.com/nuxt/ui/issues/4321)) ([4e7c1c9](https://github.com/nuxt/ui/commit/4e7c1c9c305b45dd76d4c238e70a6aeedae78c8b))
|
||||||
|
* **Form:** conditionally type form data via `transform` prop ([#4188](https://github.com/nuxt/ui/issues/4188)) ([37abcc6](https://github.com/nuxt/ui/commit/37abcc6a5b0a678be626673af5067956657a50d6))
|
||||||
|
* **Form:** expose reactive fields ([#4386](https://github.com/nuxt/ui/issues/4386)) ([1a8feb7](https://github.com/nuxt/ui/commit/1a8feb751e6827c414ef82fe9fb259ba7dcc7e08))
|
||||||
|
* **InputMenu/SelectMenu:** dynamic `empty` size ([ba3c6e8](https://github.com/nuxt/ui/commit/ba3c6e8788ed75d86d4406749797da52d7816b84)), closes [#4377](https://github.com/nuxt/ui/issues/4377)
|
||||||
|
* **InputTags:** extend emits interface ([8781a07](https://github.com/nuxt/ui/commit/8781a079096def0d3bae5b8d896db0df6ce37e23))
|
||||||
|
* **Modal/Slideover:** don't emit `close:prevent` on `closeAutoFocus` ([150b334](https://github.com/nuxt/ui/commit/150b334b1d242c6dc132193e23359c03e6f35666))
|
||||||
|
* **NavigationMenu:** nested accordion context at every level ([#4363](https://github.com/nuxt/ui/issues/4363)) ([2fa8db6](https://github.com/nuxt/ui/commit/2fa8db64ddf4c92a19e73774143518d87d001b72))
|
||||||
|
* **NavigationMenu:** set content `max-height` in `horizontal` orientation ([62bc7b2](https://github.com/nuxt/ui/commit/62bc7b25a2d205d8dffb47a109196f91ff3e823a)), closes [#4208](https://github.com/nuxt/ui/issues/4208)
|
||||||
|
* **Pagination:** match default button `size` ([#4350](https://github.com/nuxt/ui/issues/4350)) ([4dd56c8](https://github.com/nuxt/ui/commit/4dd56c8111e5a224105b82d541b7742b46abb34a))
|
||||||
|
* **Select/SelectMenu:** display falsy values ([7df7ee3](https://github.com/nuxt/ui/commit/7df7ee336a925d7ee07f866551dad9350785c9fc))
|
||||||
|
* **Select/SelectMenu:** prevent empty string display when multiple ([483e473](https://github.com/nuxt/ui/commit/483e473e3f5681cc97c3766ea47283dc95f76345))
|
||||||
|
* **SelectMenu:** dynamic input size ([b0364b9](https://github.com/nuxt/ui/commit/b0364b96b73b9e543781a35962c03b5a983352c4))
|
||||||
|
* **Table:** use `tr` as separator ([#4083](https://github.com/nuxt/ui/issues/4083)) ([edca3bc](https://github.com/nuxt/ui/commit/edca3bcb743c7eb63e6abbaa801d3858342a8777))
|
||||||
|
* **Toast:** calc height on next tick ([3bf5acb](https://github.com/nuxt/ui/commit/3bf5acb683f0ad09735b2417d265d6fcfd901b11)), closes [#4265](https://github.com/nuxt/ui/issues/4265)
|
||||||
|
* **Toaster:** smoother visibility transition for stacked toasts ([#4367](https://github.com/nuxt/ui/issues/4367)) ([abfd0ed](https://github.com/nuxt/ui/commit/abfd0ede036fa2953f9abc841d77ac71bbd3bba9))
|
||||||
|
* **useOverlay:** correct spelling of `unmount` function ([#4051](https://github.com/nuxt/ui/issues/4051)) ([546df57](https://github.com/nuxt/ui/commit/546df572fca60325315bed17c9be3367052fb7a9))
|
||||||
|
* **useOverlay:** set props to original props when `defaultOpen` is set ([#4308](https://github.com/nuxt/ui/issues/4308)) ([66355ba](https://github.com/nuxt/ui/commit/66355ba301d569b9f44527bafc5f8f09bcda63c0))
|
||||||
|
* **useOverlay:** use original props when not provided to `open` ([#4269](https://github.com/nuxt/ui/issues/4269)) ([bf56e15](https://github.com/nuxt/ui/commit/bf56e15a2eed7d51199d5641649a822e91ca41ba))
|
||||||
|
|
||||||
|
## [3.1.3](https://github.com/nuxt/ui/compare/v3.1.2...v3.1.3) (2025-05-26)
|
||||||
|
|
||||||
|
### ⚠ BREAKING CHANGES
|
||||||
|
|
||||||
|
* **NavigationMenu:** revert new `collapsible` field
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **locale:** add Kyrgyz language ([#4189](https://github.com/nuxt/ui/issues/4189)) ([4053047](https://github.com/nuxt/ui/commit/405304775e4b2b4e8b37a2364f3e5ee34b46036e))
|
||||||
|
* **locale:** add Lithuanian language ([#4171](https://github.com/nuxt/ui/issues/4171)) ([d86956e](https://github.com/nuxt/ui/commit/d86956e1d57482b3e98eef2d34bff13544284b0b))
|
||||||
|
* **locale:** add Malay language ([#4160](https://github.com/nuxt/ui/issues/4160)) ([c00f6e8](https://github.com/nuxt/ui/commit/c00f6e8cdfd88eeba58812b78d94a2326c13f164))
|
||||||
|
* **locale:** add Mongolian language ([#4214](https://github.com/nuxt/ui/issues/4214)) ([44ea02c](https://github.com/nuxt/ui/commit/44ea02c0d64322ef0cfda63b234369c00d3d0180))
|
||||||
|
* **Modal/Slideover:** add `after:enter` event ([#4187](https://github.com/nuxt/ui/issues/4187)) ([d9e9fea](https://github.com/nuxt/ui/commit/d9e9fea35e4b22d68324c9e85b3aa221a7987d0f))
|
||||||
|
* **NavigationMenu:** add `tooltip` and `popover` props ([f2682fd](https://github.com/nuxt/ui/commit/f2682fd2ae8abb7807977727fc22ef34cb5752e5)), closes [#4186](https://github.com/nuxt/ui/issues/4186)
|
||||||
|
* **NavigationMenu:** add `trigger` type in items ([9cf9f25](https://github.com/nuxt/ui/commit/9cf9f25f4424447691e03e9034155d1541badd43))
|
||||||
|
* **NavigationMenu:** handle `vertical` orientation with Accordion instead of Collapsible ([1e2a10b](https://github.com/nuxt/ui/commit/1e2a10b4bdebaef12316ac60f98a956dad21c1ec)), closes [#4072](https://github.com/nuxt/ui/issues/4072) [#3911](https://github.com/nuxt/ui/issues/3911)
|
||||||
|
* **Popover:** add `anchor` slot ([#4119](https://github.com/nuxt/ui/issues/4119)) ([473513c](https://github.com/nuxt/ui/commit/473513c2460d4329d7d2e0a0ea69bf1310a072d1))
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **CheckboxGroup/RadioGroup:** variant `table` borders in RTL mode ([#4192](https://github.com/nuxt/ui/issues/4192)) ([43d281f](https://github.com/nuxt/ui/commit/43d281f6d1d8b0017ed61d929c5e311fb5b03447))
|
||||||
|
* **CommandPalette:** add `presentation` role to viewport ([2ba94db](https://github.com/nuxt/ui/commit/2ba94db09e1ba86020d5d289f1ca1e24ef706299))
|
||||||
|
* **ContextMenu/DropdownMenu:** wrap groups in a viewport ([dcf34a7](https://github.com/nuxt/ui/commit/dcf34a7ac236b96b1302ec2eae155b8f2d3784ef)), closes [#3315](https://github.com/nuxt/ui/issues/3315)
|
||||||
|
* **Drawer:** improve title & description accessibility ([41087d4](https://github.com/nuxt/ui/commit/41087d4c9569eb00c04bd748e055cd151c2f762c)), closes [#4199](https://github.com/nuxt/ui/issues/4199)
|
||||||
|
* **icons:** update `loading` icon ([#4163](https://github.com/nuxt/ui/issues/4163)) ([fe4e1f8](https://github.com/nuxt/ui/commit/fe4e1f859d42aa3c32bb7b75302e84a280abe525))
|
||||||
|
* **Input/Textarea:** define model modifiers types ([#4195](https://github.com/nuxt/ui/issues/4195)) ([3243fb8](https://github.com/nuxt/ui/commit/3243fb88f71c5475824bfdc4d7c4f303b2d6790b))
|
||||||
|
* **InputMenu/Select/SelectMenu:** manual viewport to display scrollbars ([f95abf8](https://github.com/nuxt/ui/commit/f95abf8d1d7b9149e400d7dc6f96f93f5154da7a)), closes [#4069](https://github.com/nuxt/ui/issues/4069)
|
||||||
|
* **NavigationMenu:** incorrect hover when disabled and active ([d0be599](https://github.com/nuxt/ui/commit/d0be59946bfe30c79a6f75476385ab8538aa51b8))
|
||||||
|
* **NavigationMenu:** only display `tooltip` when collapsed ([44f536f](https://github.com/nuxt/ui/commit/44f536fd0034facb3550d910fae71d4f9442ed19))
|
||||||
|
* **NavigationMenu:** remove `font-medium` in popover children ([0236399](https://github.com/nuxt/ui/commit/02363994d66d3c2d11b9913f31167fa25f5c5de2))
|
||||||
|
* **NavigationMenu:** revert new `collapsible` field ([3c78e2f](https://github.com/nuxt/ui/commit/3c78e2fd983f19b5cec65b4a94a8a8b14e548e5e))
|
||||||
|
* **Textarea:** missing imports ([#4207](https://github.com/nuxt/ui/issues/4207)) ([6aab62e](https://github.com/nuxt/ui/commit/6aab62ec30e266c5f0da0cd24aefbb7c53f447ac))
|
||||||
|
* **theme:** define `old-neutral` color as static ([#4193](https://github.com/nuxt/ui/issues/4193)) ([dae9f0b](https://github.com/nuxt/ui/commit/dae9f0b8631b3b9fb60ef47753f7aded0c36c4a2))
|
||||||
|
* **Tooltip:** increase padding for consistency ([0634a75](https://github.com/nuxt/ui/commit/0634a756a496f5131841abafd218ae7e4aaa61e5))
|
||||||
|
|
||||||
|
## [3.1.2](https://github.com/nuxt/ui/compare/v3.1.1...v3.1.2) (2025-05-15)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **Badge:** add `square` prop ([#4008](https://github.com/nuxt/ui/issues/4008)) ([894e8a6](https://github.com/nuxt/ui/commit/894e8a61b6fea3618fc863bd77678385e9d021c2))
|
||||||
|
* **CheckboxGroup:** add `table` variant ([#3997](https://github.com/nuxt/ui/issues/3997)) ([1b6ab27](https://github.com/nuxt/ui/commit/1b6ab271ea3875a7c77ffe9367c7c341083dd53c))
|
||||||
|
* **components:** add `ui` field in items ([#4060](https://github.com/nuxt/ui/issues/4060)) ([b9adc83](https://github.com/nuxt/ui/commit/b9adc83e787db02507e6e7bb1aabc684eccc197b))
|
||||||
|
* **InputNumber:** add `increment-disabled` / `decrement-disabled` props ([#4141](https://github.com/nuxt/ui/issues/4141)) ([c7fba2e](https://github.com/nuxt/ui/commit/c7fba2e0ebfb7153f3bfb727165d653bbd3dbe54))
|
||||||
|
* **locale:** add Slovenian language ([#4140](https://github.com/nuxt/ui/issues/4140)) ([e86dc79](https://github.com/nuxt/ui/commit/e86dc79e51b2773a77ada5f12d4f0964fbc83354))
|
||||||
|
* **NavigationMenu:** add `collapsible` field in items ([2be60cd](https://github.com/nuxt/ui/commit/2be60cddfe10fd1e2466900fd53e21ee0c877227)), closes [#3353](https://github.com/nuxt/ui/issues/3353) [#3911](https://github.com/nuxt/ui/issues/3911)
|
||||||
|
* **NavigationMenu:** handle `tooltip` in items ([46c2987](https://github.com/nuxt/ui/commit/46c2987ebfd30b2b071a96a745b7270e852e96de)), closes [#4050](https://github.com/nuxt/ui/issues/4050)
|
||||||
|
* **Slider:** handle `tooltip` around thumbs ([d140acc](https://github.com/nuxt/ui/commit/d140acc608c6ae11c0a0531fe443588776ea7807)), closes [#1469](https://github.com/nuxt/ui/issues/1469)
|
||||||
|
* **Toast:** add `progress` prop to hide progress bar ([#4125](https://github.com/nuxt/ui/issues/4125)) ([92632e9](https://github.com/nuxt/ui/commit/92632e969eaa11521a166e50e346753929b7f523))
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Badge/Button:** handle zero value in label correctly ([#4108](https://github.com/nuxt/ui/issues/4108)) ([f244d15](https://github.com/nuxt/ui/commit/f244d15b96d97cd8ba34ba9c18f23965e17e3cef))
|
||||||
|
* **ButtonGroup:** add `z-index` on focused element ([204953b](https://github.com/nuxt/ui/commit/204953b780bde08dbfde230fc8887674449227b7))
|
||||||
|
* **Calendar:** wrong color for today date with `neutral` color ([7d51a9e](https://github.com/nuxt/ui/commit/7d51a9e479cb6105ea37759c5cd67ff9f7702c49)), closes [#4084](https://github.com/nuxt/ui/issues/4084) [#3629](https://github.com/nuxt/ui/issues/3629)
|
||||||
|
* **Checkbox/RadioGroup:** render correct element without `variant` ([f2fd778](https://github.com/nuxt/ui/commit/f2fd778c0a604f2d65aec9f3fe2d54b6d4e8c3a2)), closes [#3998](https://github.com/nuxt/ui/issues/3998)
|
||||||
|
* **CheckboxGroup:** relative `UCheckbox` import ([7551a85](https://github.com/nuxt/ui/commit/7551a85ad2d92b59e2909396affb862403d5b27a)), closes [#4090](https://github.com/nuxt/ui/issues/4090)
|
||||||
|
* **ColorPicker:** make thumb touch draggable ([#4101](https://github.com/nuxt/ui/issues/4101)) ([cc20a26](https://github.com/nuxt/ui/commit/cc20a26f07268d19119ab4c7c254033143bb63f4))
|
||||||
|
* **components:** `class` should have priority over `ui` prop ([e6e510b](https://github.com/nuxt/ui/commit/e6e510b848d995a286a51d50a120d67483e11232))
|
||||||
|
* **FormField:** block form field injection after use ([#4150](https://github.com/nuxt/ui/issues/4150)) ([d79da9d](https://github.com/nuxt/ui/commit/d79da9d7b60c9972af64acd8e6eef4ae7d6bc3eb))
|
||||||
|
* **FormField:** use `div` for `error` and `help` slots ([459a041](https://github.com/nuxt/ui/commit/459a0410ab729fde60865e84632b36903465f57e))
|
||||||
|
* **inertia:** link always render as anchor tag ([#3989](https://github.com/nuxt/ui/issues/3989)) ([e81464a](https://github.com/nuxt/ui/commit/e81464a43ede4e63ce3dc92429bbfef48614f731))
|
||||||
|
* **inertia:** make `useAppConfig` reactive ([12303a8](https://github.com/nuxt/ui/commit/12303a87be62dae84ef774e3a9795deb0ac90cc7))
|
||||||
|
* **Input/Textarea:** handle generic types ([3c8d6cd](https://github.com/nuxt/ui/commit/3c8d6cd01dfafed5844c376f52adbdda0c814420)), closes [nuxt/ui-pro#887](https://github.com/nuxt/ui-pro/issues/887)
|
||||||
|
* **InputNumber:** handle inside button group ([2e4c308](https://github.com/nuxt/ui/commit/2e4c3082a1e66fa597086dc3431fec37fa29ef62)), closes [#4155](https://github.com/nuxt/ui/issues/4155)
|
||||||
|
* **Link:** consistent behavior between nuxt, vue and inertia ([#4134](https://github.com/nuxt/ui/issues/4134)) ([67da90a](https://github.com/nuxt/ui/commit/67da90a2f638124f640c4271d3376c5ff3fab6a1))
|
||||||
|
* **module:** configure `@nuxt/fonts` with default weights ([276268d](https://github.com/nuxt/ui/commit/276268d311f57715cec47bc600a0ccc3d3885682))
|
||||||
|
* **NavigationMenu:** arrow position conflict ([#4137](https://github.com/nuxt/ui/issues/4137)) ([0dc4678](https://github.com/nuxt/ui/commit/0dc4678c68e4b500be49c38336dc75b73843e38d))
|
||||||
|
* **Select:** support more primitive types in `value` field ([#4105](https://github.com/nuxt/ui/issues/4105)) ([09b4699](https://github.com/nuxt/ui/commit/09b4699aeadaa195ea081509f8e237bb2c346238))
|
||||||
|
* **Slider:** handle generic types ([d7a4d02](https://github.com/nuxt/ui/commit/d7a4d029b77d2dfa0b8efcd2755d482fa5e31fd3))
|
||||||
|
* **Stepper:** use `div` tag for `title` & `description` ([a57844e](https://github.com/nuxt/ui/commit/a57844e41676c13ed1af861424961b88cee7b4da)), closes [#4096](https://github.com/nuxt/ui/issues/4096)
|
||||||
|
* **Tabs:** prevent trigger truncate without parent width ([06e5689](https://github.com/nuxt/ui/commit/06e5689da80b36205d0548d5d6b58510938e4a6e)), closes [#4056](https://github.com/nuxt/ui/issues/4056)
|
||||||
|
* **Tabs:** set `focus:outline-none` with `link` variant ([999a0f8](https://github.com/nuxt/ui/commit/999a0f84671fad20fa3dc50c6774af2e0200b32e))
|
||||||
|
* **templates:** dont write unused variants in theme files ([d3df3bb](https://github.com/nuxt/ui/commit/d3df3bb929fe6732f27b182d1664213884a662ec))
|
||||||
|
* **Toaster:** allow `base` slot override ([c63d2f3](https://github.com/nuxt/ui/commit/c63d2f380aac16f1d1e812516df3dca7fa7c8034))
|
||||||
|
* **vue:** make `useAppConfig` reactive ([869c070](https://github.com/nuxt/ui/commit/869c0708bd351c7be44e5e430c348b19dd316db9)), closes [#3952](https://github.com/nuxt/ui/issues/3952)
|
||||||
|
|
||||||
## [3.1.1](https://github.com/nuxt/ui/compare/v3.1.0...v3.1.1) (2025-05-02)
|
## [3.1.1](https://github.com/nuxt/ui/compare/v3.1.0...v3.1.1) (2025-05-02)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|||||||
@@ -31,8 +31,9 @@ const component = ({ name, primitive, pro, prose, content }) => {
|
|||||||
? `
|
? `
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { AppConfig } from '@nuxt/schema'
|
import type { AppConfig } from '@nuxt/schema'
|
||||||
|
${pro ? `import type { ComponentConfig } from '@nuxt/ui'` : ''}
|
||||||
import 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'
|
${!pro ? `import type { ComponentConfig } from '../types/utils'` : ''}
|
||||||
|
|
||||||
type ${upperName} = ComponentConfig<typeof theme, AppConfig, ${upperName}${pro ? `, '${key}'` : ''}>
|
type ${upperName} = ComponentConfig<typeof theme, AppConfig, ${upperName}${pro ? `, '${key}'` : ''}>
|
||||||
|
|
||||||
@@ -62,7 +63,7 @@ defineSlots<${upperName}Slots>()
|
|||||||
|
|
||||||
const appConfig = useAppConfig() as ${upperName}['AppConfig']
|
const appConfig = useAppConfig() as ${upperName}['AppConfig']
|
||||||
|
|
||||||
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.${camelName} || {}) })())
|
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.${pro ? 'uiPro' : 'ui'}?.${camelName} || {}) })())
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -75,8 +76,9 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.${camelName}
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { ${upperName}RootProps, ${upperName}RootEmits } from 'reka-ui'
|
import type { ${upperName}RootProps, ${upperName}RootEmits } from 'reka-ui'
|
||||||
import type { AppConfig } from '@nuxt/schema'
|
import type { AppConfig } from '@nuxt/schema'
|
||||||
|
${pro ? `import type { ComponentConfig } from '@nuxt/ui'` : ''}
|
||||||
import 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'
|
${!pro ? `import type { ComponentConfig } from '../types/utils'` : ''}
|
||||||
|
|
||||||
type ${upperName} = ComponentConfig<typeof theme, AppConfig, ${upperName}${pro ? `, '${key}'` : ''}>
|
type ${upperName} = ComponentConfig<typeof theme, AppConfig, ${upperName}${pro ? `, '${key}'` : ''}>
|
||||||
|
|
||||||
@@ -105,7 +107,7 @@ 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 = computed(() => tv({ extend: tv(theme), ...(appConfig.${pro ? 'uiPro' : 'ui'}?.${camelName} || {}) })())
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -186,6 +188,7 @@ links:${primitive
|
|||||||
- label: GitHub
|
- label: GitHub
|
||||||
icon: i-simple-icons-github
|
icon: i-simple-icons-github
|
||||||
to: https://github.com/nuxt/${pro ? 'ui-pro' : 'ui'}/tree/v3/src/runtime/components/${upperName}.vue
|
to: https://github.com/nuxt/${pro ? 'ui-pro' : 'ui'}/tree/v3/src/runtime/components/${upperName}.vue
|
||||||
|
navigation.badge: Soon
|
||||||
---
|
---
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|||||||
@@ -53,7 +53,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>
|
||||||
|
|||||||
@@ -22,9 +22,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
const navigation = inject<Ref<ContentNavigationItem[]>>('navigation')
|
const navigation = inject<Ref<ContentNavigationItem[]>>('navigation')
|
||||||
|
|
||||||
const githubLink = computed(() => {
|
const githubLink = computed(() => `https://github.com/nuxt/${value.value}`)
|
||||||
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(() => [
|
||||||
@@ -36,6 +34,16 @@ const mobileLinks = computed(() => [
|
|||||||
target: '_blank'
|
target: '_blank'
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
|
const items = computed(() => {
|
||||||
|
const ui2 = { label: 'v2.22.0', to: 'https://ui2.nuxt.com' }
|
||||||
|
const uiPro1 = { label: 'v1.8.0', to: 'https://ui2.nuxt.com/pro' }
|
||||||
|
|
||||||
|
return [
|
||||||
|
{ label: `v${config.version}`, active: true, color: 'primary' as const, checked: true, type: 'checkbox' as const },
|
||||||
|
route.path === '/' ? ui2 : route.path.startsWith('/pro') ? uiPro1 : module.value === 'ui-pro' ? uiPro1 : ui2
|
||||||
|
]
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -53,7 +61,7 @@ const mobileLinks = computed(() => [
|
|||||||
<UDropdownMenu
|
<UDropdownMenu
|
||||||
v-slot="{ open }"
|
v-slot="{ open }"
|
||||||
:modal="false"
|
:modal="false"
|
||||||
:items="[{ label: `v${config.version}`, active: true, color: 'primary', checked: true, type: 'checkbox' }, { label: module === 'ui-pro' ? 'v1.7.1' : 'v2.21.1', to: module === 'ui-pro' ? 'https://ui2.nuxt.com/pro' : 'https://ui2.nuxt.com' }]"
|
:items="items"
|
||||||
:ui="{ content: 'w-(--reka-dropdown-menu-trigger-width) min-w-0' }"
|
:ui="{ content: 'w-(--reka-dropdown-menu-trigger-width) min-w-0' }"
|
||||||
size="xs"
|
size="xs"
|
||||||
>
|
>
|
||||||
|
|||||||
77
docs/app/components/PageHeaderLinks.vue
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
const route = useRoute()
|
||||||
|
const toast = useToast()
|
||||||
|
const { copy, copied } = useClipboard()
|
||||||
|
const site = useSiteConfig()
|
||||||
|
|
||||||
|
const mdPath = computed(() => `${site.url}/raw${route.path}.md`)
|
||||||
|
|
||||||
|
const items = [
|
||||||
|
{
|
||||||
|
label: 'Copy Markdown link',
|
||||||
|
icon: 'i-lucide-link',
|
||||||
|
onSelect() {
|
||||||
|
copy(mdPath.value)
|
||||||
|
toast.add({
|
||||||
|
title: 'Copied to clipboard',
|
||||||
|
icon: 'i-lucide-check-circle'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'View as Markdown',
|
||||||
|
icon: 'i-simple-icons:markdown',
|
||||||
|
target: '_blank',
|
||||||
|
to: `/raw${route.path}.md`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Open in ChatGPT',
|
||||||
|
icon: 'i-simple-icons:openai',
|
||||||
|
target: '_blank',
|
||||||
|
to: `https://chatgpt.com/?hints=search&q=${encodeURIComponent(`Read ${mdPath.value} so I can ask questions about it.`)}`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Open in Claude',
|
||||||
|
icon: 'i-simple-icons:anthropic',
|
||||||
|
target: '_blank',
|
||||||
|
to: `https://claude.ai/new?q=${encodeURIComponent(`Read ${mdPath.value} so I can ask questions about it.`)}`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
async function copyPage() {
|
||||||
|
copy(await $fetch<string>(`/raw${route.path}.md`))
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<UButtonGroup>
|
||||||
|
<UButton
|
||||||
|
label="Copy page"
|
||||||
|
:icon="copied ? 'i-lucide-copy-check' : 'i-lucide-copy'"
|
||||||
|
color="neutral"
|
||||||
|
variant="outline"
|
||||||
|
:ui="{
|
||||||
|
leadingIcon: [copied ? 'text-primary' : 'text-neutral', 'size-3.5']
|
||||||
|
}"
|
||||||
|
@click="copyPage"
|
||||||
|
/>
|
||||||
|
<UDropdownMenu
|
||||||
|
:items="items"
|
||||||
|
:content="{
|
||||||
|
align: 'end',
|
||||||
|
side: 'bottom',
|
||||||
|
sideOffset: 8
|
||||||
|
}"
|
||||||
|
:ui="{
|
||||||
|
content: 'w-48'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<UButton
|
||||||
|
icon="i-lucide-chevron-down"
|
||||||
|
size="sm"
|
||||||
|
color="neutral"
|
||||||
|
variant="outline"
|
||||||
|
/>
|
||||||
|
</UDropdownMenu>
|
||||||
|
</UButtonGroup>
|
||||||
|
</template>
|
||||||
@@ -38,7 +38,7 @@ const schemaProps = computed(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<ProseCollapsible v-if="schemaProps?.length" class="mt-1">
|
<ProseCollapsible v-if="schemaProps?.length" class="mt-1 mb-0">
|
||||||
<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}`" />
|
||||||
|
|||||||
@@ -25,7 +25,11 @@ function getEmojiFlag(locale: string): string {
|
|||||||
kk: 'kz', // Kazakh -> Kazakhstan
|
kk: 'kz', // Kazakh -> Kazakhstan
|
||||||
km: 'kh', // Khmer -> Cambodia
|
km: 'kh', // Khmer -> Cambodia
|
||||||
ko: 'kr', // Korean -> South Korea
|
ko: 'kr', // Korean -> South Korea
|
||||||
|
ky: 'kg', // Kyrgyz -> Kyrgyzstan
|
||||||
|
lb: 'lu', // Luxembourgish -> Luxembourg
|
||||||
|
ms: 'my', // Malay -> Malaysia
|
||||||
nb: 'no', // Norwegian Bokmål -> Norway
|
nb: 'no', // Norwegian Bokmål -> Norway
|
||||||
|
sl: 'si', // Slovenian -> Slovenia
|
||||||
sv: 'se', // Swedish -> Sweden
|
sv: 'se', // Swedish -> Sweden
|
||||||
uk: 'ua', // Ukrainian -> Ukraine
|
uk: 'ua', // Ukrainian -> Ukraine
|
||||||
ur: 'pk', // Urdu -> Pakistan
|
ur: 'pk', // Urdu -> Pakistan
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ const groups = [
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #billing-label="{ item }">
|
<template #billing-label="{ item }">
|
||||||
{{ item.label }}
|
<span class="font-medium text-primary">{{ item.label }}</span>
|
||||||
|
|
||||||
<UBadge variant="subtle" size="sm">
|
<UBadge variant="subtle" size="sm">
|
||||||
50% off
|
50% off
|
||||||
|
|||||||
@@ -0,0 +1,119 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
|
const groups = [{
|
||||||
|
id: 'actions',
|
||||||
|
label: 'Actions',
|
||||||
|
items: [{
|
||||||
|
label: 'Create new',
|
||||||
|
icon: 'i-lucide-plus',
|
||||||
|
children: [{
|
||||||
|
label: 'New file',
|
||||||
|
icon: 'i-lucide-file-plus',
|
||||||
|
suffix: 'Create a new file in the current directory',
|
||||||
|
onSelect(e: Event) {
|
||||||
|
e.preventDefault()
|
||||||
|
toast.add({ title: 'New file created!' })
|
||||||
|
},
|
||||||
|
kbds: ['meta', 'N']
|
||||||
|
}, {
|
||||||
|
label: 'New folder',
|
||||||
|
icon: 'i-lucide-folder-plus',
|
||||||
|
suffix: 'Create a new folder in the current directory',
|
||||||
|
onSelect(e: Event) {
|
||||||
|
e.preventDefault()
|
||||||
|
toast.add({ title: 'New folder created!' })
|
||||||
|
},
|
||||||
|
kbds: ['meta', 'F']
|
||||||
|
}, {
|
||||||
|
label: 'New project',
|
||||||
|
icon: 'i-lucide-folder-git',
|
||||||
|
suffix: 'Create a new project from a template',
|
||||||
|
onSelect(e: Event) {
|
||||||
|
e.preventDefault()
|
||||||
|
toast.add({ title: 'New project created!' })
|
||||||
|
},
|
||||||
|
kbds: ['meta', 'P']
|
||||||
|
}]
|
||||||
|
}, {
|
||||||
|
label: 'Share',
|
||||||
|
icon: 'i-lucide-share',
|
||||||
|
children: [{
|
||||||
|
label: 'Copy link',
|
||||||
|
icon: 'i-lucide-link',
|
||||||
|
suffix: 'Copy a link to the current item',
|
||||||
|
onSelect(e: Event) {
|
||||||
|
e.preventDefault()
|
||||||
|
toast.add({ title: 'Link copied to clipboard!' })
|
||||||
|
},
|
||||||
|
kbds: ['meta', 'L']
|
||||||
|
}, {
|
||||||
|
label: 'Share via email',
|
||||||
|
icon: 'i-lucide-mail',
|
||||||
|
suffix: 'Share the current item via email',
|
||||||
|
onSelect(e: Event) {
|
||||||
|
e.preventDefault()
|
||||||
|
toast.add({ title: 'Share via email dialog opened!' })
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
label: 'Share on social',
|
||||||
|
icon: 'i-lucide-share-2',
|
||||||
|
suffix: 'Share the current item on social media',
|
||||||
|
children: [{
|
||||||
|
label: 'Twitter',
|
||||||
|
icon: 'i-simple-icons-twitter',
|
||||||
|
onSelect(e: Event) {
|
||||||
|
e.preventDefault()
|
||||||
|
toast.add({ title: 'Shared on Twitter!' })
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
label: 'LinkedIn',
|
||||||
|
icon: 'i-simple-icons-linkedin',
|
||||||
|
onSelect(e: Event) {
|
||||||
|
e.preventDefault()
|
||||||
|
toast.add({ title: 'Shared on LinkedIn!' })
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
label: 'Facebook',
|
||||||
|
icon: 'i-simple-icons-facebook',
|
||||||
|
onSelect(e: Event) {
|
||||||
|
e.preventDefault()
|
||||||
|
toast.add({ title: 'Shared on Facebook!' })
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
}, {
|
||||||
|
label: 'Settings',
|
||||||
|
icon: 'i-lucide-settings',
|
||||||
|
children: [{
|
||||||
|
label: 'General',
|
||||||
|
icon: 'i-lucide-sliders',
|
||||||
|
suffix: 'Configure general settings',
|
||||||
|
onSelect(e: Event) {
|
||||||
|
e.preventDefault()
|
||||||
|
toast.add({ title: 'General settings opened!' })
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
label: 'Appearance',
|
||||||
|
icon: 'i-lucide-palette',
|
||||||
|
suffix: 'Customize the appearance',
|
||||||
|
onSelect(e: Event) {
|
||||||
|
e.preventDefault()
|
||||||
|
toast.add({ title: 'Appearance settings opened!' })
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
label: 'Security',
|
||||||
|
icon: 'i-lucide-shield',
|
||||||
|
suffix: 'Manage security settings',
|
||||||
|
onSelect(e: Event) {
|
||||||
|
e.preventDefault()
|
||||||
|
toast.add({ title: 'Security settings opened!' })
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<UCommandPalette :groups="groups" class="flex-1" />
|
||||||
|
</template>
|
||||||
@@ -28,7 +28,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-loader-circle" class="shrink-0 size-5 text-primary animate-spin" />
|
||||||
</template>
|
</template>
|
||||||
</UContextMenu>
|
</UContextMenu>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ const open = ref(false)
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<UDrawer v-model:open="open" :dismissible="false" :ui="{ header: 'flex items-center justify-between' }">
|
<UDrawer v-model:open="open" :dismissible="false" :handle="false" :ui="{ header: 'flex items-center justify-between' }">
|
||||||
<UButton label="Open" color="neutral" variant="subtle" trailing-icon="i-lucide-chevron-up" />
|
<UButton label="Open" color="neutral" variant="subtle" trailing-icon="i-lucide-chevron-up" />
|
||||||
|
|
||||||
<template #header>
|
<template #header>
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
const open = ref(false)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<UDrawer
|
||||||
|
v-model:open="open"
|
||||||
|
:dismissible="false"
|
||||||
|
:overlay="false"
|
||||||
|
:handle="false"
|
||||||
|
:modal="false"
|
||||||
|
:ui="{ header: 'flex items-center justify-between' }"
|
||||||
|
>
|
||||||
|
<UButton label="Open" color="neutral" variant="subtle" trailing-icon="i-lucide-chevron-up" />
|
||||||
|
|
||||||
|
<template #header>
|
||||||
|
<h2 class="text-highlighted font-semibold">
|
||||||
|
Drawer non-dismissible
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<UButton color="neutral" variant="ghost" icon="i-lucide-x" @click="open = false" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #body>
|
||||||
|
<Placeholder class="h-48" />
|
||||||
|
</template>
|
||||||
|
</UDrawer>
|
||||||
|
</template>
|
||||||
@@ -15,6 +15,9 @@ const schema = z.object({
|
|||||||
select: z.string().refine(value => value === 'option-2', {
|
select: z.string().refine(value => value === 'option-2', {
|
||||||
message: 'Select Option 2'
|
message: 'Select Option 2'
|
||||||
}),
|
}),
|
||||||
|
selectMultiple: z.array(z.string()).refine(values => values.includes('option-2'), {
|
||||||
|
message: 'Include Option 2'
|
||||||
|
}),
|
||||||
selectMenu: z.any().refine(option => option?.value === 'option-2', {
|
selectMenu: z.any().refine(option => option?.value === 'option-2', {
|
||||||
message: 'Select Option 2'
|
message: 'Select Option 2'
|
||||||
}),
|
}),
|
||||||
@@ -81,6 +84,10 @@ async function onSubmit(event: FormSubmitEvent<Schema>) {
|
|||||||
<USelect v-model="state.select" :items="items" class="w-full" />
|
<USelect v-model="state.select" :items="items" class="w-full" />
|
||||||
</UFormField>
|
</UFormField>
|
||||||
|
|
||||||
|
<UFormField name="selectMultiple" label="Select (Multiple)">
|
||||||
|
<USelect v-model="state.selectMultiple" multiple :items="items" class="w-full" />
|
||||||
|
</UFormField>
|
||||||
|
|
||||||
<UFormField name="selectMenu" label="Select Menu">
|
<UFormField name="selectMenu" label="Select Menu">
|
||||||
<USelectMenu v-model="state.selectMenu" :items="items" class="w-full" />
|
<USelectMenu v-model="state.selectMenu" :items="items" class="w-full" />
|
||||||
</UFormField>
|
</UFormField>
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
const tags = ref(['Vue'])
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<UFormField label="Tags" required>
|
||||||
|
<UInputTags v-model="tags" placeholder="Enter tags..." />
|
||||||
|
</UFormField>
|
||||||
|
</template>
|
||||||
@@ -10,7 +10,7 @@ const domain = ref(domains[0])
|
|||||||
v-model="value"
|
v-model="value"
|
||||||
placeholder="nuxt"
|
placeholder="nuxt"
|
||||||
:ui="{
|
:ui="{
|
||||||
base: 'pl-[57px]',
|
base: 'pl-14.5',
|
||||||
leading: 'pointer-events-none'
|
leading: 'pointer-events-none'
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,15 +1,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const value = ref('npx nuxi module add ui')
|
import { useClipboard } from '@vueuse/core'
|
||||||
const copied = ref(false)
|
|
||||||
|
|
||||||
function copy() {
|
const value = ref('npx nuxt module add ui')
|
||||||
navigator.clipboard.writeText(value.value)
|
|
||||||
copied.value = true
|
|
||||||
|
|
||||||
setTimeout(() => {
|
const { copy, copied } = useClipboard()
|
||||||
copied.value = false
|
|
||||||
}, 2000)
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -25,7 +19,7 @@ function copy() {
|
|||||||
size="sm"
|
size="sm"
|
||||||
:icon="copied ? 'i-lucide-copy-check' : 'i-lucide-copy'"
|
:icon="copied ? 'i-lucide-copy-check' : 'i-lucide-copy'"
|
||||||
aria-label="Copy to clipboard"
|
aria-label="Copy to clipboard"
|
||||||
@click="copy"
|
@click="copy(value)"
|
||||||
/>
|
/>
|
||||||
</UTooltip>
|
</UTooltip>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { vMaska } from 'maska/vue'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<UInput v-maska="'#### #### #### ####'" placeholder="4242 4242 4242 4242" icon="i-lucide-credit-card" />
|
||||||
|
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<UInput v-maska="'##/##'" placeholder="MM/YY" icon="i-lucide-calendar" />
|
||||||
|
<UInput v-maska="'###'" placeholder="CVC" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -10,8 +10,8 @@ const open = ref(false)
|
|||||||
<Placeholder class="h-48" />
|
<Placeholder class="h-48" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #footer>
|
<template #footer="{ close }">
|
||||||
<UButton label="Cancel" color="neutral" variant="outline" @click="open = false" />
|
<UButton label="Cancel" color="neutral" variant="outline" @click="close" />
|
||||||
<UButton label="Submit" color="neutral" />
|
<UButton label="Submit" color="neutral" />
|
||||||
</template>
|
</template>
|
||||||
</UModal>
|
</UModal>
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
const open = ref(false)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<UPopover
|
||||||
|
v-model:open="open"
|
||||||
|
:dismissible="false"
|
||||||
|
:ui="{ content: 'w-(--reka-popper-anchor-width) p-4' }"
|
||||||
|
>
|
||||||
|
<template #anchor>
|
||||||
|
<UInput placeholder="Focus to open" @focus="open = true" @blur="open = false" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #content>
|
||||||
|
<Placeholder class="w-full aspect-square" />
|
||||||
|
</template>
|
||||||
|
</UPopover>
|
||||||
|
</template>
|
||||||
@@ -10,8 +10,8 @@ const open = ref(false)
|
|||||||
<Placeholder class="h-full" />
|
<Placeholder class="h-full" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #footer>
|
<template #footer="{ close }">
|
||||||
<UButton label="Cancel" color="neutral" variant="outline" @click="open = false" />
|
<UButton label="Cancel" color="neutral" variant="outline" @click="close" />
|
||||||
<UButton label="Submit" color="neutral" />
|
<UButton label="Submit" color="neutral" />
|
||||||
</template>
|
</template>
|
||||||
</USlideover>
|
</USlideover>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import { h, resolveComponent } from 'vue'
|
import { h, resolveComponent } from 'vue'
|
||||||
import { upperFirst } from 'scule'
|
import { upperFirst } from 'scule'
|
||||||
import type { TableColumn } from '@nuxt/ui'
|
import type { TableColumn } from '@nuxt/ui'
|
||||||
|
import { useClipboard } from '@vueuse/core'
|
||||||
|
|
||||||
const UButton = resolveComponent('UButton')
|
const UButton = resolveComponent('UButton')
|
||||||
const UCheckbox = resolveComponent('UCheckbox')
|
const UCheckbox = resolveComponent('UCheckbox')
|
||||||
@@ -9,6 +10,7 @@ const UBadge = resolveComponent('UBadge')
|
|||||||
const UDropdownMenu = resolveComponent('UDropdownMenu')
|
const UDropdownMenu = resolveComponent('UDropdownMenu')
|
||||||
|
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
const { copy } = useClipboard()
|
||||||
|
|
||||||
type Payment = {
|
type Payment = {
|
||||||
id: string
|
id: string
|
||||||
@@ -220,7 +222,7 @@ const columns: TableColumn<Payment>[] = [{
|
|||||||
}, {
|
}, {
|
||||||
label: 'Copy payment ID',
|
label: 'Copy payment ID',
|
||||||
onSelect() {
|
onSelect() {
|
||||||
navigator.clipboard.writeText(row.original.id)
|
copy(row.original.id)
|
||||||
|
|
||||||
toast.add({
|
toast.add({
|
||||||
title: 'Payment ID copied to clipboard!',
|
title: 'Payment ID copied to clipboard!',
|
||||||
|
|||||||
@@ -2,12 +2,14 @@
|
|||||||
import { h, resolveComponent } from 'vue'
|
import { h, resolveComponent } from 'vue'
|
||||||
import type { TableColumn } from '@nuxt/ui'
|
import type { TableColumn } from '@nuxt/ui'
|
||||||
import type { Row } from '@tanstack/vue-table'
|
import type { Row } from '@tanstack/vue-table'
|
||||||
|
import { useClipboard } from '@vueuse/core'
|
||||||
|
|
||||||
const UButton = resolveComponent('UButton')
|
const UButton = resolveComponent('UButton')
|
||||||
const UBadge = resolveComponent('UBadge')
|
const UBadge = resolveComponent('UBadge')
|
||||||
const UDropdownMenu = resolveComponent('UDropdownMenu')
|
const UDropdownMenu = resolveComponent('UDropdownMenu')
|
||||||
|
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
const { copy } = useClipboard()
|
||||||
|
|
||||||
type Payment = {
|
type Payment = {
|
||||||
id: string
|
id: string
|
||||||
@@ -119,7 +121,7 @@ function getRowItems(row: Row<Payment>) {
|
|||||||
}, {
|
}, {
|
||||||
label: 'Copy payment ID',
|
label: 'Copy payment ID',
|
||||||
onSelect() {
|
onSelect() {
|
||||||
navigator.clipboard.writeText(row.original.id)
|
copy(row.original.id)
|
||||||
|
|
||||||
toast.add({
|
toast.add({
|
||||||
title: 'Payment ID copied to clipboard!',
|
title: 'Payment ID copied to clipboard!',
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { TableColumn, DropdownMenuItem } from '@nuxt/ui'
|
import type { TableColumn, DropdownMenuItem } from '@nuxt/ui'
|
||||||
|
import { useClipboard } from '@vueuse/core'
|
||||||
|
|
||||||
interface User {
|
interface User {
|
||||||
id: number
|
id: number
|
||||||
@@ -10,6 +11,7 @@ interface User {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
const { copy } = useClipboard()
|
||||||
|
|
||||||
const data = ref<User[]>([{
|
const data = ref<User[]>([{
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -71,7 +73,8 @@ function getDropdownActions(user: User): DropdownMenuItem[][] {
|
|||||||
label: 'Copy user Id',
|
label: 'Copy user Id',
|
||||||
icon: 'i-lucide-copy',
|
icon: 'i-lucide-copy',
|
||||||
onSelect: () => {
|
onSelect: () => {
|
||||||
navigator.clipboard.writeText(user.id.toString())
|
copy(user.id.toString())
|
||||||
|
|
||||||
toast.add({
|
toast.add({
|
||||||
title: 'User ID copied to clipboard!',
|
title: 'User ID copied to clipboard!',
|
||||||
color: 'success',
|
color: 'success',
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { TimelineItem } from '@nuxt/ui'
|
||||||
|
|
||||||
|
const items: TimelineItem[] = [{
|
||||||
|
date: 'Mar 15, 2025',
|
||||||
|
title: 'Project Kickoff',
|
||||||
|
icon: 'i-lucide-rocket',
|
||||||
|
value: 'kickoff'
|
||||||
|
}, {
|
||||||
|
date: 'Mar 22, 2025',
|
||||||
|
title: 'Design Phase',
|
||||||
|
icon: 'i-lucide-palette',
|
||||||
|
value: 'design'
|
||||||
|
}, {
|
||||||
|
date: 'Mar 29, 2025',
|
||||||
|
title: 'Development Sprint',
|
||||||
|
icon: 'i-lucide-code',
|
||||||
|
value: 'development'
|
||||||
|
}, {
|
||||||
|
date: 'Apr 5, 2025',
|
||||||
|
title: 'Testing & Deployment',
|
||||||
|
icon: 'i-lucide-check-circle',
|
||||||
|
value: 'deployment'
|
||||||
|
}]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<UTimeline
|
||||||
|
:items="items"
|
||||||
|
:ui="{ item: 'even:flex-row-reverse even:-translate-x-[calc(100%-2rem)] even:text-right' }"
|
||||||
|
:default-value="2"
|
||||||
|
class="translate-x-[calc(50%-1rem)]"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { TimelineItem } from '@nuxt/ui'
|
||||||
|
|
||||||
|
const items = [{
|
||||||
|
date: 'Mar 15, 2025',
|
||||||
|
title: 'Project Kickoff',
|
||||||
|
subtitle: 'Project Initiation',
|
||||||
|
description: 'Kicked off the project with team alignment. Set up project milestones and allocated resources.',
|
||||||
|
icon: 'i-lucide-rocket',
|
||||||
|
value: 'kickoff'
|
||||||
|
}, {
|
||||||
|
date: 'Mar 22, 2025',
|
||||||
|
title: 'Design Phase',
|
||||||
|
description: 'User research and design workshops. Created wireframes and prototypes for user testing.',
|
||||||
|
icon: 'i-lucide-palette',
|
||||||
|
value: 'design'
|
||||||
|
}, {
|
||||||
|
date: 'Mar 29, 2025',
|
||||||
|
title: 'Development Sprint',
|
||||||
|
description: 'Frontend and backend development. Implemented core features and integrated with APIs.',
|
||||||
|
icon: 'i-lucide-code',
|
||||||
|
value: 'development',
|
||||||
|
slot: 'development' as const,
|
||||||
|
developers: [
|
||||||
|
{
|
||||||
|
src: 'https://github.com/J-Michalek.png'
|
||||||
|
}, {
|
||||||
|
src: 'https://github.com/benjamincanac.png'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}, {
|
||||||
|
date: 'Apr 5, 2025',
|
||||||
|
title: 'Testing & Deployment',
|
||||||
|
description: 'QA testing and performance optimization. Deployed the application to production.',
|
||||||
|
icon: 'i-lucide-check-circle',
|
||||||
|
value: 'deployment'
|
||||||
|
}] satisfies TimelineItem[]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<UTimeline :items="items" :default-value="2" class="w-96">
|
||||||
|
<template #development-title="{ item }">
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<span>{{ item.title }}</span>
|
||||||
|
|
||||||
|
<UAvatarGroup size="2xs">
|
||||||
|
<UAvatar v-for="(developer, index) of item.developers" :key="index" v-bind="developer" />
|
||||||
|
</UAvatarGroup>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</UTimeline>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { TimelineItem } from '@nuxt/ui'
|
||||||
|
|
||||||
|
const items: TimelineItem[] = [{
|
||||||
|
date: 'Mar 15, 2025',
|
||||||
|
title: 'Project Kickoff',
|
||||||
|
description: 'Kicked off the project with team alignment. Set up project milestones and allocated resources.',
|
||||||
|
icon: 'i-lucide-rocket',
|
||||||
|
value: 'kickoff'
|
||||||
|
}, {
|
||||||
|
date: 'Mar 22, 2025',
|
||||||
|
title: 'Design Phase',
|
||||||
|
description: 'User research and design workshops. Created wireframes and prototypes for user testing.',
|
||||||
|
icon: 'i-lucide-palette',
|
||||||
|
value: 'design'
|
||||||
|
}, {
|
||||||
|
date: 'Mar 29, 2025',
|
||||||
|
title: 'Development Sprint',
|
||||||
|
description: 'Frontend and backend development. Implemented core features and integrated with APIs.',
|
||||||
|
icon: 'i-lucide-code',
|
||||||
|
value: 'development'
|
||||||
|
}, {
|
||||||
|
date: 'Apr 5, 2025',
|
||||||
|
title: 'Testing & Deployment',
|
||||||
|
description: 'QA testing and performance optimization. Deployed the application to production.',
|
||||||
|
icon: 'i-lucide-check-circle',
|
||||||
|
value: 'deployment'
|
||||||
|
}]
|
||||||
|
|
||||||
|
const active = ref(0)
|
||||||
|
|
||||||
|
// Note: This is for demonstration purposes only. Don't do this at home.
|
||||||
|
onMounted(() => {
|
||||||
|
setInterval(() => {
|
||||||
|
active.value = (active.value + 1) % items.length
|
||||||
|
}, 2000)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<UTimeline v-model="active" :items="items" class="w-96" />
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { TimelineItem } from '@nuxt/ui'
|
||||||
|
import { useTimeAgo } from '@vueuse/core'
|
||||||
|
|
||||||
|
const items = [{
|
||||||
|
username: 'J-Michalek',
|
||||||
|
date: '2025-05-24T14:58:55Z',
|
||||||
|
action: 'opened this',
|
||||||
|
avatar: {
|
||||||
|
src: 'https://github.com/J-Michalek.png'
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
username: 'J-Michalek',
|
||||||
|
date: '2025-05-26T19:30:14+02:00',
|
||||||
|
action: 'marked this pull request as ready for review',
|
||||||
|
icon: 'i-lucide-check-circle'
|
||||||
|
}, {
|
||||||
|
username: 'benjamincanac',
|
||||||
|
date: '2025-05-27T11:01:20Z',
|
||||||
|
action: 'commented on this',
|
||||||
|
description: 'I\'ve made a few changes, let me know what you think! Basically I updated the design, removed unnecessary divs, used Avatar component for the indicator since it supports icon already.',
|
||||||
|
avatar: {
|
||||||
|
src: 'https://github.com/benjamincanac.png'
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
username: 'J-Michalek',
|
||||||
|
date: '2025-05-27T11:01:20Z',
|
||||||
|
action: 'commented on this',
|
||||||
|
description: 'Looks great! Good job on cleaning it up.',
|
||||||
|
avatar: {
|
||||||
|
src: 'https://github.com/J-Michalek.png'
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
username: 'benjamincanac',
|
||||||
|
date: '2025-05-27T11:01:20Z',
|
||||||
|
action: 'merged this',
|
||||||
|
icon: 'i-lucide-git-merge'
|
||||||
|
}] satisfies TimelineItem[]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<UTimeline
|
||||||
|
:items="items"
|
||||||
|
size="xs"
|
||||||
|
class="w-96"
|
||||||
|
:ui="{
|
||||||
|
date: 'float-end ms-1',
|
||||||
|
description: 'px-3 py-2 ring ring-default mt-2 rounded-md text-default'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<template #title="{ item }">
|
||||||
|
<span>{{ item.username }}</span>
|
||||||
|
<span class="font-normal text-muted"> {{ item.action }}</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #date="{ item }">
|
||||||
|
{{ useTimeAgo(new Date(item.date)) }}
|
||||||
|
</template>
|
||||||
|
</UTimeline>
|
||||||
|
</template>
|
||||||
@@ -59,7 +59,7 @@ provide('navigation', mappedNavigation)
|
|||||||
<UApp>
|
<UApp>
|
||||||
<NuxtLoadingIndicator color="#FFF" />
|
<NuxtLoadingIndicator color="#FFF" />
|
||||||
|
|
||||||
<Banner />
|
<!-- <Banner /> -->
|
||||||
|
|
||||||
<Header :links="links" />
|
<Header :links="links" />
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
import { kebabCase } from 'scule'
|
import { kebabCase } from 'scule'
|
||||||
import type { ContentNavigationItem } from '@nuxt/content'
|
import type { ContentNavigationItem } from '@nuxt/content'
|
||||||
import type { PageLink } from '@nuxt/ui-pro'
|
import type { PageLink } from '@nuxt/ui-pro'
|
||||||
import { findPageBreadcrumb, mapContentNavigation } from '@nuxt/ui-pro/utils/content'
|
import { mapContentNavigation } from '@nuxt/ui-pro/utils/content'
|
||||||
|
import { findPageBreadcrumb } from '@nuxt/content/utils'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const { framework, module } = useSharedData()
|
const { framework, module } = useSharedData()
|
||||||
@@ -37,7 +38,7 @@ const { data: surround } = await useAsyncData(`${kebabCase(route.path)}-surround
|
|||||||
|
|
||||||
const navigation = inject<Ref<ContentNavigationItem[]>>('navigation')
|
const navigation = inject<Ref<ContentNavigationItem[]>>('navigation')
|
||||||
|
|
||||||
const breadcrumb = computed(() => mapContentNavigation(findPageBreadcrumb(navigation?.value, page.value)).map(({ icon, ...link }) => link))
|
const breadcrumb = computed(() => mapContentNavigation(findPageBreadcrumb(navigation?.value, page.value?.path, { indexAsChild: true })).map(({ icon, ...link }) => link))
|
||||||
|
|
||||||
if (!import.meta.prerender) {
|
if (!import.meta.prerender) {
|
||||||
// Redirect to the correct framework version if the page is not the current framework
|
// Redirect to the correct framework version if the page is not the current framework
|
||||||
@@ -65,13 +66,17 @@ if (!import.meta.prerender) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const type = page.value?.path.includes('components') ? 'Vue Component ' : page.value?.path.includes('composables') ? 'Vue Composable ' : ''
|
const title = page.value?.navigation?.title ? page.value.navigation.title : page.value?.title
|
||||||
|
const prefix = page.value?.path.includes('components') || page.value?.path.includes('composables') ? 'Vue ' : ''
|
||||||
|
const suffix = page.value?.path.includes('components') ? 'Component ' : page.value?.path.includes('composables') ? 'Composable ' : ''
|
||||||
|
const description = page.value?.description
|
||||||
|
|
||||||
useSeoMeta({
|
useSeoMeta({
|
||||||
titleTemplate: `%s ${type}- Nuxt UI ${page.value.module === 'ui-pro' ? 'Pro' : ''} ${page.value.framework === 'vue' ? ' for Vue' : ''}`,
|
titleTemplate: `${prefix}%s ${suffix}- Nuxt UI ${page.value?.module === 'ui-pro' ? 'Pro' : ''} ${page.value?.framework === 'vue' ? ' for Vue' : ''}`,
|
||||||
title: page.value.navigation?.title ? page.value.navigation.title : page.value.title,
|
title,
|
||||||
ogTitle: `${page.value.navigation?.title ? page.value.navigation.title : page.value.title} ${type}- Nuxt UI ${page.value.module === 'ui-pro' ? 'Pro' : ''} ${page.value.framework === 'vue' ? ' for Vue' : ''}`,
|
ogTitle: `${prefix}${title} ${suffix}- Nuxt UI ${page.value?.module === 'ui-pro' ? 'Pro' : ''} ${page.value?.framework === 'vue' ? ' for Vue' : ''}`,
|
||||||
description: page.value.description,
|
description,
|
||||||
ogDescription: page.value.description
|
ogDescription: description
|
||||||
})
|
})
|
||||||
|
|
||||||
if (route.path.startsWith('/components')) {
|
if (route.path.startsWith('/components')) {
|
||||||
@@ -137,7 +142,7 @@ const communityLinks = computed(() => [{
|
|||||||
<MDC v-if="page.description" :value="page.description" unwrap="p" :cache-key="`${kebabCase(route.path)}-description`" />
|
<MDC v-if="page.description" :value="page.description" unwrap="p" :cache-key="`${kebabCase(route.path)}-description`" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-if="page.links?.length" #links>
|
<template #links>
|
||||||
<UButton
|
<UButton
|
||||||
v-for="link in page.links"
|
v-for="link in page.links"
|
||||||
:key="link.label"
|
:key="link.label"
|
||||||
@@ -150,6 +155,7 @@ const communityLinks = computed(() => [{
|
|||||||
<UAvatar v-bind="link.avatar" size="2xs" :alt="`${link.label} avatar`" />
|
<UAvatar v-bind="link.avatar" size="2xs" :alt="`${link.label} avatar`" />
|
||||||
</template>
|
</template>
|
||||||
</UButton>
|
</UButton>
|
||||||
|
<PageHeaderLinks />
|
||||||
</template>
|
</template>
|
||||||
</UPageHeader>
|
</UPageHeader>
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,20 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
const colorMode = useColorMode()
|
||||||
|
const appConfig = useAppConfig()
|
||||||
|
|
||||||
const name = route.params.slug?.[0]
|
const name = route.params.slug?.[0]
|
||||||
|
|
||||||
|
if (route.query.theme) {
|
||||||
|
colorMode.preference = route.query.theme === 'light' ? 'light' : 'dark'
|
||||||
|
}
|
||||||
|
if (route.query.neutral) {
|
||||||
|
appConfig.ui.colors.neutral = route.query.neutral as string
|
||||||
|
}
|
||||||
|
if (route.query.primary) {
|
||||||
|
appConfig.ui.colors.primary = route.query.primary as string
|
||||||
|
}
|
||||||
|
|
||||||
const width = computed(() => route.query.width && Number.parseInt(route.query.width as string) > 0 ? `${Number.parseInt(route.query.width as string) - 2}px` : '864px')
|
const width = computed(() => route.query.width && Number.parseInt(route.query.width as string) > 0 ? `${Number.parseInt(route.query.width as string) - 2}px` : '864px')
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -24,32 +24,41 @@ onMounted(async () => {
|
|||||||
const nuxtWordPosition = document.querySelector('#nuxt')?.getBoundingClientRect()
|
const nuxtWordPosition = document.querySelector('#nuxt')?.getBoundingClientRect()
|
||||||
const initialScrollX = window.scrollX
|
const initialScrollX = window.scrollX
|
||||||
const initialScrollY = window.scrollY
|
const initialScrollY = window.scrollY
|
||||||
if (figmaWordPosition && nuxtWordPosition) {
|
|
||||||
animate('#cursor1', { left: Math.round(Math.random() * window.outerWidth), top: Math.round(Math.random() * window.outerHeight) }, { duration: 0.1 })
|
|
||||||
.then(() => animate('#cursor1', { opacity: 1 }, { duration: 0.3 }))
|
|
||||||
.then(() => {
|
|
||||||
return animate('#cursor1', {
|
|
||||||
left: Math.round(figmaWordPosition.left + initialScrollX + figmaWordPosition.width / 2),
|
|
||||||
top: Math.round(figmaWordPosition.top + initialScrollY - figmaWordPosition.height / 4)
|
|
||||||
}, { duration: 1.5, delay: 0.2, ease: 'easeInOut' })
|
|
||||||
})
|
|
||||||
.then(() => animate('#cursor1', { scale: 0.8 }, { duration: 0.1, ease: 'easeOut' }))
|
|
||||||
.then(() => animate('#cursor1', { scale: 1 }, { duration: 0.1, ease: 'easeOut' }))
|
|
||||||
.then(() => animate('#figma', { color: 'var(--ui-info)' }, { duration: 0.3, ease: 'easeOut' }))
|
|
||||||
.then(() => animate('#cursor1', { left: Math.round(figmaWordPosition.left + initialScrollX + figmaWordPosition.width), top: Math.round(figmaWordPosition.top + initialScrollY) }, { duration: 0.6, ease: 'easeInOut' }))
|
|
||||||
|
|
||||||
animate('#cursor2', { left: Math.round(Math.random() * window.outerWidth), top: Math.round(Math.random() * window.outerHeight) }, { duration: 0.1, delay: 0.6 })
|
if (figmaWordPosition && nuxtWordPosition) {
|
||||||
.then(() => animate('#cursor2', { opacity: 1 }, { duration: 0.3 }))
|
const cursor1Sequence = async () => {
|
||||||
.then(() => {
|
await animate('#cursor1', { left: Math.round(Math.random() * window.outerWidth), top: Math.round(Math.random() * window.outerHeight) }, { duration: 0.1 }).finished
|
||||||
return animate('#cursor2', {
|
await animate('#cursor1', { opacity: 1 }, { duration: 0.3 }).finished
|
||||||
left: Math.round(nuxtWordPosition.left + initialScrollX + nuxtWordPosition.width / 2),
|
await animate('#cursor1', {
|
||||||
top: Math.round(nuxtWordPosition.top + initialScrollY - nuxtWordPosition.height / 4)
|
left: Math.round(figmaWordPosition.left + initialScrollX + figmaWordPosition.width / 2),
|
||||||
}, { duration: 1.5, delay: 0.2, ease: 'easeInOut' })
|
top: Math.round(figmaWordPosition.top + initialScrollY - figmaWordPosition.height / 4)
|
||||||
})
|
}, { duration: 1.5, delay: 0.2, ease: 'easeInOut' }).finished
|
||||||
.then(() => animate('#cursor2', { scale: 0.8 }, { duration: 0.1, ease: 'easeOut' }))
|
await animate('#cursor1', { scale: 0.8 }, { duration: 0.1, ease: 'easeOut' }).finished
|
||||||
.then(() => animate('#cursor2', { scale: 1 }, { duration: 0.1, ease: 'easeOut' }))
|
await animate('#cursor1', { scale: 1 }, { duration: 0.1, ease: 'easeOut' }).finished
|
||||||
.then(() => animate('#nuxt', { color: 'var(--ui-success)' }, { duration: 0.3, ease: 'easeOut' }))
|
await animate('#figma', { color: 'var(--ui-info)' }, { duration: 0.3, ease: 'easeOut' }).finished
|
||||||
.then(() => animate('#cursor2', { left: Math.round(nuxtWordPosition.left + initialScrollX + nuxtWordPosition.width), top: Math.round(nuxtWordPosition.top + initialScrollY) }, { duration: 0.6, ease: 'easeInOut' }))
|
await animate('#cursor1', {
|
||||||
|
left: Math.round(figmaWordPosition.left + initialScrollX + figmaWordPosition.width),
|
||||||
|
top: Math.round(figmaWordPosition.top + initialScrollY)
|
||||||
|
}, { duration: 0.6, ease: 'easeInOut' }).finished
|
||||||
|
}
|
||||||
|
|
||||||
|
const cursor2Sequence = async () => {
|
||||||
|
await animate('#cursor2', { left: Math.round(Math.random() * window.outerWidth), top: Math.round(Math.random() * window.outerHeight) }, { duration: 0.1, delay: 0.6 }).finished
|
||||||
|
await animate('#cursor2', { opacity: 1 }, { duration: 0.3 }).finished
|
||||||
|
await animate('#cursor2', {
|
||||||
|
left: Math.round(nuxtWordPosition.left + initialScrollX + nuxtWordPosition.width / 2),
|
||||||
|
top: Math.round(nuxtWordPosition.top + initialScrollY - nuxtWordPosition.height / 4)
|
||||||
|
}, { duration: 1.5, delay: 0.2, ease: 'easeInOut' }).finished
|
||||||
|
await animate('#cursor2', { scale: 0.8 }, { duration: 0.1, ease: 'easeOut' }).finished
|
||||||
|
await animate('#cursor2', { scale: 1 }, { duration: 0.1, ease: 'easeOut' }).finished
|
||||||
|
await animate('#nuxt', { color: 'var(--ui-success)' }, { duration: 0.3, ease: 'easeOut' }).finished
|
||||||
|
await animate('#cursor2', {
|
||||||
|
left: Math.round(nuxtWordPosition.left + initialScrollX + nuxtWordPosition.width),
|
||||||
|
top: Math.round(nuxtWordPosition.top + initialScrollY)
|
||||||
|
}, { duration: 0.6, ease: 'easeInOut' }).finished
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all([cursor1Sequence(), cursor2Sequence()])
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -67,17 +67,6 @@ defineOgImageComponent('Docs', {
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex justify-center -mb-[36px]">
|
|
||||||
<UButton
|
|
||||||
label="Submit your project"
|
|
||||||
trailing-icon="i-lucide-plus"
|
|
||||||
color="neutral"
|
|
||||||
size="lg"
|
|
||||||
to="https://github.com/nuxt/ui/edit/v3/docs/content/showcase.yml"
|
|
||||||
target="_blank"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</UPageHero>
|
</UPageHero>
|
||||||
</UMain>
|
</UMain>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ You can play with Nuxt UI components as well as your app components directly fro
|
|||||||
Install the module to your Nuxt application with one command:
|
Install the module to your Nuxt application with one command:
|
||||||
|
|
||||||
```bash [Terminal]
|
```bash [Terminal]
|
||||||
npx nuxi module add compodium
|
npx nuxt module add compodium
|
||||||
```
|
```
|
||||||
::
|
::
|
||||||
|
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ Start your project using the [nuxt/starter#ui](https://github.com/nuxt/starter/t
|
|||||||
Create a new project locally by running the following command:
|
Create a new project locally by running the following command:
|
||||||
|
|
||||||
```bash [Terminal]
|
```bash [Terminal]
|
||||||
npx nuxi init -t ui <my-app>
|
npm create nuxt@latest -- -t ui
|
||||||
```
|
```
|
||||||
|
|
||||||
::note
|
::note
|
||||||
|
|||||||
@@ -78,6 +78,22 @@ components.d.ts
|
|||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
|
::tip
|
||||||
|
Internally, Nuxt UI relies on custom alias to resolve the theme types. If you're using TypeScript, you should add an alias to your `tsconfig` to enable auto-completion in your `vite.config.ts`.
|
||||||
|
|
||||||
|
```json [tsconfig.node.json]
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"paths": {
|
||||||
|
"#build/ui": [
|
||||||
|
"./node_modules/@nuxt/ui/.nuxt/ui"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
::
|
||||||
|
|
||||||
#### Use the Nuxt UI Vue plugin in your `main.ts`
|
#### Use the Nuxt UI Vue plugin in your `main.ts`
|
||||||
|
|
||||||
```ts [main.ts]{3,14}
|
```ts [main.ts]{3,14}
|
||||||
@@ -179,7 +195,7 @@ Start your project using the [nuxtlabs/nuxt-ui-vue-starter](https://github.com/n
|
|||||||
Create a new project locally by running the following command:
|
Create a new project locally by running the following command:
|
||||||
|
|
||||||
```bash [Terminal]
|
```bash [Terminal]
|
||||||
npx nuxi init -t github:nuxtlabs/nuxt-ui-vue-starter <my-app>
|
npm create nuxt@latest -- -t github:nuxtlabs/nuxt-ui-vue-starter
|
||||||
```
|
```
|
||||||
|
|
||||||
::note
|
::note
|
||||||
|
|||||||
@@ -229,6 +229,10 @@ export default defineConfig({
|
|||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
|
::caution
|
||||||
|
When configuring your theme colors, you must use either color names from the [default Tailwind palette](https://tailwindcss.com/docs/colors) (like 'blue', 'green', etc.) or reference custom colors that you've previously defined in your [CSS file](#theme).
|
||||||
|
::
|
||||||
|
|
||||||
### Extend colors
|
### Extend colors
|
||||||
|
|
||||||
::framework-only
|
::framework-only
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ import { fr } from '@nuxt/ui-pro/locale'
|
|||||||
|
|
||||||
### Custom locale
|
### Custom locale
|
||||||
|
|
||||||
You also have the option to add your own locale using `defineLocale`:
|
You can create your own locale using the `defineLocale` composable:
|
||||||
|
|
||||||
::module-only
|
::module-only
|
||||||
|
|
||||||
@@ -125,6 +125,65 @@ Look at the `code` parameter, there you need to pass the iso code of the languag
|
|||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
|
### Extend locale :badge{label="Soon" class="align-text-top"}
|
||||||
|
|
||||||
|
You can customize an existing locale by overriding its `messages` or `code` using the `extendLocale` composable:
|
||||||
|
|
||||||
|
::module-only
|
||||||
|
|
||||||
|
#ui
|
||||||
|
:::div
|
||||||
|
|
||||||
|
```vue [app.vue]
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { en } from '@nuxt/ui/locale'
|
||||||
|
|
||||||
|
const locale = extendLocale(en, {
|
||||||
|
code: 'en-GB',
|
||||||
|
messages: {
|
||||||
|
commandPalette: {
|
||||||
|
placeholder: 'Search a component...'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<UApp :locale="locale">
|
||||||
|
<NuxtPage />
|
||||||
|
</UApp>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
#ui-pro
|
||||||
|
:::div
|
||||||
|
|
||||||
|
```vue [app.vue]
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { en } from '@nuxt/ui-pro/locale'
|
||||||
|
|
||||||
|
const locale = extendLocale(en, {
|
||||||
|
code: 'en-GB',
|
||||||
|
messages: {
|
||||||
|
commandPalette: {
|
||||||
|
placeholder: 'Search a component...'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<UApp :locale="locale">
|
||||||
|
<NuxtPage />
|
||||||
|
</UApp>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
:::
|
||||||
|
::
|
||||||
|
|
||||||
### Dynamic locale
|
### Dynamic locale
|
||||||
|
|
||||||
To dynamically switch between languages, you can use the [Nuxt I18n](https://i18n.nuxtjs.org/) module.
|
To dynamically switch between languages, you can use the [Nuxt I18n](https://i18n.nuxtjs.org/) module.
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ import { fr } from '@nuxt/ui-pro/locale'
|
|||||||
|
|
||||||
### Custom locale
|
### Custom locale
|
||||||
|
|
||||||
You also have the option to add your locale using `defineLocale`:
|
You can create your own locale using the `defineLocale` composable:
|
||||||
|
|
||||||
::module-only
|
::module-only
|
||||||
|
|
||||||
@@ -127,6 +127,67 @@ Look at the `code` parameter, there you need to pass the iso code of the languag
|
|||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
|
### Extend locale :badge{label="Soon" class="align-text-top"}
|
||||||
|
|
||||||
|
You can customize an existing locale by overriding its `messages` or `code` using the `extendLocale` composable:
|
||||||
|
|
||||||
|
::module-only
|
||||||
|
|
||||||
|
#ui
|
||||||
|
:::div
|
||||||
|
|
||||||
|
```vue [App.vue]
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { en } from '@nuxt/ui/locale'
|
||||||
|
import { extendLocale } from '@nuxt/ui/composables/defineLocale.js'
|
||||||
|
|
||||||
|
const locale = extendLocale(en, {
|
||||||
|
code: 'en-GB',
|
||||||
|
messages: {
|
||||||
|
commandPalette: {
|
||||||
|
placeholder: 'Search a component...'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<UApp :locale="locale">
|
||||||
|
<RouterView />
|
||||||
|
</UApp>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
#ui-pro
|
||||||
|
:::div
|
||||||
|
|
||||||
|
```vue [App.vue]
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { en } from '@nuxt/ui-pro/locale'
|
||||||
|
import { extendLocale } from '@nuxt/ui/composables/defineLocale.js'
|
||||||
|
|
||||||
|
const locale = extendLocale(en, {
|
||||||
|
code: 'en-GB',
|
||||||
|
messages: {
|
||||||
|
commandPalette: {
|
||||||
|
placeholder: 'Search a component...'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<UApp :locale="locale">
|
||||||
|
<RouterView />
|
||||||
|
</UApp>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
:::
|
||||||
|
::
|
||||||
|
|
||||||
### Dynamic locale
|
### Dynamic locale
|
||||||
|
|
||||||
To dynamically switch between languages, you can use the [Vue I18n](https://vue-i18n.intlify.dev/) plugin.
|
To dynamically switch between languages, you can use the [Vue I18n](https://vue-i18n.intlify.dev/) plugin.
|
||||||
|
|||||||
@@ -225,7 +225,7 @@ pnpm run test:vue # for Vue
|
|||||||
```
|
```
|
||||||
|
|
||||||
::tip
|
::tip
|
||||||
If you have to update the snapshots, press `u` when running the tests.
|
If you have to update the snapshots, press `u` after the tests have finished running.
|
||||||
::
|
::
|
||||||
|
|
||||||
### Commit Conventions
|
### Commit Conventions
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: useOverlay
|
title: useOverlay
|
||||||
description: "A composable to programmatically control overlays."
|
description: 'A composable to programmatically control overlays.'
|
||||||
---
|
---
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
@@ -9,9 +9,11 @@ Use the auto-imported `useOverlay` composable to programmatically control [Modal
|
|||||||
|
|
||||||
```vue
|
```vue
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { LazyModalExample } from '#components'
|
||||||
|
|
||||||
const overlay = useOverlay()
|
const overlay = useOverlay()
|
||||||
|
|
||||||
const modal = overlay.create(MyModal)
|
const modal = overlay.create(LazyModalExample)
|
||||||
|
|
||||||
async function openModal() {
|
async function openModal() {
|
||||||
modal.open()
|
modal.open()
|
||||||
@@ -29,71 +31,73 @@ In order to return a value from the overlay, the `overlay.open().instance.result
|
|||||||
|
|
||||||
### `create(component: T, options: OverlayOptions): OverlayInstance`
|
### `create(component: T, options: OverlayOptions): OverlayInstance`
|
||||||
|
|
||||||
Creates an overlay, and returns a factory instance
|
Create an overlay, and return a factory instance.
|
||||||
|
|
||||||
- Parameters:
|
- Parameters:
|
||||||
- `component`: The overlay component
|
- `component`: The overlay component.
|
||||||
- `options` The overlay options
|
- `options`:
|
||||||
- `defaultOpen?: boolean` Opens the overlay immediately after being created `default: false`
|
- `defaultOpen?: boolean` Open the overlay immediately after being created. Defaults to `false`.
|
||||||
- `props?: ComponentProps`: An optional object of props to pass to the rendered component.
|
- `props?: ComponentProps`: An optional object of props to pass to the rendered component.
|
||||||
- `destroyOnClose?: boolean` Removes the overlay from memory when closed `default: false`
|
- `destroyOnClose?: boolean` Removes the overlay from memory when closed. Defaults to `false`.
|
||||||
|
|
||||||
### `open(id: symbol, props?: ComponentProps<T>): OpenedOverlay<T>`
|
### `open(id: symbol, props?: ComponentProps<T>): OpenedOverlay<T>`
|
||||||
|
|
||||||
Opens the overlay using its `id`
|
Open an overlay by its `id`.
|
||||||
|
|
||||||
- Parameters:
|
- Parameters:
|
||||||
- `id`: The identifier of the overlay
|
- `id`: The identifier of the overlay.
|
||||||
- `props`: An optional object of props to pass to the rendered component.
|
- `props`: An optional object of props to pass to the rendered component.
|
||||||
|
|
||||||
### `close(id: symbol, value?: any): void`
|
### `close(id: symbol, value?: any): void`
|
||||||
|
|
||||||
Close an overlay using its `id`
|
Close an overlay by its `id`.
|
||||||
|
|
||||||
- Parameters:
|
- Parameters:
|
||||||
- `id`: The identifier of the overlay
|
- `id`: The identifier of the overlay.
|
||||||
- `value`: A value to resolve the overlay promise with
|
- `value`: A value to resolve the overlay promise with.
|
||||||
|
|
||||||
### `patch(id: symbol, props: ComponentProps<T>): void`
|
### `patch(id: symbol, props: ComponentProps<T>): void`
|
||||||
|
|
||||||
Update an overlay using its `id`
|
Update an overlay by its `id`.
|
||||||
|
|
||||||
- Parameters:
|
- Parameters:
|
||||||
- `id`: The identifier of the overlay
|
- `id`: The identifier of the overlay.
|
||||||
- `props`: An object of props to update on the rendered component.
|
- `props`: An object of props to update on the rendered component.
|
||||||
|
|
||||||
### `unMount(id: symbol): void`
|
### `unmount(id: symbol): void`
|
||||||
|
|
||||||
Removes the overlay from the DOM using its `id`
|
Remove an overlay from the DOM by its `id`.
|
||||||
|
|
||||||
- Parameters:
|
- Parameters:
|
||||||
- `id`: The identifier of the overlay
|
- `id`: The identifier of the overlay.
|
||||||
|
|
||||||
### `isOpen(id: symbol): boolean`
|
### `isOpen(id: symbol): boolean`
|
||||||
|
|
||||||
Checks if an overlay its open using its `id`
|
Check if an overlay is open using its `id`.
|
||||||
|
|
||||||
- Parameters:
|
- Parameters:
|
||||||
- `id`: The identifier of the overlay
|
- `id`: The identifier of the overlay.
|
||||||
|
|
||||||
### `overlays: Overlay[]`
|
### `overlays: Overlay[]`
|
||||||
|
|
||||||
In-memory list of overlays that were created
|
In-memory list of all overlays that were created.
|
||||||
|
|
||||||
## Overlay Instance API
|
## Instance API
|
||||||
|
|
||||||
### `open(props?: ComponentProps<T>): Promise<OpenedOverlay<T>>`
|
### `open(props?: ComponentProps<T>): Promise<OpenedOverlay<T>>`
|
||||||
|
|
||||||
Opens the overlay
|
Open the overlay.
|
||||||
|
|
||||||
- Parameters:
|
- Parameters:
|
||||||
- `props`: An optional object of props to pass to the rendered component.
|
- `props`: An optional object of props to pass to the rendered component.
|
||||||
|
|
||||||
```vue
|
```vue
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { LazyModalExample } from '#components'
|
||||||
|
|
||||||
const overlay = useOverlay()
|
const overlay = useOverlay()
|
||||||
|
|
||||||
const modal = overlay.create(MyModalContent)
|
const modal = overlay.create(LazyModalExample)
|
||||||
|
|
||||||
function openModal() {
|
function openModal() {
|
||||||
modal.open({
|
modal.open({
|
||||||
@@ -105,23 +109,25 @@ function openModal() {
|
|||||||
|
|
||||||
### `close(value?: any): void`
|
### `close(value?: any): void`
|
||||||
|
|
||||||
Close the overlay
|
Close the overlay.
|
||||||
|
|
||||||
- Parameters:
|
- Parameters:
|
||||||
- `value`: A value to resolve the overlay promise with
|
- `value`: A value to resolve the overlay promise with.
|
||||||
|
|
||||||
### `patch(props: ComponentProps<T>)`
|
### `patch(props: ComponentProps<T>)`
|
||||||
|
|
||||||
Updates the props of the overlay.
|
Update the props of the overlay.
|
||||||
|
|
||||||
- Parameters:
|
- Parameters:
|
||||||
- `props`: An object of props to update on the rendered component.
|
- `props`: An object of props to update on the rendered component.
|
||||||
|
|
||||||
```vue
|
```vue
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { LazyModalExample } from '#components'
|
||||||
|
|
||||||
const overlay = useOverlay()
|
const overlay = useOverlay()
|
||||||
|
|
||||||
const modal = overlay.create(MyModal, {
|
const modal = overlay.create(LazyModalExample, {
|
||||||
title: 'Welcome'
|
title: 'Welcome'
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -141,6 +147,8 @@ Here's a complete example of how to use the `useOverlay` composable:
|
|||||||
|
|
||||||
```vue
|
```vue
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { ModalA, ModalB, SlideoverA } from '#components'
|
||||||
|
|
||||||
const overlay = useOverlay()
|
const overlay = useOverlay()
|
||||||
|
|
||||||
// Create with default props
|
// Create with default props
|
||||||
@@ -150,7 +158,7 @@ const modalB = overlay.create(ModalB)
|
|||||||
const slideoverA = overlay.create(SlideoverA)
|
const slideoverA = overlay.create(SlideoverA)
|
||||||
|
|
||||||
const openModalA = () => {
|
const openModalA = () => {
|
||||||
// Open Modal A, but override the title prop
|
// Open modalA, but override the title prop
|
||||||
modalA.open({ title: 'Hello' })
|
modalA.open({ title: 'Hello' })
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,16 +168,37 @@ const openModalB = async () => {
|
|||||||
|
|
||||||
const input = await modalBInstance.result
|
const input = await modalBInstance.result
|
||||||
|
|
||||||
// Pass the result from modalB to the slideover, and open it.
|
// Pass the result from modalB to the slideover, and open it
|
||||||
slideoverA.open({ input })
|
slideoverA.open({ input })
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<button @click="openModalA">Open Modal</button>
|
||||||
<button @click="openModal">Open Modal</button>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
```
|
```
|
||||||
|
|
||||||
In this example, we're using the `useOverlay` composable to control multiple modals and slideovers.
|
In this example, we're using the `useOverlay` composable to control multiple modals and slideovers.
|
||||||
|
|
||||||
|
## Caveats
|
||||||
|
|
||||||
|
### Provide / Inject
|
||||||
|
|
||||||
|
When opening overlays programmatically (e.g. modals, slideovers, etc), the overlay component can only access injected values from the component containing `UApp` (typically `app.vue` or layout components). This is because overlays are mounted outside of the page context by the `UApp` component.
|
||||||
|
|
||||||
|
As such, using `provide()` in pages or parent components isn't supported directly. To pass provided values to overlays, the recommended approach is to use props instead:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { LazyModalExample } from '#components'
|
||||||
|
|
||||||
|
const providedValue = inject('valueProvidedInPage')
|
||||||
|
|
||||||
|
const modal = overlay.create(LazyModalExample, {
|
||||||
|
props: {
|
||||||
|
providedValue,
|
||||||
|
otherData: someValue
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
```
|
||||||
@@ -23,6 +23,8 @@ Use the `items` prop as an array of objects with the following properties:
|
|||||||
- `value?: string`{lang="ts-type"}
|
- `value?: string`{lang="ts-type"}
|
||||||
- `disabled?: boolean`{lang="ts-type"}
|
- `disabled?: boolean`{lang="ts-type"}
|
||||||
- [`slot?: string`{lang="ts-type"}](#with-custom-slot)
|
- [`slot?: string`{lang="ts-type"}](#with-custom-slot)
|
||||||
|
- `class?: any`{lang="ts-type"}
|
||||||
|
- `ui?: { item?: ClassNameValue, header?: ClassNameValue, trigger?: ClassNameValue, leadingIcon?: ClassNameValue, label?: ClassNameValue, trailingIcon?: ClassNameValue, content?: ClassNameValue, body?: ClassNameValue }`{lang="ts-type"}
|
||||||
|
|
||||||
::component-code
|
::component-code
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -16,8 +16,9 @@ Use the `items` prop as an array of objects with the following properties:
|
|||||||
- `label?: string`{lang="ts-type"}
|
- `label?: string`{lang="ts-type"}
|
||||||
- `icon?: string`{lang="ts-type"}
|
- `icon?: string`{lang="ts-type"}
|
||||||
- `avatar?: AvatarProps`{lang="ts-type"}
|
- `avatar?: AvatarProps`{lang="ts-type"}
|
||||||
- `class?: any`{lang="ts-type"}
|
|
||||||
- [`slot?: string`{lang="ts-type"}](#with-custom-slot)
|
- [`slot?: string`{lang="ts-type"}](#with-custom-slot)
|
||||||
|
- `class?: any`{lang="ts-type"}
|
||||||
|
- `ui?: { item?: ClassNameValue, link?: ClassNameValue, linkLeadingIcon?: ClassNameValue, linkLeadingAvatar?: ClassNameValue, linkLabel?: ClassNameValue, separator?: ClassNameValue, separatorIcon?: ClassNameValue }`{lang="ts-type"}
|
||||||
|
|
||||||
You can pass any property from the [Link](/components/link#props) component such as `to`, `target`, etc.
|
You can pass any property from the [Link](/components/link#props) component such as `to`, `target`, etc.
|
||||||
|
|
||||||
|
|||||||
@@ -258,13 +258,13 @@ This also works with the [Form](/components/form) component.
|
|||||||
|
|
||||||
### Loading Icon
|
### Loading Icon
|
||||||
|
|
||||||
Use the `loading-icon` prop to customize the loading icon. Defaults to `i-lucide-refresh-cw`.
|
Use the `loading-icon` prop to customize the loading icon. Defaults to `i-lucide-loader-circle`.
|
||||||
|
|
||||||
::component-code
|
::component-code
|
||||||
---
|
---
|
||||||
props:
|
props:
|
||||||
loading: true
|
loading: true
|
||||||
loadingIcon: 'i-lucide-repeat-2'
|
loadingIcon: 'i-lucide-loader'
|
||||||
slots:
|
slots:
|
||||||
default: Button
|
default: Button
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -27,6 +27,11 @@ class: 'p-8'
|
|||||||
---
|
---
|
||||||
::
|
::
|
||||||
|
|
||||||
|
You can also pass an array of objects with the following properties:
|
||||||
|
|
||||||
|
- `class?: any`{lang="ts-type"}
|
||||||
|
- `ui?: { item?: ClassNameValue }`{lang="ts-type"}
|
||||||
|
|
||||||
You can control how many items are visible by using the [`basis`](https://tailwindcss.com/docs/flex-basis) / [`width`](https://tailwindcss.com/docs/width) utility classes on the `item`:
|
You can control how many items are visible by using the [`basis`](https://tailwindcss.com/docs/flex-basis) / [`width`](https://tailwindcss.com/docs/width) utility classes on the `item`:
|
||||||
|
|
||||||
::component-example
|
::component-example
|
||||||
|
|||||||
@@ -49,6 +49,8 @@ You can also pass an array of objects with the following properties:
|
|||||||
- `description?: string`{lang="ts-type"}
|
- `description?: string`{lang="ts-type"}
|
||||||
- [`value?: string`{lang="ts-type"}](#value-key)
|
- [`value?: string`{lang="ts-type"}](#value-key)
|
||||||
- `disabled?: boolean`{lang="ts-type"}
|
- `disabled?: boolean`{lang="ts-type"}
|
||||||
|
- `class?: any`{lang="ts-type"}
|
||||||
|
- `ui?: { item?: ClassNameValue, container?: ClassNameValue, base?: ClassNameValue, 'indicator'?: ClassNameValue, icon?: ClassNameValue, wrapper?: ClassNameValue, label?: ClassNameValue, description?: ClassNameValue }`{lang="ts-type"}
|
||||||
|
|
||||||
::component-code
|
::component-code
|
||||||
---
|
---
|
||||||
@@ -199,6 +201,7 @@ items:
|
|||||||
variant:
|
variant:
|
||||||
- list
|
- list
|
||||||
- card
|
- card
|
||||||
|
- table
|
||||||
props:
|
props:
|
||||||
color: 'primary'
|
color: 'primary'
|
||||||
variant: 'card'
|
variant: 'card'
|
||||||
@@ -229,6 +232,7 @@ items:
|
|||||||
variant:
|
variant:
|
||||||
- list
|
- list
|
||||||
- card
|
- card
|
||||||
|
- table
|
||||||
props:
|
props:
|
||||||
size: 'xl'
|
size: 'xl'
|
||||||
variant: 'list'
|
variant: 'list'
|
||||||
@@ -259,6 +263,7 @@ items:
|
|||||||
variant:
|
variant:
|
||||||
- list
|
- list
|
||||||
- card
|
- card
|
||||||
|
- table
|
||||||
props:
|
props:
|
||||||
orientation: 'horizontal'
|
orientation: 'horizontal'
|
||||||
variant: 'list'
|
variant: 'list'
|
||||||
@@ -293,6 +298,7 @@ items:
|
|||||||
variant:
|
variant:
|
||||||
- list
|
- list
|
||||||
- card
|
- card
|
||||||
|
- table
|
||||||
props:
|
props:
|
||||||
indicator: 'end'
|
indicator: 'end'
|
||||||
variant: 'card'
|
variant: 'card'
|
||||||
|
|||||||
@@ -52,7 +52,11 @@ Each group contains an `items` array of objects that define the commands. Each i
|
|||||||
- `loading?: boolean`{lang="ts-type"}
|
- `loading?: boolean`{lang="ts-type"}
|
||||||
- `disabled?: boolean`{lang="ts-type"}
|
- `disabled?: boolean`{lang="ts-type"}
|
||||||
- [`slot?: string`{lang="ts-type"}](#with-custom-slot)
|
- [`slot?: string`{lang="ts-type"}](#with-custom-slot)
|
||||||
|
- `placeholder?: string`{lang="ts-type"} :badge{label="Soon"}
|
||||||
|
- `children?: CommandPaletteItem[]`{lang="ts-type"} :badge{label="Soon"}
|
||||||
- `onSelect?(e?: Event): void`{lang="ts-type"}
|
- `onSelect?(e?: Event): void`{lang="ts-type"}
|
||||||
|
- `class?: any`{lang="ts-type"}
|
||||||
|
- `ui?: { item?: ClassNameValue, itemLeadingIcon?: ClassNameValue, itemLeadingAvatarSize?: ClassNameValue, itemLeadingAvatar?: ClassNameValue, itemLeadingChipSize?: ClassNameValue, itemLeadingChip?: ClassNameValue, itemLabel?: ClassNameValue, itemLabelPrefix?: ClassNameValue, itemLabelBase?: ClassNameValue, itemLabelSuffix?: ClassNameValue, itemTrailing?: ClassNameValue, itemTrailingKbds?: ClassNameValue, itemTrailingKbdsSize?: ClassNameValue, itemTrailingHighlightedIcon?: ClassNameValue, itemTrailingIcon?: ClassNameValue }`{lang="ts-type"}
|
||||||
|
|
||||||
You can pass any property from the [Link](/components/link#props) component such as `to`, `target`, etc.
|
You can pass any property from the [Link](/components/link#props) component such as `to`, `target`, etc.
|
||||||
|
|
||||||
@@ -108,6 +112,10 @@ props:
|
|||||||
---
|
---
|
||||||
::
|
::
|
||||||
|
|
||||||
|
::tip{to="#with-children-in-items"}
|
||||||
|
Each item can take a `children` array of objects with the following properties to create submenus:
|
||||||
|
::
|
||||||
|
|
||||||
### Multiple
|
### Multiple
|
||||||
|
|
||||||
Use the `multiple` prop to allow multiple selections.
|
Use the `multiple` prop to allow multiple selections.
|
||||||
@@ -244,6 +252,128 @@ You can customize this icon globally in your `vite.config.ts` under `ui.icons.se
|
|||||||
:::
|
:::
|
||||||
::
|
::
|
||||||
|
|
||||||
|
### Selected Icon
|
||||||
|
|
||||||
|
Use the `selected-icon` prop to customize the selected item [Icon](/components/icon). Defaults to `i-lucide-check`.
|
||||||
|
|
||||||
|
::component-code
|
||||||
|
---
|
||||||
|
collapse: true
|
||||||
|
hide:
|
||||||
|
- autofocus
|
||||||
|
ignore:
|
||||||
|
- groups
|
||||||
|
- modelValue
|
||||||
|
- multiple
|
||||||
|
- class
|
||||||
|
external:
|
||||||
|
- groups
|
||||||
|
- modelValue
|
||||||
|
class: '!p-0'
|
||||||
|
props:
|
||||||
|
multiple: true
|
||||||
|
autofocus: false
|
||||||
|
modelValue:
|
||||||
|
- label: 'Benjamin Canac'
|
||||||
|
suffix: 'benjamincanac'
|
||||||
|
avatar:
|
||||||
|
src: 'https://github.com/benjamincanac.png'
|
||||||
|
selectedIcon: 'i-lucide-circle-check'
|
||||||
|
groups:
|
||||||
|
- id: 'users'
|
||||||
|
label: 'Users'
|
||||||
|
items:
|
||||||
|
- label: 'Benjamin Canac'
|
||||||
|
suffix: 'benjamincanac'
|
||||||
|
avatar:
|
||||||
|
src: 'https://github.com/benjamincanac.png'
|
||||||
|
- label: 'Sylvain Marroufin'
|
||||||
|
suffix: 'smarroufin'
|
||||||
|
avatar:
|
||||||
|
src: 'https://github.com/smarroufin.png'
|
||||||
|
- label: 'Sébastien Chopin'
|
||||||
|
suffix: 'atinux'
|
||||||
|
avatar:
|
||||||
|
src: 'https://github.com/atinux.png'
|
||||||
|
- label: 'Romain Hamel'
|
||||||
|
suffix: 'romhml'
|
||||||
|
avatar:
|
||||||
|
src: 'https://github.com/romhml.png'
|
||||||
|
- label: 'Haytham A. Salama'
|
||||||
|
suffix: 'Haythamasalama'
|
||||||
|
avatar:
|
||||||
|
src: 'https://github.com/Haythamasalama.png'
|
||||||
|
- label: 'Daniel Roe'
|
||||||
|
suffix: 'danielroe'
|
||||||
|
avatar:
|
||||||
|
src: 'https://github.com/danielroe.png'
|
||||||
|
- label: 'Neil Richter'
|
||||||
|
suffix: 'noook'
|
||||||
|
avatar:
|
||||||
|
src: 'https://github.com/noook.png'
|
||||||
|
class: 'flex-1'
|
||||||
|
---
|
||||||
|
::
|
||||||
|
|
||||||
|
::framework-only
|
||||||
|
#nuxt
|
||||||
|
:::tip{to="/getting-started/icons/nuxt#theme"}
|
||||||
|
You can customize this icon globally in your `app.config.ts` under `ui.icons.check` key.
|
||||||
|
:::
|
||||||
|
|
||||||
|
#vue
|
||||||
|
:::tip{to="/getting-started/icons/vue#theme"}
|
||||||
|
You can customize this icon globally in your `vite.config.ts` under `ui.icons.check` key.
|
||||||
|
:::
|
||||||
|
::
|
||||||
|
|
||||||
|
### Trailing Icon :badge{label="Soon" class="align-text-top"}
|
||||||
|
|
||||||
|
Use the `trailing-icon` prop to customize the trailing [Icon](/components/icon) when an item has children. Defaults to `i-lucide-chevron-right`.
|
||||||
|
|
||||||
|
::component-code
|
||||||
|
---
|
||||||
|
collapse: true
|
||||||
|
prettier: true
|
||||||
|
hide:
|
||||||
|
- autofocus
|
||||||
|
ignore:
|
||||||
|
- groups
|
||||||
|
- class
|
||||||
|
external:
|
||||||
|
- groups
|
||||||
|
class: '!p-0'
|
||||||
|
props:
|
||||||
|
autofocus: false
|
||||||
|
trailingIcon: 'i-lucide-arrow-right'
|
||||||
|
groups:
|
||||||
|
- id: 'actions'
|
||||||
|
items:
|
||||||
|
- label: 'Share'
|
||||||
|
icon: 'i-lucide-share'
|
||||||
|
children:
|
||||||
|
- label: 'Email'
|
||||||
|
icon: 'i-lucide-mail'
|
||||||
|
- label: 'Copy'
|
||||||
|
icon: 'i-lucide-copy'
|
||||||
|
- label: 'Link'
|
||||||
|
icon: 'i-lucide-link'
|
||||||
|
class: 'flex-1'
|
||||||
|
---
|
||||||
|
::
|
||||||
|
|
||||||
|
::framework-only
|
||||||
|
#nuxt
|
||||||
|
:::tip{to="/getting-started/icons/nuxt#theme"}
|
||||||
|
You can customize this icon globally in your `app.config.ts` under `ui.icons.chevronRight` key.
|
||||||
|
:::
|
||||||
|
|
||||||
|
#vue
|
||||||
|
:::tip{to="/getting-started/icons/vue#theme"}
|
||||||
|
You can customize this icon globally in your `vite.config.ts` under `ui.icons.chevronRight` key.
|
||||||
|
:::
|
||||||
|
::
|
||||||
|
|
||||||
### Loading
|
### Loading
|
||||||
|
|
||||||
Use the `loading` prop to show a loading icon on the CommandPalette.
|
Use the `loading` prop to show a loading icon on the CommandPalette.
|
||||||
@@ -277,7 +407,7 @@ props:
|
|||||||
|
|
||||||
### Loading Icon
|
### Loading Icon
|
||||||
|
|
||||||
Use the `loading-icon` prop to customize the loading icon. Defaults to `i-lucide-refresh-cw`.
|
Use the `loading-icon` prop to customize the loading icon. Defaults to `i-lucide-loader-circle`.
|
||||||
|
|
||||||
::component-code
|
::component-code
|
||||||
---
|
---
|
||||||
@@ -293,7 +423,7 @@ class: '!p-0'
|
|||||||
props:
|
props:
|
||||||
autofocus: false
|
autofocus: false
|
||||||
loading: true
|
loading: true
|
||||||
loadingIcon: 'i-lucide-repeat-2'
|
loadingIcon: 'i-lucide-loader'
|
||||||
groups:
|
groups:
|
||||||
- id: 'apps'
|
- id: 'apps'
|
||||||
items:
|
items:
|
||||||
@@ -319,37 +449,6 @@ You can customize this icon globally in your `vite.config.ts` under `ui.icons.lo
|
|||||||
:::
|
:::
|
||||||
::
|
::
|
||||||
|
|
||||||
### Disabled
|
|
||||||
|
|
||||||
Use the `disabled` prop to disable the CommandPalette.
|
|
||||||
|
|
||||||
::component-code
|
|
||||||
---
|
|
||||||
collapse: true
|
|
||||||
hide:
|
|
||||||
- autofocus
|
|
||||||
ignore:
|
|
||||||
- groups
|
|
||||||
- class
|
|
||||||
external:
|
|
||||||
- groups
|
|
||||||
class: '!p-0'
|
|
||||||
props:
|
|
||||||
autofocus: false
|
|
||||||
disabled: true
|
|
||||||
groups:
|
|
||||||
- id: 'apps'
|
|
||||||
items:
|
|
||||||
- label: 'Calendar'
|
|
||||||
icon: 'i-lucide-calendar'
|
|
||||||
- label: 'Music'
|
|
||||||
icon: 'i-lucide-music'
|
|
||||||
- label: 'Maps'
|
|
||||||
icon: 'i-lucide-map'
|
|
||||||
class: 'flex-1'
|
|
||||||
---
|
|
||||||
::
|
|
||||||
|
|
||||||
### Close
|
### Close
|
||||||
|
|
||||||
Use the `close` prop to display a [Button](/components/button) to dismiss the CommandPalette.
|
Use the `close` prop to display a [Button](/components/button) to dismiss the CommandPalette.
|
||||||
@@ -466,6 +565,124 @@ You can customize this icon globally in your `vite.config.ts` under `ui.icons.cl
|
|||||||
:::
|
:::
|
||||||
::
|
::
|
||||||
|
|
||||||
|
### Back :badge{label="Soon" class="align-text-top"}
|
||||||
|
|
||||||
|
Use the `back` prop to customize or hide the back button (with `false` value) displayed when navigating into a submenu.
|
||||||
|
|
||||||
|
You can pass any property from the [Button](/components/button) component to customize it.
|
||||||
|
|
||||||
|
::component-code
|
||||||
|
---
|
||||||
|
collapse: true
|
||||||
|
prettier: true
|
||||||
|
hide:
|
||||||
|
- autofocus
|
||||||
|
ignore:
|
||||||
|
- back.color
|
||||||
|
- groups
|
||||||
|
- class
|
||||||
|
external:
|
||||||
|
- groups
|
||||||
|
class: '!p-0'
|
||||||
|
props:
|
||||||
|
autofocus: false
|
||||||
|
back:
|
||||||
|
color: primary
|
||||||
|
groups:
|
||||||
|
- id: 'actions'
|
||||||
|
items:
|
||||||
|
- label: 'Share'
|
||||||
|
icon: 'i-lucide-share'
|
||||||
|
children:
|
||||||
|
- label: 'Email'
|
||||||
|
icon: 'i-lucide-mail'
|
||||||
|
- label: 'Copy'
|
||||||
|
icon: 'i-lucide-copy'
|
||||||
|
- label: 'Link'
|
||||||
|
icon: 'i-lucide-link'
|
||||||
|
class: 'flex-1'
|
||||||
|
---
|
||||||
|
::
|
||||||
|
|
||||||
|
### Back Icon :badge{label="Soon" class="align-text-top"}
|
||||||
|
|
||||||
|
Use the `back-icon` prop to customize the back button [Icon](/components/icon). Defaults to `i-lucide-arrow-left`.
|
||||||
|
|
||||||
|
::component-code
|
||||||
|
---
|
||||||
|
collapse: true
|
||||||
|
hide:
|
||||||
|
- autofocus
|
||||||
|
ignore:
|
||||||
|
- class
|
||||||
|
- groups
|
||||||
|
- back
|
||||||
|
external:
|
||||||
|
- groups
|
||||||
|
class: '!p-0'
|
||||||
|
props:
|
||||||
|
autofocus: false
|
||||||
|
back: true
|
||||||
|
backIcon: 'i-lucide-house'
|
||||||
|
groups:
|
||||||
|
- id: 'actions'
|
||||||
|
items:
|
||||||
|
- label: 'Share'
|
||||||
|
icon: 'i-lucide-share'
|
||||||
|
children:
|
||||||
|
- label: 'Email'
|
||||||
|
icon: 'i-lucide-mail'
|
||||||
|
- label: 'Copy'
|
||||||
|
icon: 'i-lucide-copy'
|
||||||
|
- label: 'Link'
|
||||||
|
icon: 'i-lucide-link'
|
||||||
|
class: 'flex-1'
|
||||||
|
---
|
||||||
|
::
|
||||||
|
|
||||||
|
::framework-only
|
||||||
|
#nuxt
|
||||||
|
:::tip{to="/getting-started/icons/nuxt#theme"}
|
||||||
|
You can customize this icon globally in your `app.config.ts` under `ui.icons.arrowLeft` key.
|
||||||
|
:::
|
||||||
|
|
||||||
|
#vue
|
||||||
|
:::tip{to="/getting-started/icons/vue#theme"}
|
||||||
|
You can customize this icon globally in your `vite.config.ts` under `ui.icons.arrowLeft` key.
|
||||||
|
:::
|
||||||
|
::
|
||||||
|
|
||||||
|
### Disabled
|
||||||
|
|
||||||
|
Use the `disabled` prop to disable the CommandPalette.
|
||||||
|
|
||||||
|
::component-code
|
||||||
|
---
|
||||||
|
collapse: true
|
||||||
|
hide:
|
||||||
|
- autofocus
|
||||||
|
ignore:
|
||||||
|
- groups
|
||||||
|
- class
|
||||||
|
external:
|
||||||
|
- groups
|
||||||
|
class: '!p-0'
|
||||||
|
props:
|
||||||
|
autofocus: false
|
||||||
|
disabled: true
|
||||||
|
groups:
|
||||||
|
- id: 'apps'
|
||||||
|
items:
|
||||||
|
- label: 'Calendar'
|
||||||
|
icon: 'i-lucide-calendar'
|
||||||
|
- label: 'Music'
|
||||||
|
icon: 'i-lucide-music'
|
||||||
|
- label: 'Maps'
|
||||||
|
icon: 'i-lucide-map'
|
||||||
|
class: 'flex-1'
|
||||||
|
---
|
||||||
|
::
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
### Control selected item(s)
|
### Control selected item(s)
|
||||||
@@ -500,6 +717,28 @@ props:
|
|||||||
This example uses the `@update:model-value` event to reset the search term when an item is selected.
|
This example uses the `@update:model-value` event to reset the search term when an item is selected.
|
||||||
::
|
::
|
||||||
|
|
||||||
|
### With children in items :badge{label="Soon" class="align-text-top"}
|
||||||
|
|
||||||
|
You can create hierarchical menus by using the `children` property in items. When an item has children, it will automatically display a chevron icon and enable navigation into a submenu.
|
||||||
|
|
||||||
|
::component-example
|
||||||
|
---
|
||||||
|
collapse: true
|
||||||
|
prettier: true
|
||||||
|
name: 'command-palette-items-children-example'
|
||||||
|
class: '!p-0'
|
||||||
|
props:
|
||||||
|
autofocus: false
|
||||||
|
---
|
||||||
|
::
|
||||||
|
|
||||||
|
::note
|
||||||
|
When navigating into a submenu:
|
||||||
|
- The search term is reset
|
||||||
|
- A back button appears in the input
|
||||||
|
- You can go back to the previous group by pressing the :kbd{value="backspace"} key
|
||||||
|
::
|
||||||
|
|
||||||
### With fetched items
|
### With fetched items
|
||||||
|
|
||||||
You can fetch items from an API and use them in the CommandPalette.
|
You can fetch items from an API and use them in the CommandPalette.
|
||||||
@@ -656,6 +895,7 @@ You will have access to the following slots:
|
|||||||
|
|
||||||
::component-example
|
::component-example
|
||||||
---
|
---
|
||||||
|
collapse: true
|
||||||
name: 'command-palette-custom-slot-example'
|
name: 'command-palette-custom-slot-example'
|
||||||
class: '!p-0'
|
class: '!p-0'
|
||||||
props:
|
props:
|
||||||
|
|||||||
@@ -28,11 +28,12 @@ Use the `items` prop as an array of objects with the following properties:
|
|||||||
- [`color?: "error" | "primary" | "secondary" | "success" | "info" | "warning" | "neutral"`{lang="ts-type"}](#with-color-items)
|
- [`color?: "error" | "primary" | "secondary" | "success" | "info" | "warning" | "neutral"`{lang="ts-type"}](#with-color-items)
|
||||||
- [`checked?: boolean`{lang="ts-type"}](#with-checkbox-items)
|
- [`checked?: boolean`{lang="ts-type"}](#with-checkbox-items)
|
||||||
- `disabled?: boolean`{lang="ts-type"}
|
- `disabled?: boolean`{lang="ts-type"}
|
||||||
- `class?: any`{lang="ts-type"}
|
|
||||||
- [`slot?: string`{lang="ts-type"}](#with-custom-slot)
|
- [`slot?: string`{lang="ts-type"}](#with-custom-slot)
|
||||||
- `onSelect?(e: Event): void`{lang="ts-type"}
|
- `onSelect?(e: Event): void`{lang="ts-type"}
|
||||||
- [`onUpdateChecked?(checked: boolean): void`{lang="ts-type"}](#with-checkbox-items)
|
- [`onUpdateChecked?(checked: boolean): void`{lang="ts-type"}](#with-checkbox-items)
|
||||||
- `children?: ContextMenuItem[] | ContextMenuItem[][]`{lang="ts-type"}
|
- `children?: ContextMenuItem[] | ContextMenuItem[][]`{lang="ts-type"}
|
||||||
|
- `class?: any`{lang="ts-type"}
|
||||||
|
- `ui?: { item?: ClassNameValue, label?: ClassNameValue, separator?: ClassNameValue, itemLeadingIcon?: ClassNameValue, itemLeadingAvatarSize?: ClassNameValue, itemLeadingAvatar?: ClassNameValue, itemLabel?: ClassNameValue, itemLabelExternalIcon?: ClassNameValue, itemTrailing?: ClassNameValue, itemTrailingIcon?: ClassNameValue, itemTrailingKbds?: ClassNameValue, itemTrailingKbdsSize?: ClassNameValue }`{lang="ts-type"}
|
||||||
|
|
||||||
You can pass any property from the [Link](/components/link#props) component such as `to`, `target`, etc.
|
You can pass any property from the [Link](/components/link#props) component such as `to`, `target`, etc.
|
||||||
|
|
||||||
|
|||||||
@@ -291,7 +291,7 @@ In this example, leveraging [`defineShortcuts`](/composables/define-shortcuts),
|
|||||||
This allows you to move the trigger outside of the Drawer or remove it entirely.
|
This allows you to move the trigger outside of the Drawer or remove it entirely.
|
||||||
::
|
::
|
||||||
|
|
||||||
### Prevent closing
|
### Disable dismissal
|
||||||
|
|
||||||
Set the `dismissible` prop to `false` to prevent the Drawer from being closed when clicking outside of it or pressing escape.
|
Set the `dismissible` prop to `false` to prevent the Drawer from being closed when clicking outside of it or pressing escape.
|
||||||
|
|
||||||
@@ -306,6 +306,17 @@ name: 'drawer-dismissible-example'
|
|||||||
In this example, the `header` slot is used to add a close button which is not done by default.
|
In this example, the `header` slot is used to add a close button which is not done by default.
|
||||||
::
|
::
|
||||||
|
|
||||||
|
### With interactive background
|
||||||
|
|
||||||
|
Set the `overlay` and `modal` props to `false` alongside the `dismissible` prop to make the Drawer's background interactive without closing the Drawer.
|
||||||
|
|
||||||
|
::component-example
|
||||||
|
---
|
||||||
|
prettier: true
|
||||||
|
name: 'drawer-modal-example'
|
||||||
|
---
|
||||||
|
::
|
||||||
|
|
||||||
### Responsive drawer
|
### Responsive drawer
|
||||||
|
|
||||||
You can render a [Modal](/components/modal) component on desktop and a Drawer on mobile for example.
|
You can render a [Modal](/components/modal) component on desktop and a Drawer on mobile for example.
|
||||||
|
|||||||
@@ -28,11 +28,12 @@ Use the `items` prop as an array of objects with the following properties:
|
|||||||
- [`color?: "error" | "primary" | "secondary" | "success" | "info" | "warning" | "neutral"`{lang="ts-type"}](#with-color-items)
|
- [`color?: "error" | "primary" | "secondary" | "success" | "info" | "warning" | "neutral"`{lang="ts-type"}](#with-color-items)
|
||||||
- [`checked?: boolean`{lang="ts-type"}](#with-checkbox-items)
|
- [`checked?: boolean`{lang="ts-type"}](#with-checkbox-items)
|
||||||
- `disabled?: boolean`{lang="ts-type"}
|
- `disabled?: boolean`{lang="ts-type"}
|
||||||
- `class?: any`{lang="ts-type"}
|
|
||||||
- [`slot?: string`{lang="ts-type"}](#with-custom-slot)
|
- [`slot?: string`{lang="ts-type"}](#with-custom-slot)
|
||||||
- `onSelect?(e: Event): void`{lang="ts-type"}
|
- `onSelect?(e: Event): void`{lang="ts-type"}
|
||||||
- [`onUpdateChecked?(checked: boolean): void`{lang="ts-type"}](#with-checkbox-items)
|
- [`onUpdateChecked?(checked: boolean): void`{lang="ts-type"}](#with-checkbox-items)
|
||||||
- `children?: DropdownMenuItem[] | DropdownMenuItem[][]`{lang="ts-type"}
|
- `children?: DropdownMenuItem[] | DropdownMenuItem[][]`{lang="ts-type"}
|
||||||
|
- `class?: any`{lang="ts-type"}
|
||||||
|
- `ui?: { item?: ClassNameValue, label?: ClassNameValue, separator?: ClassNameValue, itemLeadingIcon?: ClassNameValue, itemLeadingAvatarSize?: ClassNameValue, itemLeadingAvatar?: ClassNameValue, itemLabel?: ClassNameValue, itemLabelExternalIcon?: ClassNameValue, itemTrailing?: ClassNameValue, itemTrailingIcon?: ClassNameValue, itemTrailingKbds?: ClassNameValue, itemTrailingKbdsSize?: ClassNameValue }`{lang="ts-type"}
|
||||||
|
|
||||||
You can pass any property from the [Link](/components/link#props) component such as `to`, `target`, etc.
|
You can pass any property from the [Link](/components/link#props) component such as `to`, `target`, etc.
|
||||||
|
|
||||||
|
|||||||
@@ -55,6 +55,8 @@ You can also pass an array of objects with the following properties:
|
|||||||
- [`chip?: ChipProps`{lang="ts-type"}](#with-chip-in-items)
|
- [`chip?: ChipProps`{lang="ts-type"}](#with-chip-in-items)
|
||||||
- `disabled?: boolean`{lang="ts-type"}
|
- `disabled?: boolean`{lang="ts-type"}
|
||||||
- `onSelect?(e: Event): void`{lang="ts-type"}
|
- `onSelect?(e: Event): void`{lang="ts-type"}
|
||||||
|
- `class?: any`{lang="ts-type"}
|
||||||
|
- `ui?: { tagsItem?: ClassNameValue, tagsItemText?: ClassNameValue, tagsItemDelete?: ClassNameValue, tagsItemDeleteIcon?: ClassNameValue, label?: ClassNameValue, separator?: ClassNameValue, item?: ClassNameValue, itemLeadingIcon?: ClassNameValue, itemLeadingAvatarSize?: ClassNameValue, itemLeadingAvatar?: ClassNameValue, itemLeadingChip?: ClassNameValue, itemLeadingChipSize?: ClassNameValue, itemLabel?: ClassNameValue, itemTrailing?: ClassNameValue, itemTrailingIcon?: ClassNameValue }`{lang="ts-type"}
|
||||||
|
|
||||||
::component-code
|
::component-code
|
||||||
---
|
---
|
||||||
@@ -133,7 +135,7 @@ props:
|
|||||||
|
|
||||||
### Multiple
|
### Multiple
|
||||||
|
|
||||||
Use the `multiple` prop to allow multiple selections, the selected items will be displayed as badges.
|
Use the `multiple` prop to allow multiple selections, the selected items will be displayed as tags.
|
||||||
|
|
||||||
::component-code
|
::component-code
|
||||||
---
|
---
|
||||||
@@ -164,7 +166,7 @@ Ensure to pass an array to the `default-value` prop or the `v-model` directive.
|
|||||||
|
|
||||||
### Delete Icon
|
### Delete Icon
|
||||||
|
|
||||||
With `multiple`, use the `delete-icon` prop to customize the delete [Icon](/components/icon) in the badges. Defaults to `i-lucide-x`.
|
With `multiple`, use the `delete-icon` prop to customize the delete [Icon](/components/icon) in the tags. Defaults to `i-lucide-x`.
|
||||||
|
|
||||||
::component-code
|
::component-code
|
||||||
---
|
---
|
||||||
@@ -516,7 +518,7 @@ props:
|
|||||||
|
|
||||||
### Loading Icon
|
### Loading Icon
|
||||||
|
|
||||||
Use the `loading-icon` prop to customize the loading icon. Defaults to `i-lucide-refresh-cw`.
|
Use the `loading-icon` prop to customize the loading icon. Defaults to `i-lucide-loader-circle`.
|
||||||
|
|
||||||
::component-code
|
::component-code
|
||||||
---
|
---
|
||||||
@@ -530,7 +532,7 @@ external:
|
|||||||
props:
|
props:
|
||||||
modelValue: 'Backlog'
|
modelValue: 'Backlog'
|
||||||
loading: true
|
loading: true
|
||||||
loadingIcon: 'i-lucide-repeat-2'
|
loadingIcon: 'i-lucide-loader'
|
||||||
items:
|
items:
|
||||||
- Backlog
|
- Backlog
|
||||||
- Todo
|
- Todo
|
||||||
@@ -610,7 +612,7 @@ props:
|
|||||||
---
|
---
|
||||||
::
|
::
|
||||||
|
|
||||||
### With icons in items
|
### With icon in items
|
||||||
|
|
||||||
You can use the `icon` property to display an [Icon](/components/icon) inside the items.
|
You can use the `icon` property to display an [Icon](/components/icon) inside the items.
|
||||||
|
|
||||||
@@ -780,6 +782,14 @@ name: 'input-menu-countries-example'
|
|||||||
|
|
||||||
:component-emits
|
:component-emits
|
||||||
|
|
||||||
|
### Expose
|
||||||
|
|
||||||
|
When accessing the component via a template ref, you can use the following:
|
||||||
|
|
||||||
|
| Name | Type |
|
||||||
|
| ---- | ---- |
|
||||||
|
| `inputRef`{lang="ts-type"} | `Ref<InstanceType<typeof ComboboxTrigger> \| null>`{lang="ts-type"} |
|
||||||
|
|
||||||
## Theme
|
## Theme
|
||||||
|
|
||||||
:component-theme
|
:component-theme
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: InputNumber
|
title: InputNumber
|
||||||
description: Input numerical values with a customizable range.
|
description: An input for numerical values with a customizable range.
|
||||||
category: form
|
category: form
|
||||||
links:
|
links:
|
||||||
- label: NumberField
|
- label: NumberField
|
||||||
@@ -287,8 +287,8 @@ name: 'input-number-slots-example'
|
|||||||
When accessing the component via a template ref, you can use the following:
|
When accessing the component via a template ref, you can use the following:
|
||||||
|
|
||||||
| Name | Type |
|
| Name | Type |
|
||||||
|----------------------------|-------------------------------------------------|
|
| -------------------------- | ----------------------------------------------- |
|
||||||
| `inputRef`{lang="ts-type"} | `Ref<HTMLInputElement \| null>`{lang="ts-type"} |
|
| `inputRef`{lang="ts-type"} | `Ref<InstanceType<typeof NumberFieldInput> \| null>`{lang="ts-type"} |
|
||||||
|
|
||||||
## Theme
|
## Theme
|
||||||
|
|
||||||
|
|||||||
285
docs/content/3.components/input-tags.md
Normal file
@@ -0,0 +1,285 @@
|
|||||||
|
---
|
||||||
|
title: InputTags
|
||||||
|
description: An input element that displays interactive tags.
|
||||||
|
category: form
|
||||||
|
links:
|
||||||
|
- label: InputTags
|
||||||
|
icon: i-custom-reka-ui
|
||||||
|
to: https://reka-ui.com/docs/components/tags-input
|
||||||
|
- label: GitHub
|
||||||
|
icon: i-simple-icons-github
|
||||||
|
to: https://github.com/nuxt/ui/tree/v3/src/runtime/components/InputTags.vue
|
||||||
|
navigation.badge: Soon
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Use the `v-model` directive to control the value of the InputTags.
|
||||||
|
|
||||||
|
::component-code
|
||||||
|
---
|
||||||
|
prettier: true
|
||||||
|
ignore:
|
||||||
|
- modelValue
|
||||||
|
external:
|
||||||
|
- modelValue
|
||||||
|
props:
|
||||||
|
modelValue: ['Vue']
|
||||||
|
---
|
||||||
|
::
|
||||||
|
|
||||||
|
Use the `default-value` prop to set the initial value when you do not need to control its state.
|
||||||
|
|
||||||
|
::component-code
|
||||||
|
---
|
||||||
|
prettier: true
|
||||||
|
ignore:
|
||||||
|
- defaultValue
|
||||||
|
props:
|
||||||
|
defaultValue: ['Vue']
|
||||||
|
---
|
||||||
|
::
|
||||||
|
|
||||||
|
### Placeholder
|
||||||
|
|
||||||
|
Use the `placeholder` prop to set a placeholder text.
|
||||||
|
|
||||||
|
::component-code
|
||||||
|
---
|
||||||
|
props:
|
||||||
|
placeholder: 'Enter tags...'
|
||||||
|
---
|
||||||
|
::
|
||||||
|
|
||||||
|
### Color
|
||||||
|
|
||||||
|
Use the `color` prop to change the ring color when the InputTags is focused.
|
||||||
|
|
||||||
|
::component-code
|
||||||
|
---
|
||||||
|
prettier: true
|
||||||
|
ignore:
|
||||||
|
- modelValue
|
||||||
|
external:
|
||||||
|
- modelValue
|
||||||
|
props:
|
||||||
|
modelValue: ['Vue']
|
||||||
|
color: neutral
|
||||||
|
highlight: true
|
||||||
|
---
|
||||||
|
::
|
||||||
|
|
||||||
|
::note
|
||||||
|
The `highlight` prop is used here to show the focus state. It's used internally when a validation error occurs.
|
||||||
|
::
|
||||||
|
|
||||||
|
### Variants
|
||||||
|
|
||||||
|
Use the `variant` prop to change the appearance of the InputTags.
|
||||||
|
|
||||||
|
::component-code
|
||||||
|
---
|
||||||
|
prettier: true
|
||||||
|
ignore:
|
||||||
|
- modelValue
|
||||||
|
external:
|
||||||
|
- modelValue
|
||||||
|
props:
|
||||||
|
modelValue: ['Vue']
|
||||||
|
variant: subtle
|
||||||
|
color: neutral
|
||||||
|
highlight: false
|
||||||
|
---
|
||||||
|
::
|
||||||
|
|
||||||
|
### Sizes
|
||||||
|
|
||||||
|
Use the `size` prop to adjust the size of the InputTags.
|
||||||
|
|
||||||
|
::component-code
|
||||||
|
---
|
||||||
|
prettier: true
|
||||||
|
ignore:
|
||||||
|
- modelValue
|
||||||
|
external:
|
||||||
|
- modelValue
|
||||||
|
props:
|
||||||
|
modelValue: ['Vue']
|
||||||
|
size: xl
|
||||||
|
---
|
||||||
|
::
|
||||||
|
|
||||||
|
### Icon
|
||||||
|
|
||||||
|
Use the `icon` prop to show an [Icon](/components/icon) inside the InputTags.
|
||||||
|
|
||||||
|
::component-code
|
||||||
|
---
|
||||||
|
prettier: true
|
||||||
|
ignore:
|
||||||
|
- modelValue
|
||||||
|
external:
|
||||||
|
- modelValue
|
||||||
|
props:
|
||||||
|
modelValue: ['Vue']
|
||||||
|
icon: 'i-lucide-search'
|
||||||
|
size: md
|
||||||
|
variant: outline
|
||||||
|
---
|
||||||
|
::
|
||||||
|
|
||||||
|
::note
|
||||||
|
Use the `leading` and `trailing` props to set the icon position or the `leading-icon` and `trailing-icon` props to set a different icon for each position.
|
||||||
|
::
|
||||||
|
|
||||||
|
### Avatar
|
||||||
|
|
||||||
|
Use the `avatar` prop to show an [Avatar](/components/avatar) inside the InputTags.
|
||||||
|
|
||||||
|
::component-code
|
||||||
|
---
|
||||||
|
prettier: true
|
||||||
|
ignore:
|
||||||
|
- modelValue
|
||||||
|
external:
|
||||||
|
- modelValue
|
||||||
|
props:
|
||||||
|
modelValue: ['Vue']
|
||||||
|
avatar:
|
||||||
|
src: 'https://github.com/vuejs.png'
|
||||||
|
size: md
|
||||||
|
variant: outline
|
||||||
|
---
|
||||||
|
::
|
||||||
|
|
||||||
|
### Delete Icon
|
||||||
|
|
||||||
|
Use the `delete-icon` prop to customize the delete [Icon](/components/icon) in the tags. Defaults to `i-lucide-x`.
|
||||||
|
|
||||||
|
::component-code
|
||||||
|
---
|
||||||
|
prettier: true
|
||||||
|
ignore:
|
||||||
|
- modelValue
|
||||||
|
external:
|
||||||
|
- modelValue
|
||||||
|
props:
|
||||||
|
modelValue: ['Vue']
|
||||||
|
deleteIcon: 'i-lucide-trash'
|
||||||
|
---
|
||||||
|
::
|
||||||
|
|
||||||
|
::framework-only
|
||||||
|
#nuxt
|
||||||
|
:::tip{to="/getting-started/icons/nuxt#theme"}
|
||||||
|
You can customize this icon globally in your `app.config.ts` under `ui.icons.close` key.
|
||||||
|
:::
|
||||||
|
|
||||||
|
#vue
|
||||||
|
:::tip{to="/getting-started/icons/vue#theme"}
|
||||||
|
You can customize this icon globally in your `vite.config.ts` under `ui.icons.close` key.
|
||||||
|
:::
|
||||||
|
::
|
||||||
|
|
||||||
|
### Loading
|
||||||
|
|
||||||
|
Use the `loading` prop to show a loading icon on the InputTags.
|
||||||
|
|
||||||
|
::component-code
|
||||||
|
---
|
||||||
|
prettier: true
|
||||||
|
ignore:
|
||||||
|
- modelValue
|
||||||
|
external:
|
||||||
|
- modelValue
|
||||||
|
props:
|
||||||
|
modelValue: ['Vue']
|
||||||
|
loading: true
|
||||||
|
trailing: false
|
||||||
|
---
|
||||||
|
::
|
||||||
|
|
||||||
|
### Loading Icon
|
||||||
|
|
||||||
|
Use the `loading-icon` prop to customize the loading icon. Defaults to `i-lucide-loader-circle`.
|
||||||
|
|
||||||
|
::component-code
|
||||||
|
---
|
||||||
|
prettier: true
|
||||||
|
ignore:
|
||||||
|
- modelValue
|
||||||
|
external:
|
||||||
|
- modelValue
|
||||||
|
props:
|
||||||
|
modelValue: ['Vue']
|
||||||
|
loading: true
|
||||||
|
loadingIcon: 'i-lucide-loader'
|
||||||
|
---
|
||||||
|
::
|
||||||
|
|
||||||
|
::framework-only
|
||||||
|
#nuxt
|
||||||
|
:::tip{to="/getting-started/icons/nuxt#theme"}
|
||||||
|
You can customize this icon globally in your `app.config.ts` under `ui.icons.loading` key.
|
||||||
|
:::
|
||||||
|
|
||||||
|
#vue
|
||||||
|
:::tip{to="/getting-started/icons/vue#theme"}
|
||||||
|
You can customize this icon globally in your `vite.config.ts` under `ui.icons.loading` key.
|
||||||
|
:::
|
||||||
|
::
|
||||||
|
|
||||||
|
### Disabled
|
||||||
|
|
||||||
|
Use the `disabled` prop to disable the InputTags.
|
||||||
|
|
||||||
|
::component-code
|
||||||
|
---
|
||||||
|
prettier: true
|
||||||
|
ignore:
|
||||||
|
- modelValue
|
||||||
|
external:
|
||||||
|
- modelValue
|
||||||
|
props:
|
||||||
|
modelValue: ['Vue']
|
||||||
|
disabled: true
|
||||||
|
---
|
||||||
|
::
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Within a FormField
|
||||||
|
|
||||||
|
You can use the InputTags within a [FormField](/components/form-field) component to display a label, help text, required indicator, etc.
|
||||||
|
|
||||||
|
::component-example
|
||||||
|
---
|
||||||
|
name: 'input-tags-form-field-example'
|
||||||
|
---
|
||||||
|
::
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
### Props
|
||||||
|
|
||||||
|
:component-props
|
||||||
|
|
||||||
|
### Slots
|
||||||
|
|
||||||
|
:component-slots
|
||||||
|
|
||||||
|
### Emits
|
||||||
|
|
||||||
|
:component-emits
|
||||||
|
|
||||||
|
### Expose
|
||||||
|
|
||||||
|
When accessing the component via a template ref, you can use the following:
|
||||||
|
|
||||||
|
| Name | Type |
|
||||||
|
| -------------------------- | ----------------------------------------------- |
|
||||||
|
| `inputRef`{lang="ts-type"} | `Ref<InstanceType<typeof TagsInputInput> \| null>`{lang="ts-type"} |
|
||||||
|
|
||||||
|
## Theme
|
||||||
|
|
||||||
|
:component-theme
|
||||||
@@ -172,7 +172,7 @@ props:
|
|||||||
|
|
||||||
### Loading Icon
|
### Loading Icon
|
||||||
|
|
||||||
Use the `loading-icon` prop to customize the loading icon. Defaults to `i-lucide-refresh-cw`.
|
Use the `loading-icon` prop to customize the loading icon. Defaults to `i-lucide-loader-circle`.
|
||||||
|
|
||||||
::component-code
|
::component-code
|
||||||
---
|
---
|
||||||
@@ -180,7 +180,7 @@ ignore:
|
|||||||
- placeholder
|
- placeholder
|
||||||
props:
|
props:
|
||||||
loading: true
|
loading: true
|
||||||
loadingIcon: 'i-lucide-repeat-2'
|
loadingIcon: 'i-lucide-loader'
|
||||||
placeholder: 'Search...'
|
placeholder: 'Search...'
|
||||||
---
|
---
|
||||||
::
|
::
|
||||||
@@ -278,6 +278,16 @@ name: 'input-kbd-example'
|
|||||||
This example uses the `defineShortcuts` composable to focus the Input when the :kbd{value="/"} key is pressed.
|
This example uses the `defineShortcuts` composable to focus the Input when the :kbd{value="/"} key is pressed.
|
||||||
::
|
::
|
||||||
|
|
||||||
|
### With mask
|
||||||
|
|
||||||
|
There's no built-in support for masks, but you can use libraries like [maska](https://github.com/beholdr/maska) to mask the Input.
|
||||||
|
|
||||||
|
::component-example
|
||||||
|
---
|
||||||
|
name: 'input-mask-example'
|
||||||
|
---
|
||||||
|
::
|
||||||
|
|
||||||
### With floating label
|
### With floating label
|
||||||
|
|
||||||
You can use the `#default` slot to add a floating label to the Input.
|
You can use the `#default` slot to add a floating label to the Input.
|
||||||
|
|||||||
@@ -274,7 +274,7 @@ In this example, leveraging [`defineShortcuts`](/composables/define-shortcuts),
|
|||||||
This allows you to move the trigger outside of the Modal or remove it entirely.
|
This allows you to move the trigger outside of the Modal or remove it entirely.
|
||||||
::
|
::
|
||||||
|
|
||||||
### Prevent closing
|
### Disable dismissal
|
||||||
|
|
||||||
Set the `dismissible` prop to `false` to prevent the Modal from being closed when clicking outside of it or pressing escape. A `close:prevent` event will be emitted when the user tries to close it.
|
Set the `dismissible` prop to `false` to prevent the Modal from being closed when clicking outside of it or pressing escape. A `close:prevent` event will be emitted when the user tries to close it.
|
||||||
|
|
||||||
@@ -305,13 +305,13 @@ slots:
|
|||||||
|
|
||||||
### Programmatic usage
|
### Programmatic usage
|
||||||
|
|
||||||
You can use the [`useOverlay`](/composables/use-overlay) composable to open a Modal programatically.
|
You can use the [`useOverlay`](/composables/use-overlay) composable to open a Modal programmatically.
|
||||||
|
|
||||||
::warning
|
::warning
|
||||||
Make sure to wrap your app with the [`App`](/components/app) component which uses the [`OverlayProvider`](https://github.com/nuxt/ui/blob/v3/src/runtime/components/OverlayProvider.vue) component.
|
Make sure to wrap your app with the [`App`](/components/app) component which uses the [`OverlayProvider`](https://github.com/nuxt/ui/blob/v3/src/runtime/components/OverlayProvider.vue) component.
|
||||||
::
|
::
|
||||||
|
|
||||||
First, create a modal component that will be opened programatically:
|
First, create a modal component that will be opened programmatically:
|
||||||
|
|
||||||
::component-example
|
::component-example
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -23,16 +23,16 @@ Use the `items` prop as an array of objects with the following properties:
|
|||||||
- `badge?: string | number | BadgeProps`{lang="ts-type"}
|
- `badge?: string | number | BadgeProps`{lang="ts-type"}
|
||||||
- `tooltip?: TooltipProps`{lang="ts-type"}
|
- `tooltip?: TooltipProps`{lang="ts-type"}
|
||||||
- `trailingIcon?: string`{lang="ts-type"}
|
- `trailingIcon?: string`{lang="ts-type"}
|
||||||
- `type?: 'label' | 'link'`{lang="ts-type"}
|
- `type?: 'label' | 'trigger' | 'link'`{lang="ts-type"}
|
||||||
- `collapsible?: boolean`{lang="ts-type"}
|
|
||||||
- `defaultOpen?: boolean`{lang="ts-type"}
|
- `defaultOpen?: boolean`{lang="ts-type"}
|
||||||
- `open?: boolean`{lang="ts-type"}
|
- `open?: boolean`{lang="ts-type"}
|
||||||
- `value?: string`{lang="ts-type"}
|
- `value?: string`{lang="ts-type"}
|
||||||
- `disabled?: boolean`{lang="ts-type"}
|
- `disabled?: boolean`{lang="ts-type"}
|
||||||
- `class?: any`{lang="ts-type"}
|
|
||||||
- [`slot?: string`{lang="ts-type"}](#with-custom-slot)
|
- [`slot?: string`{lang="ts-type"}](#with-custom-slot)
|
||||||
- `onSelect?(e: Event): void`{lang="ts-type"}
|
- `onSelect?(e: Event): void`{lang="ts-type"}
|
||||||
- `children?: NavigationMenuChildItem[]`{lang="ts-type"}
|
- `children?: NavigationMenuChildItem[]`{lang="ts-type"}
|
||||||
|
- `class?: any`{lang="ts-type"}
|
||||||
|
- `ui?: { linkLeadingAvatarSize?: ClassNameValue, linkLeadingAvatar?: ClassNameValue, linkLeadingIcon?: ClassNameValue, linkLabel?: ClassNameValue, linkLabelExternalIcon?: ClassNameValue, linkTrailing?: ClassNameValue, linkTrailingBadgeSize?: ClassNameValue, linkTrailingBadge?: ClassNameValue, linkTrailingIcon?: ClassNameValue, label?: ClassNameValue, link?: ClassNameValue, content?: ClassNameValue, childList?: ClassNameValue, childLabel?: ClassNameValue, childItem?: ClassNameValue, childLink?: ClassNameValue, childLinkIcon?: ClassNameValue, childLinkWrapper?: ClassNameValue, childLinkLabel?: ClassNameValue, childLinkLabelExternalIcon?: ClassNameValue, childLinkDescription?: ClassNameValue }`{lang="ts-type"}
|
||||||
|
|
||||||
You can pass any property from the [Link](/components/link#props) component such as `to`, `target`, etc.
|
You can pass any property from the [Link](/components/link#props) component such as `to`, `target`, etc.
|
||||||
|
|
||||||
@@ -134,8 +134,8 @@ Each item can take a `children` array of objects with the following properties t
|
|||||||
- `label: string`
|
- `label: string`
|
||||||
- `description?: string`
|
- `description?: string`
|
||||||
- `icon?: string`
|
- `icon?: string`
|
||||||
- `class?: any`
|
|
||||||
- `onSelect?(e: Event): void`
|
- `onSelect?(e: Event): void`
|
||||||
|
- `class?: any`
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
@@ -144,7 +144,7 @@ Each item can take a `children` array of objects with the following properties t
|
|||||||
Use the `orientation` prop to change the orientation of the NavigationMenu.
|
Use the `orientation` prop to change the orientation of the NavigationMenu.
|
||||||
|
|
||||||
::note
|
::note
|
||||||
When orientation is `vertical`, a [Collapsible](/components/collapsible) component is used to display children. You can control the open state of each item using the `open` and `defaultOpen` properties. You can also use the `collapsible` property to control if the item is collapsible.
|
When orientation is `vertical`, an [Accordion](/components/accordion) component is used to display each group. You can control the open state of each item using the `open` and `defaultOpen` properties and change the behavior using the [`collapsible`](/components/accordion#collapsible) and [`type`](/components/accordion#multiple) props.
|
||||||
::
|
::
|
||||||
|
|
||||||
::component-code
|
::component-code
|
||||||
@@ -241,6 +241,113 @@ props:
|
|||||||
Groups will be spaced when orientation is `horizontal` and separated when orientation is `vertical`.
|
Groups will be spaced when orientation is `horizontal` and separated when orientation is `vertical`.
|
||||||
::
|
::
|
||||||
|
|
||||||
|
### Collapsed
|
||||||
|
|
||||||
|
In `vertical` orientation, use the `collapsed` prop to collapse the NavigationMenu, this can be useful in a sidebar for example.
|
||||||
|
|
||||||
|
::note
|
||||||
|
You can use the [`tooltip`](#with-tooltip-in-items) and [`popover`](#with-popover-in-items) props to display more information on the collapsed items.
|
||||||
|
::
|
||||||
|
|
||||||
|
::component-code
|
||||||
|
---
|
||||||
|
collapse: true
|
||||||
|
ignore:
|
||||||
|
- items
|
||||||
|
- orientation
|
||||||
|
- class
|
||||||
|
external:
|
||||||
|
- items
|
||||||
|
externalTypes:
|
||||||
|
- NavigationMenuItem[][]
|
||||||
|
items:
|
||||||
|
tooltip:
|
||||||
|
- true
|
||||||
|
- false
|
||||||
|
popover:
|
||||||
|
- true
|
||||||
|
- false
|
||||||
|
props:
|
||||||
|
collapsed: true
|
||||||
|
tooltip: false
|
||||||
|
popover: false
|
||||||
|
orientation: 'vertical'
|
||||||
|
items:
|
||||||
|
- - label: Links
|
||||||
|
type: 'label'
|
||||||
|
- label: Guide
|
||||||
|
icon: i-lucide-book-open
|
||||||
|
children:
|
||||||
|
- label: Introduction
|
||||||
|
description: Fully styled and customizable components for Nuxt.
|
||||||
|
icon: i-lucide-house
|
||||||
|
- label: Installation
|
||||||
|
description: Learn how to install and configure Nuxt UI in your application.
|
||||||
|
icon: i-lucide-cloud-download
|
||||||
|
- label: 'Icons'
|
||||||
|
icon: 'i-lucide-smile'
|
||||||
|
description: 'You have nothing to do, @nuxt/icon will handle it automatically.'
|
||||||
|
- label: 'Colors'
|
||||||
|
icon: 'i-lucide-swatch-book'
|
||||||
|
description: 'Choose a primary and a neutral color from your Tailwind CSS theme.'
|
||||||
|
- label: 'Theme'
|
||||||
|
icon: 'i-lucide-cog'
|
||||||
|
description: 'You can customize components by using the `class` / `ui` props or in your app.config.ts.'
|
||||||
|
- label: Composables
|
||||||
|
icon: i-lucide-database
|
||||||
|
children:
|
||||||
|
- label: defineShortcuts
|
||||||
|
icon: i-lucide-file-text
|
||||||
|
description: Define shortcuts for your application.
|
||||||
|
to: /composables/define-shortcuts
|
||||||
|
- label: useOverlay
|
||||||
|
icon: i-lucide-file-text
|
||||||
|
description: Display a modal/slideover within your application.
|
||||||
|
to: /composables/use-overlay
|
||||||
|
- label: useToast
|
||||||
|
icon: i-lucide-file-text
|
||||||
|
description: Display a toast within your application.
|
||||||
|
to: /composables/use-toast
|
||||||
|
- label: Components
|
||||||
|
icon: i-lucide-box
|
||||||
|
to: /components
|
||||||
|
active: true
|
||||||
|
children:
|
||||||
|
- label: Link
|
||||||
|
icon: i-lucide-file-text
|
||||||
|
description: Use NuxtLink with superpowers.
|
||||||
|
to: /components/link
|
||||||
|
- label: Modal
|
||||||
|
icon: i-lucide-file-text
|
||||||
|
description: Display a modal within your application.
|
||||||
|
to: /components/modal
|
||||||
|
- label: NavigationMenu
|
||||||
|
icon: i-lucide-file-text
|
||||||
|
description: Display a list of links.
|
||||||
|
to: /components/navigation-menu
|
||||||
|
- label: Pagination
|
||||||
|
icon: i-lucide-file-text
|
||||||
|
description: Display a list of pages.
|
||||||
|
to: /components/pagination
|
||||||
|
- label: Popover
|
||||||
|
icon: i-lucide-file-text
|
||||||
|
description: Display a non-modal dialog that floats around a trigger element.
|
||||||
|
to: /components/popover
|
||||||
|
- label: Progress
|
||||||
|
icon: i-lucide-file-text
|
||||||
|
description: Show a horizontal bar to indicate task progression.
|
||||||
|
to: /components/progress
|
||||||
|
- - label: GitHub
|
||||||
|
icon: i-simple-icons-github
|
||||||
|
badge: 3.8k
|
||||||
|
to: https://github.com/nuxt/ui
|
||||||
|
target: _blank
|
||||||
|
- label: Help
|
||||||
|
icon: i-lucide-circle-help
|
||||||
|
disabled: true
|
||||||
|
---
|
||||||
|
::
|
||||||
|
|
||||||
### Highlight
|
### Highlight
|
||||||
|
|
||||||
Use the `highlight` prop to display a highlighted border for the active item.
|
Use the `highlight` prop to display a highlighted border for the active item.
|
||||||
@@ -782,6 +889,222 @@ You can inspect the DOM to see each item's content being rendered.
|
|||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
|
### With tooltip in items :badge{label="New" class="align-text-top"}
|
||||||
|
|
||||||
|
When orientation is `vertical` and the menu is `collapsed`, you can set the `tooltip` prop to `true` to display a [Tooltip](/components/tooltip) around items with their label but you can also use the `tooltip` property on each item to override the default tooltip.
|
||||||
|
|
||||||
|
You can pass any property from the [Tooltip](/components/tooltip) component globally or on each item.
|
||||||
|
|
||||||
|
::component-code
|
||||||
|
---
|
||||||
|
collapse: true
|
||||||
|
ignore:
|
||||||
|
- items
|
||||||
|
- orientation
|
||||||
|
- class
|
||||||
|
external:
|
||||||
|
- items
|
||||||
|
externalTypes:
|
||||||
|
- NavigationMenuItem[][]
|
||||||
|
items:
|
||||||
|
tooltip:
|
||||||
|
- true
|
||||||
|
- false
|
||||||
|
props:
|
||||||
|
tooltip: true
|
||||||
|
collapsed: true
|
||||||
|
orientation: 'vertical'
|
||||||
|
items:
|
||||||
|
- - label: Links
|
||||||
|
type: 'label'
|
||||||
|
- label: Guide
|
||||||
|
icon: i-lucide-book-open
|
||||||
|
children:
|
||||||
|
- label: Introduction
|
||||||
|
description: Fully styled and customizable components for Nuxt.
|
||||||
|
icon: i-lucide-house
|
||||||
|
- label: Installation
|
||||||
|
description: Learn how to install and configure Nuxt UI in your application.
|
||||||
|
icon: i-lucide-cloud-download
|
||||||
|
- label: 'Icons'
|
||||||
|
icon: 'i-lucide-smile'
|
||||||
|
description: 'You have nothing to do, @nuxt/icon will handle it automatically.'
|
||||||
|
- label: 'Colors'
|
||||||
|
icon: 'i-lucide-swatch-book'
|
||||||
|
description: 'Choose a primary and a neutral color from your Tailwind CSS theme.'
|
||||||
|
- label: 'Theme'
|
||||||
|
icon: 'i-lucide-cog'
|
||||||
|
description: 'You can customize components by using the `class` / `ui` props or in your app.config.ts.'
|
||||||
|
- label: Composables
|
||||||
|
icon: i-lucide-database
|
||||||
|
children:
|
||||||
|
- label: defineShortcuts
|
||||||
|
icon: i-lucide-file-text
|
||||||
|
description: Define shortcuts for your application.
|
||||||
|
to: /composables/define-shortcuts
|
||||||
|
- label: useOverlay
|
||||||
|
icon: i-lucide-file-text
|
||||||
|
description: Display a modal/slideover within your application.
|
||||||
|
to: /composables/use-overlay
|
||||||
|
- label: useToast
|
||||||
|
icon: i-lucide-file-text
|
||||||
|
description: Display a toast within your application.
|
||||||
|
to: /composables/use-toast
|
||||||
|
- label: Components
|
||||||
|
icon: i-lucide-box
|
||||||
|
to: /components
|
||||||
|
active: true
|
||||||
|
children:
|
||||||
|
- label: Link
|
||||||
|
icon: i-lucide-file-text
|
||||||
|
description: Use NuxtLink with superpowers.
|
||||||
|
to: /components/link
|
||||||
|
- label: Modal
|
||||||
|
icon: i-lucide-file-text
|
||||||
|
description: Display a modal within your application.
|
||||||
|
to: /components/modal
|
||||||
|
- label: NavigationMenu
|
||||||
|
icon: i-lucide-file-text
|
||||||
|
description: Display a list of links.
|
||||||
|
to: /components/navigation-menu
|
||||||
|
- label: Pagination
|
||||||
|
icon: i-lucide-file-text
|
||||||
|
description: Display a list of pages.
|
||||||
|
to: /components/pagination
|
||||||
|
- label: Popover
|
||||||
|
icon: i-lucide-file-text
|
||||||
|
description: Display a non-modal dialog that floats around a trigger element.
|
||||||
|
to: /components/popover
|
||||||
|
- label: Progress
|
||||||
|
icon: i-lucide-file-text
|
||||||
|
description: Show a horizontal bar to indicate task progression.
|
||||||
|
to: /components/progress
|
||||||
|
- - label: GitHub
|
||||||
|
icon: i-simple-icons-github
|
||||||
|
badge: 3.8k
|
||||||
|
to: https://github.com/nuxt/ui
|
||||||
|
target: _blank
|
||||||
|
tooltip:
|
||||||
|
text: 'Open on GitHub'
|
||||||
|
kbds:
|
||||||
|
- 3.8k
|
||||||
|
- label: Help
|
||||||
|
icon: i-lucide-circle-help
|
||||||
|
disabled: true
|
||||||
|
---
|
||||||
|
::
|
||||||
|
|
||||||
|
### With popover in items :badge{label="New" class="align-text-top"}
|
||||||
|
|
||||||
|
When orientation is `vertical` and the menu is `collapsed`, you can set the `popover` prop to `true` to display a [Popover](/components/popover) around items with their children but you can also use the `popover` property on each item to override the default popover.
|
||||||
|
|
||||||
|
You can pass any property from the [Popover](/components/popover) component globally or on each item.
|
||||||
|
|
||||||
|
::component-code
|
||||||
|
---
|
||||||
|
collapse: true
|
||||||
|
ignore:
|
||||||
|
- items
|
||||||
|
- orientation
|
||||||
|
- class
|
||||||
|
external:
|
||||||
|
- items
|
||||||
|
externalTypes:
|
||||||
|
- NavigationMenuItem[][]
|
||||||
|
items:
|
||||||
|
popover:
|
||||||
|
- true
|
||||||
|
- false
|
||||||
|
props:
|
||||||
|
popover: true
|
||||||
|
collapsed: true
|
||||||
|
orientation: 'vertical'
|
||||||
|
items:
|
||||||
|
- - label: Links
|
||||||
|
type: 'label'
|
||||||
|
- label: Guide
|
||||||
|
icon: i-lucide-book-open
|
||||||
|
children:
|
||||||
|
- label: Introduction
|
||||||
|
description: Fully styled and customizable components for Nuxt.
|
||||||
|
icon: i-lucide-house
|
||||||
|
- label: Installation
|
||||||
|
description: Learn how to install and configure Nuxt UI in your application.
|
||||||
|
icon: i-lucide-cloud-download
|
||||||
|
- label: 'Icons'
|
||||||
|
icon: 'i-lucide-smile'
|
||||||
|
description: 'You have nothing to do, @nuxt/icon will handle it automatically.'
|
||||||
|
- label: 'Colors'
|
||||||
|
icon: 'i-lucide-swatch-book'
|
||||||
|
description: 'Choose a primary and a neutral color from your Tailwind CSS theme.'
|
||||||
|
- label: 'Theme'
|
||||||
|
icon: 'i-lucide-cog'
|
||||||
|
description: 'You can customize components by using the `class` / `ui` props or in your app.config.ts.'
|
||||||
|
- label: Composables
|
||||||
|
icon: i-lucide-database
|
||||||
|
popover:
|
||||||
|
mode: 'click'
|
||||||
|
children:
|
||||||
|
- label: defineShortcuts
|
||||||
|
icon: i-lucide-file-text
|
||||||
|
description: Define shortcuts for your application.
|
||||||
|
to: /composables/define-shortcuts
|
||||||
|
- label: useOverlay
|
||||||
|
icon: i-lucide-file-text
|
||||||
|
description: Display a modal/slideover within your application.
|
||||||
|
to: /composables/use-overlay
|
||||||
|
- label: useToast
|
||||||
|
icon: i-lucide-file-text
|
||||||
|
description: Display a toast within your application.
|
||||||
|
to: /composables/use-toast
|
||||||
|
- label: Components
|
||||||
|
icon: i-lucide-box
|
||||||
|
to: /components
|
||||||
|
active: true
|
||||||
|
children:
|
||||||
|
- label: Link
|
||||||
|
icon: i-lucide-file-text
|
||||||
|
description: Use NuxtLink with superpowers.
|
||||||
|
to: /components/link
|
||||||
|
- label: Modal
|
||||||
|
icon: i-lucide-file-text
|
||||||
|
description: Display a modal within your application.
|
||||||
|
to: /components/modal
|
||||||
|
- label: NavigationMenu
|
||||||
|
icon: i-lucide-file-text
|
||||||
|
description: Display a list of links.
|
||||||
|
to: /components/navigation-menu
|
||||||
|
- label: Pagination
|
||||||
|
icon: i-lucide-file-text
|
||||||
|
description: Display a list of pages.
|
||||||
|
to: /components/pagination
|
||||||
|
- label: Popover
|
||||||
|
icon: i-lucide-file-text
|
||||||
|
description: Display a non-modal dialog that floats around a trigger element.
|
||||||
|
to: /components/popover
|
||||||
|
- label: Progress
|
||||||
|
icon: i-lucide-file-text
|
||||||
|
description: Show a horizontal bar to indicate task progression.
|
||||||
|
to: /components/progress
|
||||||
|
- - label: GitHub
|
||||||
|
icon: i-simple-icons-github
|
||||||
|
badge: 3.8k
|
||||||
|
to: https://github.com/nuxt/ui
|
||||||
|
target: _blank
|
||||||
|
tooltip:
|
||||||
|
text: 'Open on GitHub'
|
||||||
|
kbds:
|
||||||
|
- 3.8k
|
||||||
|
- label: Help
|
||||||
|
icon: i-lucide-circle-help
|
||||||
|
disabled: true
|
||||||
|
---
|
||||||
|
::
|
||||||
|
|
||||||
|
::tip{to="#with-content-slot"}
|
||||||
|
You can use the `#content` slot to customize the content of the popover in the `vertical` orientation.
|
||||||
|
::
|
||||||
|
|
||||||
### Control active item
|
### Control active item
|
||||||
|
|
||||||
You can control the active item by using the `default-value` prop or the `v-model` directive with the index of the item.
|
You can control the active item by using the `default-value` prop or the `v-model` directive with the index of the item.
|
||||||
@@ -829,6 +1152,7 @@ Use the `#item-content` slot or the `slot` property (`#{{ item.slot }}-content`)
|
|||||||
|
|
||||||
::component-example
|
::component-example
|
||||||
---
|
---
|
||||||
|
collapse: true
|
||||||
name: 'navigation-menu-content-slot-example'
|
name: 'navigation-menu-content-slot-example'
|
||||||
---
|
---
|
||||||
::
|
::
|
||||||
|
|||||||
@@ -180,6 +180,8 @@ props:
|
|||||||
|
|
||||||
:component-emits
|
:component-emits
|
||||||
|
|
||||||
|
### Expose
|
||||||
|
|
||||||
When accessing the component via a template ref, you can use the following:
|
When accessing the component via a template ref, you can use the following:
|
||||||
|
|
||||||
| Name | Type |
|
| Name | Type |
|
||||||
|
|||||||
@@ -181,7 +181,7 @@ name: 'popover-open-example'
|
|||||||
In this example, leveraging [`defineShortcuts`](/composables/define-shortcuts), you can toggle the Popover by pressing :kbd{value="O"}.
|
In this example, leveraging [`defineShortcuts`](/composables/define-shortcuts), you can toggle the Popover by pressing :kbd{value="O"}.
|
||||||
::
|
::
|
||||||
|
|
||||||
### Prevent closing
|
### Disable dismissal
|
||||||
|
|
||||||
Set the `dismissible` prop to `false` to prevent the Popover from being closed when clicking outside of it or pressing escape. A `close:prevent` event will be emitted when the user tries to close it.
|
Set the `dismissible` prop to `false` to prevent the Popover from being closed when clicking outside of it or pressing escape. A `close:prevent` event will be emitted when the user tries to close it.
|
||||||
|
|
||||||
@@ -202,6 +202,21 @@ name: 'popover-command-palette-example'
|
|||||||
---
|
---
|
||||||
::
|
::
|
||||||
|
|
||||||
|
### With anchor slot :badge{label="New" class="align-text-top"}
|
||||||
|
|
||||||
|
You can use the `#anchor` slot to position the Popover against a custom element.
|
||||||
|
|
||||||
|
::warning
|
||||||
|
This slot only works when `mode` is `click`.
|
||||||
|
::
|
||||||
|
|
||||||
|
::component-example
|
||||||
|
---
|
||||||
|
collapse: true
|
||||||
|
name: 'popover-anchor-slot-example'
|
||||||
|
---
|
||||||
|
::
|
||||||
|
|
||||||
## API
|
## API
|
||||||
|
|
||||||
### Props
|
### Props
|
||||||
|
|||||||
@@ -46,6 +46,8 @@ You can also pass an array of objects with the following properties:
|
|||||||
- `description?: string`{lang="ts-type"}
|
- `description?: string`{lang="ts-type"}
|
||||||
- [`value?: string`{lang="ts-type"}](#value-key)
|
- [`value?: string`{lang="ts-type"}](#value-key)
|
||||||
- `disabled?: boolean`{lang="ts-type"}
|
- `disabled?: boolean`{lang="ts-type"}
|
||||||
|
- `class?: any`{lang="ts-type"}
|
||||||
|
- `ui?: { item?: ClassNameValue, container?: ClassNameValue, base?: ClassNameValue, 'indicator'?: ClassNameValue, wrapper?: ClassNameValue, label?: ClassNameValue, description?: ClassNameValue }`{lang="ts-type"}
|
||||||
|
|
||||||
::component-code
|
::component-code
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -57,6 +57,8 @@ You can also pass an array of objects with the following properties:
|
|||||||
- [`chip?: ChipProps`{lang="ts-type"}](#with-chip-in-items)
|
- [`chip?: ChipProps`{lang="ts-type"}](#with-chip-in-items)
|
||||||
- `disabled?: boolean`{lang="ts-type"}
|
- `disabled?: boolean`{lang="ts-type"}
|
||||||
- `onSelect?(e: Event): void`{lang="ts-type"}
|
- `onSelect?(e: Event): void`{lang="ts-type"}
|
||||||
|
- `class?: any`{lang="ts-type"}
|
||||||
|
- `ui?: { label?: ClassNameValue, separator?: ClassNameValue, item?: ClassNameValue, itemLeadingIcon?: ClassNameValue, itemLeadingAvatarSize?: ClassNameValue, itemLeadingAvatar?: ClassNameValue, itemLeadingChipSize?: ClassNameValue, itemLeadingChip?: ClassNameValue, itemLabel?: ClassNameValue, itemTrailing?: ClassNameValue, itemTrailingIcon?: ClassNameValue }`{lang="ts-type"}
|
||||||
|
|
||||||
::component-code
|
::component-code
|
||||||
---
|
---
|
||||||
@@ -553,7 +555,7 @@ props:
|
|||||||
|
|
||||||
### Loading Icon
|
### Loading Icon
|
||||||
|
|
||||||
Use the `loading-icon` prop to customize the loading icon. Defaults to `i-lucide-refresh-cw`.
|
Use the `loading-icon` prop to customize the loading icon. Defaults to `i-lucide-loader-circle`.
|
||||||
|
|
||||||
::component-code
|
::component-code
|
||||||
---
|
---
|
||||||
@@ -568,7 +570,7 @@ external:
|
|||||||
props:
|
props:
|
||||||
modelValue: 'Backlog'
|
modelValue: 'Backlog'
|
||||||
loading: true
|
loading: true
|
||||||
loadingIcon: 'i-lucide-repeat-2'
|
loadingIcon: 'i-lucide-loader'
|
||||||
items:
|
items:
|
||||||
- Backlog
|
- Backlog
|
||||||
- Todo
|
- Todo
|
||||||
@@ -653,7 +655,7 @@ props:
|
|||||||
---
|
---
|
||||||
::
|
::
|
||||||
|
|
||||||
### With icons in items
|
### With icon in items
|
||||||
|
|
||||||
You can use the `icon` property to display an [Icon](/components/icon) inside the items.
|
You can use the `icon` property to display an [Icon](/components/icon) inside the items.
|
||||||
|
|
||||||
@@ -813,6 +815,14 @@ name: 'select-menu-countries-example'
|
|||||||
|
|
||||||
:component-emits
|
:component-emits
|
||||||
|
|
||||||
|
### Expose
|
||||||
|
|
||||||
|
When accessing the component via a template ref, you can use the following:
|
||||||
|
|
||||||
|
| Name | Type |
|
||||||
|
| ---- | ---- |
|
||||||
|
| `triggerRef`{lang="ts-type"} | `Ref<InstanceType<typeof ComboboxTrigger> \| null>`{lang="ts-type"} |
|
||||||
|
|
||||||
## Theme
|
## Theme
|
||||||
|
|
||||||
:component-theme
|
:component-theme
|
||||||
|
|||||||
@@ -48,6 +48,8 @@ You can also pass an array of objects with the following properties:
|
|||||||
- [`avatar?: AvatarProps`{lang="ts-type"}](#with-avatar-in-items)
|
- [`avatar?: AvatarProps`{lang="ts-type"}](#with-avatar-in-items)
|
||||||
- [`chip?: ChipProps`{lang="ts-type"}](#with-chip-in-items)
|
- [`chip?: ChipProps`{lang="ts-type"}](#with-chip-in-items)
|
||||||
- `disabled?: boolean`{lang="ts-type"}
|
- `disabled?: boolean`{lang="ts-type"}
|
||||||
|
- `class?: any`{lang="ts-type"}
|
||||||
|
- `ui?: { label?: ClassNameValue, separator?: ClassNameValue, item?: ClassNameValue, itemLeadingIcon?: ClassNameValue, itemLeadingAvatarSize?: ClassNameValue, itemLeadingAvatar?: ClassNameValue, itemLeadingChipSize?: ClassNameValue, itemLeadingChip?: ClassNameValue, itemLabel?: ClassNameValue, itemTrailing?: ClassNameValue, itemTrailingIcon?: ClassNameValue }`{lang="ts-type"}
|
||||||
|
|
||||||
::component-code
|
::component-code
|
||||||
---
|
---
|
||||||
@@ -505,7 +507,7 @@ props:
|
|||||||
|
|
||||||
### Loading Icon
|
### Loading Icon
|
||||||
|
|
||||||
Use the `loading-icon` prop to customize the loading icon. Defaults to `i-lucide-refresh-cw`.
|
Use the `loading-icon` prop to customize the loading icon. Defaults to `i-lucide-loader-circle`.
|
||||||
|
|
||||||
::component-code
|
::component-code
|
||||||
---
|
---
|
||||||
@@ -520,7 +522,7 @@ external:
|
|||||||
props:
|
props:
|
||||||
modelValue: 'Backlog'
|
modelValue: 'Backlog'
|
||||||
loading: true
|
loading: true
|
||||||
loadingIcon: 'i-lucide-repeat-2'
|
loadingIcon: 'i-lucide-loader'
|
||||||
items:
|
items:
|
||||||
- Backlog
|
- Backlog
|
||||||
- Todo
|
- Todo
|
||||||
@@ -605,7 +607,7 @@ props:
|
|||||||
---
|
---
|
||||||
::
|
::
|
||||||
|
|
||||||
### With icons in items
|
### With icon in items
|
||||||
|
|
||||||
You can use the `icon` property to display an [Icon](/components/icon) inside the items.
|
You can use the `icon` property to display an [Icon](/components/icon) inside the items.
|
||||||
|
|
||||||
@@ -707,6 +709,14 @@ collapse: true
|
|||||||
|
|
||||||
:component-emits
|
:component-emits
|
||||||
|
|
||||||
|
### Expose
|
||||||
|
|
||||||
|
When accessing the component via a template ref, you can use the following:
|
||||||
|
|
||||||
|
| Name | Type |
|
||||||
|
| ---- | ---- |
|
||||||
|
| `triggerRef`{lang="ts-type"} | `Ref<InstanceType<typeof SelectTrigger> \| null>`{lang="ts-type"} |
|
||||||
|
|
||||||
## Theme
|
## Theme
|
||||||
|
|
||||||
:component-theme
|
:component-theme
|
||||||
|
|||||||
@@ -273,7 +273,7 @@ In this example, leveraging [`defineShortcuts`](/composables/define-shortcuts),
|
|||||||
This allows you to move the trigger outside of the Slideover or remove it entirely.
|
This allows you to move the trigger outside of the Slideover or remove it entirely.
|
||||||
::
|
::
|
||||||
|
|
||||||
### Prevent closing
|
### Disable dismissal
|
||||||
|
|
||||||
Set the `dismissible` prop to `false` to prevent the Slideover from being closed when clicking outside of it or pressing escape. A `close:prevent` event will be emitted when the user tries to close it.
|
Set the `dismissible` prop to `false` to prevent the Slideover from being closed when clicking outside of it or pressing escape. A `close:prevent` event will be emitted when the user tries to close it.
|
||||||
|
|
||||||
@@ -304,13 +304,13 @@ slots:
|
|||||||
|
|
||||||
### Programmatic usage
|
### Programmatic usage
|
||||||
|
|
||||||
You can use the [`useOverlay`](/composables/use-overlay) composable to open a Slideover programatically.
|
You can use the [`useOverlay`](/composables/use-overlay) composable to open a Slideover programmatically.
|
||||||
|
|
||||||
::warning
|
::warning
|
||||||
Make sure to wrap your app with the [`App`](/components/app) component which uses the [`OverlayProvider`](https://github.com/nuxt/ui/blob/v3/src/runtime/components/OverlayProvider.vue) component.
|
Make sure to wrap your app with the [`App`](/components/app) component which uses the [`OverlayProvider`](https://github.com/nuxt/ui/blob/v3/src/runtime/components/OverlayProvider.vue) component.
|
||||||
::
|
::
|
||||||
|
|
||||||
First, create a slideover component that will be opened programatically:
|
First, create a slideover component that will be opened programmatically:
|
||||||
|
|
||||||
::component-example
|
::component-example
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ props:
|
|||||||
---
|
---
|
||||||
::
|
::
|
||||||
|
|
||||||
### Tooltip :badge{label="Soon" class="align-text-top"}
|
### Tooltip :badge{label="New" class="align-text-top"}
|
||||||
|
|
||||||
Use the `tooltip` prop to display a [Tooltip](/components/tooltip) around the Slider thumbs with the current value. You can set it to `true` for default behavior or pass an object to customize it with any property from the [Tooltip](/components/tooltip#props) component.
|
Use the `tooltip` prop to display a [Tooltip](/components/tooltip) around the Slider thumbs with the current value. You can set it to `true` for default behavior or pass an object to customize it with any property from the [Tooltip](/components/tooltip#props) component.
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ Use the `items` prop as an array of objects with the following properties:
|
|||||||
- `value?: string | number`{lang="ts-type"}
|
- `value?: string | number`{lang="ts-type"}
|
||||||
- `disabled?: boolean`{lang="ts-type"}
|
- `disabled?: boolean`{lang="ts-type"}
|
||||||
- [`slot?: string`{lang="ts-type"}](#with-custom-slot)
|
- [`slot?: string`{lang="ts-type"}](#with-custom-slot)
|
||||||
|
- `class?: any`{lang="ts-type"}
|
||||||
|
- `ui?: { item?: ClassNameValue, container?: ClassNameValue, trigger?: ClassNameValue, indicator?: ClassNameValue, icon?: ClassNameValue, separator?: ClassNameValue, wrapper?: ClassNameValue, title?: ClassNameValue, description?: ClassNameValue }`{lang="ts-type"}
|
||||||
|
|
||||||
::component-code
|
::component-code
|
||||||
---
|
---
|
||||||
@@ -198,6 +200,10 @@ Use the `#content` slot to customize the content of each item.
|
|||||||
|
|
||||||
Use the `slot` property to customize a specific item.
|
Use the `slot` property to customize a specific item.
|
||||||
|
|
||||||
|
You will have access to the following slots:
|
||||||
|
|
||||||
|
- `#{{ item.slot }}`{lang="ts-type"}
|
||||||
|
|
||||||
:component-example{name="stepper-custom-slot-example"}
|
:component-example{name="stepper-custom-slot-example"}
|
||||||
|
|
||||||
## API
|
## API
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ props:
|
|||||||
|
|
||||||
### Loading Icon
|
### Loading Icon
|
||||||
|
|
||||||
Use the `loading-icon` prop to customize the loading icon. Defaults to `i-lucide-refresh-cw`.
|
Use the `loading-icon` prop to customize the loading icon. Defaults to `i-lucide-loader-circle`.
|
||||||
|
|
||||||
::component-code
|
::component-code
|
||||||
---
|
---
|
||||||
@@ -118,7 +118,7 @@ ignore:
|
|||||||
- defaultValue
|
- defaultValue
|
||||||
props:
|
props:
|
||||||
loading: true
|
loading: true
|
||||||
loadingIcon: 'i-lucide-repeat-2'
|
loadingIcon: 'i-lucide-loader'
|
||||||
defaultValue: true
|
defaultValue: true
|
||||||
label: Check me
|
label: Check me
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ Use the `items` prop as an array of objects with the following properties:
|
|||||||
- `value?: string | number`{lang="ts-type"}
|
- `value?: string | number`{lang="ts-type"}
|
||||||
- `disabled?: boolean`{lang="ts-type"}
|
- `disabled?: boolean`{lang="ts-type"}
|
||||||
- [`slot?: string`{lang="ts-type"}](#with-custom-slot)
|
- [`slot?: string`{lang="ts-type"}](#with-custom-slot)
|
||||||
|
- `class?: any`{lang="ts-type"}
|
||||||
|
- `ui?: { trigger?: ClassNameValue, leadingIcon?: ClassNameValue, leadingAvatar?: ClassNameValue, label?: ClassNameValue, content?: ClassNameValue }`{lang="ts-type"}
|
||||||
|
|
||||||
::component-code
|
::component-code
|
||||||
---
|
---
|
||||||
@@ -220,6 +222,10 @@ Use the `#content` slot to customize the content of each item.
|
|||||||
|
|
||||||
Use the `slot` property to customize a specific item.
|
Use the `slot` property to customize a specific item.
|
||||||
|
|
||||||
|
You will have access to the following slots:
|
||||||
|
|
||||||
|
- `#{{ item.slot }}`{lang="ts-type"}
|
||||||
|
|
||||||
:component-example{name="tabs-custom-slot-example"}
|
:component-example{name="tabs-custom-slot-example"}
|
||||||
|
|
||||||
## API
|
## API
|
||||||
@@ -236,6 +242,14 @@ Use the `slot` property to customize a specific item.
|
|||||||
|
|
||||||
:component-emits
|
:component-emits
|
||||||
|
|
||||||
|
### Expose
|
||||||
|
|
||||||
|
When accessing the component via a template ref, you can use the following:
|
||||||
|
|
||||||
|
| Name | Type |
|
||||||
|
| ---- | ---- |
|
||||||
|
| `triggersRef`{lang="ts-type"} | `Ref<ComponentPublicInstance[]>`{lang="ts-type"} |
|
||||||
|
|
||||||
## Theme
|
## Theme
|
||||||
|
|
||||||
:component-theme
|
:component-theme
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ props:
|
|||||||
|
|
||||||
### Loading Icon :badge{label="New" class="align-text-top"}
|
### Loading Icon :badge{label="New" class="align-text-top"}
|
||||||
|
|
||||||
Use the `loading-icon` prop to customize the loading icon. Defaults to `i-lucide-refresh-cw`.
|
Use the `loading-icon` prop to customize the loading icon. Defaults to `i-lucide-loader-circle`.
|
||||||
|
|
||||||
::component-code
|
::component-code
|
||||||
---
|
---
|
||||||
@@ -202,7 +202,7 @@ ignore:
|
|||||||
- placeholder
|
- placeholder
|
||||||
props:
|
props:
|
||||||
loading: true
|
loading: true
|
||||||
loadingIcon: 'i-lucide-repeat-2'
|
loadingIcon: 'i-lucide-loader'
|
||||||
placeholder: 'Search...'
|
placeholder: 'Search...'
|
||||||
rows: 1
|
rows: 1
|
||||||
---
|
---
|
||||||
|
|||||||
269
docs/content/3.components/timeline.md
Normal file
@@ -0,0 +1,269 @@
|
|||||||
|
---
|
||||||
|
title: Timeline
|
||||||
|
description: 'A component that displays a sequence of events with dates, titles, icons or avatars.'
|
||||||
|
category: data
|
||||||
|
links:
|
||||||
|
- label: GitHub
|
||||||
|
icon: i-simple-icons-github
|
||||||
|
to: https://github.com/nuxt/ui/tree/v3/src/runtime/components/Timeline.vue
|
||||||
|
navigation.badge: Soon
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Items
|
||||||
|
|
||||||
|
Use the `items` prop as an array of objects with the following properties:
|
||||||
|
|
||||||
|
- `date?: string`{lang="ts-type"}
|
||||||
|
- `title?: string`{lang="ts-type"}
|
||||||
|
- `description?: AvatarProps`{lang="ts-type"}
|
||||||
|
- `icon?: string`{lang="ts-type"}
|
||||||
|
- `avatar?: AvatarProps`{lang="ts-type"}
|
||||||
|
- `value?: string | number`{lang="ts-type"}
|
||||||
|
- [`slot?: string`{lang="ts-type"}](#with-custom-slot)
|
||||||
|
- `class?: any`{lang="ts-type"}
|
||||||
|
- `ui?: { item?: ClassNameValue, container?: ClassNameValue, indicator?: ClassNameValue, separator?: ClassNameValue, wrapper?: ClassNameValue, date?: ClassNameValue, title?: ClassNameValue, description?: ClassNameValue }`{lang="ts-type"}
|
||||||
|
|
||||||
|
::component-code
|
||||||
|
---
|
||||||
|
ignore:
|
||||||
|
- items
|
||||||
|
- class
|
||||||
|
- defaultValue
|
||||||
|
external:
|
||||||
|
- items
|
||||||
|
externalTypes:
|
||||||
|
- TimelineItem[]
|
||||||
|
props:
|
||||||
|
defaultValue: 2
|
||||||
|
items:
|
||||||
|
- date: 'Mar 15, 2025'
|
||||||
|
title: 'Project Kickoff'
|
||||||
|
description: 'Kicked off the project with team alignment. Set up project milestones and allocated resources.'
|
||||||
|
icon: 'i-lucide-rocket'
|
||||||
|
- date: 'Mar 22 2025'
|
||||||
|
title: 'Design Phase'
|
||||||
|
description: 'User research and design workshops. Created wireframes and prototypes for user testing.'
|
||||||
|
icon: 'i-lucide-palette'
|
||||||
|
- date: 'Mar 29 2025'
|
||||||
|
title: 'Development Sprint'
|
||||||
|
description: 'Frontend and backend development. Implemented core features and integrated with APIs.'
|
||||||
|
icon: 'i-lucide-code'
|
||||||
|
- date: 'Apr 5 2025'
|
||||||
|
title: 'Testing & Deployment'
|
||||||
|
description: 'QA testing and performance optimization. Deployed the application to production.'
|
||||||
|
icon: 'i-lucide-check-circle'
|
||||||
|
class: 'w-96'
|
||||||
|
---
|
||||||
|
::
|
||||||
|
|
||||||
|
### Color
|
||||||
|
|
||||||
|
Use the `color` prop to change the color of the active items in a Timeline.
|
||||||
|
|
||||||
|
::component-code
|
||||||
|
---
|
||||||
|
ignore:
|
||||||
|
- items
|
||||||
|
- class
|
||||||
|
- defaultValue
|
||||||
|
external:
|
||||||
|
- items
|
||||||
|
externalTypes:
|
||||||
|
- TimelineItem[]
|
||||||
|
props:
|
||||||
|
color: neutral
|
||||||
|
defaultValue: 2
|
||||||
|
items:
|
||||||
|
- date: 'Mar 15, 2025'
|
||||||
|
title: 'Project Kickoff'
|
||||||
|
description: 'Kicked off the project with team alignment. Set up project milestones and allocated resources.'
|
||||||
|
icon: 'i-lucide-rocket'
|
||||||
|
- date: 'Mar 22 2025'
|
||||||
|
title: 'Design Phase'
|
||||||
|
description: 'User research and design workshops. Created wireframes and prototypes for user testing.'
|
||||||
|
icon: 'i-lucide-palette'
|
||||||
|
- date: 'Mar 29 2025'
|
||||||
|
title: 'Development Sprint'
|
||||||
|
description: 'Frontend and backend development. Implemented core features and integrated with APIs.'
|
||||||
|
icon: 'i-lucide-code'
|
||||||
|
- date: 'Apr 5 2025'
|
||||||
|
title: 'Testing & Deployment'
|
||||||
|
description: 'QA testing and performance optimization. Deployed the application to production.'
|
||||||
|
icon: 'i-lucide-check-circle'
|
||||||
|
class: 'w-96'
|
||||||
|
---
|
||||||
|
::
|
||||||
|
|
||||||
|
### Size
|
||||||
|
|
||||||
|
Use the `size` prop to change the size of the Timeline.
|
||||||
|
|
||||||
|
::component-code
|
||||||
|
---
|
||||||
|
ignore:
|
||||||
|
- items
|
||||||
|
- class
|
||||||
|
- defaultValue
|
||||||
|
external:
|
||||||
|
- items
|
||||||
|
externalTypes:
|
||||||
|
- TimelineItem[]
|
||||||
|
props:
|
||||||
|
size: xs
|
||||||
|
defaultValue: 2
|
||||||
|
items:
|
||||||
|
- date: 'Mar 15, 2025'
|
||||||
|
title: 'Project Kickoff'
|
||||||
|
description: 'Kicked off the project with team alignment. Set up project milestones and allocated resources.'
|
||||||
|
icon: 'i-lucide-rocket'
|
||||||
|
- date: 'Mar 22 2025'
|
||||||
|
title: 'Design Phase'
|
||||||
|
description: 'User research and design workshops. Created wireframes and prototypes for user testing.'
|
||||||
|
icon: 'i-lucide-palette'
|
||||||
|
- date: 'Mar 29 2025'
|
||||||
|
title: 'Development Sprint'
|
||||||
|
description: 'Frontend and backend development. Implemented core features and integrated with APIs.'
|
||||||
|
icon: 'i-lucide-code'
|
||||||
|
- date: 'Apr 5 2025'
|
||||||
|
title: 'Testing & Deployment'
|
||||||
|
description: 'QA testing and performance optimization. Deployed the application to production.'
|
||||||
|
icon: 'i-lucide-check-circle'
|
||||||
|
class: 'w-96'
|
||||||
|
---
|
||||||
|
::
|
||||||
|
|
||||||
|
### Orientation
|
||||||
|
|
||||||
|
Use the `orientation` prop to change the orientation of the Timeline. Defaults to `vertical`.
|
||||||
|
|
||||||
|
::component-code
|
||||||
|
---
|
||||||
|
ignore:
|
||||||
|
- items
|
||||||
|
- class
|
||||||
|
- defaultValue
|
||||||
|
external:
|
||||||
|
- items
|
||||||
|
externalTypes:
|
||||||
|
- TimelineItem[]
|
||||||
|
props:
|
||||||
|
orientation: 'horizontal'
|
||||||
|
defaultValue: 2
|
||||||
|
items:
|
||||||
|
- date: 'Mar 15, 2025'
|
||||||
|
title: 'Project Kickoff'
|
||||||
|
description: 'Kicked off the project with team alignment.'
|
||||||
|
icon: 'i-lucide-rocket'
|
||||||
|
- date: 'Mar 22 2025'
|
||||||
|
title: 'Design Phase'
|
||||||
|
description: 'User research and design workshops.'
|
||||||
|
icon: 'i-lucide-palette'
|
||||||
|
- date: 'Mar 29 2025'
|
||||||
|
title: 'Development Sprint'
|
||||||
|
description: 'Frontend and backend development.'
|
||||||
|
icon: 'i-lucide-code'
|
||||||
|
- date: 'Apr 5 2025'
|
||||||
|
title: 'Testing & Deployment'
|
||||||
|
description: 'QA testing and performance optimization.'
|
||||||
|
icon: 'i-lucide-check-circle'
|
||||||
|
class: 'w-full'
|
||||||
|
class: 'overflow-x-auto'
|
||||||
|
---
|
||||||
|
::
|
||||||
|
|
||||||
|
### Reverse
|
||||||
|
|
||||||
|
Use the reverse prop to reverse the direction of the Timeline.
|
||||||
|
|
||||||
|
::component-code
|
||||||
|
---
|
||||||
|
ignore:
|
||||||
|
- items
|
||||||
|
- class
|
||||||
|
- defaultValue
|
||||||
|
external:
|
||||||
|
- items
|
||||||
|
externalTypes:
|
||||||
|
- TimelineItem[]
|
||||||
|
props:
|
||||||
|
reverse: true
|
||||||
|
modelValue: 2
|
||||||
|
orientation: 'vertical'
|
||||||
|
items:
|
||||||
|
- date: 'Mar 15, 2025'
|
||||||
|
title: 'Project Kickoff'
|
||||||
|
description: 'Kicked off the project with team alignment.'
|
||||||
|
icon: 'i-lucide-rocket'
|
||||||
|
- date: 'Mar 22 2025'
|
||||||
|
title: 'Design Phase'
|
||||||
|
description: 'User research and design workshops.'
|
||||||
|
icon: 'i-lucide-palette'
|
||||||
|
- date: 'Mar 29 2025'
|
||||||
|
title: 'Development Sprint'
|
||||||
|
description: 'Frontend and backend development.'
|
||||||
|
icon: 'i-lucide-code'
|
||||||
|
- date: 'Apr 5 2025'
|
||||||
|
title: 'Testing & Deployment'
|
||||||
|
description: 'QA testing and performance optimization.'
|
||||||
|
icon: 'i-lucide-check-circle'
|
||||||
|
class: 'w-full'
|
||||||
|
class: 'overflow-x-auto'
|
||||||
|
---
|
||||||
|
::
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Control active item
|
||||||
|
|
||||||
|
You can control the active item by using the `default-value` prop or the `v-model` directive with the index of the item.
|
||||||
|
|
||||||
|
:component-example{name="timeline-model-value-example" prettier}
|
||||||
|
|
||||||
|
::tip
|
||||||
|
You can also pass the `value` of one of the items if provided.
|
||||||
|
::
|
||||||
|
|
||||||
|
### With alternating layout
|
||||||
|
|
||||||
|
Use the `ui` prop to create a Timeline with alternating layout.
|
||||||
|
|
||||||
|
:component-example{name="timeline-alternating-layout-example" prettier}
|
||||||
|
|
||||||
|
### With custom slot
|
||||||
|
|
||||||
|
Use the `slot` property to customize a specific item.
|
||||||
|
|
||||||
|
You will have access to the following slots:
|
||||||
|
|
||||||
|
- `#{{ item.slot }}-indicator`{lang="ts-type"}
|
||||||
|
- `#{{ item.slot }}-date`{lang="ts-type"}
|
||||||
|
- `#{{ item.slot }}-title`{lang="ts-type"}
|
||||||
|
- `#{{ item.slot }}-description`{lang="ts-type"}
|
||||||
|
|
||||||
|
:component-example{name="timeline-custom-slot-example" prettier}
|
||||||
|
|
||||||
|
### With slots
|
||||||
|
|
||||||
|
Use the available slots to create a more complex Timeline.
|
||||||
|
|
||||||
|
:component-example{name="timeline-slots-example" prettier}
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
### Props
|
||||||
|
|
||||||
|
:component-props
|
||||||
|
|
||||||
|
### Slots
|
||||||
|
|
||||||
|
:component-slots
|
||||||
|
|
||||||
|
### Emits
|
||||||
|
|
||||||
|
:component-emits
|
||||||
|
|
||||||
|
## Theme
|
||||||
|
|
||||||
|
:component-theme
|
||||||
@@ -26,6 +26,8 @@ Use the `items` prop as an array of objects with the following properties:
|
|||||||
- `children?: TreeItem[]`{lang="ts-type"}
|
- `children?: TreeItem[]`{lang="ts-type"}
|
||||||
- `onToggle?(e: Event): void`{lang="ts-type"}
|
- `onToggle?(e: Event): void`{lang="ts-type"}
|
||||||
- `onSelect?(e?: Event): void`{lang="ts-type"}
|
- `onSelect?(e?: Event): void`{lang="ts-type"}
|
||||||
|
- `class?: any`{lang="ts-type"}
|
||||||
|
- `ui?: { item?: ClassNameValue, itemWithChildren?: ClassNameValue, link?: ClassNameValue, linkLeadingIcon?: ClassNameValue, linkLabel?: ClassNameValue, linkTrailing?: ClassNameValue, linkTrailingIcon?: ClassNameValue, listWithChildren?: ClassNameValue }`{lang="ts-type"}
|
||||||
|
|
||||||
::note
|
::note
|
||||||
A unique identifier is required for each item. The component will use the `value` prop as identifier, falling back to `label` if `value` is not provided. One of these must be provided for the component to work properly.
|
A unique identifier is required for each item. The component will use the `value` prop as identifier, falling back to `label` if `value` is not provided. One of these must be provided for the component to work properly.
|
||||||
@@ -405,7 +407,14 @@ This lets you select a parent item without expanding or collapsing its children.
|
|||||||
|
|
||||||
### With custom slot
|
### With custom slot
|
||||||
|
|
||||||
Use the `item.slot` property to customize a specific item.
|
Use the `slot` property to customize a specific item.
|
||||||
|
|
||||||
|
You will have access to the following slots:
|
||||||
|
|
||||||
|
- `#{{ item.slot }}`{lang="ts-type"}
|
||||||
|
- `#{{ item.slot }}-leading`{lang="ts-type"}
|
||||||
|
- `#{{ item.slot }}-label`{lang="ts-type"}
|
||||||
|
- `#{{ item.slot }}-trailing`{lang="ts-type"}
|
||||||
|
|
||||||
::component-example
|
::component-example
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export default defineNuxtModule((_, nuxt) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const name = template.name.toLowerCase().replace(/\s/g, '-')
|
const name = template.name.toLowerCase().replace(/\s/g, '-')
|
||||||
const filename = join(process.cwd(), 'docs/public/assets/showcase', `${name}.png`)
|
const filename = join(nuxt.options.rootDir, 'public/assets/showcase', `${name}.png`)
|
||||||
|
|
||||||
if (existsSync(filename)) {
|
if (existsSync(filename)) {
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -2,41 +2,49 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"name": "@nuxt/ui-docs",
|
"name": "@nuxt/ui-docs",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "nuxt dev",
|
||||||
|
"build": "nuxt build",
|
||||||
|
"generate": "nuxt generate",
|
||||||
|
"typecheck": "nuxt typecheck"
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/vue": "^1.2.11",
|
"@ai-sdk/vue": "^1.2.12",
|
||||||
"@iconify-json/logos": "^1.2.4",
|
"@iconify-json/logos": "^1.2.4",
|
||||||
"@iconify-json/lucide": "^1.2.41",
|
"@iconify-json/lucide": "^1.2.51",
|
||||||
"@iconify-json/simple-icons": "^1.2.33",
|
"@iconify-json/simple-icons": "^1.2.39",
|
||||||
"@iconify-json/vscode-icons": "^1.2.20",
|
"@iconify-json/vscode-icons": "^1.2.23",
|
||||||
"@nuxt/content": "^3.5.1",
|
"@nuxt/content": "^3.6.1",
|
||||||
"@nuxt/image": "^1.10.0",
|
"@nuxt/image": "^1.10.0",
|
||||||
"@nuxt/ui": "latest",
|
"@nuxt/ui": "workspace:*",
|
||||||
"@nuxt/ui-pro": "https://pkg.pr.new/@nuxt/ui-pro@a30de4d",
|
"@nuxt/ui-pro": "https://pkg.pr.new/@nuxt/ui-pro@55e248c",
|
||||||
"@nuxthub/core": "^0.8.27",
|
"@nuxthub/core": "^0.9.0",
|
||||||
"@nuxtjs/plausible": "^1.2.0",
|
"@nuxtjs/plausible": "^1.2.0",
|
||||||
"@octokit/rest": "^21.1.1",
|
"@octokit/rest": "^22.0.0",
|
||||||
"@rollup/plugin-yaml": "^4.1.2",
|
"@rollup/plugin-yaml": "^4.1.2",
|
||||||
"@vueuse/integrations": "^13.1.0",
|
"@vueuse/integrations": "^13.4.0",
|
||||||
"@vueuse/nuxt": "^13.1.0",
|
"@vueuse/nuxt": "^13.4.0",
|
||||||
"ai": "^4.3.15",
|
"ai": "^4.3.16",
|
||||||
|
"better-sqlite3": "^12.0.0",
|
||||||
"capture-website": "^4.2.0",
|
"capture-website": "^4.2.0",
|
||||||
"joi": "^17.13.3",
|
"joi": "^17.13.3",
|
||||||
"motion-v": "^1.0.2",
|
"maska": "^3.1.1",
|
||||||
"nuxt": "^3.17.2",
|
"motion-v": "^1.3.0",
|
||||||
|
"nuxt": "^3.17.5",
|
||||||
"nuxt-component-meta": "^0.11.0",
|
"nuxt-component-meta": "^0.11.0",
|
||||||
"nuxt-llms": "^0.1.2",
|
"nuxt-llms": "^0.1.3",
|
||||||
"nuxt-og-image": "^5.1.3",
|
"nuxt-og-image": "^5.1.7",
|
||||||
"prettier": "^3.5.3",
|
"prettier": "^3.6.0",
|
||||||
"shiki-transformer-color-highlight": "^1.0.0",
|
"shiki-transformer-color-highlight": "^1.0.0",
|
||||||
"sortablejs": "^1.15.6",
|
"sortablejs": "^1.15.6",
|
||||||
"superstruct": "^2.0.2",
|
"superstruct": "^2.0.2",
|
||||||
"ufo": "^1.6.1",
|
"ufo": "^1.6.1",
|
||||||
"valibot": "^1.1.0",
|
"valibot": "^1.1.0",
|
||||||
"workers-ai-provider": "^0.3.1",
|
"workers-ai-provider": "^0.7.0",
|
||||||
"yup": "^1.6.1",
|
"yup": "^1.6.1",
|
||||||
"zod": "^3.24.4"
|
"zod": "^3.25.67"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"wrangler": "^4.14.4"
|
"wrangler": "^4.20.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
docs/public/components/dark/changelog-version.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
docs/public/components/dark/changelog-versions.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
docs/public/components/dark/input-tags.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
docs/public/components/dark/timeline.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
docs/public/components/light/changelog-version.png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
docs/public/components/light/changelog-versions.png
Normal file
|
After Width: | Height: | Size: 8.8 KiB |
BIN
docs/public/components/light/input-tags.png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
docs/public/components/light/timeline.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
@@ -1,412 +1,8 @@
|
|||||||
import json5 from 'json5'
|
|
||||||
import { camelCase, kebabCase } from 'scule'
|
|
||||||
import { visit } from '@nuxt/content/runtime'
|
|
||||||
import type { H3Event } from 'h3'
|
import type { H3Event } from 'h3'
|
||||||
import type { PageCollectionItemBase } from '@nuxt/content'
|
import type { PageCollectionItemBase } from '@nuxt/content'
|
||||||
import * as theme from '../../.nuxt/ui'
|
|
||||||
import * as themePro from '../../.nuxt/ui-pro'
|
|
||||||
import meta from '#nuxt-component-meta'
|
|
||||||
// @ts-expect-error - no types available
|
|
||||||
import components from '#component-example/nitro'
|
|
||||||
|
|
||||||
type ComponentAttributes = {
|
|
||||||
':pro'?: string
|
|
||||||
':prose'?: string
|
|
||||||
':props'?: string
|
|
||||||
':external'?: string
|
|
||||||
':externalTypes'?: string
|
|
||||||
':ignore'?: string
|
|
||||||
':hide'?: string
|
|
||||||
':slots'?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
type ThemeConfig = {
|
|
||||||
pro: boolean
|
|
||||||
prose: boolean
|
|
||||||
componentName: string
|
|
||||||
}
|
|
||||||
|
|
||||||
type CodeConfig = {
|
|
||||||
pro: boolean
|
|
||||||
props: Record<string, unknown>
|
|
||||||
external: string[]
|
|
||||||
externalTypes: string[]
|
|
||||||
ignore: string[]
|
|
||||||
hide: string[]
|
|
||||||
componentName: string
|
|
||||||
slots?: Record<string, string>
|
|
||||||
}
|
|
||||||
|
|
||||||
type Document = {
|
|
||||||
title: string
|
|
||||||
body: any
|
|
||||||
}
|
|
||||||
|
|
||||||
const parseBoolean = (value?: string): boolean => value === 'true'
|
|
||||||
|
|
||||||
function getComponentMeta(componentName: string) {
|
|
||||||
const pascalCaseName = componentName.charAt(0).toUpperCase() + componentName.slice(1)
|
|
||||||
|
|
||||||
const strategies = [
|
|
||||||
`U${pascalCaseName}`,
|
|
||||||
`Prose${pascalCaseName}`,
|
|
||||||
pascalCaseName
|
|
||||||
]
|
|
||||||
|
|
||||||
let componentMeta: any
|
|
||||||
let finalMetaComponentName: string = pascalCaseName
|
|
||||||
|
|
||||||
for (const nameToTry of strategies) {
|
|
||||||
finalMetaComponentName = nameToTry
|
|
||||||
const metaAttempt = (meta as Record<string, any>)[nameToTry]?.meta
|
|
||||||
if (metaAttempt) {
|
|
||||||
componentMeta = metaAttempt
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!componentMeta) {
|
|
||||||
console.warn(`[getComponentMeta] Metadata not found for ${pascalCaseName} using strategies: U, Prose, or no prefix. Last tried: ${finalMetaComponentName}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
pascalCaseName,
|
|
||||||
metaComponentName: finalMetaComponentName,
|
|
||||||
componentMeta
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function replaceNodeWithPre(node: any[], language: string, code: string, filename?: string) {
|
|
||||||
node[0] = 'pre'
|
|
||||||
node[1] = { language, code }
|
|
||||||
if (filename) node[1].filename = filename
|
|
||||||
}
|
|
||||||
|
|
||||||
function visitAndReplace(doc: Document, type: string, handler: (node: any[]) => void) {
|
|
||||||
visit(doc.body, (node) => {
|
|
||||||
if (Array.isArray(node) && node[0] === type) {
|
|
||||||
handler(node)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}, node => node)
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateTSInterface(
|
|
||||||
name: string,
|
|
||||||
items: any[],
|
|
||||||
itemHandler: (item: any) => string,
|
|
||||||
description: string
|
|
||||||
) {
|
|
||||||
let code = `/**\n * ${description}\n */\ninterface ${name} {\n`
|
|
||||||
for (const item of items) {
|
|
||||||
code += itemHandler(item)
|
|
||||||
}
|
|
||||||
code += `}`
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
function propItemHandler(propValue: any): string {
|
|
||||||
if (!propValue?.name) return ''
|
|
||||||
const propName = propValue.name
|
|
||||||
const propType = propValue.type
|
|
||||||
? Array.isArray(propValue.type)
|
|
||||||
? propValue.type.map((t: any) => t.name || t).join(' | ')
|
|
||||||
: propValue.type.name || propValue.type
|
|
||||||
: 'any'
|
|
||||||
const isRequired = propValue.required || false
|
|
||||||
const hasDescription = propValue.description && propValue.description.trim().length > 0
|
|
||||||
const hasDefault = propValue.default !== undefined
|
|
||||||
let result = ''
|
|
||||||
if (hasDescription || hasDefault) {
|
|
||||||
result += ` /**\n`
|
|
||||||
if (hasDescription) {
|
|
||||||
const descLines = propValue.description.split(/\r?\n/)
|
|
||||||
descLines.forEach((line: string) => {
|
|
||||||
result += ` * ${line}\n`
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (hasDefault) {
|
|
||||||
let defaultValue = propValue.default
|
|
||||||
if (typeof defaultValue === 'string') {
|
|
||||||
defaultValue = `"${defaultValue.replace(/"/g, '\\"')}"`
|
|
||||||
} else {
|
|
||||||
defaultValue = JSON.stringify(defaultValue)
|
|
||||||
}
|
|
||||||
result += ` * @default ${defaultValue}\n`
|
|
||||||
}
|
|
||||||
result += ` */\n`
|
|
||||||
}
|
|
||||||
result += ` ${propName}${isRequired ? '' : '?'}: ${propType};\n`
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
function slotItemHandler(slotValue: any): string {
|
|
||||||
if (!slotValue?.name) return ''
|
|
||||||
const slotName = slotValue.name
|
|
||||||
const hasDescription = slotValue.description && slotValue.description.trim().length > 0
|
|
||||||
let result = ''
|
|
||||||
if (hasDescription) {
|
|
||||||
result += ` /**\n`
|
|
||||||
const descLines = slotValue.description.split(/\r?\n/)
|
|
||||||
descLines.forEach((line: string) => {
|
|
||||||
result += ` * ${line}\n`
|
|
||||||
})
|
|
||||||
result += ` */\n`
|
|
||||||
}
|
|
||||||
if (slotValue.bindings && Object.keys(slotValue.bindings).length > 0) {
|
|
||||||
let bindingsType = '{\n'
|
|
||||||
Object.entries(slotValue.bindings).forEach(([bindingName, bindingValue]: [string, any]) => {
|
|
||||||
const bindingType = bindingValue.type || 'any'
|
|
||||||
bindingsType += ` ${bindingName}: ${bindingType};\n`
|
|
||||||
})
|
|
||||||
bindingsType += ' }'
|
|
||||||
result += ` ${slotName}(bindings: ${bindingsType}): any;\n`
|
|
||||||
} else {
|
|
||||||
result += ` ${slotName}(): any;\n`
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
function emitItemHandler(event: any): string {
|
|
||||||
if (!event?.name) return ''
|
|
||||||
let payloadType = 'void'
|
|
||||||
if (event.type) {
|
|
||||||
payloadType = Array.isArray(event.type)
|
|
||||||
? event.type.map((t: any) => t.name || t).join(' | ')
|
|
||||||
: event.type.name || event.type
|
|
||||||
}
|
|
||||||
let result = ''
|
|
||||||
if (event.description && event.description.trim().length > 0) {
|
|
||||||
result += ` /**\n`
|
|
||||||
event.description.split(/\r?\n/).forEach((line: string) => {
|
|
||||||
result += ` * ${line}\n`
|
|
||||||
})
|
|
||||||
result += ` */\n`
|
|
||||||
}
|
|
||||||
result += ` ${event.name}: (payload: ${payloadType}) => void;\n`
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
const generateThemeConfig = ({ pro, prose, componentName }: ThemeConfig) => {
|
|
||||||
const computedTheme = pro ? (prose ? themePro.prose : themePro) : theme
|
|
||||||
const componentTheme = computedTheme[componentName as keyof typeof computedTheme]
|
|
||||||
|
|
||||||
return {
|
|
||||||
[pro ? 'uiPro' : 'ui']: prose
|
|
||||||
? { prose: { [componentName]: componentTheme } }
|
|
||||||
: { [componentName]: componentTheme }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const generateComponentCode = ({
|
|
||||||
pro,
|
|
||||||
props,
|
|
||||||
external,
|
|
||||||
externalTypes,
|
|
||||||
hide,
|
|
||||||
componentName,
|
|
||||||
slots
|
|
||||||
}: CodeConfig) => {
|
|
||||||
const filteredProps = Object.fromEntries(
|
|
||||||
Object.entries(props).filter(([key]) => !hide.includes(key))
|
|
||||||
)
|
|
||||||
|
|
||||||
const imports = pro
|
|
||||||
? ''
|
|
||||||
: external
|
|
||||||
.filter((_, index) => externalTypes[index] && externalTypes[index] !== 'undefined')
|
|
||||||
.map((ext, index) => {
|
|
||||||
const type = externalTypes[index]?.replace(/[[\]]/g, '')
|
|
||||||
return `import type { ${type} } from '@nuxt/${pro ? 'ui-pro' : 'ui'}'`
|
|
||||||
})
|
|
||||||
.join('\n')
|
|
||||||
|
|
||||||
let itemsCode = ''
|
|
||||||
if (props.items) {
|
|
||||||
itemsCode = pro
|
|
||||||
? `const items = ref(${json5.stringify(props.items, null, 2)})`
|
|
||||||
: `const items = ref<${externalTypes[0]}>(${json5.stringify(props.items, null, 2)})`
|
|
||||||
delete filteredProps.items
|
|
||||||
}
|
|
||||||
|
|
||||||
let calendarValueCode = ''
|
|
||||||
if (componentName === 'calendar' && props.modelValue && Array.isArray(props.modelValue)) {
|
|
||||||
calendarValueCode = `const value = ref(new CalendarDate(${props.modelValue.join(', ')}))`
|
|
||||||
}
|
|
||||||
|
|
||||||
const propsString = Object.entries(filteredProps)
|
|
||||||
.map(([key, value]) => {
|
|
||||||
const formattedKey = kebabCase(key)
|
|
||||||
if (typeof value === 'string') {
|
|
||||||
return `${formattedKey}="${value}"`
|
|
||||||
} else if (typeof value === 'number') {
|
|
||||||
return `:${formattedKey}="${value}"`
|
|
||||||
} else if (typeof value === 'boolean') {
|
|
||||||
return value ? formattedKey : `:${formattedKey}="false"`
|
|
||||||
}
|
|
||||||
return ''
|
|
||||||
})
|
|
||||||
.filter(Boolean)
|
|
||||||
.join(' ')
|
|
||||||
|
|
||||||
const itemsProp = props.items ? ':items="items"' : ''
|
|
||||||
const vModelProp = componentName === 'calendar' && props.modelValue ? 'v-model="value"' : ''
|
|
||||||
const allProps = [propsString, itemsProp, vModelProp].filter(Boolean).join(' ')
|
|
||||||
const formattedProps = allProps ? ` ${allProps}` : ''
|
|
||||||
|
|
||||||
let scriptSetup = ''
|
|
||||||
if (imports || itemsCode || calendarValueCode) {
|
|
||||||
scriptSetup = '<script setup lang="ts">'
|
|
||||||
if (imports) scriptSetup += `\n${imports}`
|
|
||||||
if (imports && (itemsCode || calendarValueCode)) scriptSetup += '\n'
|
|
||||||
if (calendarValueCode) scriptSetup += `\n${calendarValueCode}`
|
|
||||||
if (itemsCode) scriptSetup += `\n${itemsCode}`
|
|
||||||
scriptSetup += '\n</script>\n\n'
|
|
||||||
}
|
|
||||||
|
|
||||||
let componentContent = ''
|
|
||||||
let slotContent = ''
|
|
||||||
|
|
||||||
if (slots && Object.keys(slots).length > 0) {
|
|
||||||
const defaultSlot = slots.default?.trim()
|
|
||||||
if (defaultSlot) {
|
|
||||||
const indentedContent = defaultSlot
|
|
||||||
.split('\n')
|
|
||||||
.map(line => line.trim() ? ` ${line}` : line)
|
|
||||||
.join('\n')
|
|
||||||
componentContent = `\n${indentedContent}\n `
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.entries(slots).forEach(([slotName, content]) => {
|
|
||||||
if (slotName !== 'default' && content?.trim()) {
|
|
||||||
const indentedSlotContent = content.trim()
|
|
||||||
.split('\n')
|
|
||||||
.map(line => line.trim() ? ` ${line}` : line)
|
|
||||||
.join('\n')
|
|
||||||
slotContent += `\n <template #${slotName}>\n${indentedSlotContent}\n </template>`
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const pascalCaseName = componentName.charAt(0).toUpperCase() + componentName.slice(1)
|
|
||||||
|
|
||||||
let componentTemplate = ''
|
|
||||||
if (componentContent || slotContent) {
|
|
||||||
componentTemplate = `<U${pascalCaseName}${formattedProps}>${componentContent}${slotContent}</U${pascalCaseName}>` // Removed space before closing tag
|
|
||||||
} else {
|
|
||||||
componentTemplate = `<U${pascalCaseName}${formattedProps} />`
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${scriptSetup}<template>
|
|
||||||
${componentTemplate}
|
|
||||||
</template>`
|
|
||||||
}
|
|
||||||
|
|
||||||
export default defineNitroPlugin((nitroApp) => {
|
export default defineNitroPlugin((nitroApp) => {
|
||||||
nitroApp.hooks.hook('content:llms:generate:document', async (_: H3Event, doc: PageCollectionItemBase) => {
|
nitroApp.hooks.hook('content:llms:generate:document', async (_: H3Event, doc: PageCollectionItemBase) => {
|
||||||
const componentName = camelCase(doc.title)
|
transformMDC(doc as any)
|
||||||
|
|
||||||
visitAndReplace(doc, 'component-theme', (node) => {
|
|
||||||
const attributes = node[1] as Record<string, string>
|
|
||||||
const mdcSpecificName = attributes?.slug
|
|
||||||
|
|
||||||
const finalComponentName = mdcSpecificName ? camelCase(mdcSpecificName) : componentName
|
|
||||||
|
|
||||||
const pro = parseBoolean(attributes[':pro'])
|
|
||||||
const prose = parseBoolean(attributes[':prose'])
|
|
||||||
const appConfig = generateThemeConfig({ pro, prose, componentName: finalComponentName })
|
|
||||||
|
|
||||||
replaceNodeWithPre(
|
|
||||||
node,
|
|
||||||
'ts',
|
|
||||||
`export default defineAppConfig(${json5.stringify(appConfig, null, 2)?.replace(/,([ |\t\n]+[}|\])])/g, '$1')})`,
|
|
||||||
'app.config.ts'
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
visitAndReplace(doc, 'component-code', (node) => {
|
|
||||||
const attributes = node[1] as ComponentAttributes
|
|
||||||
const pro = parseBoolean(attributes[':pro'])
|
|
||||||
const props = attributes[':props'] ? json5.parse(attributes[':props']) : {}
|
|
||||||
const external = attributes[':external'] ? json5.parse(attributes[':external']) : []
|
|
||||||
const externalTypes = attributes[':externalTypes'] ? json5.parse(attributes[':externalTypes']) : []
|
|
||||||
const ignore = attributes[':ignore'] ? json5.parse(attributes[':ignore']) : []
|
|
||||||
const hide = attributes[':hide'] ? json5.parse(attributes[':hide']) : []
|
|
||||||
const slots = attributes[':slots'] ? json5.parse(attributes[':slots']) : {}
|
|
||||||
|
|
||||||
const code = generateComponentCode({
|
|
||||||
pro,
|
|
||||||
props,
|
|
||||||
external,
|
|
||||||
externalTypes,
|
|
||||||
ignore,
|
|
||||||
hide,
|
|
||||||
componentName,
|
|
||||||
slots
|
|
||||||
})
|
|
||||||
|
|
||||||
replaceNodeWithPre(node, 'vue', code)
|
|
||||||
})
|
|
||||||
|
|
||||||
visitAndReplace(doc, 'component-props', (node) => {
|
|
||||||
const attributes = node[1] as Record<string, string>
|
|
||||||
const mdcSpecificName = attributes?.name
|
|
||||||
const isProse = parseBoolean(attributes[':prose'])
|
|
||||||
|
|
||||||
const finalComponentName = mdcSpecificName ? camelCase(mdcSpecificName) : componentName
|
|
||||||
|
|
||||||
const { pascalCaseName, componentMeta } = getComponentMeta(finalComponentName)
|
|
||||||
|
|
||||||
if (!componentMeta?.props) return
|
|
||||||
|
|
||||||
const interfaceName = isProse ? `Prose${pascalCaseName}Props` : `${pascalCaseName}Props`
|
|
||||||
|
|
||||||
const interfaceCode = generateTSInterface(
|
|
||||||
interfaceName,
|
|
||||||
Object.values(componentMeta.props),
|
|
||||||
propItemHandler,
|
|
||||||
`Props for the ${isProse ? 'Prose' : ''}${pascalCaseName} component`
|
|
||||||
)
|
|
||||||
replaceNodeWithPre(node, 'ts', interfaceCode)
|
|
||||||
})
|
|
||||||
|
|
||||||
visitAndReplace(doc, 'component-slots', (node) => {
|
|
||||||
const { pascalCaseName, componentMeta } = getComponentMeta(componentName)
|
|
||||||
if (!componentMeta?.slots) return
|
|
||||||
|
|
||||||
const interfaceCode = generateTSInterface(
|
|
||||||
`${pascalCaseName}Slots`,
|
|
||||||
Object.values(componentMeta.slots),
|
|
||||||
slotItemHandler,
|
|
||||||
`Slots for the ${pascalCaseName} component`
|
|
||||||
)
|
|
||||||
replaceNodeWithPre(node, 'ts', interfaceCode)
|
|
||||||
})
|
|
||||||
|
|
||||||
visitAndReplace(doc, 'component-emits', (node) => {
|
|
||||||
const { pascalCaseName, componentMeta } = getComponentMeta(componentName)
|
|
||||||
const hasEvents = componentMeta?.events && Object.keys(componentMeta.events).length > 0
|
|
||||||
|
|
||||||
if (hasEvents) {
|
|
||||||
const interfaceCode = generateTSInterface(
|
|
||||||
`${pascalCaseName}Emits`,
|
|
||||||
Object.values(componentMeta.events),
|
|
||||||
emitItemHandler,
|
|
||||||
`Emitted events for the ${pascalCaseName} component`
|
|
||||||
)
|
|
||||||
replaceNodeWithPre(node, 'ts', interfaceCode)
|
|
||||||
} else {
|
|
||||||
node[0] = 'p'
|
|
||||||
node[1] = {}
|
|
||||||
node[2] = 'No events available for this component.'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
visitAndReplace(doc, 'component-example', (node) => {
|
|
||||||
const camelName = camelCase(node[1]['name'])
|
|
||||||
const name = camelName.charAt(0).toUpperCase() + camelName.slice(1)
|
|
||||||
const code = components[name].code
|
|
||||||
replaceNodeWithPre(node, 'vue', code, `${name}.vue`)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
30
docs/server/routes/raw/[...slug].md.get.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { stringify } from 'minimark/stringify'
|
||||||
|
import { withLeadingSlash } from 'ufo'
|
||||||
|
|
||||||
|
export default eventHandler(async (event) => {
|
||||||
|
const slug = getRouterParams(event)['slug.md']
|
||||||
|
if (!slug?.endsWith('.md')) {
|
||||||
|
throw createError({ statusCode: 404, statusMessage: 'Page not found', fatal: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
const path = withLeadingSlash(slug.replace('.md', ''))
|
||||||
|
// @ts-expect-error TODO: fix this
|
||||||
|
const page = await queryCollection(event, 'content').path(path).first()
|
||||||
|
if (!page) {
|
||||||
|
throw createError({ statusCode: 404, statusMessage: 'Page not found', fatal: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add title and description to the top of the page if missing
|
||||||
|
if (page.body.value[0]?.[0] !== 'h1') {
|
||||||
|
page.body.value.unshift(['blockquote', {}, page.description])
|
||||||
|
page.body.value.unshift(['h1', {}, page.title])
|
||||||
|
}
|
||||||
|
|
||||||
|
const transformedPage = transformMDC({
|
||||||
|
title: page.title,
|
||||||
|
body: page.body
|
||||||
|
})
|
||||||
|
|
||||||
|
setHeader(event, 'Content-Type', 'text/markdown; charset=utf-8')
|
||||||
|
return stringify({ ...transformedPage.body, type: 'minimark' }, { format: 'markdown/html' })
|
||||||
|
})
|
||||||
410
docs/server/utils/transformMDC.ts
Normal file
@@ -0,0 +1,410 @@
|
|||||||
|
import json5 from 'json5'
|
||||||
|
import { camelCase, kebabCase } from 'scule'
|
||||||
|
import { visit } from '@nuxt/content/runtime'
|
||||||
|
import * as theme from '../../.nuxt/ui'
|
||||||
|
import * as themePro from '../../.nuxt/ui-pro'
|
||||||
|
import meta from '#nuxt-component-meta'
|
||||||
|
// @ts-expect-error - no types available
|
||||||
|
import components from '#component-example/nitro'
|
||||||
|
|
||||||
|
type ComponentAttributes = {
|
||||||
|
':pro'?: string
|
||||||
|
':prose'?: string
|
||||||
|
':props'?: string
|
||||||
|
':external'?: string
|
||||||
|
':externalTypes'?: string
|
||||||
|
':ignore'?: string
|
||||||
|
':hide'?: string
|
||||||
|
':slots'?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ThemeConfig = {
|
||||||
|
pro: boolean
|
||||||
|
prose: boolean
|
||||||
|
componentName: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type CodeConfig = {
|
||||||
|
pro: boolean
|
||||||
|
props: Record<string, unknown>
|
||||||
|
external: string[]
|
||||||
|
externalTypes: string[]
|
||||||
|
ignore: string[]
|
||||||
|
hide: string[]
|
||||||
|
componentName: string
|
||||||
|
slots?: Record<string, string>
|
||||||
|
}
|
||||||
|
|
||||||
|
type Document = {
|
||||||
|
title: string
|
||||||
|
body: any
|
||||||
|
}
|
||||||
|
|
||||||
|
const parseBoolean = (value?: string): boolean => value === 'true'
|
||||||
|
|
||||||
|
function getComponentMeta(componentName: string) {
|
||||||
|
const pascalCaseName = componentName.charAt(0).toUpperCase() + componentName.slice(1)
|
||||||
|
|
||||||
|
const strategies = [
|
||||||
|
`U${pascalCaseName}`,
|
||||||
|
`Prose${pascalCaseName}`,
|
||||||
|
pascalCaseName
|
||||||
|
]
|
||||||
|
|
||||||
|
let componentMeta: any
|
||||||
|
let finalMetaComponentName: string = pascalCaseName
|
||||||
|
|
||||||
|
for (const nameToTry of strategies) {
|
||||||
|
finalMetaComponentName = nameToTry
|
||||||
|
const metaAttempt = (meta as Record<string, any>)[nameToTry]?.meta
|
||||||
|
if (metaAttempt) {
|
||||||
|
componentMeta = metaAttempt
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!componentMeta) {
|
||||||
|
console.warn(`[getComponentMeta] Metadata not found for ${pascalCaseName} using strategies: U, Prose, or no prefix. Last tried: ${finalMetaComponentName}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
pascalCaseName,
|
||||||
|
metaComponentName: finalMetaComponentName,
|
||||||
|
componentMeta
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function replaceNodeWithPre(node: any[], language: string, code: string, filename?: string) {
|
||||||
|
node[0] = 'pre'
|
||||||
|
node[1] = { language, code }
|
||||||
|
if (filename) node[1].filename = filename
|
||||||
|
}
|
||||||
|
|
||||||
|
function visitAndReplace(doc: Document, type: string, handler: (node: any[]) => void) {
|
||||||
|
visit(doc.body, (node) => {
|
||||||
|
if (Array.isArray(node) && node[0] === type) {
|
||||||
|
handler(node)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}, node => node)
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateTSInterface(
|
||||||
|
name: string,
|
||||||
|
items: any[],
|
||||||
|
itemHandler: (item: any) => string,
|
||||||
|
description: string
|
||||||
|
) {
|
||||||
|
let code = `/**\n * ${description}\n */\ninterface ${name} {\n`
|
||||||
|
for (const item of items) {
|
||||||
|
code += itemHandler(item)
|
||||||
|
}
|
||||||
|
code += `}`
|
||||||
|
return code
|
||||||
|
}
|
||||||
|
|
||||||
|
function propItemHandler(propValue: any): string {
|
||||||
|
if (!propValue?.name) return ''
|
||||||
|
const propName = propValue.name
|
||||||
|
const propType = propValue.type
|
||||||
|
? Array.isArray(propValue.type)
|
||||||
|
? propValue.type.map((t: any) => t.name || t).join(' | ')
|
||||||
|
: propValue.type.name || propValue.type
|
||||||
|
: 'any'
|
||||||
|
const isRequired = propValue.required || false
|
||||||
|
const hasDescription = propValue.description && propValue.description.trim().length > 0
|
||||||
|
const hasDefault = propValue.default !== undefined
|
||||||
|
let result = ''
|
||||||
|
if (hasDescription || hasDefault) {
|
||||||
|
result += ` /**\n`
|
||||||
|
if (hasDescription) {
|
||||||
|
const descLines = propValue.description.split(/\r?\n/)
|
||||||
|
descLines.forEach((line: string) => {
|
||||||
|
result += ` * ${line}\n`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (hasDefault) {
|
||||||
|
let defaultValue = propValue.default
|
||||||
|
if (typeof defaultValue === 'string') {
|
||||||
|
defaultValue = `"${defaultValue.replace(/"/g, '\\"')}"`
|
||||||
|
} else {
|
||||||
|
defaultValue = JSON.stringify(defaultValue)
|
||||||
|
}
|
||||||
|
result += ` * @default ${defaultValue}\n`
|
||||||
|
}
|
||||||
|
result += ` */\n`
|
||||||
|
}
|
||||||
|
result += ` ${propName}${isRequired ? '' : '?'}: ${propType};\n`
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
function slotItemHandler(slotValue: any): string {
|
||||||
|
if (!slotValue?.name) return ''
|
||||||
|
const slotName = slotValue.name
|
||||||
|
const hasDescription = slotValue.description && slotValue.description.trim().length > 0
|
||||||
|
let result = ''
|
||||||
|
if (hasDescription) {
|
||||||
|
result += ` /**\n`
|
||||||
|
const descLines = slotValue.description.split(/\r?\n/)
|
||||||
|
descLines.forEach((line: string) => {
|
||||||
|
result += ` * ${line}\n`
|
||||||
|
})
|
||||||
|
result += ` */\n`
|
||||||
|
}
|
||||||
|
if (slotValue.bindings && Object.keys(slotValue.bindings).length > 0) {
|
||||||
|
let bindingsType = '{\n'
|
||||||
|
Object.entries(slotValue.bindings).forEach(([bindingName, bindingValue]: [string, any]) => {
|
||||||
|
const bindingType = bindingValue.type || 'any'
|
||||||
|
bindingsType += ` ${bindingName}: ${bindingType};\n`
|
||||||
|
})
|
||||||
|
bindingsType += ' }'
|
||||||
|
result += ` ${slotName}(bindings: ${bindingsType}): any;\n`
|
||||||
|
} else {
|
||||||
|
result += ` ${slotName}(): any;\n`
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
function emitItemHandler(event: any): string {
|
||||||
|
if (!event?.name) return ''
|
||||||
|
let payloadType = 'void'
|
||||||
|
if (event.type) {
|
||||||
|
payloadType = Array.isArray(event.type)
|
||||||
|
? event.type.map((t: any) => t.name || t).join(' | ')
|
||||||
|
: event.type.name || event.type
|
||||||
|
}
|
||||||
|
let result = ''
|
||||||
|
if (event.description && event.description.trim().length > 0) {
|
||||||
|
result += ` /**\n`
|
||||||
|
event.description.split(/\r?\n/).forEach((line: string) => {
|
||||||
|
result += ` * ${line}\n`
|
||||||
|
})
|
||||||
|
result += ` */\n`
|
||||||
|
}
|
||||||
|
result += ` ${event.name}: (payload: ${payloadType}) => void;\n`
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
const generateThemeConfig = ({ pro, prose, componentName }: ThemeConfig) => {
|
||||||
|
const computedTheme = pro ? (prose ? themePro.prose : themePro) : theme
|
||||||
|
const componentTheme = computedTheme[componentName as keyof typeof computedTheme]
|
||||||
|
|
||||||
|
return {
|
||||||
|
[pro ? 'uiPro' : 'ui']: prose
|
||||||
|
? { prose: { [componentName]: componentTheme } }
|
||||||
|
: { [componentName]: componentTheme }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const generateComponentCode = ({
|
||||||
|
pro,
|
||||||
|
props,
|
||||||
|
external,
|
||||||
|
externalTypes,
|
||||||
|
hide,
|
||||||
|
componentName,
|
||||||
|
slots
|
||||||
|
}: CodeConfig) => {
|
||||||
|
const filteredProps = Object.fromEntries(
|
||||||
|
Object.entries(props).filter(([key]) => !hide.includes(key))
|
||||||
|
)
|
||||||
|
|
||||||
|
const imports = pro
|
||||||
|
? ''
|
||||||
|
: external
|
||||||
|
.filter((_, index) => externalTypes[index] && externalTypes[index] !== 'undefined')
|
||||||
|
.map((ext, index) => {
|
||||||
|
const type = externalTypes[index]?.replace(/[[\]]/g, '')
|
||||||
|
return `import type { ${type} } from '@nuxt/${pro ? 'ui-pro' : 'ui'}'`
|
||||||
|
})
|
||||||
|
.join('\n')
|
||||||
|
|
||||||
|
let itemsCode = ''
|
||||||
|
if (props.items) {
|
||||||
|
itemsCode = pro
|
||||||
|
? `const items = ref(${json5.stringify(props.items, null, 2)})`
|
||||||
|
: `const items = ref<${externalTypes[0]}>(${json5.stringify(props.items, null, 2)})`
|
||||||
|
delete filteredProps.items
|
||||||
|
}
|
||||||
|
|
||||||
|
let calendarValueCode = ''
|
||||||
|
if (componentName === 'calendar' && props.modelValue && Array.isArray(props.modelValue)) {
|
||||||
|
calendarValueCode = `const value = ref(new CalendarDate(${props.modelValue.join(', ')}))`
|
||||||
|
}
|
||||||
|
|
||||||
|
const propsString = Object.entries(filteredProps)
|
||||||
|
.map(([key, value]) => {
|
||||||
|
const formattedKey = kebabCase(key)
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
return `${formattedKey}="${value}"`
|
||||||
|
} else if (typeof value === 'number') {
|
||||||
|
return `:${formattedKey}="${value}"`
|
||||||
|
} else if (typeof value === 'boolean') {
|
||||||
|
return value ? formattedKey : `:${formattedKey}="false"`
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
})
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(' ')
|
||||||
|
|
||||||
|
const itemsProp = props.items ? ':items="items"' : ''
|
||||||
|
const vModelProp = componentName === 'calendar' && props.modelValue ? 'v-model="value"' : ''
|
||||||
|
const allProps = [propsString, itemsProp, vModelProp].filter(Boolean).join(' ')
|
||||||
|
const formattedProps = allProps ? ` ${allProps}` : ''
|
||||||
|
|
||||||
|
let scriptSetup = ''
|
||||||
|
if (imports || itemsCode || calendarValueCode) {
|
||||||
|
scriptSetup = '<script setup lang="ts">'
|
||||||
|
if (imports) scriptSetup += `\n${imports}`
|
||||||
|
if (imports && (itemsCode || calendarValueCode)) scriptSetup += '\n'
|
||||||
|
if (calendarValueCode) scriptSetup += `\n${calendarValueCode}`
|
||||||
|
if (itemsCode) scriptSetup += `\n${itemsCode}`
|
||||||
|
scriptSetup += '\n</script>\n\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
let componentContent = ''
|
||||||
|
let slotContent = ''
|
||||||
|
|
||||||
|
if (slots && Object.keys(slots).length > 0) {
|
||||||
|
const defaultSlot = slots.default?.trim()
|
||||||
|
if (defaultSlot) {
|
||||||
|
const indentedContent = defaultSlot
|
||||||
|
.split('\n')
|
||||||
|
.map(line => line.trim() ? ` ${line}` : line)
|
||||||
|
.join('\n')
|
||||||
|
componentContent = `\n${indentedContent}\n `
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.entries(slots).forEach(([slotName, content]) => {
|
||||||
|
if (slotName !== 'default' && content?.trim()) {
|
||||||
|
const indentedSlotContent = content.trim()
|
||||||
|
.split('\n')
|
||||||
|
.map(line => line.trim() ? ` ${line}` : line)
|
||||||
|
.join('\n')
|
||||||
|
slotContent += `\n <template #${slotName}>\n${indentedSlotContent}\n </template>`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const pascalCaseName = componentName.charAt(0).toUpperCase() + componentName.slice(1)
|
||||||
|
|
||||||
|
let componentTemplate = ''
|
||||||
|
if (componentContent || slotContent) {
|
||||||
|
componentTemplate = `<U${pascalCaseName}${formattedProps}>${componentContent}${slotContent}</U${pascalCaseName}>` // Removed space before closing tag
|
||||||
|
} else {
|
||||||
|
componentTemplate = `<U${pascalCaseName}${formattedProps} />`
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${scriptSetup}<template>
|
||||||
|
${componentTemplate}
|
||||||
|
</template>`
|
||||||
|
}
|
||||||
|
|
||||||
|
export function transformMDC(doc: Document): Document {
|
||||||
|
const componentName = camelCase(doc.title)
|
||||||
|
|
||||||
|
visitAndReplace(doc, 'component-theme', (node) => {
|
||||||
|
const attributes = node[1] as Record<string, string>
|
||||||
|
const mdcSpecificName = attributes?.slug
|
||||||
|
|
||||||
|
const finalComponentName = mdcSpecificName ? camelCase(mdcSpecificName) : componentName
|
||||||
|
|
||||||
|
const pro = parseBoolean(attributes[':pro'])
|
||||||
|
const prose = parseBoolean(attributes[':prose'])
|
||||||
|
const appConfig = generateThemeConfig({ pro, prose, componentName: finalComponentName })
|
||||||
|
|
||||||
|
replaceNodeWithPre(
|
||||||
|
node,
|
||||||
|
'ts',
|
||||||
|
`export default defineAppConfig(${json5.stringify(appConfig, null, 2)?.replace(/,([ |\t\n]+[}|\])])/g, '$1')})`,
|
||||||
|
'app.config.ts'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
visitAndReplace(doc, 'component-code', (node) => {
|
||||||
|
const attributes = node[1] as ComponentAttributes
|
||||||
|
const pro = parseBoolean(attributes[':pro'])
|
||||||
|
const props = attributes[':props'] ? json5.parse(attributes[':props']) : {}
|
||||||
|
const external = attributes[':external'] ? json5.parse(attributes[':external']) : []
|
||||||
|
const externalTypes = attributes[':externalTypes'] ? json5.parse(attributes[':externalTypes']) : []
|
||||||
|
const ignore = attributes[':ignore'] ? json5.parse(attributes[':ignore']) : []
|
||||||
|
const hide = attributes[':hide'] ? json5.parse(attributes[':hide']) : []
|
||||||
|
const slots = attributes[':slots'] ? json5.parse(attributes[':slots']) : {}
|
||||||
|
|
||||||
|
const code = generateComponentCode({
|
||||||
|
pro,
|
||||||
|
props,
|
||||||
|
external,
|
||||||
|
externalTypes,
|
||||||
|
ignore,
|
||||||
|
hide,
|
||||||
|
componentName,
|
||||||
|
slots
|
||||||
|
})
|
||||||
|
|
||||||
|
replaceNodeWithPre(node, 'vue', code)
|
||||||
|
})
|
||||||
|
|
||||||
|
visitAndReplace(doc, 'component-props', (node) => {
|
||||||
|
const attributes = node[1] as Record<string, string>
|
||||||
|
const mdcSpecificName = attributes?.name
|
||||||
|
const isProse = parseBoolean(attributes[':prose'])
|
||||||
|
|
||||||
|
const finalComponentName = mdcSpecificName ? camelCase(mdcSpecificName) : componentName
|
||||||
|
|
||||||
|
const { pascalCaseName, componentMeta } = getComponentMeta(finalComponentName)
|
||||||
|
|
||||||
|
if (!componentMeta?.props) return
|
||||||
|
|
||||||
|
const interfaceName = isProse ? `Prose${pascalCaseName}Props` : `${pascalCaseName}Props`
|
||||||
|
|
||||||
|
const interfaceCode = generateTSInterface(
|
||||||
|
interfaceName,
|
||||||
|
Object.values(componentMeta.props),
|
||||||
|
propItemHandler,
|
||||||
|
`Props for the ${isProse ? 'Prose' : ''}${pascalCaseName} component`
|
||||||
|
)
|
||||||
|
replaceNodeWithPre(node, 'ts', interfaceCode)
|
||||||
|
})
|
||||||
|
|
||||||
|
visitAndReplace(doc, 'component-slots', (node) => {
|
||||||
|
const { pascalCaseName, componentMeta } = getComponentMeta(componentName)
|
||||||
|
if (!componentMeta?.slots) return
|
||||||
|
|
||||||
|
const interfaceCode = generateTSInterface(
|
||||||
|
`${pascalCaseName}Slots`,
|
||||||
|
Object.values(componentMeta.slots),
|
||||||
|
slotItemHandler,
|
||||||
|
`Slots for the ${pascalCaseName} component`
|
||||||
|
)
|
||||||
|
replaceNodeWithPre(node, 'ts', interfaceCode)
|
||||||
|
})
|
||||||
|
|
||||||
|
visitAndReplace(doc, 'component-emits', (node) => {
|
||||||
|
const { pascalCaseName, componentMeta } = getComponentMeta(componentName)
|
||||||
|
const hasEvents = componentMeta?.events && Object.keys(componentMeta.events).length > 0
|
||||||
|
|
||||||
|
if (hasEvents) {
|
||||||
|
const interfaceCode = generateTSInterface(
|
||||||
|
`${pascalCaseName}Emits`,
|
||||||
|
Object.values(componentMeta.events),
|
||||||
|
emitItemHandler,
|
||||||
|
`Emitted events for the ${pascalCaseName} component`
|
||||||
|
)
|
||||||
|
replaceNodeWithPre(node, 'ts', interfaceCode)
|
||||||
|
} else {
|
||||||
|
node[0] = 'p'
|
||||||
|
node[1] = {}
|
||||||
|
node[2] = 'No events available for this component.'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
visitAndReplace(doc, 'component-example', (node) => {
|
||||||
|
const camelName = camelCase(node[1]['name'])
|
||||||
|
const name = camelName.charAt(0).toUpperCase() + camelName.slice(1)
|
||||||
|
const code = components[name].code
|
||||||
|
replaceNodeWithPre(node, 'vue', code, `${name}.vue`)
|
||||||
|
})
|
||||||
|
|
||||||
|
return doc
|
||||||
|
}
|
||||||
73
package.json
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "@nuxt/ui",
|
"name": "@nuxt/ui",
|
||||||
"description": "A UI Library for Modern Web Apps, powered by Vue & Tailwind CSS.",
|
"description": "A UI Library for Modern Web Apps, powered by Vue & Tailwind CSS.",
|
||||||
"version": "3.1.1",
|
"version": "3.2.0",
|
||||||
"packageManager": "pnpm@10.10.0",
|
"packageManager": "pnpm@10.12.2",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/nuxt/ui.git"
|
"url": "git+https://github.com/nuxt/ui.git"
|
||||||
@@ -96,38 +96,37 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "nuxt-module-build build",
|
"build": "nuxt-module-build build",
|
||||||
"prepack": "pnpm build",
|
"prepack": "pnpm build",
|
||||||
"dev": "nuxi dev playground --uiDev",
|
"dev": "nuxt dev playground --uiDev",
|
||||||
"dev:build": "nuxi build playground",
|
"dev:build": "nuxt build playground",
|
||||||
"dev:vue": "vite playground-vue -- --uiDev",
|
"dev:vue": "vite playground-vue -- --uiDev",
|
||||||
"dev:vue:build": "vite build playground-vue",
|
"dev:vue:build": "vite build playground-vue",
|
||||||
"dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground && nuxi prepare docs && vite build playground-vue",
|
"dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxt prepare playground && nuxt prepare docs && vite build playground-vue",
|
||||||
"docs": "nuxi dev docs --uiDev",
|
"docs": "nuxt dev docs --uiDev",
|
||||||
"docs:build": "nuxi build docs",
|
"docs:build": "nuxt build docs",
|
||||||
"docs:prepare": "nuxt-component-meta docs",
|
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"lint:fix": "eslint . --fix",
|
"lint:fix": "eslint . --fix",
|
||||||
"typecheck": "vue-tsc --noEmit && nuxi typecheck playground && nuxi typecheck docs && cd playground-vue && vue-tsc --noEmit",
|
"typecheck": "vue-tsc --noEmit && nuxt typecheck playground && nuxt typecheck docs && cd playground-vue && vue-tsc --noEmit",
|
||||||
"test": "vitest",
|
"test": "vitest",
|
||||||
"test:vue": "vitest -c vitest.vue.config.ts",
|
"test:vue": "vitest -c vitest.vue.config.ts",
|
||||||
"release": "release-it"
|
"release": "release-it"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@iconify/vue": "^4.3.0",
|
"@iconify/vue": "^5.0.0",
|
||||||
"@internationalized/date": "^3.8.0",
|
"@internationalized/date": "^3.8.2",
|
||||||
"@internationalized/number": "^3.6.1",
|
"@internationalized/number": "^3.6.3",
|
||||||
"@nuxt/fonts": "^0.11.2",
|
"@nuxt/fonts": "^0.11.4",
|
||||||
"@nuxt/icon": "^1.12.0",
|
"@nuxt/icon": "^1.14.0",
|
||||||
"@nuxt/kit": "^3.17.2",
|
"@nuxt/kit": "^3.17.5",
|
||||||
"@nuxt/schema": "^3.17.2",
|
"@nuxt/schema": "^3.17.5",
|
||||||
"@nuxtjs/color-mode": "^3.5.2",
|
"@nuxtjs/color-mode": "^3.5.2",
|
||||||
"@standard-schema/spec": "^1.0.0",
|
"@standard-schema/spec": "^1.0.0",
|
||||||
"@tailwindcss/postcss": "^4.1.5",
|
"@tailwindcss/postcss": "^4.1.10",
|
||||||
"@tailwindcss/vite": "^4.1.5",
|
"@tailwindcss/vite": "^4.1.10",
|
||||||
"@tanstack/vue-table": "^8.21.3",
|
"@tanstack/vue-table": "^8.21.3",
|
||||||
"@unhead/vue": "^2.0.8",
|
"@unhead/vue": "^2.0.10",
|
||||||
"@vueuse/core": "^13.1.0",
|
"@vueuse/core": "^13.4.0",
|
||||||
"@vueuse/integrations": "^13.1.0",
|
"@vueuse/integrations": "^13.4.0",
|
||||||
"colortranslator": "^4.1.0",
|
"colortranslator": "^5.0.0",
|
||||||
"consola": "^3.4.2",
|
"consola": "^3.4.2",
|
||||||
"defu": "^6.1.4",
|
"defu": "^6.1.4",
|
||||||
"embla-carousel-auto-height": "^8.6.0",
|
"embla-carousel-auto-height": "^8.6.0",
|
||||||
@@ -144,29 +143,29 @@
|
|||||||
"mlly": "^1.7.4",
|
"mlly": "^1.7.4",
|
||||||
"ohash": "^2.0.11",
|
"ohash": "^2.0.11",
|
||||||
"pathe": "^2.0.3",
|
"pathe": "^2.0.3",
|
||||||
"reka-ui": "^2.2.1",
|
"reka-ui": "2.3.1",
|
||||||
"scule": "^1.3.0",
|
"scule": "^1.3.0",
|
||||||
"tailwind-variants": "^1.0.0",
|
"tailwind-variants": "^1.0.0",
|
||||||
"tailwindcss": "^4.1.5",
|
"tailwindcss": "^4.1.10",
|
||||||
"tinyglobby": "^0.2.13",
|
"tinyglobby": "^0.2.14",
|
||||||
"unplugin": "^2.3.2",
|
"unplugin": "^2.3.5",
|
||||||
"unplugin-auto-import": "^19.2.0",
|
"unplugin-auto-import": "^19.3.0",
|
||||||
"unplugin-vue-components": "^28.5.0",
|
"unplugin-vue-components": "^28.7.0",
|
||||||
"vaul-vue": "^0.4.1",
|
"vaul-vue": "0.4.1",
|
||||||
"vue-component-type-helpers": "^2.2.10"
|
"vue-component-type-helpers": "^2.2.10"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nuxt/eslint-config": "^1.3.0",
|
"@nuxt/eslint-config": "^1.4.1",
|
||||||
"@nuxt/module-builder": "^1.0.1",
|
"@nuxt/module-builder": "^1.0.1",
|
||||||
"@nuxt/test-utils": "^3.18.0",
|
"@nuxt/test-utils": "^3.19.1",
|
||||||
"@release-it/conventional-changelog": "^10.0.1",
|
"@release-it/conventional-changelog": "^10.0.1",
|
||||||
"@vue/test-utils": "^2.4.6",
|
"@vue/test-utils": "^2.4.6",
|
||||||
"embla-carousel": "^8.6.0",
|
"embla-carousel": "^8.6.0",
|
||||||
"eslint": "^9.26.0",
|
"eslint": "^9.29.0",
|
||||||
"happy-dom": "^17.4.6",
|
"happy-dom": "^18.0.1",
|
||||||
"nuxt": "^3.17.2",
|
"nuxt": "^3.17.5",
|
||||||
"release-it": "^19.0.2",
|
"release-it": "^19.0.3",
|
||||||
"vitest": "^3.1.3",
|
"vitest": "^3.2.4",
|
||||||
"vitest-environment-nuxt": "^1.0.1",
|
"vitest-environment-nuxt": "^1.0.1",
|
||||||
"vue-tsc": "^2.2.10"
|
"vue-tsc": "^2.2.10"
|
||||||
},
|
},
|
||||||
@@ -209,7 +208,7 @@
|
|||||||
"debug": "4.3.7",
|
"debug": "4.3.7",
|
||||||
"rollup": "4.34.9",
|
"rollup": "4.34.9",
|
||||||
"unimport": "4.1.1",
|
"unimport": "4.1.1",
|
||||||
"unplugin": "^2.3.2"
|
"unplugin": "^2.3.5"
|
||||||
},
|
},
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
"onlyBuiltDependencies": [
|
"onlyBuiltDependencies": [
|
||||||
|
|||||||
@@ -10,13 +10,13 @@
|
|||||||
"typecheck": "vue-tsc -p ./tsconfig.app.json"
|
"typecheck": "vue-tsc -p ./tsconfig.app.json"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nuxt/ui": "latest",
|
"@nuxt/ui": "workspace:*",
|
||||||
"vue": "^3.5.13",
|
"vue": "^3.5.17",
|
||||||
"vue-router": "^4.5.1",
|
"vue-router": "^4.5.1",
|
||||||
"zod": "^3.24.4"
|
"zod": "^3.25.67"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "^5.2.3",
|
"@vitejs/plugin-vue": "^5.2.4",
|
||||||
"typescript": "^5.8.3",
|
"typescript": "^5.8.3",
|
||||||
"vite": "^6.3.5",
|
"vite": "^6.3.5",
|
||||||
"vue-tsc": "^2.2.10"
|
"vue-tsc": "^2.2.10"
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ const components = [
|
|||||||
'input',
|
'input',
|
||||||
'input-menu',
|
'input-menu',
|
||||||
'input-number',
|
'input-number',
|
||||||
|
'input-tags',
|
||||||
'kbd',
|
'kbd',
|
||||||
'link',
|
'link',
|
||||||
'modal',
|
'modal',
|
||||||
@@ -61,6 +62,7 @@ const components = [
|
|||||||
'tabs',
|
'tabs',
|
||||||
'table',
|
'table',
|
||||||
'textarea',
|
'textarea',
|
||||||
|
'timeline',
|
||||||
'toast',
|
'toast',
|
||||||
'tooltip',
|
'tooltip',
|
||||||
'tree'
|
'tree'
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ const components = [
|
|||||||
'input',
|
'input',
|
||||||
'input-menu',
|
'input-menu',
|
||||||
'input-number',
|
'input-number',
|
||||||
|
'input-tags',
|
||||||
'kbd',
|
'kbd',
|
||||||
'link',
|
'link',
|
||||||
'modal',
|
'modal',
|
||||||
@@ -61,6 +62,7 @@ const components = [
|
|||||||
'tabs',
|
'tabs',
|
||||||
'table',
|
'table',
|
||||||
'textarea',
|
'textarea',
|
||||||
|
'timeline',
|
||||||
'toast',
|
'toast',
|
||||||
'tooltip',
|
'tooltip',
|
||||||
'tree'
|
'tree'
|
||||||
|
|||||||
@@ -36,14 +36,27 @@ const variants = Object.keys(theme.variants.variant) as Array<keyof typeof theme
|
|||||||
color="neutral"
|
color="neutral"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-2 ms-[-56px]">
|
<div class="flex items-center gap-2 ms-[-90px]">
|
||||||
<UBadge v-for="size in sizes" :key="size" label="Badge" :size="size" />
|
<UBadge v-for="size in sizes" :key="size" label="Badge" :size="size" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-2 ms-[-86px]">
|
<div class="flex items-center gap-2 ms-[-122px]">
|
||||||
<UBadge v-for="size in sizes" :key="size" icon="i-lucide-rocket" label="Badge" :size="size" />
|
<UBadge v-for="size in sizes" :key="size" icon="i-lucide-rocket" label="Badge" :size="size" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-2 ms-[-86px]">
|
<div class="flex items-center gap-2 ms-[-130px]">
|
||||||
<UBadge v-for="size in sizes" :key="size" :avatar="{ src: 'https://github.com/benjamincanac.png' }" label="Badge" :size="size" />
|
<UBadge v-for="size in sizes" :key="size" :avatar="{ src: 'https://github.com/benjamincanac.png' }" label="Badge" :size="size" />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex items-center gap-2 ms-[-52px]">
|
||||||
|
<UBadge v-for="size in sizes" :key="size" icon="i-lucide-rocket" :size="size" />
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2 ms-[-60px]">
|
||||||
|
<UBadge
|
||||||
|
v-for="size in sizes"
|
||||||
|
:key="size"
|
||||||
|
:avatar="{ src: 'https://github.com/benjamincanac.png' }"
|
||||||
|
:size="size"
|
||||||
|
color="neutral"
|
||||||
|
variant="outline"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -2,19 +2,23 @@
|
|||||||
import { CalendarDate } from '@internationalized/date'
|
import { CalendarDate } from '@internationalized/date'
|
||||||
|
|
||||||
const singleValue = shallowRef(new CalendarDate(2022, 1, 10))
|
const singleValue = shallowRef(new CalendarDate(2022, 1, 10))
|
||||||
const multipleValue = shallowRef({
|
const multipleValue = shallowRef([new CalendarDate(2022, 1, 10), new CalendarDate(2022, 1, 20)])
|
||||||
|
const rangeValue = shallowRef({
|
||||||
start: new CalendarDate(2022, 1, 10),
|
start: new CalendarDate(2022, 1, 10),
|
||||||
end: new CalendarDate(2022, 1, 20)
|
end: new CalendarDate(2022, 1, 20)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex gap-4">
|
||||||
<div class="flex justify-center gap-2">
|
<div class="flex justify-center gap-2">
|
||||||
<UCalendar v-model="singleValue" />
|
<UCalendar v-model="singleValue" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-center gap-2">
|
<div class="flex justify-center gap-2">
|
||||||
<UCalendar v-model="multipleValue" range />
|
<UCalendar v-model="multipleValue" multiple />
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-center gap-2">
|
||||||
|
<UCalendar v-model="rangeValue" range />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import themeCheckbox from '#build/ui/checkbox'
|
|
||||||
import theme from '#build/ui/checkbox-group'
|
import theme from '#build/ui/checkbox-group'
|
||||||
|
|
||||||
const sizes = Object.keys(theme.variants.size) as Array<keyof typeof theme.variants.size>
|
const sizes = Object.keys(theme.variants.size) as Array<keyof typeof theme.variants.size>
|
||||||
const variants = Object.keys(themeCheckbox.variants.variant)
|
const variants = Object.keys(theme.variants.variant)
|
||||||
const variant = ref('list' as const)
|
const variant = ref('list' as const)
|
||||||
|
|
||||||
const literalOptions = [
|
const literalOptions = [
|
||||||
|
|||||||
@@ -74,6 +74,51 @@ const groups = computed(() => [{
|
|||||||
toast.add({ title: 'Label added!' })
|
toast.add({ title: 'Label added!' })
|
||||||
},
|
},
|
||||||
kbds: ['meta', 'L']
|
kbds: ['meta', 'L']
|
||||||
|
}, {
|
||||||
|
label: 'More actions',
|
||||||
|
placeholder: 'Search actions...',
|
||||||
|
children: [{
|
||||||
|
label: 'Create new file',
|
||||||
|
suffix: 'Create a new file in the current directory or workspace.',
|
||||||
|
icon: 'i-lucide-file-plus',
|
||||||
|
onSelect(e: Event) {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
toast.add({ title: 'New file added!' })
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
label: 'Create new folder',
|
||||||
|
suffix: 'Create a new folder in the current directory or workspace.',
|
||||||
|
icon: 'i-lucide-folder-plus',
|
||||||
|
onSelect(e: Event) {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
toast.add({ title: 'New folder added!' })
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
label: 'Share',
|
||||||
|
placeholder: 'Search share options...',
|
||||||
|
icon: 'i-lucide-share',
|
||||||
|
children: [{
|
||||||
|
label: 'Share with everyone',
|
||||||
|
suffix: 'Share with everyone in the current directory or workspace.',
|
||||||
|
icon: 'i-lucide-share',
|
||||||
|
onSelect(e: Event) {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
toast.add({ title: 'Shared with everyone!' })
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
label: 'Share with team',
|
||||||
|
suffix: 'Share with the team in the current directory or workspace.',
|
||||||
|
icon: 'i-lucide-users',
|
||||||
|
onSelect(e: Event) {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
toast.add({ title: 'Shared with team!' })
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}]
|
||||||
}]
|
}]
|
||||||
}])
|
}])
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col items-center gap-4">
|
<div class="flex flex-col items-center gap-4">
|
||||||
<div class="flex flex-col gap-4 w-48">
|
<div class="flex flex-col gap-4 w-48">
|
||||||
<UInputMenu :items="items" autofocus placeholder="Search..." />
|
<UInputMenu :items="items" autofocus placeholder="Search..." default-value="Apple" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<UInputMenu
|
<UInputMenu
|
||||||
@@ -145,5 +145,18 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
|
|||||||
class="w-48"
|
class="w-48"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<UInputMenu
|
||||||
|
v-for="variant in variants"
|
||||||
|
:key="variant"
|
||||||
|
:items="items"
|
||||||
|
:model-value="[fruits[0]!]"
|
||||||
|
multiple
|
||||||
|
icon="i-lucide-search"
|
||||||
|
placeholder="Search..."
|
||||||
|
:variant="variant"
|
||||||
|
class="w-48"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
87
playground/app/pages/components/input-tags.vue
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { upperFirst } from 'scule'
|
||||||
|
import theme from '#build/ui/input-tags'
|
||||||
|
|
||||||
|
const sizes = Object.keys(theme.variants.size) as Array<keyof typeof theme.variants.size>
|
||||||
|
const variants = Object.keys(theme.variants.variant) as Array<keyof typeof theme.variants.variant>
|
||||||
|
|
||||||
|
const tags = ref(['Vue', 'Nuxt'])
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="flex flex-col items-center gap-4">
|
||||||
|
<div class="flex flex-col gap-4 w-48">
|
||||||
|
<UInputTags
|
||||||
|
v-model="tags"
|
||||||
|
placeholder="Enter tags..."
|
||||||
|
autofocus
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<UInputTags
|
||||||
|
v-for="variant in variants"
|
||||||
|
:key="variant"
|
||||||
|
:placeholder="upperFirst(variant)"
|
||||||
|
:variant="variant"
|
||||||
|
class="w-48"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<UInputTags
|
||||||
|
v-for="variant in variants"
|
||||||
|
:key="variant"
|
||||||
|
:placeholder="upperFirst(variant)"
|
||||||
|
:variant="variant"
|
||||||
|
color="neutral"
|
||||||
|
class="w-48"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<UInputTags
|
||||||
|
v-for="variant in variants"
|
||||||
|
:key="variant"
|
||||||
|
:placeholder="upperFirst(variant)"
|
||||||
|
:variant="variant"
|
||||||
|
color="error"
|
||||||
|
highlight
|
||||||
|
class="w-48"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-4 w-48">
|
||||||
|
<UInputTags placeholder="Disabled" disabled />
|
||||||
|
<UInputTags placeholder="Required" required />
|
||||||
|
<UInputTags loading placeholder="Loading..." />
|
||||||
|
<UInputTags loading trailing placeholder="Loading..." />
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<UInputTags
|
||||||
|
v-for="size in sizes"
|
||||||
|
:key="size"
|
||||||
|
:size="size"
|
||||||
|
:placeholder="upperFirst(size)"
|
||||||
|
class="w-48"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<UInputTags
|
||||||
|
v-for="size in sizes"
|
||||||
|
:key="size"
|
||||||
|
icon="i-lucide-search"
|
||||||
|
placeholder="Search..."
|
||||||
|
:size="size"
|
||||||
|
class="w-48"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<UInputTags
|
||||||
|
v-for="size in sizes"
|
||||||
|
:key="size"
|
||||||
|
icon="i-lucide-search"
|
||||||
|
trailing
|
||||||
|
placeholder="Search..."
|
||||||
|
:size="size"
|
||||||
|
class="w-48"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -69,5 +69,13 @@ function openModal() {
|
|||||||
</UModal>
|
</UModal>
|
||||||
|
|
||||||
<UButton label="Open programmatically" color="neutral" variant="outline" @click="openModal" />
|
<UButton label="Open programmatically" color="neutral" variant="outline" @click="openModal" />
|
||||||
|
|
||||||
|
<UModal title="First modal">
|
||||||
|
<UButton color="neutral" variant="outline" label="Close with scoped slot close" />
|
||||||
|
|
||||||
|
<template #footer="{ close }">
|
||||||
|
<UButton label="Close with scoped slot close" @click="close" />
|
||||||
|
</template>
|
||||||
|
</UModal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||