Compare commits

...

50 Commits

Author SHA1 Message Date
Sébastien Chopin
84fc8c7c88 chore: remove all exclude 2024-11-26 18:34:14 +01:00
Benjamin Canac
1becead74e up
Co-Authored-By: Sébastien Chopin <seb@nuxt.com>
2024-11-26 18:24:02 +01:00
Benjamin Canac
ebaf4c530b up
Co-Authored-By: Sébastien Chopin <seb@nuxt.com>
2024-11-26 18:04:24 +01:00
Benjamin Canac
a7a4824ece docs(nuxt.config): disable prerender 2024-11-26 17:48:12 +01:00
Alex
15ca2f5701 docs(ComponentCode): add cast prop (#2773) 2024-11-26 16:13:47 +01:00
Benjamin Canac
08b9e4bff0 chore(README): update 2024-11-26 15:18:21 +01:00
Benjamin Canac
f49b49fd2c docs(app): update header github link 2024-11-26 15:10:17 +01:00
Benjamin Canac
a74e8c4444 docs(robots): update 2024-11-26 15:10:08 +01:00
Benjamin Canac
781081132d chore(README): update github links 2024-11-26 15:10:00 +01:00
Malik-Jouda
b1550d58ad fix(Table): handle loading animation in RTL mode (#2771) 2024-11-26 14:45:03 +01:00
Malik-Jouda
e7b69b7d6f fix(Calendar): handle icons in RTL mode (#2770) 2024-11-26 13:17:06 +01:00
Benjamin Canac
9478fc0768 fix(Calendar): omit as / asChild props 2024-11-26 13:12:11 +01:00
Alex
2e9aeb5f05 feat(Calendar): implement component (#2618)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2024-11-26 12:24:20 +01:00
Benjamin Canac
86f2b4856c fix(NavigationMenu): wrong badge class
Resolves #2766
2024-11-26 12:23:01 +01:00
Benjamin Canac
ba874c9191 docs(app): framework select global (#2719)
Co-authored-by: harlan <harlan@harlanzw.com>
2024-11-25 15:47:52 +01:00
Benjamin Canac
ffc81cc950 chore(CommandPalette): pass active to children 2024-11-25 14:39:15 +01:00
Benjamin Canac
d783387ed3 test(CommandPalette): improve 2024-11-25 14:26:58 +01:00
Benjamin Canac
37655377e9 feat(CommandPalette): add active field on items for consistency 2024-11-25 14:22:39 +01:00
renovate[bot]
7ab88d30b2 chore(deps): lock file maintenance (v3) (#2752)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-25 12:48:44 +01:00
renovate[bot]
ccb79b7ee4 chore(deps): update all non-major dependencies (v3) (#2705)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2024-11-25 12:37:53 +01:00
kyyy
c9806da6d8 fix(Form)!: resolve async validation in yup & issue directly mutate state (#2702)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2024-11-23 19:30:20 +01:00
Dafitra
3bccb6782a fix(useLocale): update locale import to enable tree shaking (#2735) 2024-11-22 23:03:44 +01:00
renovate[bot]
5a01a81577 chore(deps): update tailwindcss to v4.0.0-beta.2 (v3) (#2736)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2024-11-22 23:03:00 +01:00
Arcitezz
3baddfd121 feat(i18n): add Dutch locale (#2728) 2024-11-22 12:44:27 +01:00
Benjamin Canac
a7a1227c93 fix(Breadcrumb): missing aria-hidden on presentation items
Resolves #2725
2024-11-22 09:53:26 +01:00
Benjamin Canac
b259ddf271 docs(app): update @source usage 2024-11-21 23:41:29 +01:00
Benjamin Canac
c47ffc1cd5 docs: update links to tailwind v4 beta docs 2024-11-21 23:23:45 +01:00
Malik-Jouda
0baa3a06d4 fix(Progress): handle horizontal animation in RTL mode (#2723) 2024-11-21 23:07:59 +01:00
renovate[bot]
b140dbfe63 chore(deps): update tailwindcss to v4.0.0-beta.1 (v3) (#2721)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-21 22:32:52 +01:00
Benjamin Canac
6d917baac0 chore(css): update reset styles
https://github.com/tailwindlabs/tailwindcss/pull/15064
2024-11-21 22:31:37 +01:00
renovate[bot]
c511c95537 chore(deps): update tailwindcss to v4.0.0-alpha.36 (v3) (#2718)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Sandro Circi <sandro.circi@digitoolmedia.com>
2024-11-21 22:18:50 +01:00
Hasan Mumin
de8228e504 feat(i18n): add Turkish locale (#2716) 2024-11-21 17:27:41 +01:00
Benjamin Canac
29d2acf564 docs(getting-started): update faq 2024-11-21 16:51:28 +01:00
Benjamin Canac
f5452ba0c5 docs(icon): add missing props 2024-11-21 15:54:36 +01:00
Alex
b6a841e975 docs(i18n): display supported languages in a cards (#2709) 2024-11-21 11:21:35 +01:00
renovate[bot]
7bf85e9a09 chore(deps): update tailwindcss to v4.0.0-alpha.35 (v3) (#2707)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-21 10:56:00 +01:00
Benjamin Canac
9e8d50b2b8 chore(deps): dedupe 2024-11-20 18:54:15 +01:00
Benjamin Canac
b13b9e3ec0 docs(deps): update @nuxt/content to 3.0.0-alpha.7 2024-11-20 18:53:53 +01:00
Benjamin Canac
126c893635 docs(deps): update @nuxt/ui-pro 2024-11-20 17:56:34 +01:00
Farnabaz
7d8b721bdd docs(deps): update @nuxt/content (#2706) 2024-11-20 13:44:37 +01:00
renovate[bot]
b120e8d998 chore(deps): update all non-major dependencies (v3) (#2694)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-20 10:21:21 +01:00
renovate[bot]
2c4634a58f chore(deps): update nuxt framework to ^3.14.1592 (v3) (#2700)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-20 10:20:55 +01:00
Dewdew
2cbf83eb84 feat(locale): translate Korean (#2703)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2024-11-20 09:58:52 +01:00
Benjamin Canac
da1b0bac04 docs(dropdown-menu/context-menu): move class to ui.content
After a6ecef0f fix
2024-11-20 09:51:19 +01:00
Benjamin Canac
7cc51d2efa test: update snapshots 2024-11-20 09:35:04 +01:00
Benjamin Canac
c163ed8187 docs: improve titles 2024-11-19 22:52:23 +01:00
Benjamin Canac
a6ecef0f0d fix(components): apply class on trigger instead of content when present
Resolves #2132
2024-11-19 22:10:27 +01:00
Benjamin Canac
faec8260a4 test(Popover/Tooltip): add class / ui props 2024-11-19 22:09:33 +01:00
Malik-Jouda
7a02bfeba6 playground: improve responsive (#2675) 2024-11-19 19:26:09 +01:00
Benjamin Canac
9dd525ca26 docs(deps): update @nuxt/content to alpha.6 (#2692) 2024-11-19 18:41:04 +01:00
147 changed files with 13583 additions and 1010 deletions

View File

@@ -1,4 +1,4 @@
[![nuxt-ui.png](https://repository-images.githubusercontent.com/428329515/43fec891-9030-4601-8233-5d45ba5c6013)](https://ui.nuxt.com)
[![nuxt-ui.png](https://volta.s3.fr-par.scw.cloud/nuxt_ui_social_card_531d133fa2.png)](https://ui.nuxt.com)
# Nuxt UI
@@ -7,7 +7,7 @@
[![License][license-src]][license-href]
[![Nuxt][nuxt-src]][nuxt-href]
We're thrilled to introduce Nuxt UI v3, a significant upgrade to our UI library that delivers extensive improvements and robust new capabilities. This major update harnesses the combined strengths of [Radix Vue](https://www.radix-vue.com/), [Tailwind CSS v4](https://tailwindcss.com/blog/tailwindcss-v4-alpha), and [Tailwind Variants](https://www.tailwind-variants.org/) to offer developers an unparalleled set of tools for creating sophisticated, accessible, and highly performant user interfaces.
We're thrilled to introduce Nuxt UI v3, a significant upgrade to our UI library that delivers extensive improvements and robust new capabilities. This major update harnesses the combined strengths of [Radix Vue](https://www.radix-vue.com/), [Tailwind CSS v4](https://tailwindcss.com/docs/v4-beta), and [Tailwind Variants](https://www.tailwind-variants.org/) to offer developers an unparalleled set of tools for creating sophisticated, accessible, and highly performant user interfaces.
> [!NOTE]
> You are on the `v3` development branch, check out the [dev branch](https://github.com/nuxt/ui) for Nuxt UI v2.
@@ -105,7 +105,7 @@ Learn more in the [installation guide](https://ui3.nuxt.dev/getting-started/inst
## License
Licensed under the [MIT license](https://github.com/nuxt/ui/blob/dev/LICENSE.md).
Licensed under the [MIT license](https://github.com/nuxt/ui/blob/v3/LICENSE.md).
<!-- Badges -->
[npm-version-src]: https://img.shields.io/npm/v/@nuxt/ui/next.svg?style=flat&colorA=18181B&colorB=28CF8D
@@ -115,7 +115,7 @@ Licensed under the [MIT license](https://github.com/nuxt/ui/blob/dev/LICENSE.md)
[npm-downloads-href]: https://npm.chart.dev/@nuxt/ui
[license-src]: https://img.shields.io/github/license/nuxt/ui.svg?style=flat&colorA=18181B&colorB=28CF8D
[license-href]: https://github.com/nuxt/ui/blob/main/LICENSE.md
[license-href]: https://github.com/nuxt/ui/blob/v3/LICENSE.md
[nuxt-src]: https://img.shields.io/badge/Nuxt-18181B?logo=nuxt.js
[nuxt-href]: https://nuxt.com

View File

@@ -12,7 +12,7 @@
"dependencies": {
"@nuxt/ui": "latest",
"knitwork": "^1.1.0",
"nuxt": "^3.14.159",
"nuxt": "^3.14.1592",
"prettier": "^3.3.3",
"zod": "^3.23.8"
}

View File

@@ -73,24 +73,41 @@ useServerSeoMeta({
twitterCard: 'summary_large_image'
})
const updatedNavigation = computed(() => navigation.value?.map(item => ({
...item,
children: item.children?.map((child: typeof item) => ({
...child,
...(child.path === '/getting-started/installation' && {
title: 'Installation',
active: route.path.startsWith('/getting-started/installation'),
children: []
}),
...(child.path === '/getting-started/i18n' && {
title: 'I18n',
active: route.path.startsWith('/getting-started/i18n'),
children: []
})
})) || []
})))
const { framework, frameworks } = useSharedData()
provide('navigation', updatedNavigation)
const groups = computed(() => {
return [{
id: 'framework',
label: 'Framework',
items: frameworks.value
}]
})
function filterFrameworkItems(items: any[]) {
return items?.filter(item => !item.framework || item.framework === framework.value)
}
function processNavigationItem(item: any): any {
if (item.shadow) {
const matchingChild = filterFrameworkItems(item.children)?.[0]
return matchingChild
? {
...matchingChild,
title: item.title,
children: matchingChild.children ? processNavigationItem(matchingChild) : undefined
}
: item
}
return {
...item,
children: item.children?.length ? filterFrameworkItems(item.children)?.map(processNavigationItem) : undefined
}
}
const filteredNavigation = computed(() => navigation.value?.map(processNavigationItem))
provide('navigation', filteredNavigation)
</script>
<template>
@@ -111,7 +128,13 @@ provide('navigation', updatedNavigation)
<Footer />
<ClientOnly>
<LazyUContentSearch v-model:search-term="searchTerm" :files="files" :navigation="navigation" :fuse="{ resultLimit: 42 }" />
<LazyUContentSearch
v-model:search-term="searchTerm"
:files="files"
:groups="groups"
:navigation="filteredNavigation"
:fuse="{ resultLimit: 42 }"
/>
</ClientOnly>
</template>
</UApp>
@@ -121,7 +144,7 @@ provide('navigation', updatedNavigation)
@import "tailwindcss";
@import "@nuxt/ui-pro";
@source "../content/**/*.md";
@source "../content";
@theme {
--container-8xl: 90rem;

View File

@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 283.46 283.46">
<defs>
<style>
.cls-1{fill:#231815;}
@media (prefers-color-scheme: dark) { .cls-1{fill:#ffffff;} }
</style>
</defs>
<path class="cls-1" d="M144.89,89.86c-33.46,0-54.44,14.56-66.14,26.76a86,86,0,0,0-23.69,58.94c0,22.64,8.81,43.48,24.8,58.67,15.7,14.92,36.9,23.14,59.68,23.14,23.81,0,46-8.49,62.49-23.91,17-15.9,26.37-37.93,26.37-62C228.4,120.37,185.94,89.86,144.89,89.86Zm.49,153.67a61.49,61.49,0,0,1-46.45-20.4c-12.33-13.76-18.85-32.64-18.85-54.62,0-20.7,6.07-37.67,17.57-49.07,10.11-10,24.39-15.62,40.19-15.74,19,0,35.22,6.56,46.76,19,12.6,13.58,19.27,34,19.27,58.95C203.87,224.39,174.49,243.53,145.38,243.53Z"/>
<polygon class="cls-1" points="198.75 74.96 179.45 74.96 142.09 37.83 104.51 74.96 86.14 74.96 138.09 24.25 146.81 24.25 198.75 74.96"/>
</svg>

After

Width:  |  Height:  |  Size: 855 B

View File

@@ -0,0 +1,25 @@
<script setup lang="ts">
const { framework, frameworks } = useSharedData()
</script>
<template>
<UDropdownMenu
v-slot="{ open }"
:modal="false"
:items="frameworks"
:ui="{ content: 'w-(--radix-dropdown-menu-trigger-width)' }"
>
<UButton
color="neutral"
variant="outline"
v-bind="frameworks.find(f => f.value === framework)"
block
trailing-icon="i-lucide-chevron-down"
:class="[open && 'bg-[var(--ui-bg-elevated)]']"
:ui="{
trailingIcon: ['transition-transform duration-200', open ? 'rotate-180' : undefined].filter(Boolean).join(' ')
}"
class="-mx-2 w-[calc(100%+1rem)]"
/>
</UDropdownMenu>
</template>

View File

@@ -42,7 +42,7 @@ defineShortcuts({
<UButton
color="neutral"
variant="ghost"
to="https://github.com/nuxt/ui/tree/v3"
to="https://github.com/nuxt/ui"
target="_blank"
icon="i-simple-icons-github"
aria-label="GitHub"
@@ -55,6 +55,8 @@ defineShortcuts({
<USeparator type="dashed" class="my-4" />
<FrameworkSelect class="mb-4" />
<UContentNavigation :navigation="navigation" highlight />
</template>
</UHeader>

View File

@@ -3,9 +3,42 @@
import json5 from 'json5'
import { upperFirst, camelCase, kebabCase } from 'scule'
import { hash } from 'ohash'
import { CalendarDate } from '@internationalized/date'
import * as theme from '#build/ui'
import { get, set } from '#ui/utils'
interface Cast {
get: (args: any) => any
template: (args: any) => string
}
type CastDateValue = [number, number, number]
const castMap: Record<string, Cast> = {
'DateValue': {
get: (args: CastDateValue) => new CalendarDate(...args),
template: (value: CalendarDate) => {
return value ? `new CalendarDate(${value.year}, ${value.month}, ${value.day})` : 'null'
}
},
'DateValue[]': {
get: (args: CastDateValue[]) => args.map(date => new CalendarDate(...date)),
template: (value: CalendarDate[]) => {
return value ? `[${value.map(date => `new CalendarDate(${date.year}, ${date.month}, ${date.day})`).join(', ')}]` : '[]'
}
},
'DateRange': {
get: (args: { start: CastDateValue, end: CastDateValue }) => ({ start: new CalendarDate(...args.start), end: new CalendarDate(...args.end) }),
template: (value: { start: CalendarDate, end: CalendarDate }) => {
if (!value.start || !value.end) {
return `{ start: null, end: null }`
}
return `{ start: new CalendarDate(${value.start.year}, ${value.start.month}, ${value.start.day}), end: new CalendarDate(${value.end.year}, ${value.end.month}, ${value.end.day}) }`
}
}
}
const props = defineProps<{
/** Override the slug taken from the route */
slug?: string
@@ -18,6 +51,8 @@ const props = defineProps<{
external?: string[]
/** List of props to use with `v-model` */
model?: string[]
/** List of props to cast from code and selection */
cast?: { [key: string]: string }
/** List of items for each prop */
items?: { [key: string]: string[] }
props?: { [key: string]: any }
@@ -45,7 +80,17 @@ const camelName = camelCase(props.slug ?? route.params.slug?.[route.params.slug.
const name = `U${upperFirst(camelName)}`
const component = defineAsyncComponent(() => import(`#ui/components/${upperFirst(camelName)}.vue`))
const componentProps = reactive({ ...(props.props || {}) })
const componentProps = reactive({
...Object.fromEntries(Object.entries(props.props || {}).map(([key, value]) => {
const cast = props.cast?.[key]
if (cast && !castMap[cast]) {
throw new Error(`Unknown cast: ${cast}`)
}
return [key, cast ? castMap[cast]!.get(value) : value]
}))
})
const componentEvents = reactive({
...Object.fromEntries((props.model || []).map(key => [`onUpdate:${key}`, (e: any) => setComponentProp(key, e)])),
...(componentProps.modelValue ? { [`onUpdate:modelValue`]: (e: any) => setComponentProp('modelValue', e) } : {})
@@ -96,7 +141,7 @@ const options = computed(() => {
return {
name: key,
label: key,
type: prop?.type,
type: props?.cast?.[key] ?? prop?.type,
items
}
})
@@ -117,7 +162,10 @@ const code = computed(() => {
<script setup lang="ts">
`
for (const key of props.external) {
code += `const ${key === 'modelValue' ? 'value' : key} = ref(${json5.stringify(componentProps[key], null, 2).replace(/,([ |\t\n]+[}|\]])/g, '$1')})
const cast = props.cast?.[key]
const value = cast ? castMap[cast]!.template(componentProps[key]) : json5.stringify(componentProps[key], null, 2).replace(/,([ |\t\n]+[}|\]])/g, '$1')
code += `const ${key === 'modelValue' ? 'value' : key} = ref(${value})
`
}
code += `<\/script>

View File

@@ -35,7 +35,7 @@ const schemaProps = computed(() => {
</script>
<template>
<Collapsible v-if="schemaProps?.length" class="mt-1">
<ProseCollapsible v-if="schemaProps?.length" class="mt-1">
<ProseUl>
<ProseLi v-for="schemaProp in schemaProps" :key="schemaProp.name">
<HighlightInlineType :type="`${schemaProp.name}${schemaProp.required === false ? '?' : ''}: ${schemaProp.type}`" />
@@ -43,5 +43,5 @@ const schemaProps = computed(() => {
<MDC v-if="schemaProp.description" :value="schemaProp.description" class="text-[var(--ui-text-muted)] my-1" />
</ProseLi>
</ProseUl>
</Collapsible>
</ProseCollapsible>
</template>

View File

@@ -4,14 +4,19 @@ import { camelCase } from 'scule'
import * as theme from '#build/ui'
const route = useRoute()
const { framework } = useSharedData()
const name = camelCase(route.params.slug?.[route.params.slug.length - 1] ?? '')
const strippedCompoundVariants = ref(false)
function stripCompoundVariants(component?: any) {
if (component?.compoundVariants) {
component.compoundVariants = component.compoundVariants.filter((compoundVariant: any) => {
const strippedTheme = computed(() => {
const strippedTheme = {
...(theme as any)[name]
}
if (strippedTheme?.compoundVariants) {
strippedTheme.compoundVariants = strippedTheme.compoundVariants.filter((compoundVariant: any) => {
if (compoundVariant.color) {
if (!['primary', 'neutral'].includes(compoundVariant.color)) {
strippedCompoundVariants.value = true
@@ -40,24 +45,43 @@ function stripCompoundVariants(component?: any) {
})
}
return component
}
return strippedTheme
})
const component = computed(() => {
return {
ui: {
[name]: stripCompoundVariants((theme as any)[name])
[name]: strippedTheme.value
}
}
})
const { data: ast } = await useAsyncData(`component-theme-${name}`, async () => {
const { data: ast } = await useAsyncData(`component-theme-${name}-${framework.value}`, async () => {
const md = `
::code-collapse
${framework.value === 'nuxt'
? `
\`\`\`ts [app.config.ts]
export default defineAppConfig(${json5.stringify(component.value, null, 2).replace(/,([ |\t\n]+[}|\])])/g, '$1')})
\`\`\`\
`
: `
\`\`\`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(${json5.stringify(component.value, null, 2).replace(/,([ |\t\n]+[}|\])])/g, '$1')
.split('\n')
.map((line, i) => i === 0 ? line : ` ${line}`)
.join('\n')})
]
})
\`\`\`
`}
::
${strippedCompoundVariants.value
@@ -69,7 +93,7 @@ Some colors in \`compoundVariants\` are omitted for readability. Check out the s
`
return parseMarkdown(md)
})
}, { watch: [framework] })
</script>
<template>

View File

@@ -0,0 +1,7 @@
<script setup lang="ts">
const { framework } = useSharedData()
</script>
<template>
<slot :name="framework" />
</template>

View File

@@ -2,8 +2,13 @@
import json5 from 'json5'
import icons from '../../../../src/theme/icons'
const { data: ast } = await useAsyncData(`icons-theme`, async () => {
const { framework } = useSharedData()
const { data: ast } = await useAsyncData(`icons-theme-${framework.value}`, async () => {
const md = `
::code-collapse
${framework.value === 'nuxt'
? `
\`\`\`ts [app.config.ts]
export default defineAppConfig(${json5.stringify({
ui: {
@@ -11,10 +16,33 @@ export default defineAppConfig(${json5.stringify({
}
}, null, 2).replace(/,([ |\t\n]+[}|\])])/g, '$1')})
\`\`\`\
`
: `
\`\`\`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(${json5.stringify({
ui: {
icons
}
}, null, 2).replace(/,([ |\t\n]+[}|\])])/g, '$1')
.split('\n')
.map((line, i) => i === 0 ? line : ` ${line}`)
.join('\n')})
]
})
\`\`\`
`}
::
`
return parseMarkdown(md)
})
}, { watch: [framework] })
</script>
<template>

View File

@@ -1,8 +1,9 @@
<script setup lang="ts">
import * as locales from '@nuxt/ui/locale'
import type { Locale } from '@nuxt/ui'
import * as locales from '@nuxt/ui/locale'
type LocaleKey = keyof typeof locales
type LocaleComputed = Locale & { flag: string }
const props = withDefaults(defineProps<{
default?: string
@@ -10,8 +11,17 @@ const props = withDefaults(defineProps<{
default: 'en'
})
const countries = await $fetch('/api/locales.json')
const getLocaleKeys = Object.keys(locales) as LocaleKey[]
const localesList = getLocaleKeys.map<[LocaleKey, Locale]>(locale => [locale, locales[locale]])
const localesList = getLocaleKeys.map<LocaleComputed>((code) => {
const locale: Locale = locales[code]
return {
...locale,
flag: countries[locale.code] || ''
}
})
</script>
<!-- eslint-disable vue/singleline-html-element-content-newline -->
@@ -20,45 +30,24 @@ const localesList = getLocaleKeys.map<[LocaleKey, Locale]>(locale => [locale, lo
<ProseP>
By default, the <ProseCode>{{ props.default }}</ProseCode> locale is used.
</ProseP>
<ProseTable>
<ProseThead>
<ProseTr>
<ProseTh>
Language
</ProseTh>
<ProseTh>
Code
</ProseTh>
<ProseTh>
Direction
</ProseTh>
</ProseTr>
</ProseThead>
<ProseTbody>
<ProseTr v-for="[key, locale] in localesList" :key="key">
<ProseTd>
{{ locale.name }}
</ProseTd>
<ProseTd>
<ProseCode>
{{ locale.code }}
</ProseCode>
</ProseTd>
<ProseTd>
<ProseCode>
{{ locale.dir }}
</ProseCode>
</ProseTd>
</ProseTr>
</ProseTbody>
</ProseTable>
<Note to="https://github.com/nuxt/ui/tree/v3/src/runtime/locale" target="_blank">
<div class="grid gap-6 grid-cols-2 md:grid-cols-3">
<div v-for="locale in localesList" :key="locale.code">
<div class="flex gap-3 items-center">
<UAvatar :text="locale.flag" size="xl" />
<div class="text-sm">
<div class="font-semibold">{{ locale.name }}</div>
<div class="mt-1">Code: <ProseCode class="text-xs">{{ locale.code }}</ProseCode></div>
</div>
</div>
</div>
</div>
<ProseNote to="https://github.com/nuxt/ui/tree/v3/src/runtime/locale" target="_blank">
If you need additional languages, you can contribute by creating a PR to add a new locale in <ProseCode>src/runtime/locale/</ProseCode>.
</Note>
<Tip>
</ProseNote>
<ProseTip>
You can use the <ProseCode>nuxt-ui</ProseCode> CLI to create a new locale:
<ProsePre language="bash">nuxt-ui make locale --code "en" --name "English"</ProsePre>
</Tip>
</ProseTip>
</div>
</template>

View File

@@ -0,0 +1,21 @@
<script setup lang="ts">
import { CalendarDate, DateFormatter, getLocalTimeZone } from '@internationalized/date'
const df = new DateFormatter('en-US', {
dateStyle: 'medium'
})
const modelValue = shallowRef(new CalendarDate(2022, 1, 10))
</script>
<template>
<UPopover>
<UButton color="neutral" variant="subtle" icon="i-lucide-calendar">
{{ df.format(modelValue.toDate(getLocalTimeZone())) }}
</UButton>
<template #content>
<UCalendar v-model="modelValue" class="p-2" />
</template>
</UPopover>
</template>

View File

@@ -0,0 +1,35 @@
<script setup lang="ts">
import { CalendarDate, DateFormatter, getLocalTimeZone } from '@internationalized/date'
const df = new DateFormatter('en-US', {
dateStyle: 'medium'
})
const modelValue = shallowRef({
start: new CalendarDate(2022, 1, 20),
end: new CalendarDate(2022, 2, 10)
})
</script>
<template>
<UPopover>
<UButton color="neutral" variant="subtle" icon="i-lucide-calendar">
<template v-if="modelValue.start">
<template v-if="modelValue.end">
{{ df.format(modelValue.start.toDate(getLocalTimeZone())) }} - {{ df.format(modelValue.end.toDate(getLocalTimeZone())) }}
</template>
<template v-else>
{{ df.format(modelValue.start.toDate(getLocalTimeZone())) }}
</template>
</template>
<template v-else>
Pick a date
</template>
</UButton>
<template #content>
<UCalendar v-model="modelValue" class="p-2" :number-of-months="2" range />
</template>
</UPopover>
</template>

View File

@@ -0,0 +1,17 @@
<script setup lang="ts">
import { CalendarDate } from '@internationalized/date'
import type { Matcher } from 'radix-vue/date'
const modelValue = shallowRef({
start: new CalendarDate(2022, 1, 1),
end: new CalendarDate(2022, 1, 9)
})
const isDateDisabled: Matcher = (date) => {
return date.day >= 10 && date.day <= 16
}
</script>
<template>
<UCalendar v-model="modelValue" :is-date-disabled="isDateDisabled" range />
</template>

View File

@@ -0,0 +1,30 @@
<script setup lang="ts">
import { CalendarDate } from '@internationalized/date'
const modelValue = shallowRef(new CalendarDate(2022, 1, 10))
function getColorByDate(date: Date) {
const isWeekend = date.getDay() % 6 == 0
const isDayMeeting = date.getDay() % 3 == 0
if (isWeekend) {
return undefined
}
if (isDayMeeting) {
return 'error'
}
return 'success'
}
</script>
<template>
<UCalendar v-model="modelValue">
<template #day="{ day }">
<UChip :show="!!getColorByDate(day.toDate('UTC'))" :color="getColorByDate(day.toDate('UTC'))" size="2xs">
{{ day.day }}
</UChip>
</template>
</UCalendar>
</template>

View File

@@ -0,0 +1,11 @@
<script setup lang="ts">
import { CalendarDate } from '@internationalized/date'
const modelValue = shallowRef(new CalendarDate(2023, 9, 10))
const minDate = new CalendarDate(2023, 9, 1)
const maxDate = new CalendarDate(2023, 9, 30)
</script>
<template>
<UCalendar v-model="modelValue" :min-value="minDate" :max-value="maxDate" />
</template>

View File

@@ -0,0 +1,17 @@
<script setup lang="ts">
import { CalendarDate } from '@internationalized/date'
import type { Matcher } from 'radix-vue/date'
const modelValue = shallowRef({
start: new CalendarDate(2022, 1, 1),
end: new CalendarDate(2022, 1, 9)
})
const isDateUnavailable: Matcher = (date) => {
return date.day >= 10 && date.day <= 16
}
</script>
<template>
<UCalendar v-model="modelValue" :is-date-unavailable="isDateUnavailable" range />
</template>

View File

@@ -32,7 +32,7 @@ const items = computed(() => [{
</script>
<template>
<UContextMenu :items="items" class="w-48">
<UContextMenu :items="items" :ui="{ content: 'w-48' }">
<div class="flex items-center justify-center rounded-md border border-dashed border-[var(--ui-border-accented)] text-sm aspect-video w-72">
Right click here
</div>

View File

@@ -25,7 +25,7 @@ const items = [
</script>
<template>
<UContextMenu :items="items" class="w-48">
<UContextMenu :items="items" :ui="{ content: 'w-48' }">
<div class="flex items-center justify-center rounded-md border border-dashed border-[var(--ui-border-accented)] text-sm aspect-video w-72">
Right click here
</div>

View File

@@ -12,7 +12,7 @@ const items = [{
</script>
<template>
<UContextMenu :items="items" class="w-48">
<UContextMenu :items="items" :ui="{ content: 'w-48' }">
<div class="flex items-center justify-center rounded-md border border-dashed border-[var(--ui-border-accented)] text-sm aspect-video w-72">
Right click here
</div>

View File

@@ -40,7 +40,7 @@ const items = computed(() => [{
</script>
<template>
<UDropdownMenu :items="items" :content="{ align: 'start' }" class="w-48">
<UDropdownMenu :items="items" :content="{ align: 'start' }" :ui="{ content: 'w-48' }">
<UButton label="Open" color="neutral" variant="outline" icon="i-lucide-menu" />
</UDropdownMenu>
</template>

View File

@@ -25,7 +25,7 @@ const items = [
</script>
<template>
<UDropdownMenu :items="items" class="w-48">
<UDropdownMenu :items="items" :ui="{ content: 'w-48' }">
<UButton label="Open" color="neutral" variant="outline" icon="i-lucide-menu" />
<template #profile-trailing>

View File

@@ -13,7 +13,7 @@ const items = [{
</script>
<template>
<UDropdownMenu :items="items" class="w-48">
<UDropdownMenu :items="items" :ui="{ content: 'w-48' }">
<UButton label="Open" color="neutral" variant="outline" icon="i-lucide-menu" />
<template #profile-trailing>

View File

@@ -18,7 +18,7 @@ const items = [{
</script>
<template>
<UDropdownMenu v-model:open="open" :items="items" class="w-48">
<UDropdownMenu v-model:open="open" :items="items" :ui="{ content: 'w-48' }">
<UButton label="Open" color="neutral" variant="outline" icon="i-lucide-menu" />
</UDropdownMenu>
</template>

View File

@@ -56,7 +56,7 @@ defineProps({
<h1 class="m-0 text-[75px] font-semibold mb-2 text-white flex items-center">
<span>{{ title }}</span>
</h1>
<p v-if="description" class="text-[32px] text-[#94a3b8] leading-tight">
<p v-if="description" class="text-[32px] text-[#94a3b8] leading-tight text-balance">
{{ description.slice(0, 200) }}
</p>
</div>

View File

@@ -0,0 +1,23 @@
import { createSharedComposable } from '@vueuse/core'
function _useSharedData() {
const framework = useCookie('nuxt-ui-framework', { default: () => 'nuxt' })
const frameworks = computed(() => [{
label: 'Nuxt',
icon: 'i-logos-nuxt-icon',
value: 'nuxt',
onSelect: () => framework.value = 'nuxt'
}, {
label: 'Vue',
icon: 'i-logos-vue',
value: 'vue',
onSelect: () => framework.value = 'vue'
}].map(f => ({ ...f, active: framework.value === f.value })))
return {
framework,
frameworks
}
}
export const useSharedData = createSharedComposable(_useSharedData)

View File

@@ -10,6 +10,10 @@ const navigation = inject<Ref<ContentNavigationItem[]>>('navigation')
<UPage>
<template #left>
<UPageAside>
<template #top>
<FrameworkSelect />
</template>
<UContentNavigation :navigation="navigation" highlight />
</UPageAside>
</template>

View File

@@ -21,10 +21,32 @@ const navigation = inject<Ref<ContentNavigationItem[]>>('navigation')
const breadcrumb = computed(() => mapContentNavigation(findPageBreadcrumb(navigation?.value, page.value)).map(({ icon, ...link }) => link))
const { framework } = useSharedData()
// Redirect to the correct framework version if the page is not the current framework
if (!import.meta.prerender) {
watch(framework, () => {
if (page.value?.navigation?.framework && page.value?.navigation?.framework !== framework.value) {
if (route.path.endsWith(`/${page.value?.navigation?.framework}`)) {
navigateTo(`${route.path.split('/').slice(0, -1).join('/')}/${framework.value}`)
} else {
navigateTo(`/getting-started`)
}
}
})
}
// Update the framework if the page has a different framework
watch(page, () => {
if (page.value?.navigation?.framework && page.value?.navigation?.framework !== framework.value) {
framework.value = page.value?.navigation?.framework as string
}
}, { immediate: true })
useSeoMeta({
titleTemplate: '%s - Nuxt UI v3',
title: typeof page.value.navigation === 'object' ? page.value.navigation.title : page.value.title,
ogTitle: `${typeof page.value.navigation === 'object' ? page.value.navigation.title : page.value.title} - Nuxt UI v3`,
title: typeof page.value.navigation === 'object' && page.value.navigation.title ? page.value.navigation.title : page.value.title,
ogTitle: `${typeof page.value.navigation === 'object' && page.value.navigation.title ? page.value.navigation.title : page.value.title} - Nuxt UI v3`,
description: page.value.description,
ogDescription: page.value.description
})
@@ -75,21 +97,6 @@ const communityLinks = computed(() => [{
</template>
<template #links>
<UDropdownMenu v-if="page.select" v-slot="{ open }" :items="page.select.items" :content="{ align: 'end' }">
<UButton
color="neutral"
variant="subtle"
v-bind="page.select.items.find((item: any) => item.to === route.path)"
block
trailing-icon="i-lucide-chevron-down"
:class="[open && 'bg-[var(--ui-bg-accented)]/75']"
:ui="{
trailingIcon: ['transition-transform duration-200', open ? 'rotate-180' : undefined].filter(Boolean).join(' ')
}"
class="w-[128px]"
/>
</UDropdownMenu>
<UButton
v-for="link in page.links"
:key="link.label"

View File

@@ -5,6 +5,10 @@ export const collections = {
type: 'page',
source: '**/*',
schema: z.object({
navigation: z.object({
title: z.string().optional(),
framework: z.string().optional()
}),
links: z.array(z.object({
label: z.string(),
icon: z.string(),
@@ -14,14 +18,7 @@ export const collections = {
}).optional(),
to: z.string(),
target: z.string().optional()
})),
select: z.object({
items: z.array(z.object({
label: z.string(),
icon: z.string(),
to: z.string()
}))
})
}))
})
})
}

View File

@@ -21,15 +21,15 @@ This transition empowers Nuxt UI to become a more comprehensive and flexible UI
### Tailwind CSS v4
Nuxt UI v3 integrates the latest Tailwind CSS v4 alpha (announced March 6, 2024), bringing significant improvements:
Nuxt UI v3 integrates the latest Tailwind CSS v4 beta (released Nov 21, 2024), bringing significant improvements:
- **Faster Builds**: Up to 10x faster, especially for larger projects.
- **Unified Toolchain**: Built-in features like vendor prefixing, nesting support, and modern CSS transforms.
- **CSS-First Configuration**: New `@theme` directive for easy customization without JavaScript.
- **Optimized Performance**: Smaller engine footprint and more efficient processing.
- **Built for performance**: Full builds in the new engine are up to 5x faster, and incremental builds are over 100x faster — and measured in microseconds.
- **Unified toolchain**: Built-in import handling, vendor prefixing, and syntax transforms, with no additional tooling required.
- **CSS-first configuration**: A reimagined developer experience where you customize and extend the framework directly in CSS instead of a JavaScript configuration file.
- **Designed for the modern web**: Built on native cascade layers, wide-gamut colors, and including first-class support for modern CSS features like container queries, @starting-style, popovers, and more.
::note
For a comprehensive overview of Tailwind CSS v4 alpha features, visit the [official announcement](https://tailwindcss.com/blog/tailwindcss-v4-alpha).
For a comprehensive overview of Tailwind CSS v4 beta features, read the [prerelease documentation](https://tailwindcss.com/docs/v4-beta).
::
### Tailwind Variants
@@ -71,7 +71,7 @@ You can now use Nuxt UI in any Vue project without Nuxt by adding the Vite and V
- **Developer Experience**: Complete TypeScript support with IntelliSense and auto-completion
::tip{to="/getting-started/installation/vue"}
Learn how to install and configure Nuxt UI in a Vue project in the [Vue installation guide](/getting-started/installation/vue).
Learn how to install and configure Nuxt UI in a Vue project in the **Vue installation guide**.
::
## Migration
@@ -90,6 +90,10 @@ Key points to consider:
The transition to v3 involves significant changes, including new component structures, updated theming approaches, and revised TypeScript definitions. We recommend a careful, incremental upgrade process, starting with thorough testing in a development environment.
::
::accordion-item{label="Is Nuxt UI v3 compatible with standalone Vue projects?"}
Nuxt UI is now compatible with Vue! You can follow the [installation guide](/getting-started/installation/vue) to get started.
::
::accordion-item{label="What about Nuxt UI Pro?"}
We've also rebuilt Nuxt UI Pro from scratch and released a `v3.0.0-alpha.x` package but it only contains the components to build this documentation yet. This will be a free update, so the license you buy now will be valid for v3. We're actively working to finish the rewrite of all Nuxt UI Pro components.
::
@@ -98,16 +102,12 @@ Key points to consider:
Nuxt UI v3 is currently designed to work exclusively with Tailwind CSS. While there's interest in UnoCSS support, implementing it would require significant changes to the theme structure due to differences in class naming conventions. As a result, we don't have plans to add UnoCSS support in v3.
::
::accordion-item{label="Is Nuxt UI v3 compatible with standalone Vue projects?"}
We're planning to add Vue support in the near future. For now, Nuxt UI v3 is only available for Nuxt. Track progress on Vue compatibility [in this issue](https://github.com/nuxt/ui/issues/2129).
::
::accordion-item{label="How does Nuxt UI v3 handle accessibility?"}
Nuxt UI v3 enhances accessibility through Radix Vue integration. This provides automatic ARIA attributes, keyboard navigation support, intelligent focus management, and screen reader announcements. While offering a strong foundation, proper implementation and testing in your specific use case remains crucial for full accessibility compliance. For more detailed information, refer to [Radix Vue's accessibility documentation](https://www.radix-vue.com/overview/accessibility.html).
::
::accordion-item{label="What is the testing approach for Nuxt UI v3?"}
Nuxt UI v3 ensures reliability with 800+ Vitest tests, covering core functionality and accessibility. This robust testing suite supports the library's stability and serves as a reference for developers.
Nuxt UI v3 ensures reliability with 1000+ Vitest tests, covering core functionality and accessibility. This robust testing suite supports the library's stability and serves as a reference for developers.
::
::accordion-item{label="Is this version stable and suitable for production use?"}

View File

@@ -0,0 +1 @@
shadow: true

View File

@@ -1,17 +1,13 @@
---
title: Install in a Nuxt app
navigation.title: Nuxt
title: Installation
description: 'Learn how to install and configure Nuxt UI in your Nuxt application.'
select:
items:
- label: Nuxt
icon: i-logos-nuxt-icon
to: /getting-started/installation/nuxt
- label: Vue
icon: i-logos-vue
to: /getting-started/installation/vue
navigation.framework: nuxt
---
::callout{to="/getting-started/installation/vue" icon="i-logos-vue" class="hidden"}
Looking for the **Vue** version?
::
## Setup
::steps{level="4"}

View File

@@ -1,17 +1,13 @@
---
navigation.title: Vue
title: Install in a Vue app
title: Installation
description: 'Learn how to install and configure Nuxt UI in your Vue application.'
select:
items:
- label: Nuxt
icon: i-logos-nuxt-icon
to: /getting-started/installation/nuxt
- label: Vue
icon: i-logos-vue
to: /getting-started/installation/vue
navigation.framework: vue
---
::callout{to="/getting-started/installation/nuxt" icon="i-logos-nuxt-icon" class="hidden"}
Looking for the **Nuxt** version?
::
## Setup
::steps{level="4"}
@@ -156,10 +152,6 @@ export default defineConfig({
Use the `ui` option to provide configuration for Nuxt UI.
::warning
In the rest of the docs, there are references to the `app.config.ts` file (which is a Nuxt feature). When using Nuxt UI in a project without Nuxt, this configuration is passed in this `ui` option instead.
::
```ts [vite.config.ts]
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

View File

@@ -5,7 +5,7 @@ description: 'Learn how to customize Nuxt UI components using Tailwind CSS v4, C
## Tailwind CSS
Nuxt UI v3 uses Tailwind CSS v4 alpha which doesn't have a documentation yet, let's have a look on how to use it.
Nuxt UI v3 uses Tailwind CSS v4 beta, you can read the [prerelease documentation](https://tailwindcss.com/docs/v4-beta) for more information.
### `@theme`
@@ -36,8 +36,8 @@ Tailwind CSS v4 takes a CSS-first configuration approach, you now customize your
The `@theme` directive tells Tailwind to make new utilities and variants available based on these variables. It's the equivalent of the `theme.extend` key in Tailwind CSS v3 `tailwind.config.ts` file.
::note
You can learn more about this on [https://tailwindcss.com/blog/tailwindcss-v4-alpha](https://tailwindcss.com/blog/tailwindcss-v4-alpha#css-first-configuration).
::note{to="https://tailwindcss.com/docs/v4-beta#css-first-configuration" target="_blank"}
Learn more about Tailwind CSS v4 CSS-first configuration approach.
::
### `@source`
@@ -50,11 +50,11 @@ This can be useful when writing Tailwind classes in markdown files with [`@nuxt/
@import "tailwindcss";
@import "@nuxt/ui";
@source "../content/**/*.md";
@source "../content";
```
::note{to="https://github.com/tailwindlabs/tailwindcss/pull/14078"}
You can learn more about the `@source` directive in this pull request.
::note{to="https://tailwindcss.com/docs/v4-beta#adding-content-sources"}
Learn how to add content sources in Tailwind CSS v4.
::
### `@plugin`
@@ -68,8 +68,8 @@ You can use the `@plugin` directive to import Tailwind CSS plugins.
@plugin "@tailwindcss/typography";
```
::note{to="https://github.com/tailwindlabs/tailwindcss/pull/14264"}
You can learn more about the `@plugin` directive in this pull request.
::note{to="https://tailwindcss.com/docs/v4-beta#using-plugins"}
Learn more about using plugins in Tailwind CSS v4.
::
## Design system
@@ -78,8 +78,14 @@ Nuxt UI extends Tailwind CSS's theming capabilities, providing a flexible design
### Colors
::framework-only
#nuxt
Nuxt UI leverages Nuxt [App Config](https://nuxt.com/docs/guide/directory-structure/app-config#app-config-file) to provide customizable color aliases based on [Tailwind CSS colors](https://tailwindcss.com/docs/customizing-colors#color-palette-reference):
#vue
Nuxt UI leverages Vite config to provide customizable color aliases based on [Tailwind CSS colors](https://tailwindcss.com/docs/customizing-colors#color-palette-reference):
::
| Color | Default | Description |
| --- | --- | --- |
| `primary`{color="primary"} | `green` | Main brand color, used as the default color for components. |
@@ -90,6 +96,8 @@ Nuxt UI leverages Nuxt [App Config](https://nuxt.com/docs/guide/directory-struct
| `error`{color="error"} | `red` | Used for form error validation states. |
| `neutral` | `slate` | Neutral color for backgrounds, text, etc. |
::framework-only
#nuxt
You can configure these color aliases at runtime in your `app.config.ts` file under the `ui.colors` key, allowing for dynamic theme customization without requiring an application rebuild:
```ts [app.config.ts]
@@ -103,6 +111,30 @@ export default defineAppConfig({
})
```
#vue
You can configure these color aliases at runtime in your `vite.config.ts` file under the `ui.colors` key:
```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({
ui: {
colors: {
primary: 'blue',
neutral: 'zinc'
}
}
})
]
})
```
::
::note
Try the :prose-icon{name="i-lucide-swatch-book" class="text-[var(--ui-primary)]"} theme picker in the header above to change `primary` and `neutral` colors.
::
@@ -118,8 +150,10 @@ slots:
---
::
::tip
You can add you own dynamic color aliases in your `app.config.ts`, you just have to make sure to define them in the [`ui.theme.colors`](/getting-started/installation#themecolors) option in your `nuxt.config.ts` file.
::framework-only
#nuxt
:::tip
You can add you own dynamic color aliases in your `app.config.ts`, you just have to make sure to define them in the [`ui.theme.colors`](/getting-started/installation/nuxt#themecolors) option in your `nuxt.config.ts` file.
```ts [app.config.ts]
export default defineAppConfig({
@@ -140,7 +174,34 @@ export default defineNuxtConfig({
}
})
```
:::
#vue
:::tip
You can add you own dynamic color aliases in your `vite.config.ts`, you just have to make sure to also define them in the [`theme.colors`](/getting-started/installation/vue#themecolors) option of the `ui` plugin.
```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({
ui: {
colors: {
tertiary: 'indigo'
}
},
theme: {
colors: ['primary', 'secondary', 'tertiary', 'info', 'success', 'warning', 'error']
}
})
]
})
```
:::
::
::warning
@@ -490,7 +551,19 @@ props:
---
::
The `defaultVariants` property specifies the default values for each variant. It determines how a component looks and behaves when no prop is provided. These default values can be customized in your [`app.config.ts`](#appconfigts) to adjust the standard appearance of components throughout your application.
The `defaultVariants` property specifies the default values for each variant. It determines how a component looks and behaves when no prop is provided.
::framework-only
#nuxt
:::tip
These default values can be customized in your [`app.config.ts`](#config) to adjust the standard appearance of components throughout your application.
:::
#vue
:::tip
These default values can be customized in your [`vite.config.ts`](#config) to adjust the standard appearance of components throughout your application.
:::
::
## Customize theme
@@ -507,9 +580,11 @@ You can explore the theme for each component in two ways:
- Browse the source code directly in the GitHub repository at https://github.com/nuxt/ui/tree/v3/src/theme.
::
### `app.config.ts`
### Config
You can override the theme of components inside your `app.config.ts` by using the exact same structure as the theme object.
::framework-only
#nuxt
You can override the theme of components globally inside your `app.config.ts` by using the exact same structure as the theme object.
Let's say you want to change the font weight of all your buttons, you can do it like this:
@@ -525,13 +600,42 @@ export default defineAppConfig({
})
```
#vue
You can override the theme of components globally inside your `vite.config.ts` by using the exact same structure as the theme object.
Let's say you want to change the font weight of all your buttons, you can do it like this:
```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({
ui: {
button: {
slots: {
base: 'font-bold'
}
}
}
})
]
})
```
::
::note
In this example, the `font-bold` class will override the default `font-medium` class on all buttons.
::
### `ui` prop
### Props
You can also override a component's **slots** using the `ui` prop. This has priority over the `app.config.ts` configuration and `variants` resolution.
#### `ui` prop
You can also override a component's **slots** using the `ui` prop. This has priority over the global configuration and `variants` resolution.
::component-code{slug="button"}
---
@@ -560,9 +664,9 @@ slots:
In this example, the `trailingIcon` slot is overwritten with `size-3` even though the `md` size variant would apply a `size-5` class to it.
::
### `class` prop
#### `class` prop
The `class` prop allows you to override the classes of the `root` or `base` slot. This has priority over the `app.config.ts` configuration and `variants` resolution.
The `class` prop allows you to override the classes of the `root` or `base` slot. This has priority over the global configuration and `variants` resolution.
::component-code{slug="button"}
---

View File

@@ -0,0 +1 @@
shadow: true

View File

@@ -1,6 +1,7 @@
---
title: Icons
description: 'Nuxt UI integrates with Nuxt Icon to access over 200,000+ icons from Iconify.'
navigation.framework: nuxt
links:
- label: 'Iconify'
to: https://iconify.design/
@@ -12,14 +13,14 @@ links:
icon: i-simple-icons-github
---
::callout{to="/getting-started/icons/vue" icon="i-logos-vue" class="hidden"}
Looking for the **Vue** version?
::
## Usage
Nuxt UI automatically registers the [`@nuxt/icon`](https://github.com/nuxt/icon) module for you, so there's no additional setup required.
::note
You can use any name from the https://icones.js.org collection.
::
### Icon Component
You can use the [Icon](/components/icon) component with a `name` prop to display an icon:
@@ -32,6 +33,10 @@ props:
---
::
::note
You can use any name from the https://icones.js.org collection.
::
### Component Props
Some components also have an `icon` prop to display an icon, like the [Button](/components/button) for example:

View File

@@ -0,0 +1,55 @@
---
title: Icons
description: 'Nuxt UI integrates with Iconify to access over 200,000+ icons.'
navigation.framework: vue
links:
- label: 'Iconify'
to: https://iconify.design/
target: _blank
icon: i-simple-icons-iconify
---
::callout{to="/getting-started/icons/nuxt" icon="i-logos-nuxt-icon" class="hidden"}
Looking for the **Nuxt** version?
::
## Usage
### Icon Component
You can use the [Icon](/components/icon) component with a `name` prop to display an icon:
::component-code{slug="icon"}
---
props:
name: 'i-lucide-lightbulb'
class: 'size-5'
---
::
::note
You can use any name from the https://icones.js.org collection.
::
### Component Props
Some components also have an `icon` prop to display an icon, like the [Button](/components/button) for example:
::component-code{slug="button"}
---
ignore:
- color
- variant
props:
icon: i-lucide-sun
variant: subtle
slots:
default: Button
---
::
## Theme
You can change the default icons used by Nuxt UI components in your `vite.config.ts`:
:icons-theme

View File

@@ -1,6 +1,7 @@
---
title: Fonts
description: 'Nuxt UI integrates with Nuxt Fonts to provide plug-and-play font optimization.'
navigation.framework: nuxt
links:
- label: 'nuxt/fonts'
to: https://github.com/nuxt/fonts
@@ -10,7 +11,7 @@ links:
## Usage
Nuxt UI automatically registers the [`@nuxt/fonts`](https://github.com/nuxt/fonts) module for you, so there's no additional setup required. To use a font in your Nuxt UI application, you can simply declare it in your CSS:
Nuxt UI automatically registers the [`@nuxt/fonts`](https://github.com/nuxt/fonts) module for you, so there's no additional setup required. To use a font in your Nuxt UI application, you can simply declare it in your CSS. It will be automatically loaded and optimized for you.
```css [main.css]
@import "tailwindcss";
@@ -21,8 +22,12 @@ Nuxt UI automatically registers the [`@nuxt/fonts`](https://github.com/nuxt/font
}
```
That's it! Nuxt Fonts will detect this and you should immediately see the web font loaded in your browser.
You can disable the `@nuxt/fonts` module with the `ui.fonts` option in your `nuxt.config.ts`:
::note{to="https://fonts.nuxt.com/advanced" target="_blank"}
Read more about how `@nuxt/fonts` work behind the scenes to optimize your fonts.
::
```ts [nuxt.config.ts]
export default defineNuxtConfig({
ui: {
fonts: false
}
})
```

View File

@@ -1,27 +0,0 @@
---
title: Color Mode
description: 'Nuxt UI integrates with Nuxt Color Mode to allow for easy switching between light and dark themes.'
links:
- label: 'nuxtjs/color-mode'
to: https://github.com/nuxt-modules/color-mode
target: _blank
icon: i-simple-icons-github
---
## Usage
Nuxt UI automatically registers the [`@nuxtjs/color-mode`](https://github.com/nuxt-modules/color-mode) module for you, so there's no additional setup required.
You can disable dark mode by setting the `preference` to `light` instead of `system` in your `nuxt.config.ts`.
```ts [nuxt.config.ts]
export default defineNuxtConfig({
colorMode: {
preference: 'light'
}
})
```
::tip
If you're stuck in dark mode even after changing this setting, you might need to remove the `nuxt-color-mode` entry from your browser's local storage.
::

View File

@@ -0,0 +1 @@
shadow: true

View File

@@ -0,0 +1,58 @@
---
title: Color Mode
description: 'Nuxt UI integrates with Nuxt Color Mode to allow for easy switching between light and dark themes.'
navigation.framework: nuxt
links:
- label: 'nuxtjs/color-mode'
to: https://github.com/nuxt-modules/color-mode
target: _blank
icon: i-simple-icons-github
---
::callout{to="/getting-started/color-mode/vue" icon="i-logos-vue" class="hidden"}
Looking for the **Vue** version?
::
## Usage
Nuxt UI automatically registers the [`@nuxtjs/color-mode`](https://github.com/nuxt-modules/color-mode) module for you, so there's no additional setup required. You can simply use the `useColorMode` composable to switch between light and dark modes:
```vue [ColorModeButton.vue]
<script setup>
const colorMode = useColorMode()
const isDark = computed({
get() {
return colorMode.value === 'dark'
},
set() {
colorMode.preference = colorMode.value === 'dark' ? 'light' : 'dark'
}
})
</script>
<template>
<ClientOnly v-if="!colorMode?.forced">
<UButton
:icon="isDark ? 'i-lucide-moon' : 'i-lucide-sun'"
color="neutral"
variant="ghost"
@click="isDark = !isDark"
/>
<template #fallback>
<div class="size-8" />
</template>
</ClientOnly>
</template>
```
You can disable the `@nuxtjs/color-mode` module with the `ui.colorMode` option in your `nuxt.config.ts`:
```ts [nuxt.config.ts]
export default defineNuxtConfig({
ui: {
colorMode: false
}
})
```

View File

@@ -0,0 +1,47 @@
---
title: Color Mode
description: 'Nuxt UI integrates with VueUse to allow for easy switching between light and dark themes.'
navigation.framework: vue
---
::callout{to="/getting-started/color-mode/nuxt" icon="i-logos-nuxt-icon" class="hidden"}
Looking for the **Nuxt** version?
::
## Usage
Nuxt UI automatically registers the [useDark](https://vueuse.org/core/useDark) composable as a Vue plugin, so there's no additional setup required. You can simply use it to switch between light and dark modes:
```vue [ColorModeButton.vue]
<script setup>
import { useColorMode } from '@vueuse/core'
const mode = useColorMode()
</script>
<template>
<UButton
:icon="mode === 'dark' ? 'i-lucide-moon' : 'i-lucide-sun'"
color="neutral"
variant="ghost"
@click="mode = mode === 'dark' ? 'light' : 'dark'"
/>
</template>
```
You can disable this plugin with the `colorMode` option in your `vite.config.ts`:
```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({
colorMode: false
})
]
})
```

View File

@@ -1 +1,2 @@
badge: New
shadow: true

View File

@@ -1,21 +1,17 @@
---
navigation.title: Nuxt
title: Internationalization (i18n) in a Nuxt app
title: Internationalization (i18n)
description: 'Learn how to internationalize your Nuxt app with multi-directional support (LTR/RTL).'
select:
items:
- label: Nuxt
icon: i-logos-nuxt-icon
to: /getting-started/i18n/nuxt
- label: Vue
icon: i-logos-vue
to: /getting-started/i18n/vue
navigation.framework: nuxt
---
::callout{to="/getting-started/i18n/vue" icon="i-logos-vue" class="hidden"}
Looking for the **Vue** version?
::
## Usage
::note{to="/components/app"}
Nuxt UI provides an [App](/components/app) component that wraps your app to provide global configurations.
Nuxt UI provides an **App** component that wraps your app to provide global configurations.
::
### Locale

View File

@@ -1,21 +1,17 @@
---
navigation.title: Vue
title: Internationalization (i18n) in a Vue app
title: Internationalization (i18n)
description: 'Learn how to internationalize your Vue app with multi-directional support (LTR/RTL).'
select:
items:
- label: Nuxt
icon: i-logos-nuxt-icon
to: /getting-started/i18n/nuxt
- label: Vue
icon: i-logos-vue
to: /getting-started/i18n/vue
navigation.framework: vue
---
::callout{to="/getting-started/i18n/nuxt" icon="i-logos-nuxt-icon" class="hidden"}
Looking for the **Nuxt** version?
::
## Usage
::note{to="/components/app"}
Nuxt UI provides an [App](/components/app) component that wraps your app to provide global configurations.
Nuxt UI provides an **App** component that wraps your app to provide global configurations.
::
### Locale

View File

@@ -27,8 +27,16 @@ Use it as at the root of your app:
</template>
```
::tip{to="/getting-started/i18n/nuxt#locale"}
::framework-only
#nuxt
:::tip{to="/getting-started/i18n/nuxt#locale"}
Learn how to use the `locale` prop to change the locale of your app.
:::
#vue
:::tip{to="/getting-started/i18n/vue#locale"}
Learn how to use the `locale` prop to change the locale of your app.
:::
::
## API

View File

@@ -168,8 +168,16 @@ props:
---
::
::tip{to="/getting-started/icons#theme"}
::framework-only
#nuxt
:::tip{to="/getting-started/icons/nuxt#theme"}
You can customize this icon globally in your `app.config.ts` under `ui.icons.chevronDown` key.
:::
#vue
:::tip{to="/getting-started/icons/vue#theme"}
You can customize this icon globally in your `vite.config.ts` under `ui.icons.chevronDown` key.
:::
::
## Examples

View File

@@ -178,8 +178,16 @@ props:
---
::
::tip{to="/getting-started/icons#theme"}
::framework-only
#nuxt
:::tip{to="/getting-started/icons/nuxt#theme"}
You can customize this icon globally in your `app.config.ts` under `ui.icons.close` key.
:::
#vue
:::tip{to="/getting-started/icons/vue#theme"}
You can customize this icon globally in your `vite.config.ts` under `ui.icons.close` key.
:::
::
### Actions

View File

@@ -67,8 +67,16 @@ props:
---
::
::tip{to="/getting-started/icons#theme"}
::framework-only
#nuxt
:::tip{to="/getting-started/icons/nuxt#theme"}
You can customize this icon globally in your `app.config.ts` under `ui.icons.chevronRight` key.
:::
#vue
:::tip{to="/getting-started/icons/vue#theme"}
You can customize this icon globally in your `vite.config.ts` under `ui.icons.chevronRight` key.
:::
::
## Examples

View File

@@ -197,8 +197,16 @@ slots:
Button
::
::tip{to="/getting-started/icons#theme"}
::framework-only
#nuxt
:::tip{to="/getting-started/icons/nuxt#theme"}
You can customize this icon globally in your `app.config.ts` under `ui.icons.loading` key.
:::
#vue
:::tip{to="/getting-started/icons/vue#theme"}
You can customize this icon globally in your `vite.config.ts` under `ui.icons.loading` key.
:::
::
### Disabled

View File

@@ -0,0 +1,248 @@
---
title: Calendar
description: A calendar component for selecting single dates, multiple dates or date ranges.
links:
- label: Calendar
icon: i-custom-radix-vue
to: https://www.radix-vue.com/components/calendar.html
- label: GitHub
icon: i-simple-icons-github
to: https://github.com/nuxt/ui/tree/v3/src/runtime/components/Calendar.vue
navigation.badge: New
---
::note
This component relies on the [@internationalized/date](https://react-spectrum.adobe.com/internationalized/date/index.html) package which provides objects and functions for representing and manipulating dates and times in a locale-aware manner.
::
## Usage
Use the `v-model` directive to control the selected date.
::component-code
---
cast:
modelValue: DateValue
ignore:
- modelValue
external:
- modelValue
props:
modelValue: [2022, 2, 3]
---
::
Use the `default-value` prop to set the initial value when you do not need to control its state.
::component-code
---
cast:
defaultValue: DateValue
ignore:
- defaultValue
external:
- defaultValue
props:
defaultValue: [2022, 2, 6]
---
::
### Multiple
Use the `multiple` prop to allow multiple selections.
::component-code
---
prettier: true
cast:
modelValue: DateValue[]
ignore:
- multiple
- modelValue
external:
- modelValue
props:
multiple: true
modelValue: [[2022, 2, 4], [2022, 2, 6], [2022, 2, 8]]
---
::
### Range
Use the `range` prop to select a range of dates.
::component-code
---
prettier: true
cast:
modelValue: DateRange
ignore:
- range
- modelValue.start
- modelValue.end
external:
- modelValue
props:
range: true
modelValue:
start: [2022, 2, 3]
end: [2022, 2, 20]
---
::
### Color
Use the `color` prop to change the color of the calendar.
::component-code
---
props:
color: neutral
---
::
### Size
Use the `size` prop to change the size of the calendar.
::component-code
---
props:
size: xl
---
::
### Disabled
Use the `disabled` prop to disable the calendar.
::component-code
---
props:
disabled: true
---
::
### Number Of Months
Use the `numberOfMonths` prop to change the number of months in the calendar.
::component-code
---
props:
numberOfMonths: 3
---
::
### Month Controls
Use the `month-controls` prop to show the month controls. Defaults to `true`.
::component-code
---
props:
monthControls: false
---
::
### Year Controls
Use the `year-controls` prop to show the year controls. Defaults to `true`.
::component-code
---
props:
yearControls: false
---
::
### Fixed Weeks
Use the `fixed-weeks` prop to display the calendar with fixed weeks.
::component-code
---
props:
fixedWeeks: false
---
::
## Examples
### With chip events
Use the [Chip](/components/chip) component to add events to specific days.
::component-example
---
name: 'calendar-events-example'
---
::
### With disabled dates
Use the `is-date-disabled` prop with a function to mark specific dates as disabled.
::component-example
---
name: 'calendar-disabled-dates-example'
---
::
### With unavailable dates
Use the `is-date-unavailable` prop with a function to mark specific dates as unavailable.
::component-example
---
name: 'calendar-unavailable-dates-example'
---
::
### With min/max dates
Use the `min-value` and `max-value` props to limit the dates.
::component-example
---
name: 'calendar-min-max-dates-example'
---
::
### As a DatePicker
Use a [Button](/components/button) and a [Popover](/components/popover) component to create a date picker.
::component-example
---
name: 'calendar-date-picker-example'
---
::
### As a DateRangePicker
Use a [Button](/components/button) and a [Popover](/components/popover) component to create a date range picker.
::component-example
---
name: 'calendar-date-range-picker-example'
---
::
## API
### Props
:component-props
### Slots
:component-slots
### Emits
:component-emits
## Theme
:component-theme

View File

@@ -94,8 +94,16 @@ options:
---
::
::tip{to="/getting-started/icons#theme"}
::framework-only
#nuxt
:::tip{to="/getting-started/icons/nuxt#theme"}
You can customize these icons globally in your `app.config.ts` under `ui.icons.arrowLeft` / `ui.icons.arrowRight` key.
:::
#vue
:::tip{to="/getting-started/icons/vue#theme"}
You can customize these icons globally in your `vite.config.ts` under `ui.icons.arrowLeft` / `ui.icons.arrowRight` key.
:::
::
### Dots

View File

@@ -58,8 +58,16 @@ props:
---
::
::tip{to="/getting-started/icons#theme"}
::framework-only
#nuxt
:::tip{to="/getting-started/icons/nuxt#theme"}
You can customize this icon globally in your `app.config.ts` under `ui.icons.minus` key.
:::
#vue
:::tip{to="/getting-started/icons/vue#theme"}
You can customize this icon globally in your `vite.config.ts` under `ui.icons.minus` key.
:::
::
### Label
@@ -115,8 +123,16 @@ props:
---
::
::tip{to="/getting-started/icons#theme"}
::framework-only
#nuxt
:::tip{to="/getting-started/icons/nuxt#theme"}
You can customize this icon globally in your `app.config.ts` under `ui.icons.check` key.
:::
#vue
:::tip{to="/getting-started/icons/vue#theme"}
You can customize this icon globally in your `vite.config.ts` under `ui.icons.check` key.
:::
::
### Color

View File

@@ -42,6 +42,7 @@ Each group takes some `items` as an array of objects with the following properti
- `avatar?: AvatarProps`{lang="ts-type"}
- `chip?: ChipProps`{lang="ts-type"}
- `kbds?: string[] | KbdProps[]`{lang="ts-type"}
- `active?: boolean`{lang="ts-type"}
- `loading?: boolean`{lang="ts-type"}
- `disabled?: boolean`{lang="ts-type"}
- [`slot?: string`{lang="ts-type"}](#with-custom-slot)
@@ -215,8 +216,16 @@ props:
---
::
::tip{to="/getting-started/icons#theme"}
::framework-only
#nuxt
:::tip{to="/getting-started/icons/nuxt#theme"}
You can customize this icon globally in your `app.config.ts` under `ui.icons.search` key.
:::
#vue
:::tip{to="/getting-started/icons/vue#theme"}
You can customize this icon globally in your `vite.config.ts` under `ui.icons.search` key.
:::
::
### Loading
@@ -276,8 +285,16 @@ props:
---
::
::tip{to="/getting-started/icons#theme"}
::framework-only
#nuxt
:::tip{to="/getting-started/icons/nuxt#theme"}
You can customize this icon globally in your `app.config.ts` under `ui.icons.loading` key.
:::
#vue
:::tip{to="/getting-started/icons/vue#theme"}
You can customize this icon globally in your `vite.config.ts` under `ui.icons.loading` key.
:::
::
### Disabled
@@ -403,8 +420,16 @@ props:
---
::
::tip{to="/getting-started/icons#theme"}
::framework-only
#nuxt
:::tip{to="/getting-started/icons/nuxt#theme"}
You can customize this icon globally in your `app.config.ts` under `ui.icons.close` key.
:::
#vue
:::tip{to="/getting-started/icons/vue#theme"}
You can customize this icon globally in your `vite.config.ts` under `ui.icons.close` key.
:::
::
## Examples

View File

@@ -40,7 +40,7 @@ prettier: true
collapse: true
ignore:
- items
- class
- ui.content
external:
- items
props:
@@ -90,7 +90,8 @@ props:
- option
- meta
- j
class: 'w-48'
ui:
content: 'w-48'
slots:
default: |
@@ -119,7 +120,7 @@ Use the `size` prop to change the size of the ContextMenu.
prettier: true
ignore:
- items
- class
- ui.content
external:
- items
props:
@@ -131,7 +132,8 @@ props:
icon: i-lucide-sun
- label: Dark
icon: i-lucide-moon
class: 'w-48'
ui:
content: 'w-48'
slots:
default: |
@@ -152,7 +154,7 @@ Use the `disabled` prop to disable the ContextMenu.
prettier: true
ignore:
- items
- class
- ui.content
external:
- items
props:
@@ -164,7 +166,8 @@ props:
icon: i-lucide-sun
- label: Dark
icon: i-lucide-moon
class: 'w-48'
ui:
content: 'w-48'
slots:
default: |

View File

@@ -40,7 +40,7 @@ prettier: true
collapse: true
ignore:
- items
- class
- ui.content
external:
- items
props:
@@ -91,7 +91,8 @@ props:
- shift
- meta
- q
class: 'w-48'
ui:
content: 'w-48'
slots:
default: |
@@ -118,7 +119,7 @@ Use the `content` prop to control how the DropdownMenu content is rendered, like
prettier: true
ignore:
- items
- class
- ui.content
external:
- items
items:
@@ -143,7 +144,8 @@ props:
align: start
side: bottom
sideOffset: 8
class: 'w-48'
ui:
content: 'w-48'
slots:
default: |
@@ -163,7 +165,7 @@ prettier: true
ignore:
- arrow
- items
- class
- ui.content
external:
- items
props:
@@ -175,7 +177,8 @@ props:
icon: i-lucide-credit-card
- label: Settings
icon: i-lucide-cog
class: 'w-48'
ui:
content: 'w-48'
slots:
default: |
@@ -194,8 +197,8 @@ Use the `size` prop to control the size of the DropdownMenu.
prettier: true
ignore:
- items
- class
- content.align
- ui.content
external:
- items
props:
@@ -209,7 +212,8 @@ props:
icon: i-lucide-cog
content:
align: start
class: 'w-48'
ui:
content: 'w-48'
slots:
default: |
@@ -236,7 +240,7 @@ Use the `disabled` prop to disable the DropdownMenu.
prettier: true
ignore:
- items
- class
- ui.content
external:
- items
props:
@@ -248,7 +252,8 @@ props:
icon: i-lucide-credit-card
- label: Settings
icon: i-lucide-cog
class: 'w-48'
ui:
content: 'w-48'
slots:
default: |

View File

@@ -1,14 +1,15 @@
---
description: A wrapper around Nuxt Icon component to display icons.
description: A component to display any icon from Iconify.
links:
- label: Nuxt Icon
icon: i-simple-icons-github
to: https://github.com/nuxt/icon
- label: Icônes
to: https://icones.js.org/
target: _blank
icon: i-custom-icones-js
---
## Usage
You can use any name from the https://icones.js.org collection:
Use the `name` prop to display an icon:
::component-code
---
@@ -18,6 +19,15 @@ props:
---
::
::tip
It's highly recommended to install the icons collections you need, read more about this in [Icons](/getting-started/icons#collections).
::framework-only
#nuxt
:::caution{to="/getting-started/icons/nuxt#collections"}
It's highly recommended to install the icons collections you need, read more about this.
:::
::
## API
### Props
:component-props

View File

@@ -189,8 +189,16 @@ props:
---
::
::tip{to="/getting-started/icons#theme"}
::framework-only
#nuxt
:::tip{to="/getting-started/icons/nuxt#theme"}
You can customize this icon globally in your `app.config.ts` under `ui.icons.close` key.
:::
#vue
:::tip{to="/getting-started/icons/vue#theme"}
You can customize this icon globally in your `vite.config.ts` under `ui.icons.close` key.
:::
::
### Placeholder
@@ -441,8 +449,16 @@ props:
---
::
::tip{to="/getting-started/icons#theme"}
::framework-only
#nuxt
:::tip{to="/getting-started/icons/nuxt#theme"}
You can customize this icon globally in your `app.config.ts` under `ui.icons.chevronDown` key.
:::
#vue
:::tip{to="/getting-started/icons/vue#theme"}
You can customize this icon globally in your `vite.config.ts` under `ui.icons.chevronDown` key.
:::
::
### Selected Icon
@@ -470,8 +486,16 @@ props:
---
::
::tip{to="/getting-started/icons#theme"}
::framework-only
#nuxt
:::tip{to="/getting-started/icons/nuxt#theme"}
You can customize this icon globally in your `app.config.ts` under `ui.icons.check` key.
:::
#vue
:::tip{to="/getting-started/icons/vue#theme"}
You can customize this icon globally in your `vite.config.ts` under `ui.icons.check` key.
:::
::
### Avatar
@@ -550,8 +574,16 @@ props:
---
::
::tip{to="/getting-started/icons#theme"}
::framework-only
#nuxt
:::tip{to="/getting-started/icons/nuxt#theme"}
You can customize this icon globally in your `app.config.ts` under `ui.icons.loading` key.
:::
#vue
:::tip{to="/getting-started/icons/vue#theme"}
You can customize this icon globally in your `vite.config.ts` under `ui.icons.loading` key.
:::
::
### Disabled
@@ -739,7 +771,7 @@ name: 'input-menu-filter-fields-example'
---
::
### As a country picker
### 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

@@ -11,6 +11,10 @@ links:
navigation.badge: New
---
::note
This component relies on the [@internationalized/number](https://react-spectrum.adobe.com/internationalized/number/index.html) package which provides utilities for formatting and parsing numbers across locales and numbering systems.
::
## Usage
Use the `v-model` directive to control the value of the InputNumber.

View File

@@ -184,8 +184,16 @@ props:
---
::
::tip{to="/getting-started/icons#theme"}
::framework-only
#nuxt
:::tip{to="/getting-started/icons/nuxt#theme"}
You can customize this icon globally in your `app.config.ts` under `ui.icons.loading` key.
:::
#vue
:::tip{to="/getting-started/icons/vue#theme"}
You can customize this icon globally in your `vite.config.ts` under `ui.icons.loading` key.
:::
::
### Disabled
@@ -290,7 +298,7 @@ name: 'input-form-field-example'
::
::tip{to="/components/form"}
It also provides validation and error handling when used within a [Form](/components/form) component.
It also provides validation and error handling when used within a **Form** component.
::
### Within a ButtonGroup

View File

@@ -156,8 +156,16 @@ slots:
:placeholder{class="h-48"}
::
::tip{to="/getting-started/icons#theme"}
::framework-only
#nuxt
:::tip{to="/getting-started/icons/nuxt#theme"}
You can customize this icon globally in your `app.config.ts` under `ui.icons.close` key.
:::
#vue
:::tip{to="/getting-started/icons/vue#theme"}
You can customize this icon globally in your `vite.config.ts` under `ui.icons.close` key.
:::
::
### Overlay

View File

@@ -434,8 +434,16 @@ props:
---
::
::tip{to="/getting-started/icons#theme"}
::framework-only
#nuxt
:::tip{to="/getting-started/icons/nuxt#theme"}
You can customize this icon globally in your `app.config.ts` under `ui.icons.chevronDown` key.
:::
#vue
:::tip{to="/getting-started/icons/vue#theme"}
You can customize this icon globally in your `vite.config.ts` under `ui.icons.chevronDown` key.
:::
::
### Arrow

View File

@@ -482,8 +482,16 @@ props:
---
::
::tip{to="/getting-started/icons#theme"}
::framework-only
#nuxt
:::tip{to="/getting-started/icons/nuxt#theme"}
You can customize this icon globally in your `app.config.ts` under `ui.icons.chevronDown` key.
:::
#vue
:::tip{to="/getting-started/icons/vue#theme"}
You can customize this icon globally in your `vite.config.ts` under `ui.icons.chevronDown` key.
:::
::
### Selected Icon
@@ -513,8 +521,16 @@ props:
---
::
::tip{to="/getting-started/icons#theme"}
::framework-only
#nuxt
:::tip{to="/getting-started/icons/nuxt#theme"}
You can customize this icon globally in your `app.config.ts` under `ui.icons.check` key.
:::
#vue
:::tip{to="/getting-started/icons/vue#theme"}
You can customize this icon globally in your `vite.config.ts` under `ui.icons.check` key.
:::
::
### Avatar
@@ -599,8 +615,16 @@ props:
---
::
::tip{to="/getting-started/icons#theme"}
::framework-only
#nuxt
:::tip{to="/getting-started/icons/nuxt#theme"}
You can customize this icon globally in your `app.config.ts` under `ui.icons.loading` key.
:::
#vue
:::tip{to="/getting-started/icons/vue#theme"}
You can customize this icon globally in your `vite.config.ts` under `ui.icons.loading` key.
:::
::
### Disabled
@@ -782,7 +806,7 @@ name: 'select-menu-filter-fields-example'
---
::
### As a country picker
### 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.

View File

@@ -206,7 +206,7 @@ props:
::
::note{to="https://www.radix-vue.com/components/select.html#change-the-positioning-mode"}
Read more about the `content.position` prop in the [Radix Vue documentation](https://www.radix-vue.com/components/select.html#change-the-positioning-mode).
Read more about the `content.position` prop in the **Radix Vue** documentation.
::
<!--
@@ -378,8 +378,16 @@ props:
---
::
::tip{to="/getting-started/icons#theme"}
::framework-only
#nuxt
:::tip{to="/getting-started/icons/nuxt#theme"}
You can customize this icon globally in your `app.config.ts` under `ui.icons.chevronDown` key.
:::
#vue
:::tip{to="/getting-started/icons/vue#theme"}
You can customize this icon globally in your `vite.config.ts` under `ui.icons.chevronDown` key.
:::
::
### Selected Icon
@@ -409,8 +417,16 @@ props:
---
::
::tip{to="/getting-started/icons#theme"}
::framework-only
#nuxt
:::tip{to="/getting-started/icons/nuxt#theme"}
You can customize this icon globally in your `app.config.ts` under `ui.icons.check` key.
:::
#vue
:::tip{to="/getting-started/icons/vue#theme"}
You can customize this icon globally in your `vite.config.ts` under `ui.icons.check` key.
:::
::
### Avatar
@@ -495,8 +511,16 @@ props:
---
::
::tip{to="/getting-started/icons#theme"}
::framework-only
#nuxt
:::tip{to="/getting-started/icons/nuxt#theme"}
You can customize this icon globally in your `app.config.ts` under `ui.icons.loading` key.
:::
#vue
:::tip{to="/getting-started/icons/vue#theme"}
You can customize this icon globally in your `vite.config.ts` under `ui.icons.loading` key.
:::
::
### Disabled

View File

@@ -156,8 +156,16 @@ slots:
:placeholder{class="h-full"}
::
::tip{to="/getting-started/icons#theme"}
::framework-only
#nuxt
:::tip{to="/getting-started/icons/nuxt#theme"}
You can customize this icon globally in your `app.config.ts` under `ui.icons.close` key.
:::
#vue
:::tip{to="/getting-started/icons/vue#theme"}
You can customize this icon globally in your `vite.config.ts` under `ui.icons.close` key.
:::
::
### Side

View File

@@ -123,8 +123,16 @@ props:
---
::
::tip{to="/getting-started/icons#theme"}
::framework-only
#nuxt
:::tip{to="/getting-started/icons/nuxt#theme"}
You can customize this icon globally in your `app.config.ts` under `ui.icons.loading` key.
:::
#vue
:::tip{to="/getting-started/icons/vue#theme"}
You can customize this icon globally in your `vite.config.ts` under `ui.icons.loading` key.
:::
::
### Color

View File

@@ -128,8 +128,16 @@ name: 'toast-close-icon-example'
---
::
::tip{to="/getting-started/icons#theme"}
::framework-only
#nuxt
:::tip{to="/getting-started/icons/nuxt#theme"}
You can customize this icon globally in your `app.config.ts` under `ui.icons.close` key.
:::
#vue
:::tip{to="/getting-started/icons/vue#theme"}
You can customize this icon globally in your `vite.config.ts` under `ui.icons.close` key.
:::
::
### Actions

View File

@@ -17,7 +17,12 @@ export default defineNuxtConfig({
'@nuxtjs/plausible',
'@vueuse/nuxt',
'nuxt-component-meta',
'nuxt-og-image'
'nuxt-og-image',
(_, nuxt) => {
nuxt.hook('components:dirs', (dirs) => {
dirs.unshift({ path: resolve('./app/components/content/examples'), pathPrefix: false, prefix: '', global: true })
})
}
],
app: {
@@ -32,10 +37,24 @@ export default defineNuxtConfig({
},
content: {
database: {
type: 'd1',
binding: 'DB'
},
build: {
markdown: {
highlight: {
langs: ['bash', 'ts', 'typescript', 'diff', 'vue', 'json', 'yml', 'css', 'mdc']
},
remarkPlugins: {
'remark-mdc': {
options: {
experimental: {
autoUnwrap: false
}
}
}
}
}
}
@@ -56,6 +75,8 @@ export default defineNuxtConfig({
routeRules: {
'/': { redirect: '/getting-started', prerender: false },
'/getting-started/installation': { redirect: '/getting-started/installation/nuxt', prerender: false },
'/getting-started/icons': { redirect: '/getting-started/icons/nuxt', prerender: false },
'/getting-started/color-mode': { redirect: '/getting-started/color-mode/nuxt', prerender: false },
'/getting-started/i18n': { redirect: '/getting-started/i18n/nuxt', prerender: false },
'/composables': { redirect: '/composables/define-shortcuts', prerender: false },
'/components': { redirect: '/components/app', prerender: false }
@@ -70,31 +91,28 @@ export default defineNuxtConfig({
nitro: {
prerender: {
routes: [
'/getting-started',
'/api/countries.json'
// '/getting-started',
'/api/countries.json',
'/api/locales.json'
// '/api/releases.json',
// '/api/pulls.json'
],
crawlLinks: true,
autoSubfolderIndex: false
]
// crawlLinks: true,
// autoSubfolderIndex: false
// ignore: !process.env.NUXT_GITHUB_TOKEN ? ['/pro'] : []
},
cloudflare: {
pages: {
routes: {
exclude: [
'/components/*',
'/getting-started/*',
'/composables/*',
'/api/*'
]
exclude: []
}
}
}
},
hub: {
cache: true
cache: true,
database: true
},
componentMeta: {

View File

@@ -4,21 +4,21 @@
"type": "module",
"dependencies": {
"@iconify-json/logos": "^1.2.3",
"@iconify-json/lucide": "^1.2.15",
"@iconify-json/simple-icons": "^1.2.11",
"@iconify-json/lucide": "^1.2.16",
"@iconify-json/simple-icons": "^1.2.13",
"@iconify-json/vscode-icons": "^1.2.2",
"@nuxt/content": "3.0.0-alpha.6",
"@nuxt/content": "3.0.0-alpha.7",
"@nuxt/image": "^1.8.1",
"@nuxt/ui": "latest",
"@nuxt/ui-pro": "https://pkg.pr.new/@nuxt/ui-pro@574082c",
"@nuxt/ui-pro": "https://pkg.pr.new/@nuxt/ui-pro@1408077",
"@nuxthub/core": "^0.8.7",
"@nuxtjs/plausible": "^1.1.1",
"@octokit/rest": "^21.0.2",
"@vueuse/nuxt": "^11.2.0",
"@vueuse/nuxt": "^11.3.0",
"joi": "^17.13.3",
"nuxt": "^3.14.159",
"nuxt": "^3.14.1592",
"nuxt-component-meta": "^0.9.0",
"nuxt-og-image": "^3.0.8",
"nuxt-og-image": "^3.1.1",
"prettier": "^3.3.3",
"shiki-transformer-color-highlight": "^0.2.0",
"superstruct": "^2.0.2",
@@ -28,6 +28,6 @@
"zod": "^3.23.8"
},
"devDependencies": {
"wrangler": "^3.87.0"
"wrangler": "^3.90.0"
}
}

View File

@@ -1,2 +1 @@
user-agent: *
disallow: /dev/*

View File

@@ -0,0 +1,505 @@
const locales: Record<string, string> = {
'aa': '🇪🇷',
'aa-ER': '🇪🇷',
'af': '🇳🇦',
'af-NA': '🇳🇦',
'af-ZA': '🇿🇦',
'am': '🇪🇹',
'am-ET': '🇪🇹',
'ar': '🇪🇬',
'ar-AE': '🇦🇪',
'ar-BH': '🇧🇭',
'ar-DJ': '🇩🇯',
'ar-DZ': '🇩🇿',
'ar-EG': '🇪🇬',
'ar-ER': '🇪🇷',
'ar-IL': '🇮🇱',
'ar-IQ': '🇮🇶',
'ar-JO': '🇯🇴',
'ar-KM': '🇰🇲',
'ar-KW': '🇰🇼',
'ar-LB': '🇱🇧',
'ar-LY': '🇱🇾',
'ar-MA': '🇲🇦',
'ar-MR': '🇲🇷',
'ar-OM': '🇴🇲',
'ar-PS': '🇵🇸',
'ar-QA': '🇶🇦',
'ar-SA': '🇸🇦',
'ar-SD': '🇸🇩',
'ar-SO': '🇸🇴',
'ar-SY': '🇸🇾',
'ar-TD': '🇹🇩',
'ar-TN': '🇹🇳',
'ar-YE': '🇾🇪',
'ay': '🇧🇴',
'ay-BO': '🇧🇴',
'az': '🇦🇿',
'az-AZ': '🇦🇿',
'be': '🇧🇾',
'be-BY': '🇧🇾',
'bg': '🇧🇬',
'bg-BG': '🇧🇬',
'bi': '🇻🇺',
'bi-VU': '🇻🇺',
'bn': '🇧🇩',
'bn-BD': '🇧🇩',
'bs': '🇧🇦',
'bs-BA': '🇧🇦',
'bs-ME': '🇲🇪',
'byn': '🇪🇷',
'byn-ER': '🇪🇷',
'ca': '🇦🇩',
'ca-AD': '🇦🇩',
'ch': '🇬🇺',
'ch-GU': '🇬🇺',
'ch-MP': '🇲🇵',
'cs': '🇨🇿',
'cs-CZ': '🇨🇿',
'da': '🇩🇰',
'da-DK': '🇩🇰',
'de': '🇩🇪',
'de-AT': '🇦🇹',
'de-BE': '🇧🇪',
'de-CH': '🇨🇭',
'de-DE': '🇩🇪',
'de-LI': '🇱🇮',
'de-LU': '🇱🇺',
'de-VA': '🇻🇦',
'dv': '🇲🇻',
'dv-MV': '🇲🇻',
'dz': '🇧🇹',
'dz-BT': '🇧🇹',
'el': '🇬🇷',
'el-CY': '🇨🇾',
'el-GR': '🇬🇷',
'en': '🇬🇧',
'en-AG': '🇦🇬',
'en-AI': '🇦🇮',
'en-AQ': '🇦🇶',
'en-AS': '🇦🇸',
'en-AU': '🇦🇺',
'en-BB': '🇧🇧',
'en-BM': '🇧🇲',
'en-BS': '🇧🇸',
'en-BW': '🇧🇼',
'en-BZ': '🇧🇿',
'en-CA': '🇨🇦',
'en-CC': '🇨🇨',
'en-CK': '🇨🇰',
'en-CM': '🇨🇲',
'en-CW': '🇨🇼',
'en-CX': '🇨🇽',
'en-DM': '🇩🇲',
'en-ER': '🇪🇷',
'en-FJ': '🇫🇯',
'en-FK': '🇫🇰',
'en-FM': '🇫🇲',
'en-GB': '🇬🇧',
'en-GD': '🇬🇩',
'en-GG': '🇬🇬',
'en-GH': '🇬🇭',
'en-GI': '🇬🇮',
'en-GM': '🇬🇲',
'en-GS': '🇬🇸',
'en-GU': '🇬🇺',
'en-GY': '🇬🇾',
'en-HK': '🇭🇰',
'en-HM': '🇭🇲',
'en-IE': '🇮🇪',
'en-IM': '🇮🇲',
'en-IN': '🇮🇳',
'en-IO': '🇮🇴',
'en-JE': '🇯🇪',
'en-JM': '🇯🇲',
'en-KE': '🇰🇪',
'en-KI': '🇰🇮',
'en-KN': '🇰🇳',
'en-KY': '🇰🇾',
'en-LC': '🇱🇨',
'en-LR': '🇱🇷',
'en-LS': '🇱🇸',
'en-MF': '🇲🇫',
'en-MH': '🇲🇭',
'en-MP': '🇲🇵',
'en-MS': '🇲🇸',
'en-MT': '🇲🇹',
'en-MU': '🇲🇺',
'en-MW': '🇲🇼',
'en-NA': '🇳🇦',
'en-NF': '🇳🇫',
'en-NG': '🇳🇬',
'en-NR': '🇳🇷',
'en-NU': '🇳🇺',
'en-NZ': '🇳🇿',
'en-PG': '🇵🇬',
'en-PH': '🇵🇭',
'en-PK': '🇵🇰',
'en-PN': '🇵🇳',
'en-PR': '🇵🇷',
'en-PW': '🇵🇼',
'en-RW': '🇷🇼',
'en-SB': '🇸🇧',
'en-SC': '🇸🇨',
'en-SD': '🇸🇩',
'en-SG': '🇸🇬',
'en-SH': '🇸🇭',
'en-SL': '🇸🇱',
'en-SS': '🇸🇸',
'en-SX': '🇸🇽',
'en-SZ': '🇸🇿',
'en-TC': '🇹🇨',
'en-TK': '🇹🇰',
'en-TO': '🇹🇴',
'en-TT': '🇹🇹',
'en-TV': '🇹🇻',
'en-TZ': '🇹🇿',
'en-UG': '🇺🇬',
'en-UM': '🇺🇲',
'en-US': '🇺🇸',
'en-VC': '🇻🇨',
'en-VG': '🇻🇬',
'en-VI': '🇻🇮',
'en-VU': '🇻🇺',
'en-WS': '🇼🇸',
'en-ZA': '🇿🇦',
'en-ZM': '🇿🇲',
'en-ZW': '🇿🇼',
'es': '🇪🇸',
'es-AR': '🇦🇷',
'es-BO': '🇧🇴',
'es-BZ': '🇧🇿',
'es-CL': '🇨🇱',
'es-CO': '🇨🇴',
'es-CR': '🇨🇷',
'es-CU': '🇨🇺',
'es-DO': '🇩🇴',
'es-EC': '🇪🇨',
'es-EH': '🇪🇭',
'es-ES': '🇪🇸',
'es-GQ': '🇬🇶',
'es-GT': '🇬🇹',
'es-GU': '🇬🇺',
'es-HN': '🇭🇳',
'es-MX': '🇲🇽',
'es-NI': '🇳🇮',
'es-PA': '🇵🇦',
'es-PE': '🇵🇪',
'es-PR': '🇵🇷',
'es-PY': '🇵🇾',
'es-SV': '🇸🇻',
'es-UY': '🇺🇾',
'es-VE': '🇻🇪',
'et': '🇪🇪',
'et-EE': '🇪🇪',
'fa': '🇮🇷',
'fa-IR': '🇮🇷',
'fan': '🇬🇶',
'fan-GQ': '🇬🇶',
'ff': '🇧🇫',
'ff-BF': '🇧🇫',
'ff-GN': '🇬🇳',
'fi': '🇫🇮',
'fi-FI': '🇫🇮',
'fj': '🇫🇯',
'fj-FJ': '🇫🇯',
'fo': '🇫🇴',
'fo-FO': '🇫🇴',
'fr': '🇫🇷',
'fr-BE': '🇧🇪',
'fr-BF': '🇧🇫',
'fr-BI': '🇧🇮',
'fr-BJ': '🇧🇯',
'fr-BL': '🇧🇱',
'fr-CA': '🇨🇦',
'fr-CD': '🇨🇩',
'fr-CF': '🇨🇫',
'fr-CG': '🇨🇬',
'fr-CH': '🇨🇭',
'fr-CI': '🇨🇮',
'fr-CM': '🇨🇲',
'fr-DJ': '🇩🇯',
'fr-FR': '🇫🇷',
'fr-GA': '🇬🇦',
'fr-GF': '🇬🇫',
'fr-GG': '🇬🇬',
'fr-GN': '🇬🇳',
'fr-GP': '🇬🇵',
'fr-GQ': '🇬🇶',
'fr-HT': '🇭🇹',
'fr-JE': '🇯🇪',
'fr-KM': '🇰🇲',
'fr-LB': '🇱🇧',
'fr-LU': '🇱🇺',
'fr-MC': '🇲🇨',
'fr-MF': '🇲🇫',
'fr-MG': '🇲🇬',
'fr-ML': '🇲🇱',
'fr-MQ': '🇲🇶',
'fr-NC': '🇳🇨',
'fr-NE': '🇳🇪',
'fr-PF': '🇵🇫',
'fr-PM': '🇵🇲',
'fr-RE': '🇷🇪',
'fr-RW': '🇷🇼',
'fr-SC': '🇸🇨',
'fr-SN': '🇸🇳',
'fr-TD': '🇹🇩',
'fr-TF': '🇹🇫',
'fr-TG': '🇹🇬',
'fr-VA': '🇻🇦',
'fr-VU': '🇻🇺',
'fr-WF': '🇼🇫',
'fr-YT': '🇾🇹',
'ga': '🇮🇪',
'ga-IE': '🇮🇪',
'gn': '🇦🇷',
'gn-AR': '🇦🇷',
'gn-PY': '🇵🇾',
'gv': '🇮🇲',
'gv-IM': '🇮🇲',
'he': '🇮🇱',
'he-IL': '🇮🇱',
'hi': '🇮🇳',
'hi-IN': '🇮🇳',
'hif': '🇫🇯',
'hif-FJ': '🇫🇯',
'hr': '🇭🇷',
'hr-BA': '🇧🇦',
'hr-HR': '🇭🇷',
'hr-ME': '🇲🇪',
'ht': '🇭🇹',
'ht-HT': '🇭🇹',
'hu': '🇭🇺',
'hu-HU': '🇭🇺',
'hy': '🇦🇲',
'hy-AM': '🇦🇲',
'hy-CY': '🇨🇾',
'id': '🇮🇩',
'id-ID': '🇮🇩',
'is': '🇮🇸',
'is-IS': '🇮🇸',
'it': '🇮🇹',
'it-CH': '🇨🇭',
'it-IT': '🇮🇹',
'it-SM': '🇸🇲',
'it-VA': '🇻🇦',
'ja': '🇯🇵',
'ja-JP': '🇯🇵',
'ka': '🇬🇪',
'ka-GE': '🇬🇪',
'kg': '🇨🇩',
'kg-CD': '🇨🇩',
'kk': '🇰🇿',
'kk-KZ': '🇰🇿',
'kl': '🇬🇱',
'kl-GL': '🇬🇱',
'km': '🇰🇭',
'km-KH': '🇰🇭',
'ko': '🇰🇵',
'ko-KP': '🇰🇵',
'ko-KR': '🇰🇷',
'ku': '🇮🇶',
'ku-IQ': '🇮🇶',
'kun': '🇪🇷',
'kun-ER': '🇪🇷',
'ky': '🇰🇬',
'ky-KG': '🇰🇬',
'la': '🇻🇦',
'la-VA': '🇻🇦',
'lb': '🇱🇺',
'lb-LU': '🇱🇺',
'ln': '🇨🇩',
'ln-CD': '🇨🇩',
'ln-CG': '🇨🇬',
'lo': '🇱🇦',
'lo-LA': '🇱🇦',
'lt': '🇱🇹',
'lt-LT': '🇱🇹',
'lu': '🇨🇩',
'lu-CD': '🇨🇩',
'lv': '🇱🇻',
'lv-LV': '🇱🇻',
'mg': '🇲🇬',
'mg-MG': '🇲🇬',
'mh': '🇲🇭',
'mh-MH': '🇲🇭',
'mi': '🇳🇿',
'mi-NZ': '🇳🇿',
'mk': '🇲🇰',
'mk-MK': '🇲🇰',
'mn': '🇲🇳',
'mn-MN': '🇲🇳',
'ms': '🇧🇳',
'ms-BN': '🇧🇳',
'ms-MY': '🇲🇾',
'ms-SG': '🇸🇬',
'mt': '🇲🇹',
'mt-MT': '🇲🇹',
'my': '🇲🇲',
'my-MM': '🇲🇲',
'na': '🇳🇷',
'na-NR': '🇳🇷',
'nb': '🇧🇻',
'nb-BV': '🇧🇻',
'nb-NO': '🇳🇴',
'nd': '🇿🇼',
'nd-ZW': '🇿🇼',
'ne': '🇳🇵',
'ne-NP': '🇳🇵',
'nl': '🇳🇱',
'nl-AW': '🇦🇼',
'nl-BE': '🇧🇪',
'nl-BQ': '🇧🇶',
'nl-CW': '🇨🇼',
'nl-MF': '🇲🇫',
'nl-NL': '🇳🇱',
'nl-SR': '🇸🇷',
'nl-SX': '🇸🇽',
'nn': '🇧🇻',
'nn-BV': '🇧🇻',
'nn-NO': '🇳🇴',
'no': '🇳🇴',
'no-BV': '🇧🇻',
'no-NO': '🇳🇴',
'no-SJ': '🇸🇯',
'nr': '🇿🇦',
'nr-ZA': '🇿🇦',
'nrb': '🇪🇷',
'nrb-ER': '🇪🇷',
'ny': '🇲🇼',
'ny-MW': '🇲🇼',
'pa': '🇦🇼',
'pa-AW': '🇦🇼',
'pa-CW': '🇨🇼',
'pl': '🇵🇱',
'pl-PL': '🇵🇱',
'ps': '🇦🇫',
'ps-AF': '🇦🇫',
'pt': '🇵🇹',
'pt-AO': '🇦🇴',
'pt-BR': '🇧🇷',
'pt-CV': '🇨🇻',
'pt-GQ': '🇬🇶',
'pt-GW': '🇬🇼',
'pt-MO': '🇲🇴',
'pt-MZ': '🇲🇿',
'pt-PT': '🇵🇹',
'pt-ST': '🇸🇹',
'pt-TL': '🇹🇱',
'qu': '🇧🇴',
'qu-BO': '🇧🇴',
'rar': '🇨🇰',
'rar-CK': '🇨🇰',
'rm': '🇨🇭',
'rm-CH': '🇨🇭',
'rn': '🇧🇮',
'rn-BI': '🇧🇮',
'ro': '🇷🇴',
'ro-MD': '🇲🇩',
'ro-RO': '🇷🇴',
'rtm': '🇫🇯',
'rtm-FJ': '🇫🇯',
'ru': '🇷🇺',
'ru-AQ': '🇦🇶',
'ru-BY': '🇧🇾',
'ru-KG': '🇰🇬',
'ru-KZ': '🇰🇿',
'ru-RU': '🇷🇺',
'ru-TJ': '🇹🇯',
'ru-TM': '🇹🇲',
'ru-UZ': '🇺🇿',
'rw': '🇷🇼',
'rw-RW': '🇷🇼',
'sg': '🇨🇫',
'sg-CF': '🇨🇫',
'si': '🇱🇰',
'si-LK': '🇱🇰',
'sk': '🇸🇰',
'sk-CZ': '🇨🇿',
'sk-SK': '🇸🇰',
'sl': '🇸🇮',
'sl-SI': '🇸🇮',
'sm': '🇦🇸',
'sm-AS': '🇦🇸',
'sm-WS': '🇼🇸',
'sn': '🇿🇼',
'sn-ZW': '🇿🇼',
'so': '🇸🇴',
'so-SO': '🇸🇴',
'sq': '🇦🇱',
'sq-AL': '🇦🇱',
'sq-ME': '🇲🇪',
'sq-XK': '🇽🇰',
'sr': '🇧🇦',
'sr-BA': '🇧🇦',
'sr-ME': '🇲🇪',
'sr-RS': '🇷🇸',
'sr-XK': '🇽🇰',
'ss': '🇸🇿',
'ss-SZ': '🇸🇿',
'ss-ZA': '🇿🇦',
'ssy': '🇪🇷',
'ssy-ER': '🇪🇷',
'st': '🇱🇸',
'st-LS': '🇱🇸',
'st-ZA': '🇿🇦',
'sv': '🇦🇽',
'sv-AX': '🇦🇽',
'sv-FI': '🇫🇮',
'sv-SE': '🇸🇪',
'sw': '🇨🇩',
'sw-CD': '🇨🇩',
'sw-KE': '🇰🇪',
'sw-TZ': '🇹🇿',
'sw-UG': '🇺🇬',
'ta': '🇸🇬',
'ta-LK': '🇱🇰',
'ta-SG': '🇸🇬',
'tg': '🇹🇯',
'tg-TJ': '🇹🇯',
'th': '🇹🇭',
'th-TH': '🇹🇭',
'ti': '🇪🇷',
'ti-ER': '🇪🇷',
'tig': '🇪🇷',
'tig-ER': '🇪🇷',
'tk': '🇦🇫',
'tk-AF': '🇦🇫',
'tk-TM': '🇹🇲',
'tn': '🇧🇼',
'tn-BW': '🇧🇼',
'tn-ZA': '🇿🇦',
'to': '🇹🇴',
'to-TO': '🇹🇴',
'tr': '🇹🇷',
'tr-CY': '🇨🇾',
'tr-TR': '🇹🇷',
'ts': '🇿🇦',
'ts-ZA': '🇿🇦',
'uk': '🇺🇦',
'uk-UA': '🇺🇦',
'ur': '🇵🇰',
'ur-PK': '🇵🇰',
'uz': '🇺🇿',
'uz-AF': '🇦🇫',
'uz-UZ': '🇺🇿',
've': '🇿🇦',
've-ZA': '🇿🇦',
'vi': '🇻🇳',
'vi-VN': '🇻🇳',
'xh': '🇿🇦',
'xh-ZA': '🇿🇦',
'zh': '🇨🇳',
'zh-CN': '🇨🇳',
'zh-HK': '🇭🇰',
'zh-Hans': '🇨🇳',
'zh-Hant': '🇨🇳',
'zh-MO': '🇲🇴',
'zh-SG': '🇸🇬',
'zh-TW': '🇹🇼',
'zu': '🇿🇦',
'zu-ZA': '🇿🇦'
}
export default eventHandler(async () => locales)

View File

@@ -2,7 +2,7 @@
"name": "@nuxt/ui",
"description": "A UI Library for Modern Web Apps, powered by Vue & Tailwind CSS.",
"version": "3.0.0-alpha.9",
"packageManager": "pnpm@9.13.2",
"packageManager": "pnpm@9.14.2",
"repository": {
"type": "git",
"url": "git+https://github.com/nuxt/ui.git"
@@ -74,27 +74,28 @@
},
"dependencies": {
"@iconify/vue": "^4.1.2",
"@internationalized/number": "^3.5.4",
"@nuxt/devtools-kit": "^1.6.0",
"@internationalized/date": "^3.6.0",
"@internationalized/number": "^3.6.0",
"@nuxt/devtools-kit": "^1.6.1",
"@nuxt/fonts": "^0.10.2",
"@nuxt/icon": "^1.8.1",
"@nuxt/kit": "^3.14.159",
"@nuxt/schema": "^3.14.159",
"@nuxt/icon": "^1.8.2",
"@nuxt/kit": "^3.14.1592",
"@nuxt/schema": "^3.14.1592",
"@nuxtjs/color-mode": "^3.5.2",
"@tailwindcss/postcss": "4.0.0-alpha.34",
"@tailwindcss/vite": "4.0.0-alpha.34",
"@tailwindcss/postcss": "4.0.0-beta.2",
"@tailwindcss/vite": "4.0.0-beta.2",
"@tanstack/vue-table": "^8.20.5",
"@unhead/vue": "^1.11.11",
"@vueuse/core": "^11.2.0",
"@vueuse/integrations": "^11.2.0",
"@vueuse/core": "^11.3.0",
"@vueuse/integrations": "^11.3.0",
"consola": "^3.2.3",
"defu": "^6.1.4",
"embla-carousel-auto-height": "^8.4.0",
"embla-carousel-auto-scroll": "^8.4.0",
"embla-carousel-autoplay": "^8.4.0",
"embla-carousel-class-names": "^8.4.0",
"embla-carousel-fade": "^8.4.0",
"embla-carousel-vue": "^8.4.0",
"embla-carousel-auto-height": "^8.5.1",
"embla-carousel-auto-scroll": "^8.5.1",
"embla-carousel-autoplay": "^8.5.1",
"embla-carousel-class-names": "^8.5.1",
"embla-carousel-fade": "^8.5.1",
"embla-carousel-vue": "^8.5.1",
"embla-carousel-wheel-gestures": "^8.0.1",
"fuse.js": "^7.0.0",
"get-port-please": "^3.1.2",
@@ -107,7 +108,7 @@
"scule": "^1.3.0",
"sirv": "^3.0.0",
"tailwind-variants": "^0.3.0",
"tailwindcss": "4.0.0-alpha.34",
"tailwindcss": "4.0.0-beta.2",
"tinyglobby": "^0.2.10",
"unplugin": "^1.16.0",
"unplugin-auto-import": "^0.18.5",
@@ -121,12 +122,12 @@
"@release-it/conventional-changelog": "^9.0.3",
"@standard-schema/spec": "1.0.0-beta.3",
"@vue/test-utils": "^2.4.6",
"embla-carousel": "^8.4.0",
"embla-carousel": "^8.5.1",
"eslint": "^9.15.0",
"happy-dom": "^15.7.4",
"joi": "^17.13.3",
"knitwork": "^1.1.0",
"nuxt": "^3.14.159",
"nuxt": "^3.14.1592",
"nuxt-component-meta": "^0.9.0",
"release-it": "^17.10.0",
"superstruct": "^2.0.2",
@@ -142,8 +143,8 @@
},
"resolutions": {
"@nuxt/ui": "workspace:*",
"@nuxt/content": "3.0.0-alpha.5",
"happy-dom": "14.12.3",
"rollup": "^4.24.0"
"rollup": "^4.24.0",
"typescript": "5.6.3"
}
}

View File

@@ -15,7 +15,7 @@
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.2.0",
"typescript": "^5.6.3",
"typescript": "^5.7.2",
"unplugin-auto-import": "^0.18.5",
"unplugin-vue-components": "^0.27.4",
"vite": "^5.4.11",

View File

@@ -84,9 +84,9 @@ defineShortcuts({
<UApp :toaster="(appConfig.toaster as any)">
<div class="h-screen w-screen overflow-hidden flex min-h-0 bg-[var(--ui-bg)]" vaul-drawer-wrapper>
<UNavigationMenu :items="items" orientation="vertical" class="hidden lg:flex border-e border-[var(--ui-border)] overflow-y-auto w-48 p-4" />
<UNavigationMenu :items="items" orientation="horizontal" class="lg:hidden border-b border-[var(--ui-border)] overflow-x-auto" />
<UNavigationMenu :items="items" orientation="horizontal" class="lg:hidden border-b border-[var(--ui-border)] [&>div]:min-w-min overflow-x-auto" />
<div class="fixed top-4 right-4 flex items-center gap-2">
<div class="fixed top-15 lg:top-3 right-4 flex items-center gap-2">
<UButton
:icon="mode === 'dark' ? 'i-lucide-moon' : 'i-lucide-sun'"
color="neutral"
@@ -96,7 +96,7 @@ defineShortcuts({
/>
</div>
<div class="flex-1 flex flex-col items-center justify-around overflow-y-auto w-full py-12 px-4">
<div class="flex-1 flex flex-col items-center justify-around overflow-y-auto w-full py-14 px-4">
<Suspense>
<RouterView />
</Suspense>

View File

@@ -6,8 +6,6 @@ const router = useRouter()
const appConfig = useAppConfig()
const colorMode = useColorMode()
defineOptions({ inheritAttrs: false })
const isDark = computed({
get() {
return colorMode.value === 'dark'
@@ -26,6 +24,7 @@ const components = [
'button',
'button-group',
'card',
'calendar',
'carousel',
'checkbox',
'chip',
@@ -85,9 +84,9 @@ defineShortcuts({
<UApp :toaster="appConfig.toaster">
<div class="h-screen w-screen overflow-hidden flex flex-col lg:flex-row min-h-0 bg-[var(--ui-bg)]" vaul-drawer-wrapper>
<UNavigationMenu :items="items" orientation="vertical" class="hidden lg:flex border-e border-[var(--ui-border)] overflow-y-auto w-48 p-4" />
<UNavigationMenu :items="items" orientation="horizontal" class="lg:hidden border-b border-[var(--ui-border)] overflow-x-auto" />
<UNavigationMenu :items="items" orientation="horizontal" class="lg:hidden border-b border-[var(--ui-border)] [&>div]:min-w-min overflow-x-auto" />
<div class="fixed top-4 right-4 flex items-center gap-2">
<div class="fixed top-15 lg:top-3 right-4 flex items-center gap-2">
<ClientOnly v-if="!colorMode?.forced">
<UButton
:icon="isDark ? 'i-lucide-moon' : 'i-lucide-sun'"
@@ -103,7 +102,7 @@ defineShortcuts({
</ClientOnly>
</div>
<div class="flex-1 flex flex-col items-center justify-around overflow-y-auto w-full py-12 px-4">
<div class="flex-1 flex flex-col items-center justify-around overflow-y-auto w-full py-14 px-4">
<NuxtPage />
</div>

View File

@@ -0,0 +1,20 @@
<script setup lang="ts">
import { CalendarDate } from '@internationalized/date'
const singleValue = shallowRef(new CalendarDate(2022, 1, 10))
const multipleValue = shallowRef({
start: new CalendarDate(2022, 1, 10),
end: new CalendarDate(2022, 1, 20)
})
</script>
<template>
<div class="flex flex-col gap-4">
<div class="flex justify-center gap-2">
<UCalendar v-model="singleValue" />
</div>
<div class="flex justify-center gap-2">
<UCalendar v-model="multipleValue" range />
</div>
</div>
</template>

View File

@@ -105,13 +105,13 @@ defineShortcuts(extractShortcuts(items.value))
<USelect v-model="size" :items="sizes" placeholder="Size" />
</div>
<UContextMenu :items="items" class="min-w-48" :size="size">
<UContextMenu :items="items" :ui="{ content: 'w-48' }" :size="size">
<div class="flex items-center justify-center rounded-md border border-dashed border-[var(--ui-border-accented)] text-sm aspect-video w-72">
Right click here
</div>
</UContextMenu>
<UContextMenu :items="itemsWithColor" class="min-w-48" :size="size">
<UContextMenu :items="itemsWithColor" :ui="{ content: 'w-48' }" :size="size">
<div class="flex items-center justify-center rounded-md border border-dashed border-[var(--ui-border-accented)] text-sm aspect-video w-72">
Color right click here
</div>

View File

@@ -140,7 +140,7 @@ defineShortcuts(extractShortcuts(items.value))
<div class="flex flex-col items-center gap-8">
<USelectMenu v-model="size" :items="sizes" placeholder="Size" />
<UDropdownMenu :items="items" :size="size" arrow :content="{ side: 'bottom', align: 'start' }" class="min-w-48">
<UDropdownMenu :items="items" :size="size" arrow :content="{ side: 'bottom', align: 'start' }" :ui="{ content: 'w-48' }">
<UButton label="Open" color="neutral" variant="outline" icon="i-lucide-menu" />
<template #custom-trailing>
@@ -148,7 +148,7 @@ defineShortcuts(extractShortcuts(items.value))
</template>
</UDropdownMenu>
<UDropdownMenu :items="itemsWithColor" :size="size" arrow :content="{ side: 'bottom', align: 'start' }" class="min-w-48">
<UDropdownMenu :items="itemsWithColor" :size="size" arrow :content="{ side: 'bottom', align: 'start' }" :ui="{ content: 'w-48' }">
<UButton label="Color" color="neutral" variant="outline" icon="i-lucide-menu" />
<template #custom-trailing>

View File

@@ -276,7 +276,7 @@ onMounted(() => {
</script>
<template>
<div class="h-full flex flex-col flex-1 gap-4 w-full -my-8">
<div class="h-full flex flex-col flex-1 gap-4 w-full">
<div class="flex gap-2 items-center">
<UInput
:model-value="(table?.tableApi?.getColumn('email')?.getFilterValue() as string)"

View File

@@ -8,9 +8,9 @@
"generate": "nuxi generate"
},
"dependencies": {
"@iconify-json/lucide": "^1.2.15",
"@iconify-json/simple-icons": "^1.2.11",
"@iconify-json/lucide": "^1.2.16",
"@iconify-json/simple-icons": "^1.2.13",
"@nuxt/ui": "latest",
"nuxt": "^3.14.159"
"nuxt": "^3.14.1592"
}
}

1447
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -13,21 +13,21 @@ export interface ModuleOptions {
/**
* Prefix for components
* @defaultValue `U`
* @link https://ui3.nuxt.dev/getting-started/installation#prefix
* @link https://ui3.nuxt.dev/getting-started/installation/nuxt#prefix
*/
prefix?: string
/**
* Enable or disable `@nuxt/fonts` module
* @defaultValue `true`
* @link https://ui3.nuxt.dev/getting-started/installation#fonts
* @link https://ui3.nuxt.dev/getting-started/installation/nuxt#fonts
*/
fonts?: boolean
/**
* Enable or disable `@nuxtjs/color-mode` module
* @defaultValue `true`
* @link https://ui3.nuxt.dev/getting-started/installation#colormode
* @link https://ui3.nuxt.dev/getting-started/installation/nuxt#colormode
*/
colorMode?: boolean
@@ -39,14 +39,14 @@ export interface ModuleOptions {
/**
* Define the color aliases available for components
* @defaultValue `['primary', 'secondary', 'success', 'info', 'warning', 'error']`
* @link https://ui3.nuxt.dev/getting-started/installation#themecolors
* @link https://ui3.nuxt.dev/getting-started/installation/nuxt#themecolors
*/
colors?: string[]
/**
* Enable or disable transitions on components
* @defaultValue `true`
* @link https://ui3.nuxt.dev/getting-started/installation#themetransitions
* @link https://ui3.nuxt.dev/getting-started/installation/nuxt#themetransitions
*/
transitions?: boolean
}
@@ -70,10 +70,9 @@ export default defineNuxtModule<ModuleOptions>({
compatibility: {
nuxt: '>=3.13.1'
},
docs: 'https://ui3.nuxt.dev/getting-started/installation'
docs: 'https://ui3.nuxt.dev/getting-started/installation/nuxt'
},
defaults: defaultOptions,
async setup(options, nuxt) {
const { resolve } = createResolver(import.meta.url)

View File

@@ -126,7 +126,7 @@ const ui = breadcrumb()
</ULink>
</li>
<li v-if="index < items!.length - 1" role="presentation" :class="ui.separator({ class: props.ui?.separator })">
<li v-if="index < items!.length - 1" role="presentation" aria-hidden="true" :class="ui.separator({ class: props.ui?.separator })">
<slot name="separator">
<UIcon :name="separatorIcon" :class="ui.separatorIcon({ class: props.ui?.separatorIcon })" />
</slot>

View File

@@ -0,0 +1,170 @@
<script lang="ts">
import { tv, type VariantProps } from 'tailwind-variants'
import type { CalendarRootProps, CalendarRootEmits, RangeCalendarRootEmits, DateRange, CalendarCellTriggerProps } from 'radix-vue'
import type { DateValue } from '@internationalized/date'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/calendar'
const appConfig = _appConfig as AppConfig & { ui: { calendar: Partial<typeof theme> } }
const calendar = tv({ extend: tv(theme), ...(appConfig.ui?.calendar || {}) })
type CalendarVariants = VariantProps<typeof calendar>
type CalendarModelValue<R extends boolean = false, M extends boolean = false> = R extends true
? DateRange
: M extends true
? DateValue[]
: DateValue
export interface CalendarProps<R extends boolean, M extends boolean> extends Omit<CalendarRootProps, 'as' | 'asChild' | 'modelValue' | 'defaultValue' | 'dir' | 'locale' | 'calendarLabel' | 'multiple'> {
/**
* The element or component this component should render as.
* @defaultValue 'div'
*/
as?: any
color?: CalendarVariants['color']
size?: CalendarVariants['size']
/** Whether or not a range of dates can be selected */
range?: R & boolean
/** Whether or not multiple dates can be selected */
multiple?: M & boolean
/** Show month controls */
monthControls?: boolean
/** Show year controls */
yearControls?: boolean
defaultValue?: CalendarModelValue<R, M>
modelValue?: CalendarModelValue<R, M>
class?: any
ui?: Partial<typeof calendar.slots>
}
export interface CalendarEmits<R extends boolean, M extends boolean> extends Omit<CalendarRootEmits & RangeCalendarRootEmits, 'update:modelValue'> {
'update:modelValue': [date: CalendarModelValue<R, M>]
}
export interface CalendarSlots {
'heading': (props: { value: string }) => any
'day': (props: Pick<CalendarCellTriggerProps, 'day'>) => any
'week-day': (props: { day: string }) => any
}
</script>
<script setup lang="ts" generic="R extends boolean = false, M extends boolean = false">
import { computed } from 'vue'
import { useForwardPropsEmits } from 'radix-vue'
import { Calendar as SingleCalendar, RangeCalendar } from 'radix-vue/namespaced'
import { reactiveOmit } from '@vueuse/core'
import { useLocale } from '../composables/useLocale'
import UButton from './Button.vue'
const props = withDefaults(defineProps<CalendarProps<R, M>>(), {
fixedWeeks: true,
monthControls: true,
yearControls: true
})
const emits = defineEmits<CalendarEmits<R, M>>()
defineSlots<CalendarSlots>()
const { code: locale, dir, t } = useLocale()
const rootProps = useForwardPropsEmits(reactiveOmit(props, 'range', 'modelValue', 'defaultValue', 'color', 'size', 'monthControls', 'yearControls', 'class', 'ui'), emits)
const prevYearIcon = computed(() => dir.value === 'rtl' ? appConfig.ui.icons.chevronDoubleRight : appConfig.ui.icons.chevronDoubleLeft)
const prevMonthIcon = computed(() => dir.value === 'rtl' ? appConfig.ui.icons.chevronRight : appConfig.ui.icons.chevronLeft)
const nextYearIcon = computed(() => dir.value === 'rtl' ? appConfig.ui.icons.chevronDoubleLeft : appConfig.ui.icons.chevronDoubleRight)
const nextMonthIcon = computed(() => dir.value === 'rtl' ? appConfig.ui.icons.chevronLeft : appConfig.ui.icons.chevronRight)
const ui = computed(() => calendar({
color: props.color,
size: props.size
}))
function paginateYear(date: DateValue, sign: -1 | 1) {
if (sign === -1) {
return date.subtract({ years: 1 })
}
return date.add({ years: 1 })
}
const Calendar = computed(() => props.range ? RangeCalendar : SingleCalendar)
</script>
<template>
<Calendar.Root
v-slot="{ weekDays, grid }"
v-bind="rootProps"
:model-value="(modelValue as CalendarModelValue<true & false>)"
:default-value="(defaultValue as CalendarModelValue<true & false>)"
:locale="locale"
:dir="dir"
:class="ui.root({ class: [props.class, props.ui?.root] })"
>
<Calendar.Header :class="ui.header({ class: props.ui?.header })">
<Calendar.Prev v-if="props.yearControls" :prev-page="(date: DateValue) => paginateYear(date, -1)" :aria-label="t('calendar.prevYear')" as-child>
<UButton :icon="prevYearIcon" :size="props.size" color="neutral" variant="ghost" />
</Calendar.Prev>
<Calendar.Prev v-if="props.monthControls" :aria-label="t('calendar.prevMonth')" as-child>
<UButton :icon="prevMonthIcon" :size="props.size" color="neutral" variant="ghost" />
</Calendar.Prev>
<Calendar.Heading v-slot="{ headingValue }" :class="ui.heading({ class: props.ui?.heading })">
<slot name="heading" :value="headingValue">
{{ headingValue }}
</slot>
</Calendar.Heading>
<Calendar.Next v-if="props.monthControls" :aria-label="t('calendar.nextMonth')" as-child>
<UButton :icon="nextMonthIcon" :size="props.size" color="neutral" variant="ghost" />
</Calendar.Next>
<Calendar.Next v-if="props.yearControls" :next-page="(date: DateValue) => paginateYear(date, 1)" :aria-label="t('calendar.nextYear')" as-child>
<UButton :icon="nextYearIcon" :size="props.size" color="neutral" variant="ghost" />
</Calendar.Next>
</Calendar.Header>
<div :class="ui.body({ class: props.ui?.body })">
<Calendar.Grid
v-for="month in grid"
:key="month.value.toString()"
:class="ui.grid({ class: props.ui?.grid })"
>
<Calendar.GridHead>
<Calendar.GridRow :class="ui.gridWeekDaysRow({ class: props.ui?.gridWeekDaysRow })">
<Calendar.HeadCell
v-for="day in weekDays"
:key="day"
:class="ui.headCell({ class: props.ui?.headCell })"
>
<slot name="week-day" :day="day">
{{ day }}
</slot>
</Calendar.HeadCell>
</Calendar.GridRow>
</Calendar.GridHead>
<Calendar.GridBody :class="ui.gridBody({ class: props.ui?.gridBody })">
<Calendar.GridRow
v-for="(weekDates, index) in month.rows"
:key="`weekDate-${index}`"
:class="ui.gridRow({ class: props.ui?.gridRow })"
>
<Calendar.Cell
v-for="weekDate in weekDates"
:key="weekDate.toString()"
:date="weekDate"
:class="ui.cell({ class: props.ui?.cell })"
>
<Calendar.CellTrigger
:day="weekDate"
:month="month.value"
:class="ui.cellTrigger({ class: props.ui?.cellTrigger })"
>
<slot name="day" :day="weekDate">
{{ weekDate.day }}
</slot>
</Calendar.CellTrigger>
</Calendar.Cell>
</Calendar.GridRow>
</Calendar.GridBody>
</Calendar.Grid>
</div>
</Calendar.Root>
</template>

View File

@@ -23,6 +23,7 @@ export interface CommandPaletteItem {
avatar?: AvatarProps
chip?: ChipProps
kbds?: KbdProps['value'][] | KbdProps[]
active?: boolean
loading?: boolean
disabled?: boolean
slot?: string
@@ -276,31 +277,31 @@ const groups = computed(() => {
:key="`group-${groupIndex}-${index}`"
:value="omit(item, ['matches' as any, 'group' as any, 'onSelect', 'labelHtml', 'suffixHtml'])"
:disabled="item.disabled"
:class="ui.item({ class: props.ui?.item })"
:class="ui.item({ class: props.ui?.item, active: item.active })"
@select="item.onSelect"
>
<slot :name="item.slot || group.slot || 'item'" :item="item" :index="index">
<slot :name="item.slot ? `${item.slot}-leading` : group.slot ? `${group.slot}-leading` : `item-leading`" :item="item" :index="index">
<UIcon v-if="item.loading" :name="loadingIcon || appConfig.ui.icons.loading" :class="ui.itemLeadingIcon({ class: props.ui?.itemLeadingIcon, loading: true })" />
<UIcon v-else-if="item.icon" :name="item.icon" :class="ui.itemLeadingIcon({ class: props.ui?.itemLeadingIcon })" />
<UAvatar v-else-if="item.avatar" :size="((props.ui?.itemLeadingAvatarSize || ui.itemLeadingAvatarSize()) as AvatarProps['size'])" v-bind="item.avatar" :class="ui.itemLeadingAvatar({ class: props.ui?.itemLeadingAvatar })" />
<UIcon v-else-if="item.icon" :name="item.icon" :class="ui.itemLeadingIcon({ class: props.ui?.itemLeadingIcon, active: item.active })" />
<UAvatar v-else-if="item.avatar" :size="((props.ui?.itemLeadingAvatarSize || ui.itemLeadingAvatarSize()) as AvatarProps['size'])" v-bind="item.avatar" :class="ui.itemLeadingAvatar({ class: props.ui?.itemLeadingAvatar, active: item.active })" />
<UChip
v-else-if="item.chip"
:size="((props.ui?.itemLeadingChipSize || ui.itemLeadingChipSize()) as ChipProps['size'])"
inset
standalone
v-bind="item.chip"
:class="ui.itemLeadingChip({ class: props.ui?.itemLeadingChip })"
:class="ui.itemLeadingChip({ class: props.ui?.itemLeadingChip, active: item.active })"
/>
</slot>
<span v-if="item.labelHtml || get(item, props.labelKey as string) || !!slots[item.slot ? `${item.slot}-label` : group.slot ? `${group.slot}-label` : `item-label`]" :class="ui.itemLabel({ class: props.ui?.itemLabel })">
<span v-if="item.labelHtml || get(item, props.labelKey as string) || !!slots[item.slot ? `${item.slot}-label` : group.slot ? `${group.slot}-label` : `item-label`]" :class="ui.itemLabel({ class: props.ui?.itemLabel, active: item.active })">
<slot :name="item.slot ? `${item.slot}-label` : group.slot ? `${group.slot}-label` : `item-label`" :item="item" :index="index">
<span v-if="item.prefix" :class="ui.itemLabelPrefix({ class: props.ui?.itemLabelPrefix })">{{ item.prefix }}</span>
<span :class="ui.itemLabelBase({ class: props.ui?.itemLabelBase })" v-html="item.labelHtml || get(item, props.labelKey as string)" />
<span :class="ui.itemLabelBase({ class: props.ui?.itemLabelBase, active: item.active })" v-html="item.labelHtml || get(item, props.labelKey as string)" />
<span :class="ui.itemLabelSuffix({ class: props.ui?.itemLabelSuffix })" v-html="item.suffixHtml || item.suffix" />
<span :class="ui.itemLabelSuffix({ class: props.ui?.itemLabelSuffix, active: item.active })" v-html="item.suffixHtml || item.suffix" />
</slot>
</span>

View File

@@ -167,12 +167,12 @@ const ui = computed(() => contextMenu({
<template>
<ContextMenuRoot v-bind="rootProps">
<ContextMenuTrigger v-if="!!slots.default" as-child :disabled="disabled">
<ContextMenuTrigger v-if="!!slots.default" as-child :disabled="disabled" :class="props.class">
<slot />
</ContextMenuTrigger>
<UContextMenuContent
:class="ui.content({ class: [props.class, props.ui?.content] })"
:class="ui.content({ class: [!slots.default && props.class, props.ui?.content] })"
:ui="ui"
:ui-override="props.ui"
v-bind="contentProps"

View File

@@ -81,14 +81,14 @@ const ui = computed(() => drawer({
<template>
<DrawerRoot v-bind="rootProps">
<DrawerTrigger v-if="!!slots.default" as-child>
<DrawerTrigger v-if="!!slots.default" as-child :class="props.class">
<slot />
</DrawerTrigger>
<DrawerPortal :disabled="!portal">
<DrawerOverlay v-if="overlay" :class="ui.overlay({ class: props.ui?.overlay })" />
<DrawerContent :class="ui.content({ class: [props.class, props.ui?.content] })" v-bind="contentProps">
<DrawerContent :class="ui.content({ class: [!slots.default && props.class, props.ui?.content] })" v-bind="contentProps">
<slot name="handle">
<div v-if="handle" :class="ui.handle({ class: props.ui?.handle })" />
</slot>

View File

@@ -165,12 +165,12 @@ const ui = computed(() => dropdownMenu({
<template>
<DropdownMenuRoot v-slot="{ open }" v-bind="rootProps">
<DropdownMenuTrigger v-if="!!slots.default" as-child :disabled="disabled">
<DropdownMenuTrigger v-if="!!slots.default" as-child :class="props.class" :disabled="disabled">
<slot :open="open" />
</DropdownMenuTrigger>
<UDropdownMenuContent
:class="ui.content({ class: [props.class, props.ui?.content] })"
:class="ui.content({ class: [!slots.default && props.class, props.ui?.content] })"
:ui="ui"
:ui-override="props.ui"
v-bind="contentProps"

View File

@@ -104,6 +104,7 @@ function resolveErrorIds(errs: FormError[]): FormErrorWithId[] {
}))
}
const parsedValue = ref<T | null>(null)
async function getErrors(): Promise<FormErrorWithId[]> {
let errs = props.validate ? (await props.validate(props.state)) ?? [] : []
@@ -112,7 +113,7 @@ async function getErrors(): Promise<FormErrorWithId[]> {
if (errors) {
errs = errs.concat(errors)
} else {
Object.assign(props.state, result)
parsedValue.value = result
}
}
@@ -169,7 +170,7 @@ async function onSubmitWrapper(payload: Event) {
try {
await _validate({ nested: true })
event.data = props.state
event.data = props.schema ? parsedValue.value : props.state
await props.onSubmit?.(event)
} catch (error) {
if (!(error instanceof FormValidationException)) {

View File

@@ -107,7 +107,7 @@ export interface InputMenuProps<T extends MaybeArrayOfArrayItem<I>, I extends Ma
/** The controlled value of the Combobox. Can be binded-with with `v-model`. */
modelValue?: SelectModelValue<T, V, M>
/** Whether multiple options can be selected or not. */
multiple?: M
multiple?: M & boolean
}
export type InputMenuEmits<T, V, M extends boolean> = Omit<ComboboxRootEmits<T>, 'update:modelValue'> & {
@@ -167,11 +167,9 @@ const searchTerm = defineModel<string>('searchTerm', { default: '' })
const appConfig = useAppConfig()
const { t } = useLocale()
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'modelValue', 'defaultValue', 'selectedValue', 'open', 'defaultOpen', 'resetSearchTermOnBlur'), emits)
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'modelValue', 'defaultValue', 'selectedValue', 'open', 'defaultOpen', 'multiple', 'resetSearchTermOnBlur'), emits)
const contentProps = toRef(() => defu(props.content, { side: 'bottom', sideOffset: 8, position: 'popper' }) as ComboboxContentProps)
const arrowProps = toRef(() => props.arrow as ComboboxArrowProps)
// This is a hack due to generic boolean casting (see https://github.com/nuxt/ui/issues/2541)
const multiple = toRef(() => typeof props.multiple === 'string' ? true : props.multiple)
const { emitFormBlur, emitFormChange, emitFormInput, size: formGroupSize, color, id, name, highlight, disabled } = useFormField<InputProps>(props)
const { orientation, size: buttonGroupSize } = useButtonGroup<InputProps>(props)
@@ -189,7 +187,7 @@ const ui = computed(() => inputMenu({
highlight: highlight.value,
leading: isLeading.value || !!props.avatar || !!slots.leading,
trailing: isTrailing.value || !!slots.trailing,
multiple: multiple.value,
multiple: props.multiple,
buttonGroup: orientation.value
}))
@@ -336,7 +334,6 @@ defineExpose({
v-model:search-term="searchTerm"
:name="name"
:disabled="disabled"
:multiple="multiple"
:display-value="displayValue"
:filter-function="() => rootItems"
:class="ui.root({ class: [props.class, props.ui?.root] })"

View File

@@ -121,14 +121,14 @@ const ui = computed(() => modal({
<template>
<DialogRoot v-slot="{ open }" v-bind="rootProps">
<DialogTrigger v-if="!!slots.default" as-child>
<DialogTrigger v-if="!!slots.default" as-child :class="props.class">
<slot :open="open" />
</DialogTrigger>
<DialogPortal :disabled="!portal">
<DialogOverlay v-if="overlay" :class="ui.overlay({ class: props.ui?.overlay })" />
<DialogContent :class="ui.content({ class: [props.class, props.ui?.content] })" v-bind="contentProps" v-on="contentEvents">
<DialogContent :class="ui.content({ class: [!slots.default && props.class, props.ui?.content] })" v-bind="contentProps" v-on="contentEvents">
<slot name="content">
<div v-if="!!slots.header || (title || !!slots.title) || (description || !!slots.description) || (close || !!slots.close)" :class="ui.header({ class: props.ui?.header })">
<slot name="header">

View File

@@ -92,12 +92,12 @@ const Component = computed(() => props.mode === 'hover' ? HoverCard : Popover)
<template>
<Component.Root v-slot="{ open }" v-bind="rootProps">
<Component.Trigger v-if="!!slots.default" as-child>
<Component.Trigger v-if="!!slots.default" as-child :class="props.class">
<slot :open="open" />
</Component.Trigger>
<Component.Portal :disabled="!portal">
<Component.Content v-bind="contentProps" :class="ui.content({ class: [props.class, props.ui?.content] })" v-on="contentEvents">
<Component.Content v-bind="contentProps" :class="ui.content({ class: [!slots.default && props.class, props.ui?.content] })" v-on="contentEvents">
<slot name="content" />
<Component.Arrow v-if="!!arrow" v-bind="arrowProps" :class="ui.arrow({ class: props.ui?.arrow })" />

View File

@@ -50,6 +50,7 @@ export type ProgressSlots = {
import { computed } from 'vue'
import { ProgressIndicator, ProgressRoot, useForwardPropsEmits } from 'radix-vue'
import { reactivePick } from '@vueuse/core'
import { useLocale } from '../composables/useLocale'
const props = withDefaults(defineProps<ProgressProps>(), {
inverted: false,
@@ -59,6 +60,8 @@ const props = withDefaults(defineProps<ProgressProps>(), {
const emits = defineEmits<ProgressEmits>()
defineSlots<ProgressSlots>()
const { dir } = useLocale()
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'getValueLabel', 'modelValue'), emits)
const isIndeterminate = computed(() => rootProps.value.modelValue === null)
@@ -93,8 +96,20 @@ const indicatorStyle = computed(() => {
return
}
return {
transform: `translate${props.orientation === 'vertical' ? 'Y' : 'X'}(${props.inverted ? '' : '-'}${100 - percent.value}%)`
if (props.orientation === 'vertical') {
return {
transform: `translateY(${props.inverted ? '' : '-'}${100 - percent.value}%)`
}
} else {
if (dir.value === 'rtl') {
return {
transform: `translateX(${props.inverted ? '-' : ''}${100 - percent.value}%)`
}
} else {
return {
transform: `translateX(${props.inverted ? '' : '-'}${100 - percent.value}%)`
}
}
}
})

View File

@@ -99,7 +99,7 @@ export interface SelectMenuProps<T extends MaybeArrayOfArrayItem<I>, I extends M
/** The controlled value of the Combobox. Can be binded-with with `v-model`. */
modelValue?: SelectModelValue<T, V, M>
/** Whether multiple options can be selected or not. */
multiple?: M
multiple?: M & boolean
}
export type SelectMenuEmits<T, V, M extends boolean> = Omit<ComboboxRootEmits<T>, 'update:modelValue'> & {
@@ -160,12 +160,10 @@ const searchTerm = defineModel<string>('searchTerm', { default: '' })
const appConfig = useAppConfig()
const { t } = useLocale()
const rootProps = useForwardPropsEmits(reactivePick(props, 'modelValue', 'defaultValue', 'selectedValue', 'open', 'defaultOpen', 'resetSearchTermOnBlur'), emits)
const rootProps = useForwardPropsEmits(reactivePick(props, 'modelValue', 'defaultValue', 'selectedValue', 'open', 'defaultOpen', 'multiple', 'resetSearchTermOnBlur'), emits)
const contentProps = toRef(() => defu(props.content, { side: 'bottom', sideOffset: 8, position: 'popper' }) as ComboboxContentProps)
const arrowProps = toRef(() => props.arrow as ComboboxArrowProps)
const searchInputProps = toRef(() => defu(props.searchInput, { placeholder: 'Search...', variant: 'none' }) as InputProps)
// This is a hack due to generic boolean casting (see https://github.com/nuxt/ui/issues/2541)
const multiple = toRef(() => typeof props.multiple === 'string' ? true : props.multiple)
const [DefineCreateItemTemplate, ReuseCreateItemTemplate] = createReusableTemplate()
@@ -187,7 +185,7 @@ const ui = computed(() => selectMenu({
}))
function displayValue(value: T | T[]): string {
if (multiple.value && Array.isArray(value)) {
if (props.multiple && Array.isArray(value)) {
return value.map(v => displayValue(v)).filter(Boolean).join(', ')
}
@@ -307,7 +305,6 @@ function onUpdateOpen(value: boolean) {
as-child
:name="name"
:disabled="disabled"
:multiple="multiple"
:display-value="() => searchTerm"
:filter-function="() => rootItems"
@update:model-value="onUpdate"

View File

@@ -120,14 +120,14 @@ const ui = computed(() => slideover({
<template>
<DialogRoot v-slot="{ open }" v-bind="rootProps">
<DialogTrigger v-if="!!slots.default" as-child>
<DialogTrigger v-if="!!slots.default" as-child :class="props.class">
<slot :open="open" />
</DialogTrigger>
<DialogPortal :disabled="!portal">
<DialogOverlay v-if="overlay" :class="ui.overlay({ class: props.ui?.overlay })" />
<DialogContent :data-side="side" :class="ui.content({ class: [props.class, props.ui?.content] })" v-bind="contentProps" v-on="contentEvents">
<DialogContent :data-side="side" :class="ui.content({ class: [!slots.default && props.class, props.ui?.content] })" v-bind="contentProps" v-on="contentEvents">
<slot name="content">
<div v-if="!!slots.header || (title || !!slots.title) || (description || !!slots.description) || (close || !!slots.close)" :class="ui.header({ class: props.ui?.header })">
<slot name="header">

View File

@@ -70,12 +70,12 @@ const ui = computed(() => tooltip({
<template>
<TooltipRoot v-slot="{ open }" v-bind="rootProps">
<TooltipTrigger v-if="!!slots.default" as-child>
<TooltipTrigger v-if="!!slots.default" as-child :class="props.class">
<slot :open="open" />
</TooltipTrigger>
<TooltipPortal :disabled="!portal">
<TooltipContent v-bind="contentProps" :class="ui.content({ class: [props.class, props.ui?.content] })">
<TooltipContent v-bind="contentProps" :class="ui.content({ class: [!slots.default && props.class, props.ui?.content] })">
<slot name="content">
<span v-if="text" :class="ui.text({ class: props.ui?.text })">{{ text }}</span>

View File

@@ -2,7 +2,7 @@ import { computed, inject, ref } from 'vue'
import type { InjectionKey, Ref } from 'vue'
import type { Locale } from '../types/locale'
import { buildLocaleContext } from '../utils/locale'
import { en } from '../locale'
import en from '../locale/en'
import { createSharedComposable } from '@vueuse/core'
export const localeContextInjectionKey: InjectionKey<Ref<Locale | undefined>> = Symbol('nuxt-ui.locale-context')

View File

@@ -362,6 +362,24 @@
}
}
@keyframes carousel-rtl {
0%,
100% {
width: 50%
}
0% {
transform: translateX(100%)
}
100% {
transform: translateX(-200%)
}
}
@keyframes carousel-vertical {
0%,
@@ -394,6 +412,22 @@
}
}
@keyframes carousel-inverse-rtl {
0%,
100% {
width: 50%
}
0% {
transform: translateX(-200%)
}
100% {
transform: translateX(100%)
}
}
@keyframes carousel-inverse-vertical {
0%,

View File

@@ -10,6 +10,12 @@ export default defineLocale({
noData: 'لا توجد بيانات',
create: 'إنشاء "{label}"'
},
calendar: {
prevYear: 'السنة السابقة',
nextYear: 'السنة المقبلة',
prevMonth: 'الشهر السابق',
nextMonth: 'الشهر المقبل'
},
inputNumber: {
increment: 'زيادة',
decrement: 'تقليل'

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