Compare commits

...

47 Commits

Author SHA1 Message Date
514e9a24f1 Update showcase.yml 2025-07-21 20:15:42 +02:00
Hugo Richard
9f60443731 docs(modal/slideover): improve programmatic examples (#4556)
Co-authored-by: Eugen Istoc <eugenistoc@gmail.com>
2025-07-21 19:07:55 +02:00
Benjamin Canac
b22891abe6 fix(NavigationMenu/Tabs): display badge when not undefined 2025-07-21 18:29:48 +02:00
Benjamin Canac
9cda333631 docs(installation): add vue isolate section 2025-07-21 18:07:10 +02:00
J-Michalek
62ab01655c feat(Tabs): add badge on items (#4553)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2025-07-21 16:55:27 +02:00
Alex
f33660035f feat(Kbd): add color prop & soft variant (#4549) 2025-07-21 14:22:07 +02:00
renovate[bot]
657ec228b5 chore(deps): update nuxt framework to ^4.0.1 (v3) (#4562)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-21 11:51:01 +02:00
Hugo Richard
e9d515cb85 docs(migration): update form validation error property name (#4557) 2025-07-21 11:24:49 +02:00
renovate[bot]
f32cfeef9e chore(deps): update all non-major dependencies (v3) (#4527)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-21 10:25:24 +02:00
Alex
6b6ec8cb2c fix(ColorPicker): update color conversion logic (#4550) 2025-07-19 19:49:18 +02:00
Benjamin Canac
e2695ee7e4 feat(Drawer): add nested prop
Resolves #4320
2025-07-18 15:55:24 +02:00
Benjamin Canac
cad7c45c08 chore: remove future.compatibilityVersion 2025-07-17 11:20:59 +02:00
Benjamin Canac
5db3b0f98c docs(icons): add app prefix to assets 2025-07-17 10:56:08 +02:00
J-Michalek
6ca7c8b7bf feat(InputMenu): emit remove-tag event (#4511) 2025-07-16 22:28:45 +02:00
kyyy
bb99345f5b fix(RadioGroup): improve type safety for normalizeItem function (#4535)
Co-authored-by: Sandro Circi <sandro.circi@digitoolmedia.com>
2025-07-16 21:58:05 +02:00
Thilo Hettmer
c64c4cdea0 fix(FormField): resolve minor accessibility and rendering issues (#4515)
Co-authored-by: Romain Hamel <rom.hml@gmail.com>
2025-07-16 21:55:57 +02:00
renovate[bot]
8b42365bf4 chore(deps): lock file maintenance (v3) (#4539)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-16 15:37:18 +02:00
Benjamin Canac
cb160e6971 fix(InputMenu): reset search term on mounted
Resolves #3993
2025-07-16 12:52:58 +02:00
Benjamin Canac
4d4234d2f8 fix(InputMenu/SelectMenu): improve display value without valueKey
Resolves #4528
2025-07-16 12:43:38 +02:00
renovate[bot]
6f38d3ea8a chore(deps): update nuxt framework to v4 (v3) (major) (#4532)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2025-07-16 12:09:41 +02:00
Benjamin Canac
1b14b5dcd9 cli(templates): fix component type 2025-07-16 10:22:17 +02:00
Artem Alesenko
7ef19333f0 feat(Table): add support for colspan and rowspan (#4460)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2025-07-14 12:18:17 +02:00
renovate[bot]
d983af93b3 chore(deps): update dependency zod to v4 (v3) (#4523)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2025-07-14 11:58:02 +02:00
J-Michalek
1db21d1b00 feat(Table): add style to table and column meta (#4513)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2025-07-14 10:56:28 +02:00
J-Michalek
6f2ce5c610 refactor(components): unite syntax for emits declaration (#4512) 2025-07-14 10:46:47 +02:00
Benjamin Canac
488707e148 fix(InputMenu/SelectMenu): filter null items in search 2025-07-14 10:40:29 +02:00
J-Michalek
ef473c3848 fix(defineShortcuts): always pass event to shotcut handler (#4516) 2025-07-14 10:34:20 +02:00
renovate[bot]
93dff3264f chore(deps): update all non-major dependencies (v3) (#4479)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-14 10:23:04 +02:00
renovate[bot]
5da9084da3 chore(deps): update nuxt framework to ^3.17.7 (v3) (#4518)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-14 09:58:50 +02:00
Benjamin Canac
c92f908b8d fix(InputMenu/SelectMenu): only filter non-null fields
Resolves #4509
2025-07-12 12:15:29 +02:00
Benjamin Canac
45553dc3fe chore(github): update stale workflow 2025-07-12 12:05:24 +02:00
Benjamin Canac
55e06e97e7 fix(Carousel): improve accessibility
Resolves #4494
2025-07-10 14:18:36 +02:00
Eugen Istoc
a813ea700e test(useOverlay): add composable tests (#4482)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2025-07-10 12:04:18 +02:00
Benjamin Canac
a4d0ca7396 fix(FormField): improve error type with boolean
Resolves #4496
2025-07-10 12:04:06 +02:00
Eugen Istoc
5ad7dabbdc fix(useOverlay): don't use patch when passing props to open (#4497) 2025-07-10 10:45:55 +02:00
Benjamin Canac
d8160ba6ef fix(vue): stub clearError 2025-07-09 17:48:30 +02:00
Alex
fc24e03cc4 fix(Carousel/Tree): add type to button elements for accessibility (#4493) 2025-07-09 17:43:49 +02:00
Benjamin Canac
1902492cf2 docs(pricing): add banner 2025-07-09 16:17:24 +02:00
Benjamin Canac
0c525638d7 chore(deps): update @nuxt/ui-pro 2025-07-09 16:17:09 +02:00
Benjamin Canac
35f90b9920 feat(module): add theme.defaultVariants option (#4400) 2025-07-09 14:37:49 +02:00
Benjamin Canac
836f74849b fix(NavigationMenu/Tabs): proxy fallthrough attributes 2025-07-09 12:58:35 +02:00
Benjamin Canac
78f92a24f8 fix(module): merge user's options when installing modules 2025-07-09 12:24:18 +02:00
Benjamin Canac
52908c19f1 docs(app): update banner 2025-07-08 15:38:00 +02:00
J-Michalek
513cca25f6 docs(input-menu/select-menu/select): add example with full item width (#4419)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2025-07-08 12:43:49 +02:00
Benjamin Canac
c1427a3264 docs(ComponentSlots): hide ui slot prop content 2025-07-08 11:48:05 +02:00
Eugen Istoc
6519a74de4 fix(useOverlay): improve props handling by merging existing and new (#4478) 2025-07-08 10:51:34 +02:00
Benjamin Canac
da05c37ffe docs(input): hide password reveal button in edge
Resolves #4484
2025-07-08 10:39:25 +02:00
172 changed files with 4314 additions and 2309 deletions

View File

@@ -33,5 +33,5 @@ jobs:
Thank you for your understanding and support! Thank you for your understanding and support!
— Nuxt UI Team — Nuxt UI Team
exempt-issue-labels: 'feature,announcement' exempt-issue-labels: 'feature,announcement,release,reka-ui,upstream'
operations-per-run: 300 operations-per-run: 300

1
.nuxtrc Normal file
View File

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

View File

@@ -35,7 +35,7 @@ ${pro ? `import type { ComponentConfig } from '@nuxt/ui'` : ''}
import theme from '#build/${path}/${prose ? 'prose/' : ''}${content ? 'content/' : ''}${kebabName}' import theme from '#build/${path}/${prose ? 'prose/' : ''}${content ? 'content/' : ''}${kebabName}'
${!pro ? `import type { ComponentConfig } from '../types/utils'` : ''} ${!pro ? `import type { ComponentConfig } from '../types/utils'` : ''}
type ${upperName} = ComponentConfig<typeof theme, AppConfig, ${upperName}${pro ? `, '${key}'` : ''}> type ${upperName} = ComponentConfig<typeof theme, AppConfig, '${camelName}'${pro ? `, '${key}'` : ''}>
export interface ${upperName}Props { export interface ${upperName}Props {
/** /**
@@ -80,7 +80,7 @@ ${pro ? `import type { ComponentConfig } from '@nuxt/ui'` : ''}
import theme from '#build/${path}/${prose ? 'prose/' : ''}${content ? 'content/' : ''}${kebabName}' import theme from '#build/${path}/${prose ? 'prose/' : ''}${content ? 'content/' : ''}${kebabName}'
${!pro ? `import type { ComponentConfig } from '../types/utils'` : ''} ${!pro ? `import type { ComponentConfig } from '../types/utils'` : ''}
type ${upperName} = ComponentConfig<typeof theme, AppConfig, ${upperName}${pro ? `, '${key}'` : ''}> type ${upperName} = ComponentConfig<typeof theme, AppConfig, '${camelName}'${pro ? `, '${key}'` : ''}>
export interface ${upperName}Props extends Pick<${upperName}RootProps> { export interface ${upperName}Props extends Pick<${upperName}RootProps> {
class?: any class?: any

View File

@@ -53,7 +53,7 @@ provide('navigation', mappedNavigation)
<NuxtLoadingIndicator color="var(--ui-primary)" :height="2" /> <NuxtLoadingIndicator color="var(--ui-primary)" :height="2" />
<template v-if="!route.path.startsWith('/examples')"> <template v-if="!route.path.startsWith('/examples')">
<!-- <Banner /> --> <Banner />
<Header :links="links" /> <Header :links="links" />
</template> </template>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -35,6 +35,7 @@ const items = ref([
} }
} }
] satisfies InputMenuItem[]) ] satisfies InputMenuItem[])
const value = ref(items.value[0]) const value = ref(items.value[0])
</script> </script>

View File

@@ -40,9 +40,9 @@ const text = computed(() => {
placeholder="Password" placeholder="Password"
:color="color" :color="color"
:type="show ? 'text' : 'password'" :type="show ? 'text' : 'password'"
:ui="{ trailing: 'pe-1' }"
:aria-invalid="score < 4" :aria-invalid="score < 4"
aria-describedby="password-strength" aria-describedby="password-strength"
:ui="{ trailing: 'pe-1' }"
class="w-full" class="w-full"
> >
<template #trailing> <template #trailing>

View File

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

View File

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

View File

@@ -62,13 +62,13 @@ const items = [
<template> <template>
<UNavigationMenu <UNavigationMenu
:items="items" :items="items"
class="w-full justify-center"
:ui="{ :ui="{
viewport: 'sm:w-(--reka-navigation-menu-viewport-width)', viewport: 'sm:w-(--reka-navigation-menu-viewport-width)',
content: 'sm:w-auto', content: 'sm:w-auto',
childList: 'sm:w-96', childList: 'sm:w-96',
childLinkDescription: 'text-balance line-clamp-2' childLinkDescription: 'text-balance line-clamp-2'
}" }"
class="w-full justify-center"
> >
<template #docs-content="{ item }"> <template #docs-content="{ item }">
<ul class="grid gap-2 p-4 lg:w-[500px] lg:grid-cols-[minmax(0,.75fr)_minmax(0,1fr)]"> <ul class="grid gap-2 p-4 lg:w-[500px] lg:grid-cols-[minmax(0,.75fr)_minmax(0,1fr)]">

View File

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

View File

@@ -35,6 +35,7 @@ const items = ref([
} }
} }
] satisfies SelectMenuItem[]) ] satisfies SelectMenuItem[])
const value = ref(items.value[0]) const value = ref(items.value[0])
</script> </script>

View File

@@ -24,6 +24,7 @@ const items = ref([
} }
} }
] satisfies SelectMenuItem[]) ] satisfies SelectMenuItem[])
const value = ref(items.value[0]) const value = ref(items.value[0])
</script> </script>

View File

@@ -23,6 +23,7 @@ const items = ref([
icon: 'i-lucide-circle-check' icon: 'i-lucide-circle-check'
} }
] satisfies SelectMenuItem[]) ] satisfies SelectMenuItem[])
const value = ref(items.value[0]) const value = ref(items.value[0])
</script> </script>

View File

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

View File

@@ -24,8 +24,8 @@ function getUserAvatar(value: string) {
:loading="status === 'pending'" :loading="status === 'pending'"
icon="i-lucide-user" icon="i-lucide-user"
placeholder="Select user" placeholder="Select user"
class="w-48"
value-key="value" value-key="value"
class="w-48"
> >
<template #leading="{ modelValue, ui }"> <template #leading="{ modelValue, ui }">
<UAvatar <UAvatar

View File

@@ -35,6 +35,7 @@ const items = ref([
} }
} }
] satisfies SelectItem[]) ] 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)

View File

@@ -23,6 +23,7 @@ const items = ref([
icon: 'i-lucide-circle-check' icon: 'i-lucide-circle-check'
} }
] satisfies SelectItem[]) ] 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)

View File

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

View File

@@ -26,7 +26,7 @@ const state = reactive({
</script> </script>
<template> <template>
<UTabs :items="items" variant="link" class="gap-4 w-full" :ui="{ trigger: 'grow' }"> <UTabs :items="items" variant="link" :ui="{ trigger: 'grow' }" class="gap-4 w-full">
<template #account="{ item }"> <template #account="{ item }">
<p class="text-muted mb-4"> <p class="text-muted mb-4">
{{ item.description }} {{ item.description }}

View File

@@ -27,8 +27,8 @@ const items: TimelineItem[] = [{
<template> <template>
<UTimeline <UTimeline
:items="items" :items="items"
:ui="{ item: 'even:flex-row-reverse even:-translate-x-[calc(100%-2rem)] even:text-right' }"
:default-value="2" :default-value="2"
:ui="{ item: 'even:flex-row-reverse even:-translate-x-[calc(100%-2rem)] even:text-right' }"
class="translate-x-[calc(50%-1rem)]" class="translate-x-[calc(50%-1rem)]"
/> />
</template> </template>

View File

@@ -42,11 +42,11 @@ const items = [{
<UTimeline <UTimeline
:items="items" :items="items"
size="xs" size="xs"
class="w-96"
:ui="{ :ui="{
date: 'float-end ms-1', date: 'float-end ms-1',
description: 'px-3 py-2 ring ring-default mt-2 rounded-md text-default' description: 'px-3 py-2 ring ring-default mt-2 rounded-md text-default'
}" }"
class="w-96"
> >
<template #title="{ item }"> <template #title="{ item }">
<span>{{ item.username }}</span> <span>{{ item.username }}</span>

View File

@@ -7,12 +7,12 @@ const appConfig = useAppConfig()
<UFormField <UFormField
label="toaster.duration" label="toaster.duration"
size="sm" size="sm"
class="inline-flex ring ring-accented rounded-sm"
:ui="{ :ui="{
wrapper: 'bg-elevated/50 rounded-l-sm flex border-r border-accented', wrapper: 'bg-elevated/50 rounded-l-sm flex border-r border-accented',
label: 'text-muted px-2 py-1.5', label: 'text-muted px-2 py-1.5',
container: 'mt-0' container: 'mt-0'
}" }"
class="inline-flex ring ring-accented rounded-sm"
> >
<UInput <UInput
v-model="appConfig.toaster.duration" v-model="appConfig.toaster.duration"

View File

@@ -7,12 +7,12 @@ const appConfig = useAppConfig()
<UFormField <UFormField
label="toaster.expand" label="toaster.expand"
size="sm" size="sm"
class="inline-flex ring ring-accented rounded-sm"
:ui="{ :ui="{
wrapper: 'bg-elevated/50 rounded-l-sm flex border-r border-accented', wrapper: 'bg-elevated/50 rounded-l-sm flex border-r border-accented',
label: 'text-muted px-2 py-1.5', label: 'text-muted px-2 py-1.5',
container: 'mt-0' container: 'mt-0'
}" }"
class="inline-flex ring ring-accented rounded-sm"
> >
<USelectMenu <USelectMenu
v-model="appConfig.toaster.expand" v-model="appConfig.toaster.expand"

View File

@@ -10,12 +10,12 @@ const appConfig = useAppConfig()
<UFormField <UFormField
label="toaster.position" label="toaster.position"
size="sm" size="sm"
class="inline-flex ring ring-accented rounded-sm"
:ui="{ :ui="{
wrapper: 'bg-elevated/50 rounded-l-sm flex border-r border-accented', wrapper: 'bg-elevated/50 rounded-l-sm flex border-r border-accented',
label: 'text-muted px-2 py-1.5', label: 'text-muted px-2 py-1.5',
container: 'mt-0' container: 'mt-0'
}" }"
class="inline-flex ring ring-accented rounded-sm"
> >
<USelectMenu <USelectMenu
v-model="appConfig.toaster.position" v-model="appConfig.toaster.position"

View File

@@ -1,5 +1,5 @@
import { onMounted, watch } from 'vue' import { onMounted, watch } from 'vue'
import FaviconSvg from 'public/icon.svg?raw' import FaviconSvg from '../../public/icon.svg?raw'
export function useFaviconFromTheme() { export function useFaviconFromTheme() {
const colorMode = useColorMode() const colorMode = useColorMode()

View File

@@ -59,7 +59,7 @@ provide('navigation', mappedNavigation)
<UApp> <UApp>
<NuxtLoadingIndicator color="#FFF" /> <NuxtLoadingIndicator color="#FFF" />
<!-- <Banner /> --> <Banner />
<Header :links="links" /> <Header :links="links" />

View File

@@ -5,6 +5,17 @@ pricing:
title: Upgrade to Nuxt UI [Pro]{class="text-primary"}. title: Upgrade to Nuxt UI [Pro]{class="text-primary"}.
description: On top of 40+ open source components from Nuxt UI, Pro gives you access to 50+ premium Vue components to create beautiful & responsive Nuxt applications in minutes. It includes all primitives to build landing pages, documentations, blogs, dashboards or entire SaaS products. description: On top of 40+ open source components from Nuxt UI, Pro gives you access to 50+ premium Vue components to create beautiful & responsive Nuxt applications in minutes. It includes all primitives to build landing pages, documentations, blogs, dashboards or entire SaaS products.
freePlan: freePlan:
description: "**NuxtLabs is joining Vercel** :tada: As part of this transition, Nuxt UI is becoming even more accessible.<br><br> **In September, we're launching Nuxt UI v4**: a free, open-source library that unifies Nuxt UI and Nuxt UI Pro, offering 100+ components and a complete free Figma Kit for everyone."
orientation: horizontal
button:
label: Read the announcement
to: 'https://nuxtlabs.com/?utm_source=nuxt-ui&utm_medium=banner&utm_campaign=nuxtlabs-vercel'
target: _blank
color: 'neutral'
trailingIcon: 'i-lucide-arrow-right'
ui:
trailingIcon: 'ms-0'
devPlan:
title: Free in development title: Free in development
description: Try Nuxt UI Pro for free in development, no credit card required. Upgrade when ready to deploy. description: Try Nuxt UI Pro for free in development, no credit card required. Upgrade when ready to deploy.
orientation: horizontal orientation: horizontal
@@ -13,6 +24,9 @@ pricing:
to: '/getting-started/installation/pro/nuxt' to: '/getting-started/installation/pro/nuxt'
color: 'neutral' color: 'neutral'
variant: 'subtle' variant: 'subtle'
trailingIcon: 'i-lucide-arrow-right'
ui:
trailingIcon: 'ms-0'
figma: figma:
title: Figma Kit Pro title: Figma Kit Pro
description: Get all Nuxt UI Pro components in a Figma kit to design your next application before coding. Everything you need, from wire-framing to high-fidelity web integration. description: Get all Nuxt UI Pro components in a Figma kit to design your next application before coding. Everything you need, from wire-framing to high-fidelity web integration.

View File

@@ -34,10 +34,19 @@ useSeoMeta({
<div class="flex flex-col bg-default gap-8 lg:gap-0"> <div class="flex flex-col bg-default gap-8 lg:gap-0">
<UPricingPlan <UPricingPlan
v-bind="page.pricing.freePlan" v-bind="page.pricing.freePlan"
variant="naked" class="lg:rounded-none ring-primary/15 ring-inset -mb-px bg-primary/5 z-[1]"
class="lg:rounded-none border-x border-default border-t border-b lg:border-b-0" :ui="{ description: 'mt-0 text-primary' }"
>
<template #description>
<MDC :value="page.pricing.freePlan.description" unwrap="p" />
</template>
</UPricingPlan>
<UPricingPlan
v-bind="page.pricing.devPlan"
class="lg:rounded-none ring-inset -mb-px"
/> />
<UPricingPlans compact> <UPricingPlans compact class="-space-x-px">
<UPricingPlan <UPricingPlan
v-for="(plan, index) in page.pricing.plans" v-for="(plan, index) in page.pricing.plans"
:key="index" :key="index"
@@ -47,18 +56,17 @@ useSeoMeta({
:discount="plan.discount" :discount="plan.discount"
:billing-period="plan.billing_period" :billing-period="plan.billing_period"
:billing-cycle="plan.billing_cycle" :billing-cycle="plan.billing_cycle"
:variant="plan.highlight ? 'soft' : 'outline'" :variant="plan.highlight ? 'subtle' : 'outline'"
:class="['lg:rounded-none', { 'border-2 lg:border lg:border-x-0 border-primary lg:border-default': plan.highlight }]" class="lg:rounded-none ring-inset -mb-px"
:features="plan.features" :features="plan.features"
:button="plan.button" :button="plan.button"
/> />
</UPricingPlans> </UPricingPlans>
<UPricingPlan <UPricingPlan
v-bind="page.pricing.figma" v-bind="page.pricing.figma"
variant="naked"
:billing-period="page.pricing.figma.billing_period" :billing-period="page.pricing.figma.billing_period"
:billing-cycle="page.pricing.figma.billing_cycle" :billing-cycle="page.pricing.figma.billing_cycle"
class="lg:rounded-none border lg:border-y-0 border-default" class="lg:rounded-none ring-inset -mb-px"
> >
<template #features> <template #features>
<li v-for="(feature, index) in page.pricing.figma.features" :key="index" class="flex items-center gap-2 min-w-0"> <li v-for="(feature, index) in page.pricing.figma.features" :key="index" class="flex items-center gap-2 min-w-0">

View File

@@ -225,6 +225,27 @@ export default defineNuxtConfig({
This option adds the `transition-colors` class on components with hover or active states. This option adds the `transition-colors` class on components with hover or active states.
:: ::
### `theme.defaultVariants` :badge{label="Soon" class="align-text-top"}
Use the `theme.defaultVariants` option to override the default `color` and `size` variants for components.
- Default: `{ color: 'primary', size: 'md' }`{lang="ts-type"}
```ts [nuxt.config.ts]
export default defineNuxtConfig({
modules: ['@nuxt/ui'],
css: ['~/assets/css/main.css'],
ui: {
theme: {
defaultVariants: {
color: 'neutral',
size: 'sm'
}
}
}
})
```
## Continuous Releases ## Continuous Releases
Nuxt UI uses [pkg.pr.new](https://github.com/stackblitz-labs/pkg.pr.new) for continuous preview releases, providing developers with instant access to the latest features and bug fixes without waiting for official releases. Nuxt UI uses [pkg.pr.new](https://github.com/stackblitz-labs/pkg.pr.new) for continuous preview releases, providing developers with instant access to the latest features and bug fixes without waiting for official releases.

View File

@@ -183,7 +183,28 @@ It's recommended to install the [Tailwind CSS IntelliSense](https://marketplace.
``` ```
::note{to="/components/app"} ::note{to="/components/app"}
The `App` component provides global configurations and is required for **Toast**, **Tooltip** components to work as well as **Programmatic Overlays**. The `App` component sets up global config and is required for **Toast**, **Tooltip** and **programmatic overlays**.
::
#### Add the `isolate` class to your root container
```html [index.html]{9}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Nuxt UI</title>
</head>
<body>
<div id="app" class="isolate"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
```
::note
This ensures styles are scoped to your app and prevents issues with overlays and stacking contexts.
:: ::
:: ::
@@ -333,6 +354,32 @@ export default defineConfig({
This option adds the `transition-colors` class on components with hover or active states. This option adds the `transition-colors` class on components with hover or active states.
:: ::
### `theme.defaultVariants` :badge{label="Soon" class="align-text-top"}
Use the `theme.defaultVariants` option to override the default `color` and `size` variants for components.
- Default: `{ color: 'primary', size: 'md' }`{lang="ts-type"}
```ts [vite.config.ts]
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ui from '@nuxt/ui/vite'
export default defineConfig({
plugins: [
vue(),
ui({
theme: {
defaultVariants: {
color: 'neutral',
size: 'sm'
}
}
})
]
})
```
### `inertia` ### `inertia`
Use the `inertia` option to enable compatibility with [Inertia.js](https://inertiajs.com/). Use the `inertia` option to enable compatibility with [Inertia.js](https://inertiajs.com/).

View File

@@ -536,6 +536,33 @@ import { ModalExampleComponent } from '#components'
</script> </script>
``` ```
### Changed form validation
- The error object property for targeting form fields has been renamed from `path` to `name`:
```diff
<script setup lang="ts">
const validate = (state: any): FormError[] => {
const errors = []
if (!state.email) {
errors.push({
- path: 'email',
+ name: 'email',
message: 'Required'
})
}
if (!state.password) {
errors.push({
- path: 'password',
+ name: 'password',
message: 'Required'
})
}
return errors
}
</script>
```
--- ---
::warning ::warning

View File

@@ -87,7 +87,7 @@ Read more about this in the `@nuxt/icon` documentation.
You can use local SVG files to create a custom Iconify collection. You can use local SVG files to create a custom Iconify collection.
For example, place your icons' SVG files under a folder of your choice, for example, `./assets/icons`: For example, place your icons' SVG files under a folder of your choice, for example, `./app/assets/icons`:
```bash ```bash
assets/icons assets/icons
@@ -104,7 +104,7 @@ export default defineNuxtConfig({
icon: { icon: {
customCollections: [{ customCollections: [{
prefix: 'custom', prefix: 'custom',
dir: './assets/icons' dir: './app/assets/icons'
}] }]
} }
}) })

View File

@@ -328,6 +328,17 @@ name: 'drawer-responsive-example'
--- ---
:: ::
### Nested drawers :badge{label="Soon" class="align-text-top"}
You can nest drawers within each other by using the `nested` prop.
::component-example
---
prettier: true
name: 'drawer-nested-example'
---
::
### With footer slot ### With footer slot
Use the `#footer` slot to add content after the Drawer's body. Use the `#footer` slot to add content after the Drawer's body.

View File

@@ -757,6 +757,33 @@ name: 'input-menu-filter-fields-example'
--- ---
:: ::
### With full content width
You can expand the content to the full width of its items by using the `ui.content` key.
::component-example
---
name: 'input-menu-content-width-example'
collapse: true
---
::
::tip
You can also change the content width globally in your `app.config.ts`:
```
export default defineAppConfig({
ui: {
inputMenu: {
slots: {
content: 'min-w-fit'
}
}
}
})
```
::
### As a CountryPicker ### As a CountryPicker
This example demonstrates using the InputMenu as a country picker with lazy loading - countries are only fetched when the menu is opened. This example demonstrates using the InputMenu as a country picker with lazy loading - countries are only fetched when the menu is opened.

View File

@@ -62,6 +62,19 @@ items:
--- ---
:: ::
### Color :badge{label="Soon" class="align-text-top"}
Use the `color` prop to change the color of the Kbd.
::component-code
---
props:
color: neutral
slots:
default: K
---
::
### Variant ### Variant
Use the `variant` prop to change the variant of the Kbd. Use the `variant` prop to change the variant of the Kbd.
@@ -69,6 +82,7 @@ Use the `variant` prop to change the variant of the Kbd.
::component-code ::component-code
--- ---
props: props:
color: neutral
variant: solid variant: solid
slots: slots:
default: K default: K

View File

@@ -790,6 +790,33 @@ name: 'select-menu-filter-fields-example'
--- ---
:: ::
### With full content width
You can expand the content to the full width of its items by using the `ui.content` key.
::component-example
---
name: 'select-menu-content-width-example'
collapse: true
---
::
::tip
You can also change the content width globally in your `app.config.ts`:
```
export default defineAppConfig({
ui: {
selectMenu: {
slots: {
content: 'min-w-fit'
}
}
}
})
```
::
### As a CountryPicker ### As a CountryPicker
This example demonstrates using the SelectMenu as a country picker with lazy loading - countries are only fetched when the menu is opened. This example demonstrates using the SelectMenu as a country picker with lazy loading - countries are only fetched when the menu is opened.
@@ -801,6 +828,8 @@ name: 'select-menu-countries-example'
--- ---
:: ::
## API ## API
### Props ### Props

View File

@@ -695,6 +695,33 @@ collapse: true
--- ---
:: ::
### With full content width
You can expand the content to the full width of its items by using the `ui.content` key.
::component-example
---
name: 'select-content-width-example'
collapse: true
---
::
::tip
You can also change the content width globally in your `app.config.ts`:
```
export default defineAppConfig({
ui: {
select: {
slots: {
content: 'min-w-fit'
}
}
}
})
```
::
## API ## API
### Props ### Props

View File

@@ -83,6 +83,9 @@ Use the `columns` prop as an array of [ColumnDef](https://tanstack.com/table/lat
- `class`: - `class`:
- `td`: [The classes to apply to the `td` element.]{class="text-muted"} - `td`: [The classes to apply to the `td` element.]{class="text-muted"}
- `th`: [The classes to apply to the `th` element.]{class="text-muted"} - `th`: [The classes to apply to the `th` element.]{class="text-muted"}
- `style`:
- `td`: [The style to apply to the `td` element.]{class="text-muted"}
- `th`: [The style to apply to the `th` element.]{class="text-muted"}
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.
@@ -112,6 +115,8 @@ Use the `meta` prop as an object ([TableMeta](https://tanstack.com/table/latest/
- `class`: - `class`:
- `tr`: [The classes to apply to the `tr` element.]{class="text-muted"} - `tr`: [The classes to apply to the `tr` element.]{class="text-muted"}
- `style`:
- `tr`: [The style to apply to the `tr` element.]{class="text-muted"}
### Loading ### Loading

View File

@@ -19,12 +19,13 @@ Use the `items` prop as an array of objects with the following properties:
- `label?: string`{lang="ts-type"} - `label?: string`{lang="ts-type"}
- `icon?: string`{lang="ts-type"} - `icon?: string`{lang="ts-type"}
- `avatar?: AvatarProps`{lang="ts-type"} - `avatar?: AvatarProps`{lang="ts-type"}
- `badge?: string | number | BadgeProps`{lang="ts-type"}
- `content?: string`{lang="ts-type"} - `content?: string`{lang="ts-type"}
- `value?: string | number`{lang="ts-type"} - `value?: string | number`{lang="ts-type"}
- `disabled?: boolean`{lang="ts-type"} - `disabled?: boolean`{lang="ts-type"}
- [`slot?: string`{lang="ts-type"}](#with-custom-slot) - [`slot?: string`{lang="ts-type"}](#with-custom-slot)
- `class?: any`{lang="ts-type"} - `class?: any`{lang="ts-type"}
- `ui?: { trigger?: ClassNameValue, leadingIcon?: ClassNameValue, leadingAvatar?: ClassNameValue, label?: ClassNameValue, content?: ClassNameValue }`{lang="ts-type"} - `ui?: { trigger?: ClassNameValue, leadingIcon?: ClassNameValue, leadingAvatar?: ClassNameValue, leadingAvatarSize?: ClassNameValue, label?: ClassNameValue, trailingBadge?: ClassNameValue, trailingBadgeSize?: ClassNameValue, content?: ClassNameValue }`{lang="ts-type"}
::component-code ::component-code
--- ---

View File

@@ -51,3 +51,5 @@ items:
url: https://wiredash.com/ url: https://wiredash.com/
- name: Zielgestalt - name: Zielgestalt
url: https://zielgestalt.de/ url: https://zielgestalt.de/
- name: Arthur Danjou's Porfolio
url: https://arthurdanjou.fr/

View File

@@ -143,10 +143,6 @@ export default defineNuxtConfig({
'/releases': { redirect: 'https://github.com/nuxt/ui/releases', prerender: false } '/releases': { redirect: 'https://github.com/nuxt/ui/releases', prerender: false }
}, },
future: {
compatibilityVersion: 4
},
compatibilityDate: '2024-07-09', compatibilityDate: '2024-07-09',
nitro: { nitro: {

View File

@@ -11,26 +11,26 @@
"dependencies": { "dependencies": {
"@ai-sdk/vue": "^1.2.12", "@ai-sdk/vue": "^1.2.12",
"@iconify-json/logos": "^1.2.4", "@iconify-json/logos": "^1.2.4",
"@iconify-json/lucide": "^1.2.56", "@iconify-json/lucide": "^1.2.57",
"@iconify-json/simple-icons": "^1.2.42", "@iconify-json/simple-icons": "^1.2.44",
"@iconify-json/vscode-icons": "^1.2.23", "@iconify-json/vscode-icons": "^1.2.23",
"@nuxt/content": "^3.6.3", "@nuxt/content": "^3.6.3",
"@nuxt/image": "^1.10.0", "@nuxt/image": "^1.10.0",
"@nuxt/ui": "workspace:*", "@nuxt/ui": "workspace:*",
"@nuxt/ui-pro": "https://pkg.pr.new/@nuxt/ui-pro@22fdc5e", "@nuxt/ui-pro": "https://pkg.pr.new/@nuxt/ui-pro@17684e4",
"@nuxthub/core": "^0.9.0", "@nuxthub/core": "^0.9.0",
"@nuxtjs/plausible": "^1.2.0", "@nuxtjs/plausible": "^1.2.0",
"@octokit/rest": "^22.0.0", "@octokit/rest": "^22.0.0",
"@rollup/plugin-yaml": "^4.1.2", "@rollup/plugin-yaml": "^4.1.2",
"@vueuse/integrations": "^13.5.0", "@vueuse/integrations": "^13.5.0",
"@vueuse/nuxt": "^13.5.0", "@vueuse/nuxt": "^13.5.0",
"ai": "^4.3.16", "ai": "^4.3.19",
"better-sqlite3": "^12.2.0", "better-sqlite3": "^12.2.0",
"capture-website": "^4.2.0", "capture-website": "^4.2.0",
"joi": "^17.13.3", "joi": "^17.13.3",
"maska": "^3.2.0", "maska": "^3.2.0",
"motion-v": "^1.5.0", "motion-v": "^1.5.0",
"nuxt": "^3.17.6", "nuxt": "^4.0.1",
"nuxt-component-meta": "^0.12.1", "nuxt-component-meta": "^0.12.1",
"nuxt-llms": "^0.1.3", "nuxt-llms": "^0.1.3",
"nuxt-og-image": "^5.1.9", "nuxt-og-image": "^5.1.9",
@@ -40,11 +40,11 @@
"superstruct": "^2.0.2", "superstruct": "^2.0.2",
"ufo": "^1.6.1", "ufo": "^1.6.1",
"valibot": "^1.1.0", "valibot": "^1.1.0",
"workers-ai-provider": "^0.7.1", "workers-ai-provider": "^0.7.2",
"yup": "^1.6.1", "yup": "^1.6.1",
"zod": "^3.25.75" "zod": "^4.0.5"
}, },
"devDependencies": { "devDependencies": {
"wrangler": "^4.23.0" "wrangler": "^4.25.0"
} }
} }

View File

@@ -2,7 +2,7 @@
"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.2.0", "version": "3.2.0",
"packageManager": "pnpm@10.12.4", "packageManager": "pnpm@10.13.1",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/nuxt/ui.git" "url": "git+https://github.com/nuxt/ui.git"
@@ -116,8 +116,8 @@
"@internationalized/number": "^3.6.3", "@internationalized/number": "^3.6.3",
"@nuxt/fonts": "^0.11.4", "@nuxt/fonts": "^0.11.4",
"@nuxt/icon": "^1.15.0", "@nuxt/icon": "^1.15.0",
"@nuxt/kit": "^3.17.6", "@nuxt/kit": "^4.0.1",
"@nuxt/schema": "^3.17.6", "@nuxt/schema": "^4.0.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.1.11", "@tailwindcss/postcss": "^4.1.11",
@@ -155,16 +155,16 @@
"vue-component-type-helpers": "^3.0.1" "vue-component-type-helpers": "^3.0.1"
}, },
"devDependencies": { "devDependencies": {
"@nuxt/eslint-config": "^1.5.2", "@nuxt/eslint-config": "^1.6.0",
"@nuxt/module-builder": "^1.0.1", "@nuxt/module-builder": "^1.0.1",
"@nuxt/test-utils": "^3.19.2", "@nuxt/test-utils": "^3.19.2",
"@release-it/conventional-changelog": "^10.0.1", "@release-it/conventional-changelog": "^10.0.1",
"@vue/test-utils": "^2.4.6", "@vue/test-utils": "^2.4.6",
"embla-carousel": "^8.6.0", "embla-carousel": "^8.6.0",
"eslint": "^9.30.1", "eslint": "^9.31.0",
"happy-dom": "^18.0.1", "happy-dom": "^18.0.1",
"nuxt": "^3.17.6", "nuxt": "^4.0.1",
"release-it": "^19.0.3", "release-it": "^19.0.4",
"vitest": "^3.2.4", "vitest": "^3.2.4",
"vitest-environment-nuxt": "^1.0.1", "vitest-environment-nuxt": "^1.0.1",
"vue-tsc": "^3.0.1" "vue-tsc": "^3.0.1"
@@ -177,7 +177,7 @@
"valibot": "^1.0.0", "valibot": "^1.0.0",
"vue-router": "^4.5.0", "vue-router": "^4.5.0",
"yup": "^1.6.0", "yup": "^1.6.0",
"zod": "^3.24.0" "zod": "^3.24.0 || ^4.0.0"
}, },
"peerDependenciesMeta": { "peerDependenciesMeta": {
"@inertiajs/vue3": { "@inertiajs/vue3": {

View File

@@ -13,12 +13,12 @@
"@nuxt/ui": "workspace:*", "@nuxt/ui": "workspace:*",
"vue": "^3.5.17", "vue": "^3.5.17",
"vue-router": "^4.5.1", "vue-router": "^4.5.1",
"zod": "^3.25.75" "zod": "^4.0.5"
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "^5.2.4", "@vitejs/plugin-vue": "^6.0.0",
"typescript": "^5.8.3", "typescript": "^5.8.3",
"vite": "^6.3.5", "vite": "^7.0.5",
"vue-tsc": "^3.0.1" "vue-tsc": "^3.0.1"
} }
} }

View File

@@ -1,12 +1,16 @@
<script setup lang="ts"> <script setup lang="ts">
const colorHex = ref('#9C27B0') const colorHex = ref('#9C27B0')
function handleColorChange(event: Event) {
colorHex.value = (event.target as HTMLInputElement).value
}
</script> </script>
<template> <template>
<div class="flex flex-col gap-5"> <div class="flex flex-col gap-5">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<span :style="{ backgroundColor: colorHex }" class="inline-flex w-5 h-5 rounded" /> <span :style="{ backgroundColor: colorHex }" class="inline-flex w-5 h-5 rounded" />
<code class="font-mono">{{ colorHex }}</code> <UInput :model-value="colorHex" @change="handleColorChange" />
</div> </div>
<USeparator /> <USeparator />
<div class="flex justify-between gap-2"> <div class="flex justify-between gap-2">
@@ -21,6 +25,6 @@ const colorHex = ref('#9C27B0')
</UButton> </UButton>
</div> </div>
<USeparator /> <USeparator />
<UColorPicker v-model="colorHex" @update:model-value="() => console.log('model update')" /> <UColorPicker v-model="colorHex" />
</div> </div>
</template> </template>

View File

@@ -28,6 +28,20 @@ const inset = ref(false)
</template> </template>
</UDrawer> </UDrawer>
<UDrawer title="Drawer with nested" :inset="inset" :ui="{ content: 'h-full' }" should-scale-background>
<UButton color="neutral" variant="outline" label="Open nested" />
<template #footer>
<UDrawer :inset="inset" nested :ui="{ content: 'h-full' }">
<UButton color="neutral" variant="outline" label="Open nested" />
<template #content>
<Placeholder class="flex-1 m-4" />
</template>
</UDrawer>
</template>
</UDrawer>
<UDrawer title="Drawer with bottom direction" direction="bottom" :inset="inset"> <UDrawer title="Drawer with bottom direction" direction="bottom" :inset="inset">
<UButton color="neutral" variant="outline" label="Open on bottom" /> <UButton color="neutral" variant="outline" label="Open on bottom" />

View File

@@ -3,20 +3,16 @@ import theme from '#build/ui/kbd'
import { kbdKeysMap } from '@nuxt/ui/composables/useKbd.js' import { kbdKeysMap } from '@nuxt/ui/composables/useKbd.js'
const sizes = Object.keys(theme.variants.size) as Array<keyof typeof theme.variants.size> const sizes = Object.keys(theme.variants.size) as Array<keyof typeof theme.variants.size>
const variants = Object.keys(theme.variants.variant) as Array<keyof typeof theme.variants.variant>
const colors = Object.keys(theme.variants.color) as Array<keyof typeof theme.variants.color>
const kbdKeys = Object.keys(kbdKeysMap) const kbdKeys = Object.keys(kbdKeysMap)
</script> </script>
<template> <template>
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<div class="flex items-center gap-1"> <div v-for="color in colors" :key="color" class="flex items-center gap-1 ms-[-22px]">
<UKbd value="meta" /> <UKbd v-for="variant in variants" :key="`${color}-${variant}`" value="meta" :variant="variant" :color="color" />
</div>
<div class="flex items-center gap-1">
<UKbd value="meta" variant="subtle" />
</div>
<div class="flex items-center gap-1">
<UKbd value="meta" variant="solid" />
</div> </div>
<div class="flex items-center gap-1 ms-[-220px]"> <div class="flex items-center gap-1 ms-[-220px]">
<UKbd v-for="(kdbKey, index) in kbdKeys" :key="index" :value="kdbKey" /> <UKbd v-for="(kdbKey, index) in kbdKeys" :key="index" :value="kdbKey" />

View File

@@ -25,7 +25,8 @@ const items = [{
label: 'Tab3', label: 'Tab3',
icon: 'i-lucide-bell', icon: 'i-lucide-bell',
content: 'Finally, this is the content for Tab3', content: 'Finally, this is the content for Tab3',
slot: 'custom' as const slot: 'custom' as const,
badge: '300'
}] }]
</script> </script>

View File

@@ -10,10 +10,6 @@ export default defineNuxtConfig({
css: ['~/assets/css/main.css'], css: ['~/assets/css/main.css'],
future: {
compatibilityVersion: 4
},
compatibilityDate: '2024-07-09', compatibilityDate: '2024-07-09',
vite: { vite: {

View File

@@ -9,13 +9,13 @@
"typecheck": "nuxt typecheck" "typecheck": "nuxt typecheck"
}, },
"dependencies": { "dependencies": {
"@iconify-json/lucide": "^1.2.56", "@iconify-json/lucide": "^1.2.57",
"@iconify-json/simple-icons": "^1.2.42", "@iconify-json/simple-icons": "^1.2.44",
"@internationalized/date": "^3.8.2", "@internationalized/date": "^3.8.2",
"@nuxt/ui": "workspace:*", "@nuxt/ui": "workspace:*",
"@nuxthub/core": "^0.9.0", "@nuxthub/core": "^0.9.0",
"nuxt": "^3.17.6", "nuxt": "^4.0.1",
"zod": "^3.25.75" "zod": "^4.0.5"
}, },
"devDependencies": { "devDependencies": {
"typescript": "^5.8.3", "typescript": "^5.8.3",

3181
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -6,6 +6,9 @@ import { name, version } from '../package.json'
export type * from './runtime/types' export type * from './runtime/types'
type Color = 'primary' | 'secondary' | 'success' | 'info' | 'warning' | 'error' | (string & {})
type Size = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | (string & {})
export interface ModuleOptions { export interface ModuleOptions {
/** /**
* Prefix for components * Prefix for components
@@ -38,7 +41,7 @@ export interface ModuleOptions {
* @defaultValue `['primary', 'secondary', 'success', 'info', 'warning', 'error']` * @defaultValue `['primary', 'secondary', 'success', 'info', 'warning', 'error']`
* @link https://ui.nuxt.com/getting-started/installation/nuxt#themecolors * @link https://ui.nuxt.com/getting-started/installation/nuxt#themecolors
*/ */
colors?: string[] colors?: Color[]
/** /**
* Enable or disable transitions on components * Enable or disable transitions on components
@@ -46,6 +49,20 @@ export interface ModuleOptions {
* @link https://ui.nuxt.com/getting-started/installation/nuxt#themetransitions * @link https://ui.nuxt.com/getting-started/installation/nuxt#themetransitions
*/ */
transitions?: boolean transitions?: boolean
defaultVariants?: {
/**
* The default color variant to use for components
* @defaultValue `'primary'`
*/
color?: Color
/**
* The default size variant to use for components
* @defaultValue `'md'`
*/
size?: Size
}
} }
} }
@@ -85,7 +102,7 @@ export default defineNuxtModule<ModuleOptions>({
async function registerModule(name: string, key: string, options: Record<string, any>) { async function registerModule(name: string, key: string, options: Record<string, any>) {
if (!hasNuxtModule(name)) { if (!hasNuxtModule(name)) {
await installModule(name, options) await installModule(name, defu((nuxt.options as any)[key], options))
} else { } else {
(nuxt.options as any)[key] = defu((nuxt.options as any)[key], options) (nuxt.options as any)[key] = defu((nuxt.options as any)[key], options)
} }

View File

@@ -57,7 +57,7 @@ export interface AlertProps {
} }
export interface AlertEmits { export interface AlertEmits {
(e: 'update:open', value: boolean): void 'update:open': [value: boolean]
} }
export interface AlertSlots { export interface AlertSlots {

View File

@@ -256,6 +256,7 @@ const scrollSnaps = ref<number[]>([])
function onInit(api: EmblaCarouselType) { function onInit(api: EmblaCarouselType) {
scrollSnaps.value = api?.scrollSnapList() || [] scrollSnaps.value = api?.scrollSnapList() || []
} }
function onSelect(api: EmblaCarouselType) { function onSelect(api: EmblaCarouselType) {
canScrollNext.value = api?.canScrollNext() || false canScrollNext.value = api?.canScrollNext() || false
canScrollPrev.value = api?.canScrollPrev() || false canScrollPrev.value = api?.canScrollPrev() || false
@@ -300,8 +301,7 @@ defineExpose({
<div <div
v-for="(item, index) in items" v-for="(item, index) in items"
:key="index" :key="index"
role="group" v-bind="dots ? { role: 'tabpanel' } : { 'role': 'group', 'aria-roledescription': 'slide' }"
aria-roledescription="slide"
:class="ui.item({ class: [props.ui?.item, isCarouselItem(item) && item.ui?.item, isCarouselItem(item) && item.class] })" :class="ui.item({ class: [props.ui?.item, isCarouselItem(item) && item.ui?.item, isCarouselItem(item) && item.class] })"
> >
<slot :item="item" :index="index" /> <slot :item="item" :index="index" />
@@ -333,13 +333,15 @@ defineExpose({
/> />
</div> </div>
<div v-if="dots" :class="ui.dots({ class: props.ui?.dots })"> <div v-if="dots" role="tablist" :aria-label="t('carousel.dots')" :class="ui.dots({ class: props.ui?.dots })">
<template v-for="(_, index) in scrollSnaps" :key="index"> <template v-for="(_, index) in scrollSnaps" :key="index">
<button <button
type="button"
role="tab"
:aria-label="t('carousel.goto', { slide: index + 1 })" :aria-label="t('carousel.goto', { slide: index + 1 })"
:aria-selected="selectedIndex === index"
:class="ui.dot({ class: props.ui?.dot, active: selectedIndex === index })" :class="ui.dot({ class: props.ui?.dot, active: selectedIndex === index })"
:data-state="selectedIndex === index ? 'active' : undefined" :data-state="selectedIndex === index ? 'active' : undefined"
:aria-current="selectedIndex === index ? true : undefined"
@click="scrollTo(index)" @click="scrollTo(index)"
/> />
</template> </template>

View File

@@ -35,7 +35,7 @@ export interface ChipProps {
} }
export interface ChipEmits { export interface ChipEmits {
(e: 'update:show', payload: boolean): void 'update:show': [payload: boolean]
} }
export interface ChipSlots { export interface ChipSlots {

View File

@@ -31,7 +31,7 @@ function HSVtoHSL(hsv: HSVColor): HSLObject {
return { return {
H: hsv.h, H: hsv.h,
S: x === 0 || x === 200 ? 0 : Math.round(hsv.s * hsv.v / (x <= 100 ? x : 200 - x)), S: x === 0 || x === 200 ? 0 : Math.round(hsv.s * hsv.v / (x <= 100 ? x : 200 - x)),
L: Math.round(x / 2) L: x / 2
} }
} }
@@ -102,7 +102,6 @@ const pickedColor = computed<HSVColor>({
}, },
set(value) { set(value) {
const color = new ColorTranslator(HSVtoHSL(value), { const color = new ColorTranslator(HSVtoHSL(value), {
decimals: 2,
labUnit: 'percent', labUnit: 'percent',
cmykUnit: 'percent', cmykUnit: 'percent',
cmykFunction: 'cmyk' cmykFunction: 'cmyk'

View File

@@ -37,6 +37,11 @@ export interface DrawerProps extends Pick<DrawerRootProps, 'activeSnapPoint' | '
* @defaultValue true * @defaultValue true
*/ */
portal?: boolean | string | HTMLElement portal?: boolean | string | HTMLElement
/**
* Whether the drawer is nested in another drawer.
* @defaultValue false
*/
nested?: boolean
class?: any class?: any
ui?: Drawer['slots'] ui?: Drawer['slots']
} }
@@ -57,7 +62,7 @@ export interface DrawerSlots {
<script setup lang="ts"> <script setup lang="ts">
import { computed, toRef } from 'vue' import { computed, toRef } from 'vue'
import { VisuallyHidden, useForwardPropsEmits } from 'reka-ui' import { VisuallyHidden, useForwardPropsEmits } from 'reka-ui'
import { DrawerRoot, DrawerTrigger, DrawerPortal, DrawerOverlay, DrawerContent, DrawerTitle, DrawerDescription, DrawerHandle } from 'vaul-vue' import { DrawerRoot, DrawerRootNested, DrawerTrigger, DrawerPortal, DrawerOverlay, DrawerContent, DrawerTitle, DrawerDescription, DrawerHandle } from 'vaul-vue'
import { reactivePick } from '@vueuse/core' import { reactivePick } from '@vueuse/core'
import { useAppConfig } from '#imports' import { useAppConfig } from '#imports'
import { usePortal } from '../composables/usePortal' import { usePortal } from '../composables/usePortal'
@@ -90,7 +95,7 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.drawer || {}
</script> </script>
<template> <template>
<DrawerRoot v-bind="rootProps"> <component :is="nested ? DrawerRootNested : DrawerRoot" v-bind="rootProps">
<DrawerTrigger v-if="!!slots.default" as-child :class="props.class"> <DrawerTrigger v-if="!!slots.default" as-child :class="props.class">
<slot /> <slot />
</DrawerTrigger> </DrawerTrigger>
@@ -144,5 +149,5 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.drawer || {}
</slot> </slot>
</DrawerContent> </DrawerContent>
</DrawerPortal> </DrawerPortal>
</DrawerRoot> </component>
</template> </template>

View File

@@ -53,8 +53,8 @@ export interface FormProps<S extends FormSchema, T extends boolean = true> {
} }
export interface FormEmits<S extends FormSchema, T extends boolean = true> { export interface FormEmits<S extends FormSchema, T extends boolean = true> {
(e: 'submit', payload: FormSubmitEvent<FormData<S, T>>): void submit: [payload: FormSubmitEvent<FormData<S, T>>]
(e: 'error', payload: FormErrorEvent): void error: [payload: FormErrorEvent]
} }
export interface FormSlots { export interface FormSlots {

View File

@@ -18,7 +18,7 @@ export interface FormFieldProps {
label?: string label?: string
description?: string description?: string
help?: string help?: string
error?: string | boolean error?: boolean | string
hint?: string hint?: string
/** /**
* @defaultValue 'md' * @defaultValue 'md'
@@ -41,8 +41,8 @@ export interface FormFieldSlots {
hint(props: { hint?: string }): any hint(props: { hint?: string }): any
description(props: { description?: string }): any description(props: { description?: string }): any
help(props: { help?: string }): any help(props: { help?: string }): any
error(props: { error?: string | boolean }): any error(props: { error?: boolean | string }): any
default(props: { error?: string | boolean }): any default(props: { error?: boolean | string }): any
} }
</script> </script>
@@ -121,7 +121,7 @@ provide(formFieldInjectionKey, computed(() => ({
{{ error }} {{ error }}
</slot> </slot>
</div> </div>
<div v-else-if="help || !!slots.help" :class="ui.help({ class: props.ui?.help })"> <div v-else-if="help || !!slots.help" :id="`${ariaId}-help`" :class="ui.help({ class: props.ui?.help })">
<slot name="help" :help="help"> <slot name="help" :help="help">
{{ help }} {{ help }}
</slot> </slot>

View File

@@ -52,9 +52,9 @@ export interface InputProps<T extends AcceptableValue = AcceptableValue> extends
} }
export interface InputEmits<T extends AcceptableValue = AcceptableValue> { export interface InputEmits<T extends AcceptableValue = AcceptableValue> {
(e: 'update:modelValue', payload: T): void 'update:modelValue': [payload: T]
(e: 'blur', event: FocusEvent): void 'blur': [event: FocusEvent]
(e: 'change', event: Event): void 'change': [event: Event]
} }
export interface InputSlots { export interface InputSlots {

View File

@@ -128,15 +128,16 @@ export interface InputMenuProps<T extends ArrayOrNested<InputMenuItem> = ArrayOr
} }
export type InputMenuEmits<A extends ArrayOrNested<InputMenuItem>, VK extends GetItemKeys<A> | undefined, M extends boolean> = Pick<ComboboxRootEmits, 'update:open'> & { export type InputMenuEmits<A extends ArrayOrNested<InputMenuItem>, VK extends GetItemKeys<A> | undefined, M extends boolean> = Pick<ComboboxRootEmits, 'update:open'> & {
change: [payload: Event] 'change': [payload: Event]
blur: [payload: FocusEvent] 'blur': [payload: FocusEvent]
focus: [payload: FocusEvent] 'focus': [payload: FocusEvent]
create: [item: string] 'create': [item: string]
/** Event handler when highlighted element changes. */ /** Event handler when highlighted element changes. */
highlight: [payload: { 'highlight': [payload: {
ref: HTMLElement ref: HTMLElement
value: GetModelValue<A, VK, M> value: GetModelValue<A, VK, M>
} | undefined] } | undefined]
'remove-tag': [item: GetModelValue<A, VK, M>]
} & GetModelValueEmits<A, VK, M> } & GetModelValueEmits<A, VK, M>
type SlotProps<T extends InputMenuItem> = (props: { item: T, index: number }) => any type SlotProps<T extends InputMenuItem> = (props: { item: T, index: number }) => any
@@ -171,7 +172,7 @@ export interface InputMenuSlots<
</script> </script>
<script setup lang="ts" generic="T extends ArrayOrNested<InputMenuItem>, VK extends GetItemKeys<T> | undefined = undefined, M extends boolean = false"> <script setup lang="ts" generic="T extends ArrayOrNested<InputMenuItem>, VK extends GetItemKeys<T> | undefined = undefined, M extends boolean = false">
import { computed, ref, toRef, onMounted, toRaw } from 'vue' import { computed, ref, toRef, onMounted, toRaw, nextTick } from 'vue'
import { ComboboxRoot, ComboboxArrow, ComboboxAnchor, ComboboxInput, ComboboxTrigger, ComboboxPortal, ComboboxContent, ComboboxEmpty, ComboboxGroup, ComboboxLabel, ComboboxSeparator, ComboboxItem, ComboboxItemIndicator, TagsInputRoot, TagsInputItem, TagsInputItemText, TagsInputItemDelete, TagsInputInput, useForwardPropsEmits, useFilter } from 'reka-ui' import { ComboboxRoot, ComboboxArrow, ComboboxAnchor, ComboboxInput, ComboboxTrigger, ComboboxPortal, ComboboxContent, ComboboxEmpty, ComboboxGroup, ComboboxLabel, ComboboxSeparator, ComboboxItem, ComboboxItemIndicator, TagsInputRoot, TagsInputItem, TagsInputItemText, TagsInputItemDelete, TagsInputInput, useForwardPropsEmits, useFilter } from 'reka-ui'
import { defu } from 'defu' import { defu } from 'defu'
import { isEqual } from 'ohash/utils' import { isEqual } from 'ohash/utils'
@@ -233,11 +234,7 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.inputMenu ||
})) }))
function displayValue(value: T): string { function displayValue(value: T): string {
if (!props.valueKey) { const item = items.value.find(item => compare(typeof item === 'object' && props.valueKey ? get(item as Record<string, any>, props.valueKey as string) : item, value))
return value && (typeof value === 'object' ? get(value, props.labelKey as string) : value)
}
const item = items.value.find(item => compare(typeof item === 'object' ? get(item as Record<string, any>, props.valueKey as string) : item, value))
return item && (typeof item === 'object' ? get(item, props.labelKey as string) : item) return item && (typeof item === 'object' ? get(item, props.labelKey as string) : item)
} }
@@ -258,8 +255,12 @@ const filteredGroups = computed(() => {
const fields = Array.isArray(props.filterFields) ? props.filterFields : [props.labelKey] as string[] const fields = Array.isArray(props.filterFields) ? props.filterFields : [props.labelKey] as string[]
return groups.value.map(group => group.filter((item) => { return groups.value.map(items => items.filter((item) => {
if (typeof item !== 'object' || item === null) { if (item === undefined || item === null) {
return false
}
if (typeof item !== 'object') {
return contains(String(item), searchTerm.value) return contains(String(item), searchTerm.value)
} }
@@ -267,7 +268,10 @@ const filteredGroups = computed(() => {
return true return true
} }
return fields.some(field => contains(get(item, field), searchTerm.value)) return fields.some((field) => {
const value = get(item, field)
return value !== undefined && value !== null && contains(String(value), searchTerm.value)
})
})).filter(group => group.filter(item => })).filter(group => group.filter(item =>
!isInputItem(item) || (!item.type || !['label', 'separator'].includes(item.type)) !isInputItem(item) || (!item.type || !['label', 'separator'].includes(item.type))
).length > 0) ).length > 0)
@@ -298,6 +302,10 @@ function autoFocus() {
} }
onMounted(() => { onMounted(() => {
nextTick(() => {
searchTerm.value = ''
})
setTimeout(() => { setTimeout(() => {
autoFocus() autoFocus()
}, props.autofocusDelay) }, props.autofocusDelay)
@@ -359,6 +367,7 @@ function onRemoveTag(event: any) {
const modelValue = props.modelValue as GetModelValue<T, VK, true> const modelValue = props.modelValue as GetModelValue<T, VK, true>
const filteredValue = modelValue.filter(value => !isEqual(value, event)) const filteredValue = modelValue.filter(value => !isEqual(value, event))
emits('update:modelValue', filteredValue as GetModelValue<T, VK, M>) emits('update:modelValue', filteredValue as GetModelValue<T, VK, M>)
emits('remove-tag', event)
onUpdate(filteredValue) onUpdate(filteredValue)
} }
} }
@@ -432,7 +441,7 @@ defineExpose({
<TagsInputItem v-for="(item, index) in tags" :key="index" :value="item" :class="ui.tagsItem({ class: [props.ui?.tagsItem, isInputItem(item) && item.ui?.tagsItem] })"> <TagsInputItem v-for="(item, index) in tags" :key="index" :value="item" :class="ui.tagsItem({ class: [props.ui?.tagsItem, isInputItem(item) && item.ui?.tagsItem] })">
<TagsInputItemText :class="ui.tagsItemText({ class: [props.ui?.tagsItemText, isInputItem(item) && item.ui?.tagsItemText] })"> <TagsInputItemText :class="ui.tagsItemText({ class: [props.ui?.tagsItemText, isInputItem(item) && item.ui?.tagsItemText] })">
<slot name="tags-item-text" :item="(item as NestedItem<T>)" :index="index"> <slot name="tags-item-text" :item="(item as NestedItem<T>)" :index="index">
{{ displayValue(item as T) }} {{ displayValue(item as T) ?? item }}
</slot> </slot>
</TagsInputItemText> </TagsInputItemText>

View File

@@ -63,9 +63,9 @@ export interface InputNumberProps extends Pick<NumberFieldRootProps, 'modelValue
} }
export interface InputNumberEmits { export interface InputNumberEmits {
(e: 'update:modelValue', payload: number): void 'update:modelValue': [payload: number]
(e: 'blur', event: FocusEvent): void 'blur': [event: FocusEvent]
(e: 'change', payload: Event): void 'change': [payload: Event]
} }
export interface InputNumberSlots { export interface InputNumberSlots {

View File

@@ -13,6 +13,10 @@ export interface KbdProps {
*/ */
as?: any as?: any
value?: KbdKey | string value?: KbdKey | string
/**
* @defaultValue 'neutral'
*/
color?: Kbd['variants']['color']
/** /**
* @defaultValue 'outline' * @defaultValue 'outline'
*/ */
@@ -48,7 +52,7 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.kbd || {}) }
</script> </script>
<template> <template>
<Primitive :as="as" :class="ui({ variant, size, class: props.class })"> <Primitive :as="as" :class="ui({ class: props.class, color: props.color, variant: props.variant, size: props.size })">
<slot> <slot>
{{ getKbdKey(value) }} {{ getKbdKey(value) }}
</slot> </slot>

View File

@@ -177,6 +177,8 @@ import UBadge from './Badge.vue'
import UPopover from './Popover.vue' import UPopover from './Popover.vue'
import UTooltip from './Tooltip.vue' import UTooltip from './Tooltip.vue'
defineOptions({ inheritAttrs: false })
const props = withDefaults(defineProps<NavigationMenuProps<T>>(), { const props = withDefaults(defineProps<NavigationMenuProps<T>>(), {
orientation: 'horizontal', orientation: 'horizontal',
contentOrientation: 'horizontal', contentOrientation: 'horizontal',
@@ -270,7 +272,7 @@ function getAccordionDefaultValue(list: NavigationMenuItem[], level = 0) {
<component :is="orientation === 'vertical' && item.children?.length && !collapsed ? AccordionTrigger : 'span'" v-if="(!collapsed || orientation !== 'vertical') && (item.badge || (orientation === 'horizontal' && (item.children?.length || !!slots[(item.slot ? `${item.slot}-content` : 'item-content') as keyof NavigationMenuSlots<T>])) || (orientation === 'vertical' && item.children?.length) || item.trailingIcon || !!slots[(item.slot ? `${item.slot}-trailing` : 'item-trailing') as keyof NavigationMenuSlots<T>])" as="span" :class="ui.linkTrailing({ class: [props.ui?.linkTrailing, item.ui?.linkTrailing] })" @click.stop.prevent> <component :is="orientation === 'vertical' && item.children?.length && !collapsed ? AccordionTrigger : 'span'" v-if="(!collapsed || orientation !== 'vertical') && (item.badge || (orientation === 'horizontal' && (item.children?.length || !!slots[(item.slot ? `${item.slot}-content` : 'item-content') as keyof NavigationMenuSlots<T>])) || (orientation === 'vertical' && item.children?.length) || item.trailingIcon || !!slots[(item.slot ? `${item.slot}-trailing` : 'item-trailing') as keyof NavigationMenuSlots<T>])" as="span" :class="ui.linkTrailing({ class: [props.ui?.linkTrailing, item.ui?.linkTrailing] })" @click.stop.prevent>
<slot :name="((item.slot ? `${item.slot}-trailing` : 'item-trailing') as keyof NavigationMenuSlots<T>)" :item="item" :active="active" :index="index"> <slot :name="((item.slot ? `${item.slot}-trailing` : 'item-trailing') as keyof NavigationMenuSlots<T>)" :item="item" :active="active" :index="index">
<UBadge <UBadge
v-if="item.badge" v-if="item.badge !== undefined"
color="neutral" color="neutral"
variant="outline" variant="outline"
:size="((item.ui?.linkTrailingBadgeSize || props.ui?.linkTrailingBadgeSize || ui.linkTrailingBadgeSize()) as BadgeProps['size'])" :size="((item.ui?.linkTrailingBadgeSize || props.ui?.linkTrailingBadgeSize || ui.linkTrailingBadgeSize()) as BadgeProps['size'])"
@@ -392,7 +394,7 @@ function getAccordionDefaultValue(list: NavigationMenuItem[], level = 0) {
</component> </component>
</DefineItemTemplate> </DefineItemTemplate>
<NavigationMenuRoot v-bind="rootProps" :data-collapsed="collapsed" :class="ui.root({ class: [props.ui?.root, props.class] })"> <NavigationMenuRoot v-bind="{ ...rootProps, ...$attrs }" :data-collapsed="collapsed" :class="ui.root({ class: [props.ui?.root, props.class] })">
<slot name="list-leading" /> <slot name="list-leading" />
<template v-for="(list, listIndex) in lists" :key="`list-${listIndex}`"> <template v-for="(list, listIndex) in lists" :key="`list-${listIndex}`">

View File

@@ -70,7 +70,9 @@ export type RadioGroupEmits = RadioGroupRootEmits & {
change: [payload: Event] change: [payload: Event]
} }
type SlotProps<T extends RadioGroupItem> = (props: { item: T & { id: string }, modelValue?: RadioGroupValue }) => any type NormalizeItem<T extends RadioGroupItem> = Exclude<T & { id: string }, RadioGroupValue>
type SlotProps<T extends RadioGroupItem> = (props: { item: NormalizeItem<T>, modelValue?: RadioGroupValue }) => any
export interface RadioGroupSlots<T extends RadioGroupItem = RadioGroupItem> { export interface RadioGroupSlots<T extends RadioGroupItem = RadioGroupItem> {
legend(props?: {}): any legend(props?: {}): any
@@ -114,21 +116,21 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.radioGroup |
indicator: props.indicator indicator: props.indicator
})) }))
function normalizeItem(item: any) { function normalizeItem(item: T): NormalizeItem<T> {
if (item === null) { if (item === null) {
return { return {
id: `${id}:null`, id: `${id}:null`,
value: undefined, value: undefined,
label: undefined label: undefined
} } as NormalizeItem<T>
} }
if (typeof item === 'string' || typeof item === 'number') { if (typeof item === 'string' || typeof item === 'number' || typeof item === 'bigint') {
return { return {
id: `${id}:${item}`, id: `${id}:${item}`,
value: String(item), value: String(item),
label: String(item) label: String(item)
} } as NormalizeItem<T>
} }
const value = get(item, props.valueKey as string) const value = get(item, props.valueKey as string)
@@ -136,7 +138,7 @@ function normalizeItem(item: any) {
const description = get(item, props.descriptionKey as string) const description = get(item, props.descriptionKey as string)
return { return {
...item, ...(item as NormalizeItem<T>),
value, value,
label, label,
description, description,

View File

@@ -234,11 +234,7 @@ function displayValue(value: GetItemValue<T, VK> | GetItemValue<T, VK>[]): strin
return values?.length ? values.join(', ') : undefined return values?.length ? values.join(', ') : undefined
} }
if (!props.valueKey) { const item = items.value.find(item => compare(typeof item === 'object' && props.valueKey ? get(item as Record<string, any>, props.valueKey as string) : item, value))
return value && (typeof value === 'object' ? get(value, props.labelKey as string) : value)
}
const item = items.value.find(item => compare(typeof item === 'object' ? get(item as Record<string, any>, props.valueKey as string) : item, value))
return item && (typeof item === 'object' ? get(item, props.labelKey as string) : item) return item && (typeof item === 'object' ? get(item, props.labelKey as string) : item)
} }
@@ -260,7 +256,11 @@ const filteredGroups = computed(() => {
const fields = Array.isArray(props.filterFields) ? props.filterFields : [props.labelKey] as string[] const fields = Array.isArray(props.filterFields) ? props.filterFields : [props.labelKey] as string[]
return groups.value.map(items => items.filter((item) => { return groups.value.map(items => items.filter((item) => {
if (typeof item !== 'object' || item === null) { if (item === undefined || item === null) {
return false
}
if (typeof item !== 'object') {
return contains(String(item), searchTerm.value) return contains(String(item), searchTerm.value)
} }
@@ -268,7 +268,10 @@ const filteredGroups = computed(() => {
return true return true
} }
return fields.some(field => contains(get(item, field), searchTerm.value)) return fields.some((field) => {
const value = get(item, field)
return value !== undefined && value !== null && contains(String(value), searchTerm.value)
})
})).filter(group => group.filter(item => })).filter(group => group.filter(item =>
!isSelectItem(item) || (!item.type || !['label', 'separator'].includes(item.type)) !isSelectItem(item) || (!item.type || !['label', 'separator'].includes(item.type))
).length > 0) ).length > 0)

View File

@@ -39,8 +39,8 @@ export interface SliderProps extends Pick<SliderRootProps, 'name' | 'disabled' |
} }
export interface SliderEmits<T extends number | number[] = number | number[]> { export interface SliderEmits<T extends number | number[] = number | number[]> {
(e: 'update:modelValue', payload: T): void 'update:modelValue': [payload: T]
(e: 'change', payload: Event): void 'change': [payload: Event]
} }
</script> </script>

View File

@@ -45,12 +45,26 @@ declare module '@tanstack/table-core' {
th?: string | ((cell: Header<TData, TValue>) => string) th?: string | ((cell: Header<TData, TValue>) => string)
td?: string | ((cell: Cell<TData, TValue>) => string) td?: string | ((cell: Cell<TData, TValue>) => string)
} }
style?: {
th?: string | Record<string, string> | ((cell: Header<TData, TValue>) => string | Record<string, string>)
td?: string | Record<string, string> | ((cell: Cell<TData, TValue>) => string | Record<string, string>)
}
colspan?: {
td?: string | ((cell: Cell<TData, TValue>) => string)
}
rowspan?: {
td?: string | ((cell: Cell<TData, TValue>) => string)
}
} }
interface TableMeta<TData> { interface TableMeta<TData> {
class?: { class?: {
tr?: string | ((row: Row<TData>) => string) tr?: string | ((row: Row<TData>) => string)
} }
style?: {
tr?: string | Record<string, string> | ((row: Row<TData>) => string | Record<string, string>)
}
} }
} }
@@ -369,6 +383,14 @@ function onRowContextmenu(e: Event, row: TableRow<T>) {
} }
} }
function resolveValue<T, A = undefined>(prop: T | ((arg: A) => T), arg?: A): T | undefined {
if (typeof prop === 'function') {
// @ts-expect-error: TS can't know if prop is a function here
return prop(arg)
}
return prop
}
watch( watch(
() => props.data, () => { () => props.data, () => {
data.value = props.data ? [...props.data] : [] data.value = props.data ? [...props.data] : []
@@ -398,10 +420,11 @@ defineExpose({
:data-pinned="header.column.getIsPinned()" :data-pinned="header.column.getIsPinned()"
:scope="header.colSpan > 1 ? 'colgroup' : 'col'" :scope="header.colSpan > 1 ? 'colgroup' : 'col'"
:colspan="header.colSpan > 1 ? header.colSpan : undefined" :colspan="header.colSpan > 1 ? header.colSpan : undefined"
:rowspan="header.rowSpan > 1 ? header.rowSpan : undefined"
:class="ui.th({ :class="ui.th({
class: [ class: [
props.ui?.th, props.ui?.th,
typeof header.column.columnDef.meta?.class?.th === 'function' ? header.column.columnDef.meta.class.th(header) : header.column.columnDef.meta?.class?.th resolveValue(header.column.columnDef.meta?.class?.th, header)
], ],
pinned: !!header.column.getIsPinned() pinned: !!header.column.getIsPinned()
})" })"
@@ -429,9 +452,10 @@ defineExpose({
:class="ui.tr({ :class="ui.tr({
class: [ class: [
props.ui?.tr, props.ui?.tr,
typeof tableApi.options.meta?.class?.tr === 'function' ? tableApi.options.meta.class.tr(row) : tableApi.options.meta?.class?.tr resolveValue(tableApi.options.meta?.class?.tr, row)
] ]
})" })"
:style="resolveValue(tableApi.options.meta?.style?.tr, row)"
@click="onRowSelect($event, row)" @click="onRowSelect($event, row)"
@pointerenter="onRowHover($event, row)" @pointerenter="onRowHover($event, row)"
@pointerleave="onRowHover($event, null)" @pointerleave="onRowHover($event, null)"
@@ -441,13 +465,16 @@ defineExpose({
v-for="cell in row.getVisibleCells()" v-for="cell in row.getVisibleCells()"
:key="cell.id" :key="cell.id"
:data-pinned="cell.column.getIsPinned()" :data-pinned="cell.column.getIsPinned()"
:colspan="resolveValue(cell.column.columnDef.meta?.colspan?.td, cell)"
:rowspan="resolveValue(cell.column.columnDef.meta?.rowspan?.td, cell)"
:class="ui.td({ :class="ui.td({
class: [ class: [
props.ui?.td, props.ui?.td,
typeof cell.column.columnDef.meta?.class?.td === 'function' ? cell.column.columnDef.meta.class.td(cell) : cell.column.columnDef.meta?.class?.td resolveValue(cell.column.columnDef.meta?.class?.td, cell)
], ],
pinned: !!cell.column.getIsPinned() pinned: !!cell.column.getIsPinned()
})" })"
:style="resolveValue(cell.column.columnDef.meta?.style?.td, cell)"
> >
<slot :name="`${cell.column.id}-cell`" v-bind="cell.getContext()"> <slot :name="`${cell.column.id}-cell`" v-bind="cell.getContext()">
<FlexRender :render="cell.column.columnDef.cell" :props="cell.getContext()" /> <FlexRender :render="cell.column.columnDef.cell" :props="cell.getContext()" />
@@ -488,13 +515,15 @@ defineExpose({
:key="header.id" :key="header.id"
:data-pinned="header.column.getIsPinned()" :data-pinned="header.column.getIsPinned()"
:colspan="header.colSpan > 1 ? header.colSpan : undefined" :colspan="header.colSpan > 1 ? header.colSpan : undefined"
:rowspan="header.rowSpan > 1 ? header.rowSpan : undefined"
:class="ui.th({ :class="ui.th({
class: [ class: [
props.ui?.th, props.ui?.th,
typeof header.column.columnDef.meta?.class?.th === 'function' ? header.column.columnDef.meta.class.th(header) : header.column.columnDef.meta?.class?.th resolveValue(header.column.columnDef.meta?.class?.th, header)
], ],
pinned: !!header.column.getIsPinned() pinned: !!header.column.getIsPinned()
})" })"
:style="resolveValue(header.column.columnDef.meta?.style?.th, header)"
> >
<slot :name="`${header.id}-footer`" v-bind="header.getContext()"> <slot :name="`${header.id}-footer`" v-bind="header.getContext()">
<FlexRender v-if="!header.isPlaceholder" :render="header.column.columnDef.footer" :props="header.getContext()" /> <FlexRender v-if="!header.isPlaceholder" :render="header.column.columnDef.footer" :props="header.getContext()" />

View File

@@ -3,7 +3,7 @@
import type { TabsRootProps, TabsRootEmits } from 'reka-ui' import type { TabsRootProps, TabsRootEmits } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema' import type { AppConfig } from '@nuxt/schema'
import theme from '#build/ui/tabs' import theme from '#build/ui/tabs'
import type { AvatarProps } from '../types' import type { AvatarProps, BadgeProps } from '../types'
import type { DynamicSlots, ComponentConfig } from '../types/utils' import type { DynamicSlots, ComponentConfig } from '../types/utils'
type Tabs = ComponentConfig<typeof theme, AppConfig, 'tabs'> type Tabs = ComponentConfig<typeof theme, AppConfig, 'tabs'>
@@ -15,13 +15,18 @@ export interface TabsItem {
*/ */
icon?: string icon?: string
avatar?: AvatarProps avatar?: AvatarProps
/**
* Display a badge on the item.
* `{ size: 'sm', color: 'neutral', variant: 'outline' }`{lang="ts-type"}
*/
badge?: string | number | BadgeProps
slot?: string slot?: string
content?: string content?: string
/** A unique value for the tab item. Defaults to the index. */ /** A unique value for the tab item. Defaults to the index. */
value?: string | number value?: string | number
disabled?: boolean disabled?: boolean
class?: any class?: any
ui?: Pick<Tabs['slots'], 'trigger' | 'leadingIcon' | 'leadingAvatar' | 'label' | 'content'> ui?: Pick<Tabs['slots'], 'trigger' | 'leadingIcon' | 'leadingAvatar' | 'leadingAvatarSize' | 'label' | 'trailingBadge' | 'trailingBadgeSize' | 'content'>
[key: string]: any [key: string]: any
} }
@@ -134,14 +139,23 @@ defineExpose({
> >
<slot name="leading" :item="item" :index="index"> <slot name="leading" :item="item" :index="index">
<UIcon v-if="item.icon" :name="item.icon" :class="ui.leadingIcon({ class: [props.ui?.leadingIcon, item.ui?.leadingIcon] })" /> <UIcon v-if="item.icon" :name="item.icon" :class="ui.leadingIcon({ class: [props.ui?.leadingIcon, item.ui?.leadingIcon] })" />
<UAvatar v-else-if="item.avatar" :size="((props.ui?.leadingAvatarSize || ui.leadingAvatarSize()) as AvatarProps['size'])" v-bind="item.avatar" :class="ui.leadingAvatar({ class: [props.ui?.leadingAvatar, item.ui?.leadingAvatar] })" /> <UAvatar v-else-if="item.avatar" :size="((item.ui?.leadingAvatarSize || props.ui?.leadingAvatarSize || ui.leadingAvatarSize()) as AvatarProps['size'])" v-bind="item.avatar" :class="ui.leadingAvatar({ class: [props.ui?.leadingAvatar, item.ui?.leadingAvatar] })" />
</slot> </slot>
<span v-if="get(item, props.labelKey as string) || !!slots.default" :class="ui.label({ class: [props.ui?.label, item.ui?.label] })"> <span v-if="get(item, props.labelKey as string) || !!slots.default" :class="ui.label({ class: [props.ui?.label, item.ui?.label] })">
<slot :item="item" :index="index">{{ get(item, props.labelKey as string) }}</slot> <slot :item="item" :index="index">{{ get(item, props.labelKey as string) }}</slot>
</span> </span>
<slot name="trailing" :item="item" :index="index" /> <slot name="trailing" :item="item" :index="index">
<UBadge
v-if="item.badge !== undefined"
color="neutral"
variant="outline"
:size="((item.ui?.trailingBadgeSize || props.ui?.trailingBadgeSize || ui.trailingBadgeSize()) as BadgeProps['size'])"
v-bind="(typeof item.badge === 'string' || typeof item.badge === 'number') ? { label: item.badge } : item.badge"
:class="ui.trailingBadge({ class: [props.ui?.trailingBadge, item.ui?.trailingBadge] })"
/>
</slot>
</TabsTrigger> </TabsTrigger>
<slot name="list-trailing" /> <slot name="list-trailing" />

View File

@@ -55,9 +55,9 @@ export interface TextareaProps<T extends TextareaValue = TextareaValue> extends
} }
export interface TextareaEmits<T extends TextareaValue = TextareaValue> { export interface TextareaEmits<T extends TextareaValue = TextareaValue> {
(e: 'update:modelValue', payload: T): void 'update:modelValue': [payload: T]
(e: 'blur', event: FocusEvent): void 'blur': [event: FocusEvent]
(e: 'change', event: Event): void 'change': [event: Event]
} }
export interface TextareaSlots { export interface TextareaSlots {

View File

@@ -107,6 +107,8 @@ import { get } from '../utils'
import { tv } from '../utils/tv' import { tv } from '../utils/tv'
import UIcon from './Icon.vue' import UIcon from './Icon.vue'
defineOptions({ inheritAttrs: false })
const props = withDefaults(defineProps<TreeProps<T, VK, M>>(), { const props = withDefaults(defineProps<TreeProps<T, VK, M>>(), {
labelKey: 'label' as never, labelKey: 'label' as never,
valueKey: 'value' as never valueKey: 'value' as never
@@ -161,7 +163,7 @@ const defaultExpanded = computed(() =>
@toggle="item.onToggle" @toggle="item.onToggle"
@select="item.onSelect" @select="item.onSelect"
> >
<button :disabled="item.disabled || disabled" :class="ui.link({ class: [props.ui?.link, item.ui?.link, item.class], selected: isSelected, disabled: item.disabled || disabled })"> <button type="button" :disabled="item.disabled || disabled" :class="ui.link({ class: [props.ui?.link, item.ui?.link, item.class], selected: isSelected, disabled: item.disabled || disabled })">
<slot :name="((item.slot || 'item') as keyof TreeSlots<T>)" v-bind="{ index, level, expanded: isExpanded, selected: isSelected }" :item="(item as Extract<NestedItem<T>, { slot: string; }>)"> <slot :name="((item.slot || 'item') as keyof TreeSlots<T>)" v-bind="{ index, level, expanded: isExpanded, selected: isSelected }" :item="(item as Extract<NestedItem<T>, { slot: string; }>)">
<slot :name="((item.slot ? `${item.slot}-leading`: 'item-leading') as keyof TreeSlots<T>)" v-bind="{ index, level, expanded: isExpanded, selected: isSelected }" :item="(item as Extract<NestedItem<T>, { slot: string; }>)"> <slot :name="((item.slot ? `${item.slot}-leading`: 'item-leading') as keyof TreeSlots<T>)" v-bind="{ index, level, expanded: isExpanded, selected: isSelected }" :item="(item as Extract<NestedItem<T>, { slot: string; }>)">
<UIcon <UIcon
@@ -199,7 +201,7 @@ const defaultExpanded = computed(() =>
</DefineTreeTemplate> </DefineTreeTemplate>
<TreeRoot <TreeRoot
v-bind="(rootProps as unknown as TreeRootProps<NestedItem<T>>)" v-bind="{ ...(rootProps as unknown as TreeRootProps<NestedItem<T>>), ...$attrs }"
:class="ui.root({ class: [props.ui?.root, props.class] })" :class="ui.root({ class: [props.ui?.root, props.class] })"
:get-key="getItemValue" :get-key="getItemValue"
:default-expanded="defaultExpanded" :default-expanded="defaultExpanded"

View File

@@ -122,7 +122,7 @@ export function defineShortcuts(config: MaybeRef<ShortcutsConfig>, options: Shor
if (shortcut.enabled) { if (shortcut.enabled) {
e.preventDefault() e.preventDefault()
shortcut.handler() shortcut.handler(e)
} }
clearChainedInput() clearChainedInput()
return return

View File

@@ -89,10 +89,15 @@ export function useFormField<T>(props?: Props<T>, opts?: { bind?: boolean, defer
.filter(type => formField?.value?.[type]) .filter(type => formField?.value?.[type])
.map(type => `${formField?.value.ariaId}-${type}`) || [] .map(type => `${formField?.value.ariaId}-${type}`) || []
return { const attrs: Record<string, any> = {
'aria-describedby': descriptiveAttrs.join(' '),
'aria-invalid': !!formField?.value.error 'aria-invalid': !!formField?.value.error
} }
if (descriptiveAttrs.length > 0) {
attrs['aria-describedby'] = descriptiveAttrs.join(' ')
}
return attrs
}) })
} }
} }

View File

@@ -71,7 +71,7 @@ function _useOverlay() {
isMounted: !!defaultOpen, isMounted: !!defaultOpen,
destroyOnClose: !!destroyOnClose, destroyOnClose: !!destroyOnClose,
originalProps: props || {}, originalProps: props || {},
props: { ...(props || {}) } props: { ...props }
}) })
overlays.push(options) overlays.push(options)
@@ -87,11 +87,11 @@ function _useOverlay() {
const open = <T extends Component>(id: symbol, props?: ComponentProps<T>): OpenedOverlay<T> => { const open = <T extends Component>(id: symbol, props?: ComponentProps<T>): OpenedOverlay<T> => {
const overlay = getOverlay(id) const overlay = getOverlay(id)
// If props are provided, update the overlay's props // If props are provided, merge them with the original props, otherwise use the original props
if (props) { if (props) {
patch(overlay.id, props) overlay.props = { ...overlay.originalProps, ...props }
} else { } else {
patch(overlay.id, overlay.originalProps) overlay.props = { ...overlay.originalProps }
} }
overlay.isOpen = true overlay.isOpen = true
@@ -135,7 +135,7 @@ function _useOverlay() {
const patch = <T extends Component>(id: symbol, props: Partial<ComponentProps<T>>): void => { const patch = <T extends Component>(id: symbol, props: Partial<ComponentProps<T>>): void => {
const overlay = getOverlay(id) const overlay = getOverlay(id)
overlay.props = { ...props } overlay.props = { ...overlay.props, ...props }
} }
const getOverlay = (id: symbol): Overlay => { const getOverlay = (id: symbol): Overlay => {

View File

@@ -1,11 +1,10 @@
import { ref, onScopeDispose } from 'vue' import { ref, onScopeDispose } from 'vue'
import type { Ref, Plugin as VuePlugin } from 'vue' import type { Ref, Plugin as VuePlugin } from 'vue'
import { createHooks } from 'hookable' import { createHooks } from 'hookable'
import { usePage } from '@inertiajs/vue3'
import { useColorMode as useColorModeVueUse } from '@vueuse/core'
import appConfig from '#build/app.config' import appConfig from '#build/app.config'
import type { NuxtApp } from '#app' import type { NuxtApp } from '#app'
import { useColorMode as useColorModeVueUse } from '@vueuse/core'
import { usePage } from '@inertiajs/vue3'
export { useHead } from '@unhead/vue' export { useHead } from '@unhead/vue'
@@ -16,6 +15,7 @@ export { useLocale } from '../composables/useLocale'
export const useRoute = () => { export const useRoute = () => {
const page = usePage() const page = usePage()
return { return {
fullPath: page.url fullPath: page.url
} }
@@ -25,6 +25,10 @@ export const useRouter = () => {
} }
export const clearError = () => {
}
export const useColorMode = () => { export const useColorMode = () => {
if (!appConfig.colorMode) { if (!appConfig.colorMode) {
return { return {

View File

@@ -40,7 +40,8 @@ export default defineLocale<Messages>({
carousel: { carousel: {
prev: 'السابق', prev: 'السابق',
next: 'التالي', next: 'التالي',
goto: 'الذهاب إلي شريحة {slide}' dots: 'اختر الشريحة المراد عرضها',
goto: 'الذهاب إلى شريحة {slide}'
}, },
modal: { modal: {
close: 'إغلاق' close: 'إغلاق'

View File

@@ -39,6 +39,7 @@ export default defineLocale<Messages>({
carousel: { carousel: {
prev: 'Əvvəlki', prev: 'Əvvəlki',
next: 'Növbəti', next: 'Növbəti',
dots: 'Göstərmək üçün slayd seçin',
goto: 'Slayd {slide} keç' goto: 'Slayd {slide} keç'
}, },
modal: { modal: {

View File

@@ -39,6 +39,7 @@ export default defineLocale<Messages>({
carousel: { carousel: {
prev: 'Назад', prev: 'Назад',
next: 'Напред', next: 'Напред',
dots: 'Изберете слайд за показване',
goto: 'Отидете на слайд {slide}' goto: 'Отидете на слайд {slide}'
}, },
modal: { modal: {

View File

@@ -39,6 +39,7 @@ export default defineLocale<Messages>({
carousel: { carousel: {
prev: 'পূর্ববর্তী', prev: 'পূর্ববর্তী',
next: 'পরবর্তী', next: 'পরবর্তী',
dots: 'প্রদর্শনের জন্য স্লাইড নির্বাচন করুন',
goto: 'স্লাইড {slide} এ যান' goto: 'স্লাইড {slide} এ যান'
}, },
modal: { modal: {

View File

@@ -39,6 +39,7 @@ export default defineLocale<Messages>({
carousel: { carousel: {
prev: 'Anterior', prev: 'Anterior',
next: 'Següent', next: 'Següent',
dots: 'Tria la diapositiva a mostrar',
goto: 'Anar a la diapositiva {slide}' goto: 'Anar a la diapositiva {slide}'
}, },
modal: { modal: {

View File

@@ -38,8 +38,9 @@ export default defineLocale<Messages>({
close: 'داخستن' close: 'داخستن'
}, },
carousel: { carousel: {
prev: 'پێشوو', prev: 'پێشووی',
next: 'داهاتوو', next: 'داهاتوو',
dots: 'سلایدێک هەڵبژێرە بۆ پیشاندان',
goto: 'بڕۆ بۆ سلایدی {slide}' goto: 'بڕۆ بۆ سلایدی {slide}'
}, },
modal: { modal: {

View File

@@ -39,6 +39,7 @@ export default defineLocale<Messages>({
carousel: { carousel: {
prev: 'Předchozí', prev: 'Předchozí',
next: 'Další', next: 'Další',
dots: 'Vyberte snímek k zobrazení',
goto: 'Přejít na {slide}' goto: 'Přejít na {slide}'
}, },
modal: { modal: {

View File

@@ -39,6 +39,7 @@ export default defineLocale<Messages>({
carousel: { carousel: {
prev: 'Forrige', prev: 'Forrige',
next: 'Næste', next: 'Næste',
dots: 'Vælg dias til visning',
goto: 'Gå til slide {slide}' goto: 'Gå til slide {slide}'
}, },
modal: { modal: {

View File

@@ -39,6 +39,7 @@ export default defineLocale<Messages>({
carousel: { carousel: {
prev: 'Zurück', prev: 'Zurück',
next: 'Weiter', next: 'Weiter',
dots: 'Folie zur Anzeige auswählen',
goto: 'Gehe zu {slide}' goto: 'Gehe zu {slide}'
}, },
modal: { modal: {

View File

@@ -39,6 +39,7 @@ export default defineLocale<Messages>({
carousel: { carousel: {
prev: 'Προηγούμενο', prev: 'Προηγούμενο',
next: 'Επόμενο', next: 'Επόμενο',
dots: 'Επιλέξτε διαφάνεια για εμφάνιση',
goto: 'Μετάβαση στη διαφάνεια {slide}' goto: 'Μετάβαση στη διαφάνεια {slide}'
}, },
modal: { modal: {

View File

@@ -39,6 +39,7 @@ export default defineLocale<Messages>({
carousel: { carousel: {
prev: 'Prev', prev: 'Prev',
next: 'Next', next: 'Next',
dots: 'Choose slide to display',
goto: 'Go to slide {slide}' goto: 'Go to slide {slide}'
}, },
modal: { modal: {

View File

@@ -39,6 +39,7 @@ export default defineLocale<Messages>({
carousel: { carousel: {
prev: 'Anterior', prev: 'Anterior',
next: 'Siguiente', next: 'Siguiente',
dots: 'Elegir diapositiva a mostrar',
goto: 'Ir a la diapositiva {slide}' goto: 'Ir a la diapositiva {slide}'
}, },
modal: { modal: {

View File

@@ -39,6 +39,7 @@ export default defineLocale<Messages>({
carousel: { carousel: {
prev: 'Eel', prev: 'Eel',
next: 'Järg', next: 'Järg',
dots: 'Valige kuvatav slaid',
goto: 'Mine slaidile {slide}' goto: 'Mine slaidile {slide}'
}, },
modal: { modal: {

View File

@@ -40,6 +40,7 @@ export default defineLocale<Messages>({
carousel: { carousel: {
prev: 'قبلی', prev: 'قبلی',
next: 'بعدی', next: 'بعدی',
dots: 'اسلاید مورد نظر برای نمایش را انتخاب کنید',
goto: 'رفتن به اسلاید {slide}' goto: 'رفتن به اسلاید {slide}'
}, },
modal: { modal: {

View File

@@ -39,6 +39,7 @@ export default defineLocale<Messages>({
carousel: { carousel: {
prev: 'Edellinen', prev: 'Edellinen',
next: 'Seuraava', next: 'Seuraava',
dots: 'Valitse näytettävä dia',
goto: 'Siirry sivulle {slide}' goto: 'Siirry sivulle {slide}'
}, },
modal: { modal: {

View File

@@ -39,6 +39,7 @@ export default defineLocale<Messages>({
carousel: { carousel: {
prev: 'Précédent', prev: 'Précédent',
next: 'Suivant', next: 'Suivant',
dots: 'Choisir la diapositive à afficher',
goto: 'Aller à {slide}' goto: 'Aller à {slide}'
}, },
modal: { modal: {

View File

@@ -38,6 +38,7 @@ export default defineLocale<Messages>({
carousel: { carousel: {
prev: 'הקודם', prev: 'הקודם',
next: 'הבא', next: 'הבא',
dots: 'בחר שקופית להצגה',
goto: 'מעבר ל {slide}' goto: 'מעבר ל {slide}'
}, },
modal: { modal: {

View File

@@ -39,7 +39,8 @@ export default defineLocale<Messages>({
carousel: { carousel: {
prev: 'पिछला', prev: 'पिछला',
next: 'अगला', next: 'अगला',
goto: 'स्लाइड {slide} पर जाएँ' dots: 'प्रदर्शित करने के लिए स्लाइड चुनें',
goto: 'स्लाइड {slide} पर जाएं'
}, },
modal: { modal: {
close: 'बंद करें' close: 'बंद करें'

View File

@@ -39,6 +39,7 @@ export default defineLocale<Messages>({
carousel: { carousel: {
prev: 'Előző', prev: 'Előző',
next: 'Következő', next: 'Következő',
dots: 'Válassza ki a megjelenítendő diát',
goto: 'Ugrás ide {slide}' goto: 'Ugrás ide {slide}'
}, },
modal: { modal: {

View File

@@ -39,6 +39,7 @@ export default defineLocale<Messages>({
carousel: { carousel: {
prev: 'Հետ', prev: 'Հետ',
next: 'Առաջ', next: 'Առաջ',
dots: 'Ընտրեք ցուցադրելու սլայդը',
goto: 'Անցնել {slide}-ին' goto: 'Անցնել {slide}-ին'
}, },
modal: { modal: {

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