Compare commits

...

60 Commits

Author SHA1 Message Date
Benjamin Canac
f05dbd26d1 chore(release): v3.0.2 2025-03-28 16:57:21 +01:00
Benjamin Canac
9be36d328c playground(app): add title 2025-03-28 16:55:11 +01:00
renovate[bot]
4f28e1fe96 chore(deps): update all non-major dependencies (v3) (#3698)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-28 16:43:12 +01:00
Eugen Istoc
bd99c2d850 fix(useOverlay): refine open method type to infer close emit return type (#3716) 2025-03-28 16:42:48 +01:00
Benjamin Canac
23bfeb9370 fix(vue): mock nuxtApp.hooks & useRuntimeHook 2025-03-28 15:07:54 +01:00
仿生狮子
88f349d0d7 fix(Avatar): proxy $attrs to default slot (#3712) 2025-03-28 10:27:25 +01:00
Daniele Nicosia
e7e6745599 fix(types): add missing export for ButtonGroup (#3709) 2025-03-27 23:41:33 +01:00
Benjamin Canac
d2c832292a chore(deps): update @nuxt/ui-pro 2025-03-27 23:22:44 +01:00
Benjamin Canac
bc61d29cce fix(CommandPalette): use group.id as key 2025-03-27 21:32:38 +01:00
Malik
777fe4a309 playground: improve rtl (#3707) 2025-03-27 19:18:58 +01:00
Benjamin Canac
3074632523 fix(InputMenu): reset searchTerm on update:open
Resolves #3620
2025-03-27 16:27:48 +01:00
Benjamin Canac
94b6e520f5 fix(InputMenu/SelectMenu): empty search results 2025-03-27 12:18:46 +01:00
Benjamin Canac
754dd36473 docs(SupportedLanguages): reorder mapping 2025-03-27 12:16:39 +01:00
Benjamin Canac
67a52b6f4e test(Table): update snapshots 2025-03-26 17:40:36 +01:00
Benjamin Canac
2e7d4989b5 chore(github): prevent running module nuxt-ui-pro job on forks 2025-03-26 17:33:32 +01:00
Benjamin Canac
c07a79571f docs(slug): add purchase and affilitate in community links 2025-03-26 17:31:24 +01:00
Benjamin Canac
4cebdb5152 chore(deps): update @nuxt/ui-pro 2025-03-26 17:31:19 +01:00
Benjamin Canac
06dfb963be chore(CommandPalette): improve placeholder tsdoc 2025-03-26 17:31:19 +01:00
Benjamin Canac
4ebb94cd7e fix(Table): wrong condition on caption slot 2025-03-26 17:31:19 +01:00
Benjamin Canac
afff54fecd feat(Table): add empty prop 2025-03-26 17:31:18 +01:00
renovate[bot]
7ec08017e0 chore(deps): update all non-major dependencies (v3) (#3695)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-26 15:36:26 +01:00
Romain Hamel
3dd88bacec fix(Form): clear dirty state after submit (#3692) 2025-03-26 13:48:28 +01:00
renovate[bot]
d6d7971d44 chore(deps): update tailwindcss to ^4.0.17 (v3) (#3690)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-26 13:48:02 +01:00
Romain Hamel
20c33920d0 fix(FormField): add help to aria-describedby attribute (#3691) 2025-03-26 13:47:38 +01:00
Stijn Slats
a63047b79a docs(accordion): add drag and drop example (#3684)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2025-03-26 11:07:47 +01:00
renovate[bot]
f3e32ba5a2 chore(deps): update all non-major dependencies (v3) (#3667)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-26 10:59:21 +01:00
renovate[bot]
bd3f54aa80 chore(deps): update tailwindcss to ^4.0.16 (v3) (#3682)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-26 10:44:06 +01:00
Hugo Richard
9509c37af8 docs: improve lighthouse score and accessibility (#3673) 2025-03-25 16:23:35 +01:00
Benjamin Canac
df00149598 fix(Container): add w-full class 2025-03-25 16:23:05 +01:00
Bobbie Goede
f72c886d3a docs(i18n): remove next tag from @nuxtjs/i18n installation (#3675) 2025-03-25 10:07:58 +01:00
Benjamin Canac
c531d0248b fix(Link): handle aria-current like NuxtLink / RouterLink 2025-03-24 23:57:46 +01:00
Benjamin Canac
d73768b704 fix(Link): prevent active="true" binding on html 2025-03-24 23:56:28 +01:00
Sandro Circi
b9983549a4 fix(components): improve generic types (#3331)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2025-03-24 21:38:13 +01:00
Benjamin Canac
370054b20c fix(Link): proxy onClick
Resolves #3631
2025-03-24 19:08:09 +01:00
Hugo Richard
4a2b77d86c feat(Calendar): allow year and month buttons styling (#3672) 2025-03-24 19:00:28 +01:00
Benjamin Canac
ade16b76cf fix(Link): properly pick all aria-* & data-* attrs 2025-03-24 18:58:49 +01:00
Benjamin Canac
1babad4f74 chore(github): update stale to close needs reproduction 2025-03-24 16:22:22 +01:00
Benjamin Canac
8fb8dc29cf docs(nuxt.config): update vite.optimizeDeps 2025-03-24 16:22:22 +01:00
renovate[bot]
e0ec60d1b1 chore(deps): update devdependency wrangler to v4 (v3) (#3554)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-24 15:36:34 +01:00
Benjamin Canac
3db3058395 docs(index): improve marquees performances 2025-03-24 15:33:01 +01:00
Benjamin Canac
0095d8916b fix(NavigationMenu): add z-index on viewport
Resolves #3654
2025-03-24 15:11:47 +01:00
Sébastien Chopin
58ae62413d docs: add missing redirect 2025-03-24 14:49:05 +01:00
Benjamin Canac
1965495768 chore(deps): remove typescript resolution 2025-03-24 14:41:50 +01:00
Benjamin Canac
5465fcb61d chore(deps): update peerDependencies 2025-03-24 14:12:19 +01:00
Benjamin Canac
ef8ebaf687 chore(renovate): ignore peerDependencies 2025-03-24 14:01:57 +01:00
Benjamin Canac
1094881878 playground(deps): add zod 2025-03-24 14:01:14 +01:00
Romain Hamel
d6fd18fc4f chore(deps): declare form validation libraries as peerDependencies (#3666) 2025-03-24 13:53:35 +01:00
Benjamin Canac
e2e079f0d8 chore(github): improve module workflow 2025-03-24 12:37:04 +01:00
Benjamin Canac
cec9dadc7a chore(docs/playground): add vite.optimizeDeps 2025-03-24 12:37:04 +01:00
Vahe Sargsyan
c76f590097 feat(locale): add Armenian language (#3664) 2025-03-24 11:29:32 +01:00
renovate[bot]
4dee7c3bd3 chore(deps): lock file maintenance (v3) (#3662)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-24 11:26:00 +01:00
renovate[bot]
47bbe35d2a chore(deps): update all non-major dependencies (v3) (#3636)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2025-03-24 11:16:11 +01:00
Benjamin Canac
60bcd4fdf0 chore(defineLocale): put back @__NO_SIDE_EFFECTS__ 2025-03-24 11:03:26 +01:00
Benjamin Canac
c231fe5f26 fix(Button): use focus:outline-none instead of focus:outline-hidden
Resolves #3658
2025-03-24 10:50:06 +01:00
Benjamin Canac
1769d5ed6e fix(Tabs): remove focus:outline-hidden class 2025-03-24 10:48:50 +01:00
Benjamin Canac
68787b26fd fix(Switch): prevent transition on focus outline 2025-03-24 10:48:03 +01:00
Benjamin Canac
f7604e565f fix(Drawer): remove fadeFromIndex prop proxy 2025-03-23 15:56:33 +01:00
Benjamin Canac
82e26655a4 fix(defineLocale/defineShortcuts): remove @__NO_SIDE_EFFECTS__ 2025-03-23 14:53:58 +01:00
Benjamin Canac
2453714136 chore(deps): move @standard-schema/spec to dependencies 2025-03-23 12:52:33 +01:00
Benjamin Canac
973f36539d chore(github): enable ui pro integration 2025-03-21 21:24:47 +01:00
196 changed files with 5955 additions and 3724 deletions

View File

@@ -50,16 +50,19 @@ jobs:
run: pnpm run typecheck
- name: Test
run: pnpm run test
run: pnpm run test run
- name: Test (vue)
run: pnpm run test:vue
run: pnpm run test:vue run
- name: Build
run: pnpm run build
- name: Build vue fixture
run: pnpm run test:vue:build
- name: Build playground
run: pnpm run dev:build
- name: Build playground (vue)
run: pnpm run dev:vue:build
- name: Publish
run: pnpx pkg-pr-new publish --compact --no-template --pnpm
@@ -154,54 +157,57 @@ jobs:
- name: Build
run: pnpm run build
# nuxt-ui-pro:
# needs: build
nuxt-ui-pro:
needs: build
# runs-on: ${{ matrix.os }}
# Only run this job if not a fork PR (when push event or PR from same repo)
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository
# permissions:
# contents: read
# pull-requests: read
runs-on: ${{ matrix.os }}
# strategy:
# matrix:
# os: [ubuntu-latest] # macos-latest, windows-latest
# node: [22]
permissions:
contents: read
pull-requests: read
# env:
# NUXT_UI_PRO_LICENSE: ${{ secrets.NUXT_UI_PRO_LICENSE }}
strategy:
matrix:
os: [ubuntu-latest] # macos-latest, windows-latest
node: [22]
# steps:
# - name: Checkout
# uses: actions/checkout@v4
# with:
# repository: nuxt/ui-pro
# token: ${{ secrets.NUXT_GITHUB_TOKEN }}
env:
NUXT_UI_PRO_LICENSE: ${{ secrets.NUXT_UI_PRO_LICENSE }}
# - name: Store commit SHA
# run: |
# echo "COMMIT_SHA=$(echo ${{ github.workflow_sha }} | cut -c1-7)" >> $GITHUB_ENV
steps:
- name: Checkout
uses: actions/checkout@v4
with:
repository: nuxt/ui-pro
token: ${{ secrets.NUXT_GITHUB_TOKEN }}
# - name: Install pnpm
# uses: pnpm/action-setup@v4
- name: Store commit SHA
run: |
echo "COMMIT_SHA=$(echo ${{ github.workflow_sha }} | cut -c1-7)" >> $GITHUB_ENV
# - name: Install node
# uses: actions/setup-node@v4
# with:
# node-version: 22
# cache: pnpm
- name: Install pnpm
uses: pnpm/action-setup@v4
# - name: Install latest nuxt/ui
# run: pnpm install https://pkg.pr.new/@nuxt/ui@${{ env.COMMIT_SHA }} --lockfile-only
- name: Install node
uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
# - name: Install dependencies
# run: pnpm install
- name: Install latest nuxt/ui
run: pnpm install https://pkg.pr.new/@nuxt/ui@${{ env.COMMIT_SHA }} --lockfile-only
# - name: Prepare
# run: pnpm run dev:prepare
- name: Install dependencies
run: pnpm install
# - name: Typecheck
# run: pnpm run typecheck
- name: Prepare
run: pnpm run dev:prepare
# - name: Build
# run: pnpm run build
- name: Typecheck
run: pnpm run typecheck
- name: Build
run: pnpm run build

View File

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

View File

@@ -1,5 +1,38 @@
# Changelog
## [3.0.2](https://github.com/nuxt/ui/compare/v3.0.1...v3.0.2) (2025-03-28)
### Features
* **Calendar:** allow year and month buttons styling ([#3672](https://github.com/nuxt/ui/issues/3672)) ([4a2b77d](https://github.com/nuxt/ui/commit/4a2b77d86c28806234002340eda39de4dc78cce0))
* **locale:** add Armenian language ([#3664](https://github.com/nuxt/ui/issues/3664)) ([c76f590](https://github.com/nuxt/ui/commit/c76f5900970e3f5c451192b1207ccea04771e8b3))
* **Table:** add `empty` prop ([afff54f](https://github.com/nuxt/ui/commit/afff54fecd31497238461e0a44abd8668ed734c3))
### Bug Fixes
* **Avatar:** proxy `$attrs` to default slot ([#3712](https://github.com/nuxt/ui/issues/3712)) ([88f349d](https://github.com/nuxt/ui/commit/88f349d0d74eb1c2ce5066818731759c25a9e83e))
* **Button:** use `focus:outline-none` instead of `focus:outline-hidden` ([c231fe5](https://github.com/nuxt/ui/commit/c231fe5f26ca7614df46a7ec8a5ce7f4ec8884e7)), closes [#3658](https://github.com/nuxt/ui/issues/3658)
* **CommandPalette:** use `group.id` as key ([bc61d29](https://github.com/nuxt/ui/commit/bc61d29cce531715a6279444845f02a002a22af7))
* **components:** improve generic types ([#3331](https://github.com/nuxt/ui/issues/3331)) ([b998354](https://github.com/nuxt/ui/commit/b9983549a4b743724ea3ef99cc4a243f5ca41e53))
* **Container:** add `w-full` class ([df00149](https://github.com/nuxt/ui/commit/df001495980647cab1e67fd16154f1bc778de5e2))
* **defineLocale/defineShortcuts:** remove `@__NO_SIDE_EFFECTS__` ([82e2665](https://github.com/nuxt/ui/commit/82e26655a40782555299516f32a76046fa0dbd3a))
* **Drawer:** remove `fadeFromIndex` prop proxy ([f7604e5](https://github.com/nuxt/ui/commit/f7604e565f717001a4d4c2974cf23559a3f01c21))
* **Form:** clear dirty state after submit ([#3692](https://github.com/nuxt/ui/issues/3692)) ([3dd88ba](https://github.com/nuxt/ui/commit/3dd88bacecb2945efba8cc3cb4fe59fcbc056e9a))
* **FormField:** add `help` to `aria-describedby` attribute ([#3691](https://github.com/nuxt/ui/issues/3691)) ([20c3392](https://github.com/nuxt/ui/commit/20c33920d005332db3c83f33a8c54c7c227ce0a0))
* **InputMenu/SelectMenu:** empty search results ([94b6e52](https://github.com/nuxt/ui/commit/94b6e520f5ccf011204e953421fcc5b44b637e51))
* **InputMenu:** reset `searchTerm` on `update:open` ([3074632](https://github.com/nuxt/ui/commit/3074632523e67fa6a0ad3d9a71e5692c285bdc3a)), closes [#3620](https://github.com/nuxt/ui/issues/3620)
* **Link:** handle `aria-current` like `NuxtLink` / `RouterLink` ([c531d02](https://github.com/nuxt/ui/commit/c531d0248be7863980a1f676643c2dea8301c009))
* **Link:** prevent `active="true"` binding on html ([d73768b](https://github.com/nuxt/ui/commit/d73768b70453d60dd4186a996c1cf808b0294bf6))
* **Link:** properly pick all `aria-*` & `data-*` attrs ([ade16b7](https://github.com/nuxt/ui/commit/ade16b76cf535924a8d0f402b4d5d65cb67a55eb))
* **Link:** proxy `onClick` ([370054b](https://github.com/nuxt/ui/commit/370054b20c0201c9dba84ddfcd1e916594619b93)), closes [#3631](https://github.com/nuxt/ui/issues/3631)
* **NavigationMenu:** add `z-index` on viewport ([0095d89](https://github.com/nuxt/ui/commit/0095d8916bf361c0c89972e2f86b79850510c6a9)), closes [#3654](https://github.com/nuxt/ui/issues/3654)
* **Switch:** prevent transition on focus outline ([68787b2](https://github.com/nuxt/ui/commit/68787b26fdf2bd5f9d9e812e5bfddb19abe45d1d))
* **Table:** wrong condition on `caption` slot ([4ebb94c](https://github.com/nuxt/ui/commit/4ebb94cd7ef909b3547bce0922f75fe3ff74de4c))
* **Tabs:** remove `focus:outline-hidden` class ([1769d5e](https://github.com/nuxt/ui/commit/1769d5ed6ea46b1f7eafdc48cb6456512229f98b))
* **types:** add missing export for ButtonGroup ([#3709](https://github.com/nuxt/ui/issues/3709)) ([e7e6745](https://github.com/nuxt/ui/commit/e7e674559981177ad08be42418746060d7737df9))
* **useOverlay:** refine `open` method type to infer close emit return type ([#3716](https://github.com/nuxt/ui/issues/3716)) ([bd99c2d](https://github.com/nuxt/ui/commit/bd99c2d850d57baccc51e049c0b578a6fc6ab431))
* **vue:** mock `nuxtApp.hooks` & `useRuntimeHook` ([23bfeb9](https://github.com/nuxt/ui/commit/23bfeb937004d619187a67fb43e4c76b13d00069))
## [3.0.1](https://github.com/nuxt/ui/compare/v3.0.0...v3.0.1) (2025-03-21)
### ⚠ BREAKING CHANGES

View File

@@ -38,7 +38,7 @@ onMounted(() => {
}
.carbon-poweredby {
@apply block text-[10px] text-center text-(--ui-text-dimmed) pt-2;
@apply block text-xs text-center text-(--ui-text-muted) pt-2;
}
&:hover {

View File

@@ -1,5 +1,6 @@
<!-- eslint-disable no-useless-escape -->
<script setup lang="ts">
import type { ChipProps } from '@nuxt/ui'
import json5 from 'json5'
import { upperFirst, camelCase, kebabCase } from 'scule'
import { hash } from 'ohash'
@@ -53,6 +54,8 @@ const props = defineProps<{
hide?: string[]
/** List of props to externalize in script setup */
external?: string[]
/** The types of the externalized props */
externalTypes?: string[]
/** List of props to use with `v-model` */
model?: string[]
/** List of props to cast from code and selection */
@@ -209,11 +212,21 @@ ${props.slots?.default}
code += `
<script setup lang="ts">
`
for (const key of props.external) {
if (props.externalTypes?.length) {
const removeArrayBrackets = (type: string): string => type.endsWith('[]') ? removeArrayBrackets(type.slice(0, -2)) : type
const types = props.externalTypes.map(type => removeArrayBrackets(type))
code += `import type { ${types.join(', ')} } from '@nuxt/ui${props.pro ? '-pro' : ''}'
`
}
for (const [i, key] of props.external.entries()) {
const cast = props.cast?.[key]
const value = cast ? castMap[cast]!.template(componentProps[key]) : json5.stringify(componentProps[key], null, 2)?.replace(/,([ |\t\n]+[}|\]])/g, '$1')
const type = props.externalTypes?.[i] ? `<${props.externalTypes[i]}>` : ''
code += `const ${key === 'modelValue' ? 'value' : key} = ref(${value})
code += `const ${key === 'modelValue' ? 'value' : key} = ref${type}(${value})
`
}
code += `<\/script>
@@ -346,7 +359,7 @@ const { data: ast } = await useAsyncData(`component-code-${name}-${hash({ props:
inset
standalone
:color="(modelValue as any)"
:size="ui.itemLeadingChipSize()"
:size="(ui.itemLeadingChipSize() as ChipProps['size'])"
class="size-2"
/>
</template>

View File

@@ -1,4 +1,5 @@
<script setup lang="ts">
import type { ChipProps } from '@nuxt/ui'
import { camelCase } from 'scule'
import { useElementSize } from '@vueuse/core'
import { get, set } from '#ui/utils'
@@ -185,7 +186,7 @@ const urlSearchParams = computed(() => {
inset
standalone
:color="(modelValue as any)"
:size="ui.itemLeadingChipSize()"
:size="(ui.itemLeadingChipSize() as ChipProps['size'])"
class="size-2"
/>
</template>

View File

@@ -12,14 +12,15 @@ function getEmojiFlag(locale: string): string {
ar: 'sa', // Arabic -> Saudi Arabia
bn: 'bd', // Bengali -> Bangladesh
ca: 'es', // Catalan -> Spain
cs: 'cz', // Czech -> Czech Republic (note: modern country code is actually 'cz')
ckb: 'iq', // Central Kurdish -> Iraq
cs: 'cz', // Czech -> Czech Republic (note: modern country code is actually 'cz')
da: 'dk', // Danish -> Denmark
el: 'gr', // Greek -> Greece
et: 'ee', // Estonian -> Estonia
en: 'gb', // English -> Great Britain
et: 'ee', // Estonian -> Estonia
he: 'il', // Hebrew -> Israel
hi: 'in', // Hindi -> India
hy: 'am', // Armenian -> Armenia
ja: 'jp', // Japanese -> Japan
km: 'kh', // Khmer -> Cambodia
ko: 'kr', // Korean -> South Korea

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,7 @@
<script setup lang="ts">
const items = [{
import type { BreadcrumbItem } from '@nuxt/ui'
const items: BreadcrumbItem[] = [{
label: 'Home',
to: '/'
}, {

View File

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

View File

@@ -11,7 +11,7 @@ const groups = [{
label: 'Billing',
icon: 'i-lucide-credit-card',
kbds: ['meta', 'B'],
slot: 'billing'
slot: 'billing' as const
},
{
label: 'Notifications',
@@ -25,7 +25,7 @@ const groups = [{
}, {
id: 'users',
label: 'Users',
slot: 'users',
slot: 'users' as const,
items: [
{
label: 'Benjamin Canac',

View File

@@ -1,8 +1,10 @@
<script setup lang="ts">
import type { ContextMenuItem } from '@nuxt/ui'
const showSidebar = ref(true)
const showToolbar = ref(false)
const items = computed(() => [{
const items = computed<ContextMenuItem[]>(() => [{
label: 'View',
type: 'label' as const
}, {

View File

@@ -1,5 +1,7 @@
<script setup lang="ts">
const items = [
import type { ContextMenuItem } from '@nuxt/ui'
const items: ContextMenuItem[][] = [
[
{
label: 'View',

View File

@@ -1,7 +1,9 @@
<script setup lang="ts">
import type { ContextMenuItem } from '@nuxt/ui'
const loading = ref(true)
const items = [{
const items: ContextMenuItem[] = [{
label: 'Refresh the Page',
slot: 'refresh'
}, {

View File

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

View File

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

View File

@@ -1,15 +1,19 @@
<script setup lang="ts">
const items = [{
label: 'Profile',
icon: 'i-lucide-user',
slot: 'profile'
}, {
label: 'Billing',
icon: 'i-lucide-credit-card'
}, {
label: 'Settings',
icon: 'i-lucide-cog'
}]
import type { DropdownMenuItem } from '@nuxt/ui'
const items = [
{
label: 'Profile',
icon: 'i-lucide-user',
slot: 'profile' as const
}, {
label: 'Billing',
icon: 'i-lucide-credit-card'
}, {
label: 'Settings',
icon: 'i-lucide-cog'
}
] satisfies DropdownMenuItem[]
</script>
<template>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,9 +1,11 @@
<script setup lang="ts">
import type { NavigationMenuItem } from '@nuxt/ui'
const items = [
{
label: 'Docs',
icon: 'i-lucide-book-open',
slot: 'docs',
slot: 'docs' as const,
children: [
{
label: 'Icons',
@@ -22,7 +24,7 @@ const items = [
{
label: 'Components',
icon: 'i-lucide-box',
slot: 'components',
slot: 'components' as const,
children: [
{
label: 'Link',
@@ -54,7 +56,7 @@ const items = [
label: 'GitHub',
icon: 'i-simple-icons-github'
}
]
] satisfies NavigationMenuItem[]
</script>
<template>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,6 @@
<script setup lang="ts">
import type { AvatarProps } from '@nuxt/ui'
const searchTerm = ref('')
const searchTermDebounced = refDebounced(searchTerm, 200)
@@ -10,7 +12,7 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
label: user.name,
value: String(user.id),
avatar: { src: `https://i.pravatar.cc/120?img=${user.id}` }
})) || []
}))
},
lazy: true
})
@@ -19,7 +21,7 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
<template>
<USelectMenu
v-model:search-term="searchTerm"
:items="users || []"
:items="users"
:loading="status === 'pending'"
ignore-filter
icon="i-lucide-user"
@@ -30,7 +32,7 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
<UAvatar
v-if="modelValue"
v-bind="modelValue.avatar"
:size="ui.leadingAvatarSize()"
:size="(ui.leadingAvatarSize() as AvatarProps['size'])"
:class="ui.leadingAvatar()"
/>
</template>

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,6 @@
<script setup lang="ts">
import type { SelectItem } from '@nuxt/ui'
const items = ref([
{
label: 'benjamincanac',
@@ -23,13 +25,21 @@ const items = ref([
src: 'https://github.com/noook.png',
alt: 'noook'
}
},
{
label: 'sandros94',
value: 'sandros94',
avatar: {
src: 'https://github.com/sandros94.png',
alt: 'sandros94'
}
}
])
] satisfies SelectItem[])
const value = ref(items.value[0]?.value)
const avatar = computed(() => items.value.find(item => item.value === value.value)?.avatar)
</script>
<template>
<USelect v-model="value" :avatar="avatar" :items="items" class="w-48" />
<USelect v-model="value" :items="items" value-key="value" :avatar="avatar" class="w-48" />
</template>

View File

@@ -1,27 +1,30 @@
<script setup lang="ts">
import type { SelectItem, ChipProps } from '@nuxt/ui'
const items = ref([
{
label: 'bug',
value: 'bug',
chip: {
color: 'error' as const
color: 'error'
}
},
{
label: 'feature',
value: 'feature',
chip: {
color: 'success' as const
color: 'success'
}
},
{
label: 'enhancement',
value: 'enhancement',
chip: {
color: 'info' as const
color: 'info'
}
}
])
] satisfies SelectItem[])
const value = ref(items.value[0]?.value)
function getChip(value: string) {
@@ -30,14 +33,14 @@ function getChip(value: string) {
</script>
<template>
<USelect v-model="value" :items="items" class="w-48">
<USelect v-model="value" :items="items" value-key="value" class="w-48">
<template #leading="{ modelValue, ui }">
<UChip
v-if="modelValue"
v-bind="getChip(modelValue as string)"
v-bind="getChip(modelValue)"
inset
standalone
:size="ui.itemLeadingChipSize()"
:size="(ui.itemLeadingChipSize() as ChipProps['size'])"
:class="ui.itemLeadingChip()"
/>
</template>

View File

@@ -1,4 +1,6 @@
<script setup lang="ts">
import type { SelectItem } from '@nuxt/ui'
const items = ref([
{
label: 'Backlog',
@@ -20,12 +22,12 @@ const items = ref([
value: 'done',
icon: 'i-lucide-circle-check'
}
])
] satisfies SelectItem[])
const value = ref(items.value[0]?.value)
const icon = computed(() => items.value.find(item => item.value === value.value)?.icon)
</script>
<template>
<USelect v-model="value" :icon="icon" :items="items" class="w-48" />
<USelect v-model="value" :items="items" value-key="value" :icon="icon" class="w-48" />
</template>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -145,12 +145,12 @@ const columns: TableColumn<Payment>[] = [{
header: ({ table }) => h(UCheckbox, {
'modelValue': table.getIsSomePageRowsSelected() ? 'indeterminate' : table.getIsAllPageRowsSelected(),
'onUpdate:modelValue': (value: boolean | 'indeterminate') => table.toggleAllPageRowsSelected(!!value),
'ariaLabel': 'Select all'
'aria-label': 'Select all'
}),
cell: ({ row }) => h(UCheckbox, {
'modelValue': row.getIsSelected(),
'onUpdate:modelValue': (value: boolean | 'indeterminate') => row.toggleSelected(!!value),
'ariaLabel': 'Select row'
'aria-label': 'Select row'
}),
enableSorting: false,
enableHiding: false
@@ -242,15 +242,17 @@ const columns: TableColumn<Payment>[] = [{
}]
return h('div', { class: 'text-right' }, h(UDropdownMenu, {
content: {
'content': {
align: 'end'
},
items
items,
'aria-label': 'Actions dropdown'
}, () => h(UButton, {
icon: 'i-lucide-ellipsis-vertical',
color: 'neutral',
variant: 'ghost',
class: 'ml-auto'
'icon': 'i-lucide-ellipsis-vertical',
'color': 'neutral',
'variant': 'ghost',
'class': 'ml-auto',
'aria-label': 'Actions dropdown'
})))
}
}]
@@ -294,6 +296,7 @@ function randomize() {
variant="outline"
trailing-icon="i-lucide-chevron-down"
class="ml-auto"
aria-label="Columns select dropdown"
/>
</UDropdownMenu>
</div>

View File

@@ -17,7 +17,7 @@ const { data, status } = await useFetch<User[]>('https://jsonplaceholder.typicod
transform: (data) => {
return data?.map(user => ({
...user,
avatar: { src: `https://i.pravatar.cc/120?img=${user.id}` }
avatar: { src: `https://i.pravatar.cc/120?img=${user.id}`, alt: `${user.name} avatar` }
})) || []
},
lazy: true

View File

@@ -97,15 +97,17 @@ const columns: TableColumn<Payment>[] = [{
id: 'actions',
cell: ({ row }) => {
return h('div', { class: 'text-right' }, h(UDropdownMenu, {
content: {
'content': {
align: 'end'
},
items: getRowItems(row)
'items': getRowItems(row),
'aria-label': 'Actions dropdown'
}, () => h(UButton, {
icon: 'i-lucide-ellipsis-vertical',
color: 'neutral',
variant: 'ghost',
class: 'ml-auto'
'icon': 'i-lucide-ellipsis-vertical',
'color': 'neutral',
'variant': 'ghost',
'class': 'ml-auto',
'aria-label': 'Actions dropdown'
})))
}
}]

View File

@@ -48,14 +48,15 @@ const data = ref<Payment[]>([{
const columns: TableColumn<Payment>[] = [{
id: 'expand',
cell: ({ row }) => h(UButton, {
color: 'neutral',
variant: 'ghost',
icon: 'i-lucide-chevron-down',
square: true,
ui: {
'color': 'neutral',
'variant': 'ghost',
'icon': 'i-lucide-chevron-down',
'square': true,
'aria-label': 'Expand',
'ui': {
leadingIcon: ['transition-transform', row.getIsExpanded() ? 'duration-200 rotate-180' : '']
},
onClick: () => row.toggleExpanded()
'onClick': () => row.toggleExpanded()
})
}, {
accessorKey: 'id',

View File

@@ -50,12 +50,12 @@ const columns: TableColumn<Payment>[] = [{
header: ({ table }) => h(UCheckbox, {
'modelValue': table.getIsSomePageRowsSelected() ? 'indeterminate' : table.getIsAllPageRowsSelected(),
'onUpdate:modelValue': (value: boolean | 'indeterminate') => table.toggleAllPageRowsSelected(!!value),
'ariaLabel': 'Select all'
'aria-label': 'Select all'
}),
cell: ({ row }) => h(UCheckbox, {
'modelValue': row.getIsSelected(),
'onUpdate:modelValue': (value: boolean | 'indeterminate') => row.toggleSelected(!!value),
'ariaLabel': 'Select row'
'aria-label': 'Select row'
})
}, {
accessorKey: 'date',

View File

@@ -50,12 +50,12 @@ const columns: TableColumn<Payment>[] = [{
header: ({ table }) => h(UCheckbox, {
'modelValue': table.getIsSomePageRowsSelected() ? 'indeterminate' : table.getIsAllPageRowsSelected(),
'onUpdate:modelValue': (value: boolean | 'indeterminate') => table.toggleAllPageRowsSelected(!!value),
'ariaLabel': 'Select all'
'aria-label': 'Select all'
}),
cell: ({ row }) => h(UCheckbox, {
'modelValue': row.getIsSelected(),
'onUpdate:modelValue': (value: boolean | 'indeterminate') => row.toggleSelected(!!value),
'ariaLabel': 'Select row'
'aria-label': 'Select row'
})
}, {
accessorKey: 'date',

View File

@@ -95,7 +95,7 @@ function getDropdownActions(user: User): DropdownMenuItem[][] {
<UTable :data="data" :columns="columns" class="flex-1">
<template #name-cell="{ row }">
<div class="flex items-center gap-3">
<UAvatar :src="`https://i.pravatar.cc/120?img=${row.original.id}`" size="lg" />
<UAvatar :src="`https://i.pravatar.cc/120?img=${row.original.id}`" size="lg" :alt="`${row.original.name} avatar`" />
<div>
<p class="font-medium text-(--ui-text-highlighted)">
{{ row.original.name }}
@@ -108,7 +108,7 @@ function getDropdownActions(user: User): DropdownMenuItem[][] {
</template>
<template #action-cell="{ row }">
<UDropdownMenu :items="getDropdownActions(row.original)">
<UButton icon="i-lucide-ellipsis-vertical" color="neutral" variant="ghost" />
<UButton icon="i-lucide-ellipsis-vertical" color="neutral" variant="ghost" aria-label="Actions" />
</UDropdownMenu>
</template>
</UTable>

View File

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

View File

@@ -1,18 +1,20 @@
<script setup lang="ts">
import type { TabsItem } from '@nuxt/ui'
const items = [
{
label: 'Account',
description: 'Make changes to your account here. Click save when you\'re done.',
icon: 'i-lucide-user',
slot: 'account'
slot: 'account' as const
},
{
label: 'Password',
description: 'Change your password here. After saving, you\'ll be logged out.',
icon: 'i-lucide-lock',
slot: 'password'
slot: 'password' as const
}
]
] satisfies TabsItem[]
const state = reactive({
name: 'Benjamin Canac',

View File

@@ -1,5 +1,7 @@
<script setup lang="ts">
const items = [
import type { TabsItem } from '@nuxt/ui'
const items: TabsItem[] = [
{
label: 'Account'
},

View File

@@ -1,10 +1,10 @@
<script setup lang="ts">
import type { TreeItem } from '@nuxt/ui'
const items: TreeItem[] = [
const items = [
{
label: 'app/',
slot: 'app',
slot: 'app' as const,
defaultExpanded: true,
children: [{
label: 'composables/',
@@ -24,7 +24,7 @@ const items: TreeItem[] = [
},
{ label: 'app.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'nuxt.config.ts', icon: 'i-vscode-icons-file-type-nuxt' }
]
] satisfies TreeItem[]
</script>
<template>

View File

@@ -25,7 +25,7 @@ const items: TreeItem[] = [
{ label: 'nuxt.config.ts', icon: 'i-vscode-icons-file-type-nuxt' }
]
const value = ref(items[items.length - 1])
const value = ref()
</script>
<template>

View File

@@ -1,7 +1,8 @@
<script setup lang="ts">
import { kebabCase } from 'scule'
import type { ContentNavigationItem } from '@nuxt/content'
import { findPageBreadcrumb, mapContentNavigation } from '#ui-pro/utils/content'
import type { PageLink } from '@nuxt/ui-pro'
import { findPageBreadcrumb, mapContentNavigation } from '@nuxt/ui-pro/utils/content'
const route = useRoute()
const { framework, module } = useSharedData()
@@ -100,6 +101,16 @@ const communityLinks = computed(() => [{
label: 'Star on GitHub',
to: `https://github.com/nuxt/${page.value?.module === 'ui-pro' ? 'ui-pro' : 'ui'}`,
target: '_blank'
}, module.value === 'ui-pro' && {
icon: 'i-lucide-credit-card',
label: 'Purchase a license',
to: 'https://nuxt.lemonsqueezy.com/checkout/buy/057dacb2-87ba-4dc1-9256-59ee5b3bd394',
target: '_blank'
}, module.value === 'ui-pro' && {
icon: 'i-lucide-ticket-percent',
label: 'Become an affiliate',
to: 'https://nuxt.lemonsqueezy.com/affiliates',
target: '_blank'
}, {
icon: 'i-lucide-life-buoy',
label: 'Contribution',
@@ -108,7 +119,7 @@ const communityLinks = computed(() => [{
label: 'Roadmap',
icon: 'i-lucide-map',
to: '/roadmap'
}])
}].filter(Boolean) as PageLink[])
</script>
<template>
@@ -136,7 +147,7 @@ const communityLinks = computed(() => [{
v-bind="link"
>
<template v-if="link.avatar" #leading>
<UAvatar v-bind="link.avatar" size="2xs" />
<UAvatar v-bind="link.avatar" size="2xs" :alt="`${link.label} avatar`" />
</template>
</UButton>
</template>

View File

@@ -169,6 +169,7 @@ onMounted(() => {
:loading="index >= 4 ? 'lazy' : 'eager'"
width="640"
height="360"
:alt="`${component.name} preview`"
/>
</div>
</UPageCard>

View File

@@ -103,10 +103,14 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
:to="component.path"
>
<UColorModeImage
:light="`${component.path.replace('/components/', '/components/light/')}.png`"
:dark="`${component.path.replace('/components/', '/components/dark/')}.png`"
:alt="`${component.title} preview`"
width="290"
height="163"
format="webp"
class="hover:scale-105 lg:hover:scale-110 transition-transform aspect-video w-full border-x lg:border-x-0 lg:border-y border-(--ui-border) 2xl:border-y-0"
loading="lazy"
/>
<UBadge color="neutral" variant="outline" size="md" :label="component.title" class="hidden lg:block absolute mx-auto top-4 left-6 xl:left-4 group-hover/link:opacity-100 opacity-0 transition-all duration-300 pointer-events-none -translate-y-2 group-hover/link:translate-y-0" />
</ULink>
@@ -130,7 +134,12 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
<UColorModeImage
:light="`${component.path.replace('/components/', '/components/light/')}.png`"
:dark="`${component.path.replace('/components/', '/components/dark/')}.png`"
:alt="`${component.title} preview`"
width="290"
height="163"
format="webp"
class="hover:scale-105 lg:hover:scale-110 transition-transform aspect-video w-full border-x lg:border-x-0 lg:border-y border-(--ui-border) 2xl:border-y-0"
loading="lazy"
/>
<UBadge color="neutral" variant="outline" size="md" :label="component.title" class="hidden lg:block absolute mx-auto top-4 left-6 xl:left-4 group-hover/link:opacity-100 opacity-0 transition-all duration-300 pointer-events-none -translate-y-2 group-hover/link:translate-y-0" />
</ULink>
@@ -152,7 +161,9 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
:in-view-options="{ once: true }"
class="flex items-start gap-x-3 relative group"
>
<NuxtLink v-if="feature.to" :to="feature.to" class="absolute inset-0 z-10" />
<NuxtLink v-if="feature.to" :to="feature.to" class="absolute inset-0 z-10">
<span class="sr-only">Go to {{ feature.title }}</span>
</NuxtLink>
<div class="relative p-3">
<svg class="absolute inset-0" viewBox="0 0 44 44" fill="none" xmlns="http://www.w3.org/2000/svg">
@@ -218,26 +229,32 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
class="border-b border-(--ui-border)"
>
<template #features>
<NuxtLink to="https://npm.chart.dev/@nuxt/ui" target="_blank" class="min-w-0">
<p class="text-4xl font-semibold text-(--ui-text-highlighted) truncate">
{{ format(module?.stats?.downloads ?? 0) }}+
</p>
<p class="text-(--ui-text-muted) text-sm truncate">monthly downloads</p>
</NuxtLink>
<li>
<NuxtLink to="https://npm.chart.dev/@nuxt/ui" target="_blank" class="min-w-0">
<p class="text-4xl font-semibold text-(--ui-text-highlighted) truncate">
{{ format(module?.stats?.downloads ?? 0) }}+
</p>
<p class="text-(--ui-text-muted) text-sm truncate">monthly downloads</p>
</NuxtLink>
</li>
<NuxtLink to="https://github.com/nuxt/ui" target="_blank" class="min-w-0">
<p class="text-4xl font-semibold text-(--ui-text-highlighted) truncate">
{{ format(module?.stats?.stars ?? 0) }}+
</p>
<p class="text-(--ui-text-muted) text-sm truncate">GitHub stars</p>
</NuxtLink>
<li>
<NuxtLink to="https://github.com/nuxt/ui" target="_blank" class="min-w-0">
<p class="text-4xl font-semibold text-(--ui-text-highlighted) truncate">
{{ format(module?.stats?.stars ?? 0) }}+
</p>
<p class="text-(--ui-text-muted) text-sm truncate">GitHub stars</p>
</NuxtLink>
</li>
<NuxtLink to="https://github.com/nuxt/ui/graphs/contributors" target="_blank" class="min-w-0">
<p class="text-4xl font-semibold text-(--ui-text-highlighted) truncate">
175+
</p>
<p class="text-(--ui-text-muted) text-sm truncate">Contributors</p>
</NuxtLink>
<li>
<NuxtLink to="https://github.com/nuxt/ui/graphs/contributors" target="_blank" class="min-w-0">
<p class="text-4xl font-semibold text-(--ui-text-highlighted) truncate">
175+
</p>
<p class="text-(--ui-text-muted) text-sm truncate">Contributors</p>
</NuxtLink>
</li>
</template>
<div ref="contributorsRef" class="p-4 sm:px-6 md:px-8 lg:px-12 xl:px-14 overflow-hidden flex relative">

View File

@@ -56,6 +56,7 @@ useSeoMeta({
v-if="template.thumbnail"
v-bind="template.thumbnail"
class="w-full h-auto border lg:border-y lg:border-x-0 border-(--ui-border) rounded-(--ui-radius) lg:rounded-none"
:alt="`Template ${index} thumbnail`"
width="656"
height="369"
loading="lazy"

View File

@@ -6,7 +6,7 @@ navigation.icon: i-lucide-house
<iframe width="100%" height="100%" src="https://www.youtube-nocookie.com/embed/_eQxomah-nA?si=pDSzchUBDKb2NQu7" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen style="aspect-ratio: 16/9;" class="rounded-[calc(var(--ui-radius)*1.5)]"></iframe>
### Reka UI
## Reka UI
We've transitioned from [Headless UI](https://headlessui.com/) to [Reka UI](https://reka-ui.com/) as our core component foundation. This shift brings several key advantages:
@@ -17,7 +17,7 @@ We've transitioned from [Headless UI](https://headlessui.com/) to [Reka UI](http
This transition empowers Nuxt UI to become a more comprehensive and flexible UI library, offering developers greater power and customization options.
### Tailwind CSS v4
## Tailwind CSS v4
Nuxt UI integrates the latest Tailwind CSS v4, bringing significant improvements:
@@ -30,7 +30,7 @@ Nuxt UI integrates the latest Tailwind CSS v4, bringing significant improvements
Learn about all the breaking changes in Tailwind CSS v4.
::
### Tailwind Variants
## Tailwind Variants
We've adopted [Tailwind Variants](https://www.tailwind-variants.org/) to manage our design system, offering:
@@ -40,7 +40,7 @@ We've adopted [Tailwind Variants](https://www.tailwind-variants.org/) to manage
This integration unifies the styling of components, ensuring consistency and code maintainability.
### TypeScript Integration
## TypeScript Integration
Nuxt UI offers significantly improved TypeScript integration, providing a superior developer experience:
@@ -60,7 +60,7 @@ Nuxt UI offers significantly improved TypeScript integration, providing a superi
Check out an example of the Accordion component with auto-completion for props and slots.
::
### Vue compatibility
## Vue compatibility
You can now use Nuxt UI in any Vue project without Nuxt by adding the Vite and Vue plugins to your configuration. This provides:
@@ -72,7 +72,7 @@ You can now use Nuxt UI in any Vue project without Nuxt by adding the Vite and V
Learn how to install and configure Nuxt UI in a Vue project in the **Vue installation guide**.
::
### Nuxt DevTools Integration
## Nuxt DevTools Integration
You can play with Nuxt UI components as well as your app components directly from Nuxt Devtools with the [compodium](https://github.com/romhml/compodium) module, providing a powerful development experience:

View File

@@ -136,19 +136,19 @@ To dynamically switch between languages, you can use the [Nuxt I18n](https://i18
::code-group{sync="pm"}
```bash [pnpm]
pnpm add @nuxtjs/i18n@next
pnpm add @nuxtjs/i18n
```
```bash [yarn]
yarn add @nuxtjs/i18n@next
yarn add @nuxtjs/i18n
```
```bash [npm]
npm install @nuxtjs/i18n@next
npm install @nuxtjs/i18n
```
```bash [bun]
bun add @nuxtjs/i18n@next
bun add @nuxtjs/i18n
```
::

View File

@@ -30,6 +30,8 @@ ignore:
- items
external:
- items
externalTypes:
- AccordionItem[]
hide:
- class
props:
@@ -58,6 +60,8 @@ ignore:
- items
external:
- items
externalTypes:
- AccordionItem[]
hide:
- class
props:
@@ -87,6 +91,8 @@ ignore:
- items
external:
- items
externalTypes:
- AccordionItem[]
hide:
- class
props:
@@ -115,6 +121,8 @@ ignore:
- items
external:
- items
externalTypes:
- AccordionItem[]
hide:
- class
props:
@@ -149,6 +157,8 @@ ignore:
- items
external:
- items
externalTypes:
- AccordionItem[]
hide:
- class
props:
@@ -182,6 +192,8 @@ ignore:
- items
external:
- items
externalTypes:
- AccordionItem[]
hide:
- class
props:
@@ -280,6 +292,18 @@ props:
---
::
### With drag and drop
Use the [`useSortable`](https://vueuse.org/integrations/useSortable/) composable from [`@vueuse/integrations`](https://vueuse.org/integrations/README.html) to enable drag and drop functionality on the accordion. This integration wraps [Sortable.js](https://sortablejs.github.io/Sortable/) to provide a seamless drag and drop experience.
The `useSortable` composable accepts various options, see the [Usage](https://vueuse.org/integrations/useSortable/#usage) for more examples.
::component-example
---
name: 'accordion-drag-and-drop-example'
---
::
## API
### Props

View File

@@ -27,6 +27,8 @@ ignore:
- items
external:
- items
externalTypes:
- BreadcrumbItem[]
props:
items:
- label: 'Home'
@@ -54,6 +56,8 @@ ignore:
- items
external:
- items
externalTypes:
- BreadcrumbItem[]
props:
separatorIcon: 'i-lucide-arrow-right'
items:

View File

@@ -44,6 +44,8 @@ ignore:
- ui.content
external:
- items
externalTypes:
- ContextMenuItem[][]
props:
items:
- - label: Appearance
@@ -124,6 +126,8 @@ ignore:
- ui.content
external:
- items
externalTypes:
- ContextMenuItem[]
props:
size: xl
items:
@@ -158,6 +162,8 @@ ignore:
- ui.content
external:
- items
externalTypes:
- ContextMenuItem[]
props:
disabled: true
items:

View File

@@ -44,6 +44,8 @@ ignore:
- ui.content
external:
- items
externalTypes:
- DropdownMenuItem[][]
props:
items:
- - label: Benjamin
@@ -123,6 +125,8 @@ ignore:
- ui.content
external:
- items
externalTypes:
- DropdownMenuItem[]
items:
content.align:
- start
@@ -169,6 +173,8 @@ ignore:
- ui.content
external:
- items
externalTypes:
- DropdownMenuItem[]
props:
arrow: true
items:
@@ -202,6 +208,8 @@ ignore:
- ui.content
external:
- items
externalTypes:
- DropdownMenuItem[]
props:
size: xl
items:
@@ -244,6 +252,8 @@ ignore:
- ui.content
external:
- items
externalTypes:
- DropdownMenuItem[]
props:
disabled: true
items:
@@ -334,7 +344,9 @@ Inside the `defineShortcuts` composable, there is an `extractShortcuts` utility
```vue
<script setup lang="ts">
const items = [{
import type { DropdownMenuItem } from '@nuxt/ui'
const items: DropdownMenuItem[] = [{
label: 'Invite users',
icon: 'i-lucide-user-plus',
children: [{

View File

@@ -39,6 +39,8 @@ ignore:
- class
external:
- items
externalTypes:
- NavigationMenuItem[]
props:
items:
- label: Guide
@@ -148,6 +150,8 @@ ignore:
- class
external:
- items
externalTypes:
- NavigationMenuItem[][]
props:
orientation: 'vertical'
items:
@@ -247,6 +251,8 @@ ignore:
- class
external:
- items
externalTypes:
- NavigationMenuItem[][]
props:
highlight: true
highlightColor: 'primary'
@@ -346,6 +352,8 @@ ignore:
- class
external:
- items
externalTypes:
- NavigationMenuItem[][]
props:
color: neutral
items:
@@ -379,6 +387,8 @@ ignore:
- class
external:
- items
externalTypes:
- NavigationMenuItem[][]
props:
color: neutral
variant: link
@@ -423,6 +433,8 @@ ignore:
- class
external:
- items
externalTypes:
- NavigationMenuItem[]
props:
trailingIcon: 'i-lucide-arrow-down'
items:
@@ -519,6 +531,8 @@ ignore:
- class
external:
- items
externalTypes:
- NavigationMenuItem[]
props:
arrow: true
items:
@@ -611,6 +625,8 @@ ignore:
- class
external:
- items
externalTypes:
- NavigationMenuItem[]
props:
arrow: true
contentOrientation: 'vertical'
@@ -682,6 +698,8 @@ ignore:
- class
external:
- items
externalTypes:
- NavigationMenuItem[]
props:
unmountOnHide: false
items:

View File

@@ -28,6 +28,9 @@ ignore:
external:
- items
- modelValue
externalTypes:
- RadioGroupItem[]
- RadioGroupValue
props:
modelValue: 'System'
items:
@@ -52,6 +55,9 @@ ignore:
external:
- items
- modelValue
externalTypes:
- RadioGroupItem[]
- RadioGroupValue
props:
modelValue: 'system'
items:
@@ -84,6 +90,9 @@ ignore:
external:
- items
- modelValue
externalTypes:
- RadioGroupItem[]
- RadioGroupValue
props:
modelValue: 'light'
valueKey: 'id'
@@ -112,6 +121,8 @@ ignore:
- items
external:
- items
externalTypes:
- RadioGroupItem[]
props:
legend: 'Theme'
defaultValue: 'System'
@@ -134,6 +145,8 @@ ignore:
- items
external:
- items
externalTypes:
- RadioGroupItem[]
props:
orientation: 'horizontal'
defaultValue: 'System'
@@ -156,6 +169,8 @@ ignore:
- items
external:
- items
externalTypes:
- RadioGroupItem[]
props:
color: neutral
defaultValue: 'System'
@@ -178,6 +193,8 @@ ignore:
- items
external:
- items
externalTypes:
- RadioGroupItem[]
props:
size: 'xl'
defaultValue: 'System'
@@ -200,6 +217,8 @@ ignore:
- items
external:
- items
externalTypes:
- RadioGroupItem[]
props:
disabled: true
defaultValue: 'System'

View File

@@ -31,6 +31,8 @@ ignore:
- class
external:
- items
externalTypes:
- StepperItem[]
props:
items:
- title: 'Address'
@@ -61,6 +63,8 @@ ignore:
- class
external:
- items
externalTypes:
- StepperItem[]
props:
color: neutral
items:
@@ -88,6 +92,8 @@ ignore:
- class
external:
- items
externalTypes:
- StepperItem[]
props:
size: xl
items:
@@ -115,6 +121,8 @@ ignore:
- class
external:
- items
externalTypes:
- StepperItem[]
props:
orientation: vertical
items:
@@ -142,6 +150,8 @@ ignore:
- class
external:
- items
externalTypes:
- StepperItem[]
props:
disabled: true
items:

View File

@@ -23,7 +23,7 @@ class: '!p-0'
---
::
::callout{icon="i-simple-icons-github" to="https://github.com/nuxt/ui/tree/v3/docs/app/components/content/examples/table/TableExample.vue"}
::callout{icon="i-simple-icons-github" to="https://github.com/nuxt/ui/tree/v3/docs/app/components/content/examples/table/TableExample.vue" aria-label="View source code"}
This example demonstrates the most common use case of the `Table` component. Check out the source code on GitHub.
::
@@ -85,7 +85,7 @@ Use the `columns` prop as an array of [ColumnDef](https://tanstack.com/table/lat
In order to render components or other HTML elements, you will need to use the Vue [`h` function](https://vuejs.org/api/render-function.html#h) inside the `header` and `cell` props. This is different from other components that use slots but allows for more flexibility.
::tip{to="#with-slots"}
::tip{to="#with-slots" aria-label="Table columns with slots"}
You can also use slots to customize the header and data cells of the table.
::

View File

@@ -31,6 +31,8 @@ ignore:
- class
external:
- items
externalTypes:
- TabsItem[]
props:
items:
- label: Account
@@ -55,6 +57,8 @@ ignore:
- class
external:
- items
externalTypes:
- TabsItem[]
props:
content: false
items:
@@ -80,6 +84,8 @@ ignore:
- class
external:
- items
externalTypes:
- TabsItem[]
props:
unmountOnHide: false
items:
@@ -109,6 +115,8 @@ ignore:
- class
external:
- items
externalTypes:
- TabsItem[]
props:
color: neutral
content: false
@@ -131,6 +139,8 @@ ignore:
- class
external:
- items
externalTypes:
- TabsItem[]
props:
color: neutral
variant: link
@@ -154,6 +164,8 @@ ignore:
- class
external:
- items
externalTypes:
- TabsItem[]
props:
size: md
variant: pill
@@ -177,6 +189,8 @@ ignore:
- class
external:
- items
externalTypes:
- TabsItem[]
props:
orientation: vertical
variant: pill

View File

@@ -139,6 +139,7 @@ export default defineNuxtConfig({
'/pro/components/pricing-grid': { redirect: { to: '/components/pricing-plans', statusCode: 301 }, prerender: false },
'/pro/components/pricing-switch': { redirect: { to: '/components/switch', statusCode: 301 }, prerender: false },
'/pro/components/**': { redirect: { to: '/components/**', statusCode: 301 }, prerender: false },
'/getting-started/shortcuts': { redirect: { to: '/composables/define-shortcuts', statusCode: 301 }, prerender: false },
'/releases': { redirect: 'https://github.com/nuxt/ui/releases', prerender: false }
},
@@ -182,6 +183,10 @@ export default defineNuxtConfig({
fs: {
allow: process.env.NUXT_UI_PRO_PATH ? [resolve(process.env.NUXT_UI_PRO_PATH)] : undefined
}
},
optimizeDeps: {
// prevents reloading page when navigating between components
include: ['@internationalized/date', '@vueuse/shared', '@vueuse/integrations/useFuse', '@tanstack/vue-table', 'reka-ui', 'reka-ui/namespaced', 'embla-carousel-vue', 'embla-carousel-autoplay', 'embla-carousel-auto-scroll', 'embla-carousel-auto-height', 'embla-carousel-class-names', 'embla-carousel-fade', 'embla-carousel-wheel-gestures', 'colortranslator', 'tailwindcss/colors', 'tailwind-variants', 'ufo', 'zod', 'vaul-vue', 'scule', 'motion-v', 'json5', 'ohash', 'shiki-transformer-color-highlight']
}
},
@@ -220,6 +225,7 @@ export default defineNuxtConfig({
},
image: {
format: ['webp', 'jpeg', 'jpg', 'png', 'svg'],
provider: 'ipx'
},

View File

@@ -4,24 +4,26 @@
"type": "module",
"dependencies": {
"@iconify-json/logos": "^1.2.4",
"@iconify-json/lucide": "^1.2.31",
"@iconify-json/lucide": "^1.2.33",
"@iconify-json/simple-icons": "^1.2.29",
"@iconify-json/vscode-icons": "^1.2.16",
"@iconify-json/vscode-icons": "^1.2.17",
"@nuxt/content": "^3.4.0",
"@nuxt/image": "^1.10.0",
"@nuxt/ui": "latest",
"@nuxt/ui-pro": "https://pkg.pr.new/@nuxt/ui-pro@d96a086",
"@nuxthub/core": "^0.8.18",
"@nuxt/ui-pro": "https://pkg.pr.new/@nuxt/ui-pro@63da8be",
"@nuxthub/core": "^0.8.21",
"@nuxtjs/plausible": "^1.2.0",
"@octokit/rest": "^21.1.1",
"@rollup/plugin-yaml": "^4.1.2",
"@vueuse/nuxt": "^13.0.0",
"@vueuse/integrations": "^13.0.0",
"sortablejs": "^1.15.6",
"joi": "^17.13.3",
"motion-v": "0.13.1",
"nuxt": "^3.16.1",
"nuxt-component-meta": "^0.10.0",
"nuxt-llms": "^0.1.0",
"nuxt-og-image": "^5.0.5",
"nuxt-component-meta": "^0.10.1",
"nuxt-llms": "^0.1.2",
"nuxt-og-image": "^5.1.1",
"prettier": "^3.5.3",
"shiki-transformer-color-highlight": "^1.0.0",
"superstruct": "^2.0.2",
@@ -31,6 +33,6 @@
"zod": "^3.24.2"
},
"devDependencies": {
"wrangler": "^3.114.2"
"wrangler": "^4.6.0"
}
}

View File

@@ -1,8 +1,8 @@
{
"name": "@nuxt/ui",
"description": "A UI Library for Modern Web Apps, powered by Vue & Tailwind CSS.",
"version": "3.0.1",
"packageManager": "pnpm@10.6.5",
"version": "3.0.2",
"packageManager": "pnpm@10.7.0",
"repository": {
"type": "git",
"url": "git+https://github.com/nuxt/ui.git"
@@ -63,8 +63,9 @@
"build": "nuxt-module-build build",
"prepack": "pnpm build",
"dev": "DEV=true nuxi dev playground",
"dev:vue": "DEV=true vite playground-vue",
"dev:build": "nuxi build playground",
"dev:vue": "DEV=true vite 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",
"docs": "DEV=true nuxi dev docs",
"docs:build": "NODE_OPTIONS='--max-old-space-size=8192' nuxi build docs",
@@ -74,7 +75,6 @@
"typecheck": "vue-tsc --noEmit && nuxi typecheck playground && nuxi typecheck docs && cd playground-vue && vue-tsc --noEmit",
"test": "vitest",
"test:vue": "vitest -c vitest.vue.config.ts",
"test:vue:build": "vite build playground-vue",
"release": "release-it"
},
"dependencies": {
@@ -86,10 +86,11 @@
"@nuxt/kit": "^3.16.1",
"@nuxt/schema": "^3.16.1",
"@nuxtjs/color-mode": "^3.5.2",
"@tailwindcss/postcss": "^4.0.15",
"@tailwindcss/vite": "^4.0.15",
"@standard-schema/spec": "^1.0.0",
"@tailwindcss/postcss": "^4.0.17",
"@tailwindcss/vite": "^4.0.17",
"@tanstack/vue-table": "^8.21.2",
"@unhead/vue": "^2.0.0-rc.13",
"@unhead/vue": "^2.0.2",
"@vueuse/core": "^13.0.0",
"@vueuse/integrations": "^13.0.0",
"colortranslator": "^4.1.0",
@@ -103,18 +104,19 @@
"embla-carousel-vue": "^8.5.2",
"embla-carousel-wheel-gestures": "^8.0.1",
"fuse.js": "^7.1.0",
"hookable": "^5.5.3",
"knitwork": "^1.2.0",
"magic-string": "^0.30.17",
"mlly": "^1.7.4",
"ohash": "^2.0.11",
"pathe": "^2.0.3",
"reka-ui": "^2.1.0",
"reka-ui": "^2.1.1",
"scule": "^1.3.0",
"tailwind-variants": "^1.0.0",
"tailwindcss": "^4.0.15",
"tailwindcss": "^4.0.17",
"tinyglobby": "^0.2.12",
"unplugin": "^2.2.1",
"unplugin-auto-import": "^19.1.1",
"unplugin": "^2.2.2",
"unplugin-auto-import": "^19.1.2",
"unplugin-vue-components": "^28.4.1",
"vaul-vue": "^0.4.1",
"vue": "^3.5.13",
@@ -125,32 +127,47 @@
"@nuxt/module-builder": "^0.8.4",
"@nuxt/test-utils": "^3.17.2",
"@release-it/conventional-changelog": "^10.0.0",
"@standard-schema/spec": "^1.0.0",
"@vue/test-utils": "^2.4.6",
"embla-carousel": "^8.5.2",
"eslint": "^9.22.0",
"eslint": "^9.23.0",
"happy-dom": "^17.4.4",
"joi": "^17.13.3",
"nuxt": "^3.16.1",
"release-it": "^18.1.2",
"superstruct": "^2.0.2",
"valibot": "^1.0.0",
"vitest": "^3.0.9",
"vitest-environment-nuxt": "^1.0.1",
"vue-tsc": "^2.2.0",
"yup": "^1.6.1",
"zod": "^3.24.2"
"vue-tsc": "^2.2.0"
},
"peerDependencies": {
"typescript": "^5.6.3"
"joi": "^17.13.0",
"superstruct": "^2.0.0",
"typescript": "^5.6.3",
"valibot": "^1.0.0",
"yup": "^1.6.0",
"zod": "^3.24.0"
},
"peerDependenciesMeta": {
"joi": {
"optional": true
},
"valibot": {
"optional": true
},
"superstruct": {
"optional": true
},
"yup": {
"optional": true
},
"zod": {
"optional": true
}
},
"resolutions": {
"@nuxt/ui": "workspace:*",
"chokidar": "3.6.0",
"debug": "4.3.7",
"rollup": "4.34.9",
"typescript": "5.6.3",
"unplugin": "^2.2.1",
"unplugin": "^2.2.2",
"vue-tsc": "2.2.0"
},
"pnpm": {

View File

@@ -12,12 +12,13 @@
"dependencies": {
"@nuxt/ui": "latest",
"vue": "^3.5.13",
"vue-router": "^4.5.0"
"vue-router": "^4.5.0",
"zod": "^3.24.2"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.2.3",
"typescript": "^5.6.3",
"vite": "^6.2.2",
"typescript": "^5.8.2",
"vite": "^6.2.3",
"vue-tsc": "^2.2.0"
}
}

View File

@@ -88,7 +88,7 @@ defineShortcuts({
<UNavigationMenu :items="items" orientation="vertical" class="hidden lg:flex border-e border-(--ui-border) overflow-y-auto w-48 p-4" />
<UNavigationMenu :items="items" orientation="horizontal" class="lg:hidden border-b border-(--ui-border) [&>div]:min-w-min overflow-x-auto" />
<div class="fixed top-15 lg:top-3 right-4 flex items-center gap-2">
<div class="fixed top-15 lg:top-3 end-4 flex items-center gap-2">
<UButton
:icon="mode === 'dark' ? 'i-lucide-moon' : 'i-lucide-sun'"
color="neutral"

View File

@@ -29,6 +29,6 @@ export default defineConfig({
],
optimizeDeps: {
// prevents reloading page when navigating between components
include: ['reka-ui/namespaced', 'vaul-vue', 'embla-carousel-vue', 'embla-carousel-autoplay', 'embla-carousel-auto-scroll', 'embla-carousel-auto-height', 'embla-carousel-class-names', 'embla-carousel-fade', 'embla-carousel-wheel-gestures']
include: ['@unhead/vue/client', '@iconify/vue', '@internationalized/date', '@vueuse/shared', '@vueuse/integrations/useFuse', '@tanstack/vue-table', 'reka-ui', 'reka-ui/namespaced', 'embla-carousel-vue', 'embla-carousel-autoplay', 'embla-carousel-auto-scroll', 'embla-carousel-auto-height', 'embla-carousel-class-names', 'embla-carousel-fade', 'embla-carousel-wheel-gestures', 'colortranslator', 'tailwindcss/colors', 'tailwind-variants', 'ufo', 'zod', 'vaul-vue']
}
})

View File

@@ -80,6 +80,10 @@ function onSelect(item: any) {
defineShortcuts({
meta_k: () => isCommandPaletteOpen.value = true
})
useHead({
title: 'Nuxt UI - Playground'
})
</script>
<template>
@@ -89,7 +93,7 @@ defineShortcuts({
<UNavigationMenu :items="items" orientation="vertical" class="hidden lg:flex border-e border-(--ui-border) overflow-y-auto w-48 p-4" />
<UNavigationMenu :items="items" orientation="horizontal" class="lg:hidden border-b border-(--ui-border) [&>div]:min-w-min overflow-x-auto" />
<div class="fixed top-15 lg:top-3 right-4 flex items-center gap-2">
<div class="fixed top-15 lg:top-3 end-4 flex items-center gap-2">
<ClientOnly v-if="!colorMode?.forced">
<UButton
:icon="isDark ? 'i-lucide-moon' : 'i-lucide-sun'"

View File

@@ -18,6 +18,7 @@ const items = [{
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed neque elit, tristique placerat feugiat ac, facilisis vitae arcu. Proin eget egestas augue. Praesent ut sem nec arcu pellentesque aliquet. Duis dapibus diam vel metus tempus vulputate.'
}, {
label: 'Components',
slot: 'test' as const,
icon: 'i-lucide-layers-3',
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed neque elit, tristique placerat feugiat ac, facilisis vitae arcu. Proin eget egestas augue. Praesent ut sem nec arcu pellentesque aliquet. Duis dapibus diam vel metus tempus vulputate.'
}, {
@@ -37,6 +38,11 @@ const items = [{
</p>
</template>
<template #custom="{ item }">
<p class="text-(--ui-text-muted)">
Custom: {{ item.content }}
</p>
</template>
<template #custom-body="{ item }">
<p class="text-(--ui-text-muted)">
Custom: {{ item.content }}

View File

@@ -150,10 +150,6 @@ defineShortcuts(extractShortcuts(items.value))
<UDropdownMenu :items="itemsWithColor" :size="size" arrow :content="{ side: 'bottom', align: 'start' }" :ui="{ content: 'w-48' }">
<UButton label="Color" color="neutral" variant="outline" icon="i-lucide-menu" />
<template #custom-trailing>
<UIcon name="i-lucide-badge-check" class="shrink-0 size-5 text-(--ui-primary)" />
</template>
</UDropdownMenu>
</div>
</div>

View File

@@ -1,4 +1,6 @@
<script setup lang="ts">
import type { InputMenuItem, AvatarProps } from '@nuxt/ui'
import { upperFirst } from 'scule'
import { refDebounced } from '@vueuse/core'
import type { User } from '~/types'
@@ -10,7 +12,7 @@ const variants = Object.keys(theme.variants.variant) as Array<keyof typeof theme
const fruits = ['Apple', 'Banana', 'Blueberry', 'Grapes', 'Pineapple']
const vegetables = ['Aubergine', 'Broccoli', 'Carrot', 'Courgette', 'Leek']
const items = [[{ label: 'Fruits', type: 'label' }, ...fruits], [{ label: 'Vegetables', type: 'label' }, ...vegetables]]
const items = [[{ label: 'Fruits', type: 'label' as const }, ...fruits], [{ label: 'Vegetables', type: 'label' as const }, ...vegetables]]
const selectedItems = ref([fruits[0]!, vegetables[0]!])
const statuses = [{
@@ -28,7 +30,7 @@ const statuses = [{
}, {
label: 'Canceled',
icon: 'i-lucide-circle-x'
}]
}] satisfies InputMenuItem[]
const searchTerm = ref('')
const searchTermDebounced = refDebounced(searchTerm, 200)
@@ -126,7 +128,7 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
class="w-48"
>
<template #leading="{ modelValue, ui }">
<UAvatar v-if="modelValue?.avatar" :size="ui.itemLeadingAvatarSize()" v-bind="modelValue.avatar" />
<UAvatar v-if="modelValue?.avatar" :size="(ui.itemLeadingAvatarSize() as AvatarProps['size'])" v-bind="modelValue.avatar" />
</template>
</UInputMenu>
</div>

View File

@@ -1,4 +1,6 @@
<script setup lang="ts">
import type { SelectMenuItem, AvatarProps } from '@nuxt/ui'
import { upperFirst } from 'scule'
import { refDebounced } from '@vueuse/core'
import theme from '#build/ui/select-menu'
@@ -10,7 +12,7 @@ const variants = Object.keys(theme.variants.variant) as Array<keyof typeof theme
const fruits = ['Apple', 'Banana', 'Blueberry', 'Grapes', 'Pineapple']
const vegetables = ['Aubergine', 'Broccoli', 'Carrot', 'Courgette', 'Leek']
const items = [[{ label: 'Fruits', type: 'label' }, ...fruits], [{ label: 'Vegetables', type: 'label' }, ...vegetables]]
const items = [[{ label: 'Fruits', type: 'label' }, ...fruits], [{ label: 'Vegetables', type: 'label' }, ...vegetables]] satisfies SelectMenuItem[][]
const selectedItems = ref([fruits[0]!, vegetables[0]!])
const statuses = [{
@@ -33,7 +35,7 @@ const statuses = [{
label: 'Canceled',
value: 'canceled',
icon: 'i-lucide-circle-x'
}]
}] satisfies SelectMenuItem[]
const searchTerm = ref('')
const searchTermDebounced = refDebounced(searchTerm, 200)
@@ -41,7 +43,7 @@ const searchTermDebounced = refDebounced(searchTerm, 200)
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
params: { q: searchTermDebounced },
transform: (data: User[]) => {
return data?.map(user => ({ id: user.id, label: user.name, avatar: { src: `https://i.pravatar.cc/120?img=${user.id}` } })) || []
return data?.map(user => ({ id: user.id, label: user.name, avatar: { src: `https://i.pravatar.cc/120?img=${user.id}` } }))
},
lazy: true
})
@@ -122,7 +124,7 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
v-for="size in sizes"
:key="size"
v-model:search-term="searchTerm"
:items="users || []"
:items="users"
:loading="status === 'pending'"
ignore-filter
icon="i-lucide-user"
@@ -132,7 +134,7 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
@update:open="searchTerm = ''"
>
<template #leading="{ modelValue, ui }">
<UAvatar v-if="modelValue?.avatar" :size="ui.itemLeadingAvatarSize()" v-bind="modelValue.avatar" />
<UAvatar v-if="modelValue?.avatar" :size="(ui.itemLeadingAvatarSize() as AvatarProps['size'])" v-bind="modelValue.avatar" />
</template>
</USelectMenu>
</div>

View File

@@ -1,4 +1,5 @@
<script setup lang="ts">
import type { SelectItem, AvatarProps } from '@nuxt/ui'
import { upperFirst } from 'scule'
import theme from '#build/ui/select'
import type { User } from '~/types'
@@ -9,7 +10,7 @@ const variants = Object.keys(theme.variants.variant) as Array<keyof typeof theme
const fruits = ['Apple', 'Banana', 'Blueberry', 'Grapes', 'Pineapple']
const vegetables = ['Aubergine', 'Broccoli', 'Carrot', 'Courgette', 'Leek']
const items = [[{ label: 'Fruits', type: 'label' }, ...fruits], [{ label: 'Vegetables', type: 'label' }, ...vegetables]]
const items = [[{ label: 'Fruits', type: 'label' as const }, ...fruits], [{ label: 'Vegetables', type: 'label' as const }, ...vegetables]]
const selectedItems = ref([fruits[0]!, vegetables[0]!])
const statuses = [{
@@ -32,7 +33,7 @@ const statuses = [{
label: 'Canceled',
value: 'canceled',
icon: 'i-lucide-circle-x'
}]
}] satisfies SelectItem[]
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
transform: (data: User[]) => {
@@ -114,9 +115,10 @@ function getUserAvatar(value: string) {
trailing-icon="i-lucide-chevrons-up-down"
:size="size"
class="w-48"
value-key="value"
>
<template #leading="{ modelValue, ui }">
<UIcon v-if="modelValue" :name="getStatusIcon(modelValue as string)" :class="ui.leadingIcon()" />
<UIcon v-if="modelValue" :name="getStatusIcon(modelValue)" :class="ui.leadingIcon()" />
</template>
</USelect>
</div>
@@ -130,9 +132,10 @@ function getUserAvatar(value: string) {
placeholder="Search users..."
:size="size"
class="w-48"
value-key="value"
>
<template #leading="{ modelValue, ui }">
<UAvatar v-if="modelValue" :size="ui.itemLeadingAvatarSize()" v-bind="getUserAvatar(modelValue as string)" />
<UAvatar v-if="modelValue" :size="(ui.itemLeadingAvatarSize() as AvatarProps['size'])" v-bind="getUserAvatar(modelValue)" />
</template>
</USelect>
</div>

View File

@@ -11,22 +11,22 @@ const size = ref('md' as const)
const items = [
{
slot: 'address',
slot: 'address' as const,
title: 'Address',
description: 'Add your address here',
icon: 'i-lucide-house'
}, {
slot: 'shipping',
slot: 'shipping' as const,
title: 'Shipping',
description: 'Set your preferred shipping method',
icon: 'i-lucide-truck'
}, {
slot: 'payment',
slot: 'payment' as const,
title: 'Payment',
description: 'Select your payment method',
icon: 'i-lucide-credit-card'
}, {
slot: 'checkout',
slot: 'checkout' as const,
title: 'Checkout',
description: 'Confirm your order'
}
@@ -50,27 +50,27 @@ const stepper = useTemplateRef('stepper')
:orientation="orientation"
:size="size"
>
<template #address>
<template #address="{ item }">
<Placeholder class="size-full min-h-60 min-w-60">
Address
{{ item.title }}
</Placeholder>
</template>
<template #shipping>
<template #shipping="{ item }">
<Placeholder class="size-full min-h-60 min-w-60">
Shipping
{{ item.title }}
</Placeholder>
</template>
<template #payment>
<template #payment="{ item }">
<Placeholder class="size-full min-h-60 min-w-60">
Payment
{{ item.title }}
</Placeholder>
</template>
<template #checkout>
<template #checkout="{ item }">
<Placeholder class="size-full min-h-60 min-w-60">
Checkout
{{ item.title }}
</Placeholder>
</template>
</UStepper>

View File

@@ -148,12 +148,12 @@ const columns: TableColumn<Payment>[] = [{
header: ({ table }) => h(UCheckbox, {
'modelValue': table.getIsSomePageRowsSelected() ? 'indeterminate' : table.getIsAllPageRowsSelected(),
'onUpdate:modelValue': (value: boolean | 'indeterminate') => table.toggleAllPageRowsSelected(!!value),
'ariaLabel': 'Select all'
'aria-label': 'Select all'
}),
cell: ({ row }) => h(UCheckbox, {
'modelValue': row.getIsSelected(),
'onUpdate:modelValue': (value: boolean | 'indeterminate') => row.toggleSelected(!!value),
'ariaLabel': 'Select row'
'aria-label': 'Select row'
}),
enableSorting: false,
enableHiding: false
@@ -251,15 +251,17 @@ const columns: TableColumn<Payment>[] = [{
}]
return h('div', { class: 'text-right' }, h(UDropdownMenu, {
content: {
'content': {
align: 'end'
},
items
items,
'aria-label': 'Actions dropdown'
}, () => h(UButton, {
icon: 'i-lucide-ellipsis-vertical',
color: 'neutral',
variant: 'ghost',
class: 'ms-auto'
'icon': 'i-lucide-ellipsis-vertical',
'color': 'neutral',
'variant': 'ghost',
'class': 'ms-auto',
'aria-label': 'Actions dropdown'
})))
}
}]

View File

@@ -41,8 +41,8 @@ const itemsWithMappedId = [
{ id: 'id3', title: 'obiwan kenobi' }
]
const modelValue = ref<TreeItem>()
const modelValues = ref<TreeItem[]>([])
const modelValue = ref<string>()
const modelValues = ref<string[]>([])
</script>
<template>
@@ -64,22 +64,14 @@ const modelValues = ref<TreeItem[]>([])
<!-- Typescript tests -->
<template v-if="false">
<!-- @vue-expect-error - multiple props should type modelValue to array. -->
<UTree :model-value="modelValue" :items="items" multiple />
<!-- @vue-expect-error - multiple props should type defaultValue to array. -->
<UTree :default-value="modelValue" :items="items" multiple />
<!-- @vue-expect-error - multiple props should type @update:modelValue to array. -->
<UTree :items="items" multiple @update:model-value="(payload: TreeItem) => payload" />
<!-- @vue-expect-error - default should type modelValue to single item. -->
<UTree :model-value="modelValues" :items="items" />
<!-- @vue-expect-error - default should type defaultValue to single item. -->
<UTree :default-value="modelValues" :items="items" />
<!-- @vue-expect-error - default should type @update:modelValue to single item. -->
<UTree :items="items" @update:model-value="(payload: TreeItem[]) => payload" />
<UTree :model-value="modelValues" :items="items" multiple />
<UTree :default-value="modelValues" :items="items" multiple />
<UTree :items="items" multiple @update:model-value="(payload) => payload" />
<UTree :model-value="modelValue" :items="items" />
<UTree :default-value="modelValue" :items="items" />
<UTree :items="items" @update:model-value="(payload) => payload" />
<!-- @vue-expect-error - value key should type v-model. -->
<UTree v-model="modelValue" :items="itemsWithMappedId" value-key="id" />
<!-- @vue-expect-error - label key should type v-model. -->
<UTree v-model="modelValue" :items="itemsWithMappedId" label-key="title" />
</template>
</div>

View File

@@ -14,5 +14,12 @@ export default defineNuxtConfig({
compatibilityVersion: 4
},
compatibilityDate: '2024-07-09'
compatibilityDate: '2024-07-09',
vite: {
optimizeDeps: {
// prevents reloading page when navigating between components
include: ['@internationalized/date', '@vueuse/shared', '@vueuse/integrations/useFuse', '@tanstack/vue-table', 'reka-ui', 'reka-ui/namespaced', 'embla-carousel-vue', 'embla-carousel-autoplay', 'embla-carousel-auto-scroll', 'embla-carousel-auto-height', 'embla-carousel-class-names', 'embla-carousel-fade', 'embla-carousel-wheel-gestures', 'colortranslator', 'tailwindcss/colors', 'tailwind-variants', 'ufo', 'zod', 'vaul-vue']
}
}
})

View File

@@ -8,10 +8,11 @@
"generate": "nuxi generate"
},
"dependencies": {
"@iconify-json/lucide": "^1.2.31",
"@iconify-json/lucide": "^1.2.33",
"@iconify-json/simple-icons": "^1.2.29",
"@nuxt/ui": "latest",
"@nuxthub/core": "^0.8.18",
"nuxt": "^3.16.1"
"@nuxthub/core": "^0.8.21",
"nuxt": "^3.16.1",
"zod": "^3.24.2"
}
}

2379
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -6,7 +6,6 @@
"enabled": true
},
"ignoreDeps": [
"typescript",
"vue-tsc"
],
"baseBranches": ["v2", "v3"],
@@ -20,6 +19,9 @@
"@tailwindcss/postcss",
"@tailwindcss/vite"
]
}, {
"matchDepTypes": ["peerDependencies"],
"enabled": false
}, {
"matchDepTypes": ["resolutions"],
"enabled": false

View File

@@ -26,9 +26,10 @@ export interface AccordionItem {
/** A unique value for the accordion item. Defaults to the index. */
value?: string
disabled?: boolean
[key: string]: any
}
export interface AccordionProps<T> extends Pick<AccordionRootProps, 'collapsible' | 'defaultValue' | 'modelValue' | 'type' | 'disabled' | 'unmountOnHide'> {
export interface AccordionProps<T extends AccordionItem = AccordionItem> extends Pick<AccordionRootProps, 'collapsible' | 'defaultValue' | 'modelValue' | 'type' | 'disabled' | 'unmountOnHide'> {
/**
* The element or component this component should render as.
* @defaultValue 'div'
@@ -52,15 +53,15 @@ export interface AccordionProps<T> extends Pick<AccordionRootProps, 'collapsible
export interface AccordionEmits extends AccordionRootEmits {}
type SlotProps<T> = (props: { item: T, index: number, open: boolean }) => any
type SlotProps<T extends AccordionItem> = (props: { item: T, index: number, open: boolean }) => any
export type AccordionSlots<T extends { slot?: string }> = {
export type AccordionSlots<T extends AccordionItem = AccordionItem> = {
leading: SlotProps<T>
default: SlotProps<T>
trailing: SlotProps<T>
content: SlotProps<T>
body: SlotProps<T>
} & DynamicSlots<T, SlotProps<T>>
} & DynamicSlots<T, 'body', { index: number, open: boolean }>
</script>
@@ -92,7 +93,7 @@ const ui = computed(() => accordion({
<template>
<AccordionRoot v-bind="rootProps" :class="ui.root({ class: [props.class, props.ui?.root] })">
<AccordionItem
v-for="(item, index) in items"
v-for="(item, index) in props.items"
v-slot="{ open }"
:key="index"
:value="item.value || String(index)"
@@ -115,10 +116,10 @@ const ui = computed(() => accordion({
</AccordionTrigger>
</AccordionHeader>
<AccordionContent v-if="item.content || !!slots.content || (item.slot && !!slots[item.slot]) || !!slots.body || (item.slot && !!slots[`${item.slot}-body`])" :class="ui.content({ class: props.ui?.content })">
<slot :name="item.slot || 'content'" :item="item" :index="index" :open="open">
<AccordionContent v-if="item.content || !!slots.content || (item.slot && !!slots[item.slot as keyof AccordionSlots<T>]) || !!slots.body || (item.slot && !!slots[`${item.slot}-body` as keyof AccordionSlots<T>])" :class="ui.content({ class: props.ui?.content })">
<slot :name="((item.slot || 'content') as keyof AccordionSlots<T>)" :item="item" :index="index" :open="open">
<div :class="ui.body({ class: props.ui?.body })">
<slot :name="item.slot ? `${item.slot}-body`: 'body'" :item="item" :index="index" :open="open">
<slot :name="((item.slot ? `${item.slot}-body`: 'body') as keyof AccordionSlots<T>)" :item="item" :index="index" :open="open">
{{ item.content }}
</slot>
</div>

View File

@@ -71,7 +71,7 @@ export interface AlertSlots {
title(props?: {}): any
description(props?: {}): any
actions(props?: {}): any
close(props: { ui: any }): any
close(props: { ui: ReturnType<typeof alert> }): any
}
</script>

View File

@@ -17,7 +17,7 @@ export default {
}
</script>
<script setup lang="ts" generic="T extends Messages = Messages">
<script setup lang="ts" generic="T extends Messages">
import { toRef, useId, provide } from 'vue'
import { ConfigProvider, TooltipProvider, useForwardProps } from 'reka-ui'
import { reactivePick } from '@vueuse/core'

View File

@@ -40,7 +40,7 @@ export interface AvatarSlots {
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
import { Primitive } from 'reka-ui'
import { Primitive, Slot } from 'reka-ui'
import ImageComponent from '#build/ui-image-component'
import { useAvatarGroup } from '../composables/useAvatarGroup'
import UIcon from './Icon.vue'
@@ -98,9 +98,11 @@ function onError() {
@error="onError"
/>
<slot v-else>
<UIcon v-if="icon" :name="icon" :class="ui.icon({ class: props.ui?.icon })" />
<span v-else :class="ui.fallback({ class: props.ui?.fallback })">{{ fallback || '&nbsp;' }}</span>
</slot>
<Slot v-else v-bind="$attrs">
<slot>
<UIcon v-if="icon" :name="icon" :class="ui.icon({ class: props.ui?.icon })" />
<span v-else :class="ui.fallback({ class: props.ui?.fallback })">{{ fallback || '&nbsp;' }}</span>
</slot>
</Slot>
</Primitive>
</template>

View File

@@ -19,9 +19,10 @@ export interface BreadcrumbItem extends Omit<LinkProps, 'raw' | 'custom'> {
icon?: string
avatar?: AvatarProps
slot?: string
[key: string]: any
}
export interface BreadcrumbProps<T> {
export interface BreadcrumbProps<T extends BreadcrumbItem = BreadcrumbItem> {
/**
* The element or component this component should render as.
* @defaultValue 'nav'
@@ -43,15 +44,15 @@ export interface BreadcrumbProps<T> {
ui?: PartialString<typeof breadcrumb.slots>
}
type SlotProps<T> = (props: { item: T, index: number, active?: boolean }) => any
type SlotProps<T extends BreadcrumbItem> = (props: { item: T, index: number, active?: boolean }) => any
export type BreadcrumbSlots<T extends { slot?: string }> = {
export type BreadcrumbSlots<T extends BreadcrumbItem = BreadcrumbItem> = {
'item': SlotProps<T>
'item-leading': SlotProps<T>
'item-label': SlotProps<T>
'item-trailing': SlotProps<T>
'separator'(props?: {}): any
} & DynamicSlots<T, SlotProps<T>>
'separator': any
} & DynamicSlots<T, 'leading' | 'label' | 'trailing', { index: number, active?: boolean }>
</script>
@@ -88,19 +89,19 @@ const ui = breadcrumb()
<li :class="ui.item({ class: props.ui?.item })">
<ULink v-slot="{ active, ...slotProps }" v-bind="pickLinkProps(item)" custom>
<ULinkBase v-bind="slotProps" as="span" :aria-current="active && (index === items!.length - 1) ? 'page' : undefined" :class="ui.link({ class: [props.ui?.link, item.class], active: index === items!.length - 1, disabled: !!item.disabled, to: !!item.to })">
<slot :name="item.slot || 'item'" :item="item" :index="index">
<slot :name="item.slot ? `${item.slot}-leading`: 'item-leading'" :item="item" :active="index === items!.length - 1" :index="index">
<slot :name="((item.slot || 'item') as keyof BreadcrumbSlots<T>)" :item="item" :index="index">
<slot :name="((item.slot ? `${item.slot}-leading`: 'item-leading') as keyof BreadcrumbSlots<T>)" :item="item" :active="index === items!.length - 1" :index="index">
<UIcon v-if="item.icon" :name="item.icon" :class="ui.linkLeadingIcon({ class: props.ui?.linkLeadingIcon, active: index === items!.length - 1 })" />
<UAvatar v-else-if="item.avatar" :size="((props.ui?.linkLeadingAvatarSize || ui.linkLeadingAvatarSize()) as AvatarProps['size'])" v-bind="item.avatar" :class="ui.linkLeadingAvatar({ class: props.ui?.linkLeadingAvatar, active: index === items!.length - 1 })" />
</slot>
<span v-if="get(item, props.labelKey as string) || !!slots[item.slot ? `${item.slot}-label`: 'item-label']" :class="ui.linkLabel({ class: props.ui?.linkLabel })">
<slot :name="item.slot ? `${item.slot}-label`: 'item-label'" :item="item" :active="index === items!.length - 1" :index="index">
<span v-if="get(item, props.labelKey as string) || !!slots[(item.slot ? `${item.slot}-label`: 'item-label') as keyof BreadcrumbSlots<T>]" :class="ui.linkLabel({ class: props.ui?.linkLabel })">
<slot :name="((item.slot ? `${item.slot}-label`: 'item-label') as keyof BreadcrumbSlots<T>)" :item="item" :active="index === items!.length - 1" :index="index">
{{ get(item, props.labelKey as string) }}
</slot>
</span>
<slot :name="item.slot ? `${item.slot}-trailing`: 'item-trailing'" :item="item" :active="index === items!.length - 1" :index="index" />
<slot :name="((item.slot ? `${item.slot}-trailing`: 'item-trailing') as keyof BreadcrumbSlots<T>)" :item="item" :active="index === items!.length - 1" :index="index" />
</slot>
</ULinkBase>
</ULink>

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