Compare commits

..

5 Commits

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

View File

@@ -160,9 +160,6 @@ jobs:
nuxt-ui-pro: nuxt-ui-pro:
needs: build needs: build
# Only run this job if not a fork PR (when push event or PR from same repo)
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
permissions: permissions:

View File

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

View File

@@ -1,38 +1,5 @@
# Changelog # 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) ## [3.0.1](https://github.com/nuxt/ui/compare/v3.0.0...v3.0.1) (2025-03-21)
### ⚠ BREAKING CHANGES ### ⚠ BREAKING CHANGES

View File

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

View File

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

View File

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

View File

@@ -12,15 +12,14 @@ function getEmojiFlag(locale: string): string {
ar: 'sa', // Arabic -> Saudi Arabia ar: 'sa', // Arabic -> Saudi Arabia
bn: 'bd', // Bengali -> Bangladesh bn: 'bd', // Bengali -> Bangladesh
ca: 'es', // Catalan -> Spain ca: 'es', // Catalan -> Spain
ckb: 'iq', // Central Kurdish -> Iraq
cs: 'cz', // Czech -> Czech Republic (note: modern country code is actually 'cz') cs: 'cz', // Czech -> Czech Republic (note: modern country code is actually 'cz')
ckb: 'iq', // Central Kurdish -> Iraq
da: 'dk', // Danish -> Denmark da: 'dk', // Danish -> Denmark
el: 'gr', // Greek -> Greece el: 'gr', // Greek -> Greece
en: 'gb', // English -> Great Britain
et: 'ee', // Estonian -> Estonia et: 'ee', // Estonian -> Estonia
en: 'gb', // English -> Great Britain
he: 'il', // Hebrew -> Israel he: 'il', // Hebrew -> Israel
hi: 'in', // Hindi -> India hi: 'in', // Hindi -> India
hy: 'am', // Armenian -> Armenia
ja: 'jp', // Japanese -> Japan ja: 'jp', // Japanese -> Japan
km: 'kh', // Khmer -> Cambodia km: 'kh', // Khmer -> Cambodia
ko: 'kr', // Korean -> South Korea ko: 'kr', // Korean -> South Korea
@@ -28,7 +27,8 @@ function getEmojiFlag(locale: string): string {
sv: 'se', // Swedish -> Sweden sv: 'se', // Swedish -> Sweden
uk: 'ua', // Ukrainian -> Ukraine uk: 'ua', // Ukrainian -> Ukraine
ur: 'pk', // Urdu -> Pakistan ur: 'pk', // Urdu -> Pakistan
vi: 'vn' // Vietnamese -> Vietnam vi: 'vn', // Vietnamese -> Vietnam
hy: 'am' // Armenian -> Armenia
} }
const baseLanguage = locale.split('-')[0]?.toLowerCase() || locale const baseLanguage = locale.split('-')[0]?.toLowerCase() || locale

View File

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

View File

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

View File

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

View File

