Compare commits

...

44 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
170 changed files with 4304 additions and 2306 deletions

View File

@@ -33,5 +33,5 @@ jobs:
Thank you for your understanding and support!
— Nuxt UI Team
exempt-issue-labels: 'feature,announcement'
operations-per-run: 300
exempt-issue-labels: 'feature,announcement,release,reka-ui,upstream'
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}'
${!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 {
/**
@@ -80,7 +80,7 @@ ${pro ? `import type { ComponentConfig } from '@nuxt/ui'` : ''}
import theme from '#build/${path}/${prose ? 'prose/' : ''}${content ? 'content/' : ''}${kebabName}'
${!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> {
class?: any

View File

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

View File

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

View File

@@ -14,8 +14,8 @@ const items = [
v-slot="{ item }"
orientation="vertical"
:items="items"
class="w-full max-w-xs mx-auto"
:ui="{ container: 'h-[336px]' }"
class="w-full max-w-xs mx-auto"
>
<img :src="item" width="320" height="320" class="rounded-lg">
</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[])
const value = ref(items.value[0])
</script>

View File

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

View File

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

View File

@@ -62,13 +62,13 @@ const items = [
<template>
<UNavigationMenu
:items="items"
class="w-full justify-center"
:ui="{
viewport: 'sm:w-(--reka-navigation-menu-viewport-width)',
content: 'sm:w-auto',
childList: 'sm:w-96',
childLinkDescription: 'text-balance line-clamp-2'
}"
class="w-full justify-center"
>
<template #docs-content="{ item }">
<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[])
const value = ref(items.value[0])
</script>

View File

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

View File

@@ -23,6 +23,7 @@ const items = ref([
icon: 'i-lucide-circle-check'
}
] satisfies SelectMenuItem[])
const value = ref(items.value[0])
</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'"
icon="i-lucide-user"
placeholder="Select user"
class="w-48"
value-key="value"
class="w-48"
>
<template #leading="{ modelValue, ui }">
<UAvatar

View File

@@ -35,6 +35,7 @@ const items = ref([
}
}
] satisfies SelectItem[])
const value = ref(items.value[0]?.value)
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'
}
] satisfies SelectItem[])
const value = ref(items.value[0]?.value)
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 overlay = useOverlay()
const slideover = overlay.create(LazySlideoverExample, {
props: {
count: count.value
}
})
const slideover = overlay.create(LazySlideoverExample)
async function open() {
const instance = slideover.open()
const instance = slideover.open({
count: count.value
})
const shouldIncrement = await instance.result

View File

@@ -26,7 +26,7 @@ const state = reactive({
</script>
<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 }">
<p class="text-muted mb-4">
{{ item.description }}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,6 +5,17 @@ pricing:
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.
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
description: Try Nuxt UI Pro for free in development, no credit card required. Upgrade when ready to deploy.
orientation: horizontal
@@ -13,6 +24,9 @@ pricing:
to: '/getting-started/installation/pro/nuxt'
color: 'neutral'
variant: 'subtle'
trailingIcon: 'i-lucide-arrow-right'
ui:
trailingIcon: 'ms-0'
figma:
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.

View File

@@ -34,10 +34,19 @@ useSeoMeta({
<div class="flex flex-col bg-default gap-8 lg:gap-0">
<UPricingPlan
v-bind="page.pricing.freePlan"
variant="naked"
class="lg:rounded-none border-x border-default border-t border-b lg:border-b-0"
class="lg:rounded-none ring-primary/15 ring-inset -mb-px bg-primary/5 z-[1]"
: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
v-for="(plan, index) in page.pricing.plans"
:key="index"
@@ -47,18 +56,17 @@ useSeoMeta({
:discount="plan.discount"
:billing-period="plan.billing_period"
:billing-cycle="plan.billing_cycle"
:variant="plan.highlight ? 'soft' : 'outline'"
:class="['lg:rounded-none', { 'border-2 lg:border lg:border-x-0 border-primary lg:border-default': plan.highlight }]"
:variant="plan.highlight ? 'subtle' : 'outline'"
class="lg:rounded-none ring-inset -mb-px"
:features="plan.features"
:button="plan.button"
/>
</UPricingPlans>
<UPricingPlan
v-bind="page.pricing.figma"
variant="naked"
:billing-period="page.pricing.figma.billing_period"
: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>
<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.
::
### `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
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"}
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.
::
### `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`
Use the `inertia` option to enable compatibility with [Inertia.js](https://inertiajs.com/).

View File

@@ -536,6 +536,33 @@ import { ModalExampleComponent } from '#components'
</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

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.
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
assets/icons
@@ -104,7 +104,7 @@ export default defineNuxtConfig({
icon: {
customCollections: [{
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
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
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
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
---
props:
color: neutral
variant: solid
slots:
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
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
### 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
### Props

View File

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

View File

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

View File

@@ -51,3 +51,5 @@ items:
url: https://wiredash.com/
- name: Zielgestalt
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 }
},
future: {
compatibilityVersion: 4
},
compatibilityDate: '2024-07-09',
nitro: {

View File

@@ -11,26 +11,26 @@
"dependencies": {
"@ai-sdk/vue": "^1.2.12",
"@iconify-json/logos": "^1.2.4",
"@iconify-json/lucide": "^1.2.56",
"@iconify-json/simple-icons": "^1.2.42",
"@iconify-json/lucide": "^1.2.57",
"@iconify-json/simple-icons": "^1.2.44",
"@iconify-json/vscode-icons": "^1.2.23",
"@nuxt/content": "^3.6.3",
"@nuxt/image": "^1.10.0",
"@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",
"@nuxtjs/plausible": "^1.2.0",
"@octokit/rest": "^22.0.0",
"@rollup/plugin-yaml": "^4.1.2",
"@vueuse/integrations": "^13.5.0",
"@vueuse/nuxt": "^13.5.0",
"ai": "^4.3.16",
"ai": "^4.3.19",
"better-sqlite3": "^12.2.0",
"capture-website": "^4.2.0",
"joi": "^17.13.3",
"maska": "^3.2.0",
"motion-v": "^1.5.0",
"nuxt": "^3.17.6",
"nuxt": "^4.0.1",
"nuxt-component-meta": "^0.12.1",
"nuxt-llms": "^0.1.3",
"nuxt-og-image": "^5.1.9",
@@ -40,11 +40,11 @@
"superstruct": "^2.0.2",
"ufo": "^1.6.1",
"valibot": "^1.1.0",
"workers-ai-provider": "^0.7.1",
"workers-ai-provider": "^0.7.2",
"yup": "^1.6.1",
"zod": "^3.25.75"
"zod": "^4.0.5"
},
"devDependencies": {
"wrangler": "^4.23.0"
"wrangler": "^4.25.0"
}
}

View File

@@ -2,7 +2,7 @@
"name": "@nuxt/ui",
"description": "A UI Library for Modern Web Apps, powered by Vue & Tailwind CSS.",
"version": "3.2.0",
"packageManager": "pnpm@10.12.4",
"packageManager": "pnpm@10.13.1",
"repository": {
"type": "git",
"url": "git+https://github.com/nuxt/ui.git"
@@ -116,8 +116,8 @@
"@internationalized/number": "^3.6.3",
"@nuxt/fonts": "^0.11.4",
"@nuxt/icon": "^1.15.0",
"@nuxt/kit": "^3.17.6",
"@nuxt/schema": "^3.17.6",
"@nuxt/kit": "^4.0.1",
"@nuxt/schema": "^4.0.1",
"@nuxtjs/color-mode": "^3.5.2",
"@standard-schema/spec": "^1.0.0",
"@tailwindcss/postcss": "^4.1.11",
@@ -155,16 +155,16 @@
"vue-component-type-helpers": "^3.0.1"
},
"devDependencies": {
"@nuxt/eslint-config": "^1.5.2",
"@nuxt/eslint-config": "^1.6.0",
"@nuxt/module-builder": "^1.0.1",
"@nuxt/test-utils": "^3.19.2",
"@release-it/conventional-changelog": "^10.0.1",
"@vue/test-utils": "^2.4.6",
"embla-carousel": "^8.6.0",
"eslint": "^9.30.1",
"eslint": "^9.31.0",
"happy-dom": "^18.0.1",
"nuxt": "^3.17.6",
"release-it": "^19.0.3",
"nuxt": "^4.0.1",
"release-it": "^19.0.4",
"vitest": "^3.2.4",
"vitest-environment-nuxt": "^1.0.1",
"vue-tsc": "^3.0.1"
@@ -177,7 +177,7 @@
"valibot": "^1.0.0",
"vue-router": "^4.5.0",
"yup": "^1.6.0",
"zod": "^3.24.0"
"zod": "^3.24.0 || ^4.0.0"
},
"peerDependenciesMeta": {
"@inertiajs/vue3": {

View File

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

View File

@@ -1,12 +1,16 @@
<script setup lang="ts">
const colorHex = ref('#9C27B0')
function handleColorChange(event: Event) {
colorHex.value = (event.target as HTMLInputElement).value
}
</script>
<template>
<div class="flex flex-col gap-5">
<div class="flex items-center gap-2">
<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>
<USeparator />
<div class="flex justify-between gap-2">
@@ -21,6 +25,6 @@ const colorHex = ref('#9C27B0')
</UButton>
</div>
<USeparator />
<UColorPicker v-model="colorHex" @update:model-value="() => console.log('model update')" />
<UColorPicker v-model="colorHex" />
</div>
</template>

View File

@@ -28,6 +28,20 @@ const inset = ref(false)
</template>
</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">
<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'
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)
</script>
<template>
<div class="flex flex-col gap-2">
<div class="flex items-center gap-1">
<UKbd value="meta" />
</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 v-for="color in colors" :key="color" class="flex items-center gap-1 ms-[-22px]">
<UKbd v-for="variant in variants" :key="`${color}-${variant}`" value="meta" :variant="variant" :color="color" />
</div>
<div class="flex items-center gap-1 ms-[-220px]">
<UKbd v-for="(kdbKey, index) in kbdKeys" :key="index" :value="kdbKey" />

View File

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

View File

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

View File

@@ -9,13 +9,13 @@
"typecheck": "nuxt typecheck"
},
"dependencies": {
"@iconify-json/lucide": "^1.2.56",
"@iconify-json/simple-icons": "^1.2.42",
"@iconify-json/lucide": "^1.2.57",
"@iconify-json/simple-icons": "^1.2.44",
"@internationalized/date": "^3.8.2",
"@nuxt/ui": "workspace:*",
"@nuxthub/core": "^0.9.0",
"nuxt": "^3.17.6",
"zod": "^3.25.75"
"nuxt": "^4.0.1",
"zod": "^4.0.5"
},
"devDependencies": {
"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'
type Color = 'primary' | 'secondary' | 'success' | 'info' | 'warning' | 'error' | (string & {})
type Size = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | (string & {})
export interface ModuleOptions {
/**
* Prefix for components
@@ -38,7 +41,7 @@ export interface ModuleOptions {
* @defaultValue `['primary', 'secondary', 'success', 'info', 'warning', 'error']`
* @link https://ui.nuxt.com/getting-started/installation/nuxt#themecolors
*/
colors?: string[]
colors?: Color[]
/**
* Enable or disable transitions on components
@@ -46,6 +49,20 @@ export interface ModuleOptions {
* @link https://ui.nuxt.com/getting-started/installation/nuxt#themetransitions
*/
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>) {
if (!hasNuxtModule(name)) {
await installModule(name, options)
await installModule(name, defu((nuxt.options as any)[key], options))
} else {
(nuxt.options as any)[key] = defu((nuxt.options as any)[key], options)
}

View File

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

View File

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

View File

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

View File

@@ -31,7 +31,7 @@ function HSVtoHSL(hsv: HSVColor): HSLObject {
return {
H: hsv.h,
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) {
const color = new ColorTranslator(HSVtoHSL(value), {
decimals: 2,
labUnit: 'percent',
cmykUnit: 'percent',
cmykFunction: 'cmyk'

View File

@@ -37,6 +37,11 @@ export interface DrawerProps extends Pick<DrawerRootProps, 'activeSnapPoint' | '
* @defaultValue true
*/
portal?: boolean | string | HTMLElement
/**
* Whether the drawer is nested in another drawer.
* @defaultValue false
*/
nested?: boolean
class?: any
ui?: Drawer['slots']
}
@@ -57,7 +62,7 @@ export interface DrawerSlots {
<script setup lang="ts">
import { computed, toRef } from 'vue'
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 { useAppConfig } from '#imports'
import { usePortal } from '../composables/usePortal'
@@ -90,7 +95,7 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.drawer || {}
</script>
<template>
<DrawerRoot v-bind="rootProps">
<component :is="nested ? DrawerRootNested : DrawerRoot" v-bind="rootProps">
<DrawerTrigger v-if="!!slots.default" as-child :class="props.class">
<slot />
</DrawerTrigger>
@@ -144,5 +149,5 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.drawer || {}
</slot>
</DrawerContent>
</DrawerPortal>
</DrawerRoot>
</component>
</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> {
(e: 'submit', payload: FormSubmitEvent<FormData<S, T>>): void
(e: 'error', payload: FormErrorEvent): void
submit: [payload: FormSubmitEvent<FormData<S, T>>]
error: [payload: FormErrorEvent]
}
export interface FormSlots {

View File

@@ -18,7 +18,7 @@ export interface FormFieldProps {
label?: string
description?: string
help?: string
error?: string | boolean
error?: boolean | string
hint?: string
/**
* @defaultValue 'md'
@@ -41,8 +41,8 @@ export interface FormFieldSlots {
hint(props: { hint?: string }): any
description(props: { description?: string }): any
help(props: { help?: string }): any
error(props: { error?: string | boolean }): any
default(props: { error?: string | boolean }): any
error(props: { error?: boolean | string }): any
default(props: { error?: boolean | string }): any
}
</script>
@@ -121,7 +121,7 @@ provide(formFieldInjectionKey, computed(() => ({
{{ error }}
</slot>
</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">
{{ help }}
</slot>

View File

@@ -52,9 +52,9 @@ export interface InputProps<T extends AcceptableValue = AcceptableValue> extends
}
export interface InputEmits<T extends AcceptableValue = AcceptableValue> {
(e: 'update:modelValue', payload: T): void
(e: 'blur', event: FocusEvent): void
(e: 'change', event: Event): void
'update:modelValue': [payload: T]
'blur': [event: FocusEvent]
'change': [event: Event]
}
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'> & {
change: [payload: Event]
blur: [payload: FocusEvent]
focus: [payload: FocusEvent]
create: [item: string]
'change': [payload: Event]
'blur': [payload: FocusEvent]
'focus': [payload: FocusEvent]
'create': [item: string]
/** Event handler when highlighted element changes. */
highlight: [payload: {
'highlight': [payload: {
ref: HTMLElement
value: GetModelValue<A, VK, M>
} | undefined]
'remove-tag': [item: GetModelValue<A, VK, M>]
} & GetModelValueEmits<A, VK, M>
type SlotProps<T extends InputMenuItem> = (props: { item: T, index: number }) => any
@@ -171,7 +172,7 @@ export interface InputMenuSlots<
</script>
<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 { defu } from 'defu'
import { isEqual } from 'ohash/utils'
@@ -233,11 +234,7 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.inputMenu ||
}))
function displayValue(value: T): string {
if (!props.valueKey) {
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))
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 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[]
return groups.value.map(group => group.filter((item) => {
if (typeof item !== 'object' || item === null) {
return groups.value.map(items => items.filter((item) => {
if (item === undefined || item === null) {
return false
}
if (typeof item !== 'object') {
return contains(String(item), searchTerm.value)
}
@@ -267,7 +268,10 @@ const filteredGroups = computed(() => {
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 =>
!isInputItem(item) || (!item.type || !['label', 'separator'].includes(item.type))
).length > 0)
@@ -298,6 +302,10 @@ function autoFocus() {
}
onMounted(() => {
nextTick(() => {
searchTerm.value = ''
})
setTimeout(() => {
autoFocus()
}, props.autofocusDelay)
@@ -359,6 +367,7 @@ function onRemoveTag(event: any) {
const modelValue = props.modelValue as GetModelValue<T, VK, true>
const filteredValue = modelValue.filter(value => !isEqual(value, event))
emits('update:modelValue', filteredValue as GetModelValue<T, VK, M>)
emits('remove-tag', event)
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] })">
<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">
{{ displayValue(item as T) }}
{{ displayValue(item as T) ?? item }}
</slot>
</TagsInputItemText>

View File

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

View File

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

View File

@@ -177,6 +177,8 @@ import UBadge from './Badge.vue'
import UPopover from './Popover.vue'
import UTooltip from './Tooltip.vue'
defineOptions({ inheritAttrs: false })
const props = withDefaults(defineProps<NavigationMenuProps<T>>(), {
orientation: '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>
<slot :name="((item.slot ? `${item.slot}-trailing` : 'item-trailing') as keyof NavigationMenuSlots<T>)" :item="item" :active="active" :index="index">
<UBadge
v-if="item.badge"
v-if="item.badge !== undefined"
color="neutral"
variant="outline"
:size="((item.ui?.linkTrailingBadgeSize || props.ui?.linkTrailingBadgeSize || ui.linkTrailingBadgeSize()) as BadgeProps['size'])"
@@ -392,7 +394,7 @@ function getAccordionDefaultValue(list: NavigationMenuItem[], level = 0) {
</component>
</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" />
<template v-for="(list, listIndex) in lists" :key="`list-${listIndex}`">

View File

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

View File

@@ -234,11 +234,7 @@ function displayValue(value: GetItemValue<T, VK> | GetItemValue<T, VK>[]): strin
return values?.length ? values.join(', ') : undefined
}
if (!props.valueKey) {
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))
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 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[]
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)
}
@@ -268,7 +268,10 @@ const filteredGroups = computed(() => {
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 =>
!isSelectItem(item) || (!item.type || !['label', 'separator'].includes(item.type))
).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[]> {
(e: 'update:modelValue', payload: T): void
(e: 'change', payload: Event): void
'update:modelValue': [payload: T]
'change': [payload: Event]
}
</script>

View File

@@ -45,12 +45,26 @@ declare module '@tanstack/table-core' {
th?: string | ((cell: Header<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> {
class?: {
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(
() => props.data, () => {
data.value = props.data ? [...props.data] : []
@@ -398,10 +420,11 @@ defineExpose({
:data-pinned="header.column.getIsPinned()"
:scope="header.colSpan > 1 ? 'colgroup' : 'col'"
:colspan="header.colSpan > 1 ? header.colSpan : undefined"
:rowspan="header.rowSpan > 1 ? header.rowSpan : undefined"
:class="ui.th({
class: [
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()
})"
@@ -429,9 +452,10 @@ defineExpose({
:class="ui.tr({
class: [
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)"
@pointerenter="onRowHover($event, row)"
@pointerleave="onRowHover($event, null)"
@@ -441,13 +465,16 @@ defineExpose({
v-for="cell in row.getVisibleCells()"
:key="cell.id"
: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: [
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()
})"
:style="resolveValue(cell.column.columnDef.meta?.style?.td, cell)"
>
<slot :name="`${cell.column.id}-cell`" v-bind="cell.getContext()">
<FlexRender :render="cell.column.columnDef.cell" :props="cell.getContext()" />
@@ -488,13 +515,15 @@ defineExpose({
:key="header.id"
:data-pinned="header.column.getIsPinned()"
:colspan="header.colSpan > 1 ? header.colSpan : undefined"
:rowspan="header.rowSpan > 1 ? header.rowSpan : undefined"
:class="ui.th({
class: [
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()
})"
:style="resolveValue(header.column.columnDef.meta?.style?.th, header)"
>
<slot :name="`${header.id}-footer`" v-bind="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 { AppConfig } from '@nuxt/schema'
import theme from '#build/ui/tabs'
import type { AvatarProps } from '../types'
import type { AvatarProps, BadgeProps } from '../types'
import type { DynamicSlots, ComponentConfig } from '../types/utils'
type Tabs = ComponentConfig<typeof theme, AppConfig, 'tabs'>
@@ -15,13 +15,18 @@ export interface TabsItem {
*/
icon?: string
avatar?: AvatarProps
/**
* Display a badge on the item.
* `{ size: 'sm', color: 'neutral', variant: 'outline' }`{lang="ts-type"}
*/
badge?: string | number | BadgeProps
slot?: string
content?: string
/** A unique value for the tab item. Defaults to the index. */
value?: string | number
disabled?: boolean
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
}
@@ -134,14 +139,23 @@ defineExpose({
>
<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] })" />
<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>
<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>
</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>
<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> {
(e: 'update:modelValue', payload: T): void
(e: 'blur', event: FocusEvent): void
(e: 'change', event: Event): void
'update:modelValue': [payload: T]
'blur': [event: FocusEvent]
'change': [event: Event]
}
export interface TextareaSlots {

View File

@@ -107,6 +107,8 @@ import { get } from '../utils'
import { tv } from '../utils/tv'
import UIcon from './Icon.vue'
defineOptions({ inheritAttrs: false })
const props = withDefaults(defineProps<TreeProps<T, VK, M>>(), {
labelKey: 'label' as never,
valueKey: 'value' as never
@@ -161,7 +163,7 @@ const defaultExpanded = computed(() =>
@toggle="item.onToggle"
@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.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
@@ -199,7 +201,7 @@ const defaultExpanded = computed(() =>
</DefineTreeTemplate>
<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] })"
:get-key="getItemValue"
:default-expanded="defaultExpanded"

View File

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

View File

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

View File

@@ -87,11 +87,11 @@ function _useOverlay() {
const open = <T extends Component>(id: symbol, props?: ComponentProps<T>): OpenedOverlay<T> => {
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) {
patch(overlay.id, props)
overlay.props = { ...overlay.originalProps, ...props }
} else {
patch(overlay.id, overlay.originalProps)
overlay.props = { ...overlay.originalProps }
}
overlay.isOpen = true

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -39,6 +39,7 @@ export default defineLocale<Messages>({
carousel: {
prev: 'Sebelumnya',
next: 'Berikutnya',
dots: 'Pilih slide untuk ditampilkan',
goto: 'Pergi ke slide {slide}'
},
modal: {

View File

@@ -39,6 +39,7 @@ export default defineLocale<Messages>({
carousel: {
prev: 'Precedente',
next: 'Successiva',
dots: 'Scegli diapositiva da visualizzare',
goto: 'Vai alla slide {slide}'
},
modal: {

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