@@ -1,33 +0,0 @@
<script setup lang="ts">
import type { AccordionItem } from '@nuxt/ui'
import { useSortable } from '@vueuse/integrations/useSortable'
const items = shallowRef<AccordionItem[]>([
{
label: 'Icons',
icon: 'i-lucide-smile',
content: 'You have nothing to do, @nuxt/icon will handle it automatically.'
},
{
label: 'Colors',
icon: 'i-lucide-swatch-book',
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,7 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import type { AccordionItem } from '@nuxt/ui' const items = [
const items: AccordionItem[] = [
{ {
label: 'Icons', label: 'Icons',
icon: 'i-lucide-smile', icon: 'i-lucide-smile',

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -97,11 +97,10 @@ function getHeader(column: Column<Payment>, label: string) {
const isSorted = column.getIsSorted() const isSorted = column.getIsSorted()
return h(UDropdownMenu, { return h(UDropdownMenu, {
'content': { content: {
align: 'start' align: 'start'
}, },
'aria-label': 'Actions dropdown', items: [{
'items': [{
label: 'Asc', label: 'Asc',
type: 'checkbox', type: 'checkbox',
icon: 'i-lucide-arrow-up-narrow-wide', icon: 'i-lucide-arrow-up-narrow-wide',
@@ -127,12 +126,11 @@ function getHeader(column: Column<Payment>, label: string) {
} }
}] }]
}, () => h(UButton, { }, () => h(UButton, {
'color': 'neutral', color: 'neutral',
'variant': 'ghost', variant: 'ghost',
label, label,
'icon': isSorted ? (isSorted === 'asc' ? 'i-lucide-arrow-up-narrow-wide' : 'i-lucide-arrow-down-wide-narrow') : 'i-lucide-arrow-up-down', icon: isSorted ? (isSorted === 'asc' ? 'i-lucide-arrow-up-narrow-wide' : 'i-lucide-arrow-down-wide-narrow') : 'i-lucide-arrow-up-down',
'class': '-mx-2.5 data-[state=open]:bg-(--ui-bg-elevated)', 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, { header: ({ table }) => h(UCheckbox, {
'modelValue': table.getIsSomePageRowsSelected() ? 'indeterminate' : table.getIsAllPageRowsSelected(), 'modelValue': table.getIsSomePageRowsSelected() ? 'indeterminate' : table.getIsAllPageRowsSelected(),
'onUpdate:modelValue': (value: boolean | 'indeterminate') => table.toggleAllPageRowsSelected(!!value), 'onUpdate:modelValue': (value: boolean | 'indeterminate') => table.toggleAllPageRowsSelected(!!value),
'aria-label': 'Select all' 'ariaLabel': 'Select all'
}), }),
cell: ({ row }) => h(UCheckbox, { cell: ({ row }) => h(UCheckbox, {
'modelValue': row.getIsSelected(), 'modelValue': row.getIsSelected(),
'onUpdate:modelValue': (value: boolean | 'indeterminate') => row.toggleSelected(!!value), 'onUpdate:modelValue': (value: boolean | 'indeterminate') => row.toggleSelected(!!value),
'aria-label': 'Select row' 'ariaLabel': 'Select row'
}), }),
enableSorting: false, enableSorting: false,
enableHiding: false enableHiding: false
@@ -242,17 +242,15 @@ const columns: TableColumn<Payment>[] = [{
}] }]
return h('div', { class: 'text-right' }, h(UDropdownMenu, { return h('div', { class: 'text-right' }, h(UDropdownMenu, {
'content': { content: {
align: 'end' align: 'end'
}, },
items, items
'aria-label': 'Actions dropdown'
}, () => h(UButton, { }, () => h(UButton, {
'icon': 'i-lucide-ellipsis-vertical', icon: 'i-lucide-ellipsis-vertical',
'color': 'neutral', color: 'neutral',
'variant': 'ghost', variant: 'ghost',
'class': 'ml-auto', class: 'ml-auto'
'aria-label': 'Actions dropdown'
}))) })))
} }
}] }]
@@ -296,7 +294,6 @@ function randomize() {
variant="outline" variant="outline"
trailing-icon="i-lucide-chevron-down" trailing-icon="i-lucide-chevron-down"
class="ml-auto" class="ml-auto"
aria-label="Columns select dropdown"
/> />
</UDropdownMenu> </UDropdownMenu>
</div> </div>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,8 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
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 { findPageBreadcrumb, mapContentNavigation } from '#ui-pro/utils/content'
import { findPageBreadcrumb, mapContentNavigation } from '@nuxt/ui-pro/utils/content'
const route = useRoute() const route = useRoute()
const { framework, module } = useSharedData() const { framework, module } = useSharedData()
@@ -101,16 +100,6 @@ const communityLinks = computed(() => [{
label: 'Star on GitHub', label: 'Star on GitHub',
to: `https://github.com/nuxt/${page.value?.module === 'ui-pro' ? 'ui-pro' : 'ui'}`, to: `https://github.com/nuxt/${page.value?.module === 'ui-pro' ? 'ui-pro' : 'ui'}`,
target: '_blank' 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', icon: 'i-lucide-life-buoy',
label: 'Contribution', label: 'Contribution',
@@ -119,7 +108,7 @@ const communityLinks = computed(() => [{
label: 'Roadmap', label: 'Roadmap',
icon: 'i-lucide-map', icon: 'i-lucide-map',
to: '/roadmap' to: '/roadmap'
}].filter(Boolean) as PageLink[]) }])
</script> </script>
<template> <template>
@@ -147,7 +136,7 @@ const communityLinks = computed(() => [{
v-bind="link" v-bind="link"
> >
<template v-if="link.avatar" #leading> <template v-if="link.avatar" #leading>
<UAvatar v-bind="link.avatar" size="2xs" :alt="`${link.label} avatar`" /> <UAvatar v-bind="link.avatar" size="2xs" />
</template> </template>
</UButton> </UButton>
</template> </template>

View File

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

View File

@@ -103,14 +103,10 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
:to="component.path" :to="component.path"
> >
<UColorModeImage <UColorModeImage
:light="`${component.path.replace('/components/', '/components/light/')}.png`" :light="`${component.path.replace('/components/', '/components/light/')}.png`"
:dark="`${component.path.replace('/components/', '/components/dark/')}.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" 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" /> <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> </ULink>
@@ -134,12 +130,7 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
<UColorModeImage <UColorModeImage
:light="`${component.path.replace('/components/', '/components/light/')}.png`" :light="`${component.path.replace('/components/', '/components/light/')}.png`"
:dark="`${component.path.replace('/components/', '/components/dark/')}.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" 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" /> <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> </ULink>
@@ -161,9 +152,7 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
:in-view-options="{ once: true }" :in-view-options="{ once: true }"
class="flex items-start gap-x-3 relative group" 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"> <div class="relative p-3">
<svg class="absolute inset-0" viewBox="0 0 44 44" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg class="absolute inset-0" viewBox="0 0 44 44" fill="none" xmlns="http://www.w3.org/2000/svg">
@@ -229,32 +218,26 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
class="border-b border-(--ui-border)" class="border-b border-(--ui-border)"
> >
<template #features> <template #features>
<li> <NuxtLink to="https://npm.chart.dev/@nuxt/ui" target="_blank" class="min-w-0">
<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">
<p class="text-4xl font-semibold text-(--ui-text-highlighted) truncate"> {{ format(module?.stats?.downloads ?? 0) }}+
{{ format(module?.stats?.downloads ?? 0) }}+ </p>
</p> <p class="text-(--ui-text-muted) text-sm truncate">monthly downloads</p>
<p class="text-(--ui-text-muted) text-sm truncate">monthly downloads</p> </NuxtLink>
</NuxtLink>
</li>
<li> <NuxtLink to="https://github.com/nuxt/ui" target="_blank" class="min-w-0">
<NuxtLink to="https://github.com/nuxt/ui" target="_blank" class="min-w-0"> <p class="text-4xl font-semibold text-(--ui-text-highlighted) truncate">
<p class="text-4xl font-semibold text-(--ui-text-highlighted) truncate"> {{ format(module?.stats?.stars ?? 0) }}+
{{ format(module?.stats?.stars ?? 0) }}+ </p>
</p> <p class="text-(--ui-text-muted) text-sm truncate">GitHub stars</p>
<p class="text-(--ui-text-muted) text-sm truncate">GitHub stars</p> </NuxtLink>
</NuxtLink>
</li>
<li> <NuxtLink to="https://github.com/nuxt/ui/graphs/contributors" target="_blank" class="min-w-0">
<NuxtLink to="https://github.com/nuxt/ui/graphs/contributors" target="_blank" class="min-w-0"> <p class="text-4xl font-semibold text-(--ui-text-highlighted) truncate">
<p class="text-4xl font-semibold text-(--ui-text-highlighted) truncate"> 175+
175+ </p>
</p> <p class="text-(--ui-text-muted) text-sm truncate">Contributors</p>
<p class="text-(--ui-text-muted) text-sm truncate">Contributors</p> </NuxtLink>
</NuxtLink>
</li>
</template> </template>
<div ref="contributorsRef" class="p-4 sm:px-6 md:px-8 lg:px-12 xl:px-14 overflow-hidden flex relative"> <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,7 +56,6 @@ useSeoMeta({
v-if="template.thumbnail" v-if="template.thumbnail"
v-bind="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" 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" width="656"
height="369" height="369"
loading="lazy" 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> <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: 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. 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: 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. 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: 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. 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: 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. 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: 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**. 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: 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"} ::code-group{sync="pm"}
```bash [pnpm] ```bash [pnpm]
pnpm add @nuxtjs/i18n pnpm add @nuxtjs/i18n@next
``` ```
```bash [yarn] ```bash [yarn]
yarn add @nuxtjs/i18n yarn add @nuxtjs/i18n@next
``` ```
```bash [npm] ```bash [npm]
npm install @nuxtjs/i18n npm install @nuxtjs/i18n@next
``` ```
```bash [bun] ```bash [bun]
bun add @nuxtjs/i18n bun add @nuxtjs/i18n@next
``` ```
:: ::

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -31,8 +31,6 @@ ignore:
- class - class
external: external:
- items - items
externalTypes:
- StepperItem[]
props: props:
items: items:
- title: 'Address' - title: 'Address'
@@ -63,8 +61,6 @@ ignore:
- class - class
external: external:
- items - items
externalTypes:
- StepperItem[]
props: props:
color: neutral color: neutral
items: items:
@@ -92,8 +88,6 @@ ignore:
- class - class
external: external:
- items - items
externalTypes:
- StepperItem[]
props: props:
size: xl size: xl
items: items:
@@ -121,8 +115,6 @@ ignore:
- class - class
external: external:
- items - items
externalTypes:
- StepperItem[]
props: props:
orientation: vertical orientation: vertical
items: items:
@@ -150,8 +142,6 @@ ignore:
- class - class
external: external:
- items - items
externalTypes:
- StepperItem[]
props: props:
disabled: true disabled: true
items: 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" aria-label="View source code"} ::callout{icon="i-simple-icons-github" to="https://github.com/nuxt/ui/tree/v3/docs/app/components/content/examples/table/TableExample.vue"}
This example demonstrates the most common use case of the `Table` component. Check out the source code on GitHub. 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. 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" aria-label="Table columns with slots"} ::tip{to="#with-slots"}
You can also use slots to customize the header and data cells of the table. You can also use slots to customize the header and data cells of the table.
:: ::

View File

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

View File

@@ -139,7 +139,6 @@ export default defineNuxtConfig({
'/pro/components/pricing-grid': { redirect: { to: '/components/pricing-plans', statusCode: 301 }, prerender: false }, '/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/pricing-switch': { redirect: { to: '/components/switch', statusCode: 301 }, prerender: false },
'/pro/components/**': { redirect: { to: '/components/**', 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 } '/releases': { redirect: 'https://github.com/nuxt/ui/releases', prerender: false }
}, },
@@ -186,7 +185,7 @@ export default defineNuxtConfig({
}, },
optimizeDeps: { optimizeDeps: {
// prevents reloading page when navigating between components // 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'] 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']
} }
}, },
@@ -225,7 +224,6 @@ export default defineNuxtConfig({
}, },
image: { image: {
format: ['webp', 'jpeg', 'jpg', 'png', 'svg'],
provider: 'ipx' provider: 'ipx'
}, },

View File

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

View File

@@ -1,8 +1,8 @@
{ {
"name": "@nuxt/ui", "name": "@nuxt/ui",
"description": "A UI Library for Modern Web Apps, powered by Vue & Tailwind CSS.", "description": "A UI Library for Modern Web Apps, powered by Vue & Tailwind CSS.",
"version": "3.0.2", "version": "3.0.1",
"packageManager": "pnpm@10.7.0", "packageManager": "pnpm@10.6.5",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/nuxt/ui.git" "url": "git+https://github.com/nuxt/ui.git"
@@ -64,7 +64,7 @@
"prepack": "pnpm build", "prepack": "pnpm build",
"dev": "DEV=true nuxi dev playground", "dev": "DEV=true nuxi dev playground",
"dev:build": "nuxi build playground", "dev:build": "nuxi build playground",
"dev:vue": "DEV=true vite playground-vue", "dev:vue": "DEV=true pnpm --filter playground-vue dev",
"dev:vue:build": "vite build playground-vue", "dev:vue:build": "vite build playground-vue",
"dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground && nuxi prepare docs && vite build playground-vue", "dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground && nuxi prepare docs && vite build playground-vue",
"docs": "DEV=true nuxi dev docs", "docs": "DEV=true nuxi dev docs",
@@ -87,10 +87,10 @@
"@nuxt/schema": "^3.16.1", "@nuxt/schema": "^3.16.1",
"@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.0.17", "@tailwindcss/postcss": "^4.0.15",
"@tailwindcss/vite": "^4.0.17", "@tailwindcss/vite": "^4.0.15",
"@tanstack/vue-table": "^8.21.2", "@tanstack/vue-table": "^8.21.2",
"@unhead/vue": "^2.0.2", "@unhead/vue": "^2.0.0",
"@vueuse/core": "^13.0.0", "@vueuse/core": "^13.0.0",
"@vueuse/integrations": "^13.0.0", "@vueuse/integrations": "^13.0.0",
"colortranslator": "^4.1.0", "colortranslator": "^4.1.0",
@@ -104,7 +104,6 @@
"embla-carousel-vue": "^8.5.2", "embla-carousel-vue": "^8.5.2",
"embla-carousel-wheel-gestures": "^8.0.1", "embla-carousel-wheel-gestures": "^8.0.1",
"fuse.js": "^7.1.0", "fuse.js": "^7.1.0",
"hookable": "^5.5.3",
"knitwork": "^1.2.0", "knitwork": "^1.2.0",
"magic-string": "^0.30.17", "magic-string": "^0.30.17",
"mlly": "^1.7.4", "mlly": "^1.7.4",
@@ -113,10 +112,10 @@
"reka-ui": "^2.1.1", "reka-ui": "^2.1.1",
"scule": "^1.3.0", "scule": "^1.3.0",
"tailwind-variants": "^1.0.0", "tailwind-variants": "^1.0.0",
"tailwindcss": "^4.0.17", "tailwindcss": "^4.0.15",
"tinyglobby": "^0.2.12", "tinyglobby": "^0.2.12",
"unplugin": "^2.2.2", "unplugin": "^2.2.2",
"unplugin-auto-import": "^19.1.2", "unplugin-auto-import": "^19.1.1",
"unplugin-vue-components": "^28.4.1", "unplugin-vue-components": "^28.4.1",
"vaul-vue": "^0.4.1", "vaul-vue": "^0.4.1",
"vue": "^3.5.13", "vue": "^3.5.13",
@@ -167,6 +166,7 @@
"chokidar": "3.6.0", "chokidar": "3.6.0",
"debug": "4.3.7", "debug": "4.3.7",
"rollup": "4.34.9", "rollup": "4.34.9",
"typescript": "5.6.3",
"unplugin": "^2.2.2", "unplugin": "^2.2.2",
"vue-tsc": "2.2.0" "vue-tsc": "2.2.0"
}, },

View File

@@ -16,8 +16,9 @@
"zod": "^3.24.2" "zod": "^3.24.2"
}, },
"devDependencies": { "devDependencies": {
"@compodium/vue": "https://pkg.pr.new/romhml/compodium/@compodium/vue@18f083d",
"@vitejs/plugin-vue": "^5.2.3", "@vitejs/plugin-vue": "^5.2.3",
"typescript": "^5.8.2", "typescript": "^5.6.3",
"vite": "^6.2.3", "vite": "^6.2.3",
"vue-tsc": "^2.2.0" "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="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" /> <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 end-4 flex items-center gap-2"> <div class="fixed top-15 lg:top-3 right-4 flex items-center gap-2">
<UButton <UButton
:icon="mode === 'dark' ? 'i-lucide-moon' : 'i-lucide-sun'" :icon="mode === 'dark' ? 'i-lucide-moon' : 'i-lucide-sun'"
color="neutral" color="neutral"

View File

@@ -10,7 +10,7 @@ const pages = import.meta.glob('../../playground/app/pages/**/*.vue')
const components = import.meta.glob('../../playground/app/components/**/*.vue') const components = import.meta.glob('../../playground/app/components/**/*.vue')
const routes = Object.keys(pages).map((path) => { const routes = Object.keys(pages).map((path) => {
const name = path.match(/\.\.\/\.\.\/playground\/app\/pages(.*)\.vue$/)![1].toLowerCase() const name = path.match(/\.\.\/\.\.\/playground\/app\/pages(.*)\.vue$/)!.[1].toLowerCase()
return { return {
path: name === '/index' ? '/' : name, path: name === '/index' ? '/' : name,
component: pages[path] component: pages[path]

View File

@@ -1,6 +1,7 @@
import { defineConfig } from 'vite' import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue' import vue from '@vitejs/plugin-vue'
import { compodium } from '@compodium/vue'
import ui from '../src/vite' import ui from '../src/vite'
// https://vitejs.dev/config/ // https://vitejs.dev/config/
@@ -25,7 +26,21 @@ export default defineConfig({
components: { components: {
dirs: ['../playground/app/components'] dirs: ['../playground/app/components']
} }
}),
compodium({
dir: '../playground/compodium',
includeLibraryCollections: false,
componentDirs: [
{ path: '../src/runtime/components', prefix: 'U', pathPrefix: false }
],
extras: {
colors: {
neutral: 'slate'
}
}
}) })
], ],
optimizeDeps: { optimizeDeps: {
// prevents reloading page when navigating between components // prevents reloading page when navigating between components

View File

@@ -1,127 +1,5 @@
<script setup lang="ts">
import { splitByCase, upperFirst } from 'scule'
import { useColorMode } from '#imports'
const router = useRouter()
const appConfig = useAppConfig()
const colorMode = useColorMode()
const isDark = computed({
get() {
return colorMode.value === 'dark'
},
set() {
colorMode.preference = colorMode.value === 'dark' ? 'light' : 'dark'
}
})
const components = [
'accordion',
'alert',
'avatar',
'badge',
'breadcrumb',
'button',
'button-group',
'card',
'calendar',
'carousel',
'checkbox',
'chip',
'collapsible',
'color-picker',
'context-menu',
'command-palette',
'drawer',
'dropdown-menu',
'form',
'form-field',
'input',
'input-menu',
'input-number',
'kbd',
'link',
'modal',
'navigation-menu',
'pagination',
'pin-input',
'popover',
'progress',
'radio-group',
'select',
'select-menu',
'separator',
'shortcuts',
'skeleton',
'slideover',
'slider',
'stepper',
'switch',
'tabs',
'table',
'textarea',
'toast',
'tooltip',
'tree'
]
const items = components.map(component => ({ label: upperName(component), to: `/components/${component}` }))
function upperName(name: string) {
return splitByCase(name).map(p => upperFirst(p)).join('')
}
const isCommandPaletteOpen = ref(false)
function onSelect(item: any) {
router.push(item.to)
}
defineShortcuts({
meta_k: () => isCommandPaletteOpen.value = true
})
useHead({
title: 'Nuxt UI - Playground'
})
</script>
<template> <template>
<template v-if="!$route.path.startsWith('/__nuxt_ui__')"> <UApp>
<UApp :toaster="appConfig.toaster">
<div class="h-screen w-screen overflow-hidden flex flex-col lg:flex-row min-h-0 bg-(--ui-bg)" data-vaul-drawer-wrapper>
<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 end-4 flex items-center gap-2">
<ClientOnly v-if="!colorMode?.forced">
<UButton
:icon="isDark ? 'i-lucide-moon' : 'i-lucide-sun'"
color="neutral"
variant="ghost"
:aria-label="`Switch to ${isDark ? 'light' : 'dark'} mode`"
@click="isDark = !isDark"
/>
<template #fallback>
<div class="size-8" />
</template>
</ClientOnly>
</div>
<div class="flex-1 flex flex-col items-center justify-around overflow-y-auto w-full py-14 px-4">
<NuxtPage />
</div>
<UModal v-model:open="isCommandPaletteOpen" class="sm:h-96">
<template #content>
<UCommandPalette placeholder="Search a component..." :groups="[{ id: 'items', items }]" :fuse="{ resultLimit: 100 }" @update:model-value="onSelect" @update:open="value => isCommandPaletteOpen = value" />
</template>
</UModal>
</div>
</UApp>
</template>
<template v-else>
<NuxtPage /> <NuxtPage />
</template> </UApp>
</template> </template>

View File

@@ -0,0 +1,118 @@
<script setup lang="ts">
import { splitByCase, upperFirst } from 'scule'
import { useColorMode } from '#imports'
const router = useRouter()
const appConfig = useAppConfig()
const colorMode = useColorMode()
const isDark = computed({
get() {
return colorMode.value === 'dark'
},
set() {
colorMode.preference = colorMode.value === 'dark' ? 'light' : 'dark'
}
})
const components = [
'accordion',
'alert',
'avatar',
'badge',
'breadcrumb',
'button',
'button-group',
'card',
'calendar',
'carousel',
'checkbox',
'chip',
'collapsible',
'color-picker',
'context-menu',
'command-palette',
'drawer',
'dropdown-menu',
'form',
'form-field',
'input',
'input-menu',
'input-number',
'kbd',
'link',
'modal',
'navigation-menu',
'pagination',
'pin-input',
'popover',
'progress',
'radio-group',
'select',
'select-menu',
'separator',
'shortcuts',
'skeleton',
'slideover',
'slider',
'stepper',
'switch',
'tabs',
'table',
'textarea',
'toast',
'tooltip',
'tree'
]
const items = components.map(component => ({ label: upperName(component), to: `/components/${component}` }))
function upperName(name: string) {
return splitByCase(name).map(p => upperFirst(p)).join('')
}
const isCommandPaletteOpen = ref(false)
function onSelect(item: any) {
router.push(item.to)
}
defineShortcuts({
meta_k: () => isCommandPaletteOpen.value = true
})
</script>
<template>
<UApp :toaster="appConfig.toaster">
<div class="h-screen w-screen overflow-hidden flex flex-col lg:flex-row min-h-0 bg-(--ui-bg)" data-vaul-drawer-wrapper>
<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">
<ClientOnly v-if="!colorMode?.forced">
<UButton
:icon="isDark ? 'i-lucide-moon' : 'i-lucide-sun'"
color="neutral"
variant="ghost"
:aria-label="`Switch to ${isDark ? 'light' : 'dark'} mode`"
@click="isDark = !isDark"
/>
<template #fallback>
<div class="size-8" />
</template>
</ClientOnly>
</div>
<div class="flex-1 flex flex-col items-center justify-around overflow-y-auto w-full py-14 px-4">
<NuxtPage />
</div>
<UModal v-model:open="isCommandPaletteOpen" class="sm:h-96">
<template #content>
<UCommandPalette placeholder="Search a component..." :groups="[{ id: 'items', items }]" :fuse="{ resultLimit: 100 }" @update:model-value="onSelect" @update:open="value => isCommandPaletteOpen = value" />
</template>
</UModal>
</div>
</UApp>
</template>

View File

@@ -18,7 +18,6 @@ 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.' 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', label: 'Components',
slot: 'test' as const,
icon: 'i-lucide-layers-3', 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.' 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.'
}, { }, {
@@ -38,11 +37,6 @@ const items = [{
</p> </p>
</template> </template>
<template #custom="{ item }">
<p class="text-(--ui-text-muted)">
Custom: {{ item.content }}
</p>
</template>
<template #custom-body="{ item }"> <template #custom-body="{ item }">
<p class="text-(--ui-text-muted)"> <p class="text-(--ui-text-muted)">
Custom: {{ item.content }} Custom: {{ item.content }}

View File

@@ -150,6 +150,10 @@ defineShortcuts(extractShortcuts(items.value))
<UDropdownMenu :items="itemsWithColor" :size="size" arrow :content="{ side: 'bottom', align: 'start' }" :ui="{ content: 'w-48' }"> <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" /> <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> </UDropdownMenu>
</div> </div>
</div> </div>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,11 +1,20 @@
<script setup lang="ts">
</script>
<template> <template>
<div class="text-center"> <div class="w-screen h-screen flex flex-col justify-center items-center gap-2">
<h1 class="font-semibold mb-1"> <h1 class="text-3xl font-bold mb-1">
Playground Nuxt UI Playground
</h1> </h1>
<div class="flex items-center justify-center gap-1"> <div class="flex items-center justify-center gap-1">
<UKbd value="meta" /> <UKbd value="K" /> <UButton size="lg" to="/__compodium__/devtools" external>
Browse components
</UButton>
<UButton variant="outline" color="neutral" size="lg">
Documentation
</UButton>
</div> </div>
</div> </div>
</template> </template>

View File

@@ -0,0 +1,55 @@
<script setup lang="ts">
defineOptions({
inheritAttrs: false
})
extendCompodiumMeta({
defaultProps: {
items: [{
label: 'Getting Started',
icon: 'i-lucide-info',
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: 'Installation',
icon: 'i-lucide-download',
disabled: true,
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: 'Theming',
icon: 'i-lucide-pipette',
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: 'Layouts',
icon: 'i-lucide-layout-dashboard',
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',
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.'
}, {
label: 'Utilities',
slot: 'custom' as const,
icon: 'i-lucide-wrench',
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.'
}]
}
})
</script>
<template>
<UCard :ui="{ body: 'p-0 sm:p-0' }">
<UAccordion v-bind="$attrs" class="w-96" :ui="{ trigger: 'px-3.5', body: 'px-3.5' }">
<template #body="{ item }">
<p class="text-(--ui-text-muted)">
{{ item.content }}
</p>
</template>
<template #custom-body="{ item }">
<p class="text-(--ui-text-muted)">
Custom: {{ item.content }}
</p>
</template>
</UAccordion>
</UCard>
</template>

View File

@@ -0,0 +1,12 @@
<script setup lang="ts">
extendCompodiumMeta({
defaultProps: {
title: 'Heads up!',
description: 'You can change the primary color in your app config.'
}
})
</script>
<template>
<UAlert />
</template>

View File

@@ -0,0 +1,57 @@
<script setup lang="ts">
extendCompodiumMeta({
combo: ['variant'],
defaultProps: {
icon: 'i-lucide-terminal',
title: 'Heads up!',
description: 'You can change the primary color in your app config.',
close: true,
actions: [
{
label: 'Action',
onClick() {
console.log('Action clicked')
}
},
{
label: 'Another action',
color: 'warning',
onClick() {
console.log('Another action clicked')
}
},
{
label: 'One more action',
color: 'error',
onClick() {
console.log('One more action clicked')
}
},
{
label: 'And one more',
color: 'info',
icon: 'i-lucide-info',
onClick() {
console.log('And one more clicked')
}
},
{
label: 'Last one',
color: 'neutral',
icon: 'i-lucide-info',
onClick() {
console.log('Last one clicked')
}
}
]
}
})
const multipleActions = (color: string) => [
]
</script>
<template>
<UAlert />
</template>

View File

@@ -0,0 +1,14 @@
<script setup lang="ts">
extendCompodiumMeta({
combo: ['variant'],
defaultProps: {
avatar: { src: 'https://github.com/benjamincanac.png' },
title: 'Heads up!',
description: 'You can change the primary color in your app config.'
}
})
</script>
<template>
<UAlert />
</template>

View File

@@ -0,0 +1,14 @@
<script setup lang="ts">
extendCompodiumMeta({
combo: ['variant'],
defaultProps: {
icon: 'i-lucide-terminal',
title: 'Heads up!',
description: 'You can change the primary color in your app config.'
}
})
</script>
<template>
<UAlert />
</template>

View File

@@ -0,0 +1,13 @@
<script setup lang="ts">
extendCompodiumMeta({
combo: ['variant', 'color'],
defaultProps: {
title: 'Heads up!',
description: 'You can change the primary color in your app config.'
}
})
</script>
<template>
<UAlert />
</template>

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