mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-23 00:15:05 +01:00
Compare commits
44 Commits
fix/modal-
...
v3.0.0-alp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bc10a1cabe | ||
|
|
dcd86144a2 | ||
|
|
761680b5cb | ||
|
|
3183e4afe3 | ||
|
|
cd16b95c98 | ||
|
|
d27be06164 | ||
|
|
f3958773d6 | ||
|
|
2006ec0646 | ||
|
|
3320e0473c | ||
|
|
c0b485d563 | ||
|
|
891ba1fec6 | ||
|
|
1a95104631 | ||
|
|
ac86ee01b9 | ||
|
|
8f7f579da0 | ||
|
|
8e96daa5cc | ||
|
|
36d7402be1 | ||
|
|
63b7de4159 | ||
|
|
f8b4de587e | ||
|
|
890c3d0840 | ||
|
|
7441b6451d | ||
|
|
2b7ff3edf6 | ||
|
|
9b5a957cdd | ||
|
|
00c5f26111 | ||
|
|
aafddd8eed | ||
|
|
8d941e1360 | ||
|
|
ba3d5e2c7d | ||
|
|
1b989c419d | ||
|
|
b8b7a8366d | ||
|
|
fb94ee379c | ||
|
|
a5ed62f83a | ||
|
|
12b6c78a17 | ||
|
|
9cafd1295e | ||
|
|
545c3917a1 | ||
|
|
3e2e5a075d | ||
|
|
629dcfab16 | ||
|
|
53d636aa9b | ||
|
|
eb068b2f90 | ||
|
|
75a470d588 | ||
|
|
90dc03cd03 | ||
|
|
088dc9bf38 | ||
|
|
b95b91391a | ||
|
|
b8d99726ef | ||
|
|
b88f67ccfe | ||
|
|
55b233dc3d |
42
CHANGELOG.md
42
CHANGELOG.md
@@ -1,5 +1,47 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [3.0.0-alpha.12](https://github.com/nuxt/ui/compare/v3.0.0-alpha.11...v3.0.0-alpha.12) (2025-01-27)
|
||||||
|
|
||||||
|
### ⚠ BREAKING CHANGES
|
||||||
|
|
||||||
|
* **ColorPicker:** migrate from `color` to `colortranslator` (#3097)
|
||||||
|
* **Form:** include nested state in submit data (#3028)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **css:** add `light` variant to reverse colors ([75f7064](https://github.com/nuxt/ui/commit/75f7064b409a47d068007d0b4f3af007fb24c679))
|
||||||
|
* **FormField:** set `aria-describedby` and `aria-invalid` attributes ([#3123](https://github.com/nuxt/ui/issues/3123)) ([b95b913](https://github.com/nuxt/ui/commit/b95b91391af21ee0fd96c69fb6ccf99b3126bc79))
|
||||||
|
* **Form:** form validation properties ([#3137](https://github.com/nuxt/ui/issues/3137)) ([c0b485d](https://github.com/nuxt/ui/commit/c0b485d56376d6655d15d6241daeef19f25db25f))
|
||||||
|
* **locale:** add Hebrew language ([#3181](https://github.com/nuxt/ui/issues/3181)) ([f395877](https://github.com/nuxt/ui/commit/f3958773d610d64fe15cf57525044eec22dc1f96))
|
||||||
|
* **locale:** add Hindi language ([#3170](https://github.com/nuxt/ui/issues/3170)) ([8e96daa](https://github.com/nuxt/ui/commit/8e96daa5cc57e1a2c7605d54f8640f8e012a645d))
|
||||||
|
* **locale:** add Hungarian language ([#3129](https://github.com/nuxt/ui/issues/3129)) ([891ba1f](https://github.com/nuxt/ui/commit/891ba1fec64255ba4db0f4447e044cc9140ced94))
|
||||||
|
* **locale:** add Khmer language ([#3119](https://github.com/nuxt/ui/issues/3119)) ([64421a1](https://github.com/nuxt/ui/commit/64421a190ff43563cc73f64b6a9141d69e3f5ca5))
|
||||||
|
* **locale:** add Norwegian Bokmål language ([#3095](https://github.com/nuxt/ui/issues/3095)) ([9ccfe8f](https://github.com/nuxt/ui/commit/9ccfe8fbb3284a5bdd0766ba5831135d298b563f))
|
||||||
|
* **NavigationMenu:** add `collapsed` prop ([3fc2210](https://github.com/nuxt/ui/commit/3fc2210e0392b63b065e4f4899ff864f1a3717b1))
|
||||||
|
* **NavigationMenu:** add `contentOrientation` prop ([ac86ee0](https://github.com/nuxt/ui/commit/ac86ee01b9fc9b5dc882b210d88b8fef73148e42))
|
||||||
|
* **NavigationMenu:** handle `label` type in items ([27fdc8e](https://github.com/nuxt/ui/commit/27fdc8e260bb8d2ca815c84cfdc30b6ca3baa038)), closes [#2993](https://github.com/nuxt/ui/issues/2993)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Alert:** allow actions wrap ([#3083](https://github.com/nuxt/ui/issues/3083)) ([e7c10bc](https://github.com/nuxt/ui/commit/e7c10bcb0dbbfbbe48bbdea7cbd99d4535be1adb))
|
||||||
|
* **Avatar:** handle loading manually to support `@nuxt/image` ([00c5f26](https://github.com/nuxt/ui/commit/00c5f261117fd986c8be70ecdc21762023e7ebc0)), closes [nuxt/ui-pro#727](https://github.com/nuxt/ui-pro/issues/727)
|
||||||
|
* **Avatar:** hide fallback when image is loaded ([36d7402](https://github.com/nuxt/ui/commit/36d7402be1f823c753c7cd44cca82bbb5fd4cddd)), closes [nuxt/ui-pro#727](https://github.com/nuxt/ui-pro/issues/727)
|
||||||
|
* **Button:** wrong avatar size with `block` prop ([ba1dd13](https://github.com/nuxt/ui/commit/ba1dd13173835c9b72b862eb9f875a8cd79c5604))
|
||||||
|
* **colors:** move css variables to `base` layer ([533ccec](https://github.com/nuxt/ui/commit/533ccec11007ec9078fd8daefd88f6b146991939)), closes [#3075](https://github.com/nuxt/ui/issues/3075)
|
||||||
|
* **components:** prevent multiple `appConfig` identifier import ([#3186](https://github.com/nuxt/ui/issues/3186)) ([cd16b95](https://github.com/nuxt/ui/commit/cd16b95c98c0ec29bc0586ba890555f79be00290))
|
||||||
|
* **ContextMenu/DropdownMenu:** remove unnecessary bindings in html ([9b5a957](https://github.com/nuxt/ui/commit/9b5a957cdd01baafaa981864ad7d03902ad6918d))
|
||||||
|
* **Form:** include nested state in submit data ([#3028](https://github.com/nuxt/ui/issues/3028)) ([de9ecb1](https://github.com/nuxt/ui/commit/de9ecb1d767060f88c1dbdf69b9c04d5731b049d))
|
||||||
|
* **Form:** standard schema validation no longer wrapped in `value` object ([#3104](https://github.com/nuxt/ui/issues/3104)) ([8f7f579](https://github.com/nuxt/ui/commit/8f7f579da0fc58575184dc445ff0dda0c0ca1298))
|
||||||
|
* **locale:** remove emoji fallback for Chinese ([#3134](https://github.com/nuxt/ui/issues/3134)) ([1a95104](https://github.com/nuxt/ui/commit/1a951046319eaf85c2adb44928a0255dedef093d))
|
||||||
|
* **locale:** year translation missing `ñ` in `es` ([#3090](https://github.com/nuxt/ui/issues/3090)) ([1bf370e](https://github.com/nuxt/ui/commit/1bf370e6fd27fab644689335b7356bbf4c359663))
|
||||||
|
* **NavigationMenu:** handle children recursively in vertical orientation ([2b7ff3e](https://github.com/nuxt/ui/commit/2b7ff3edf6620d7ed4a491d89f0e616b5916984b)), closes [#3128](https://github.com/nuxt/ui/issues/3128)
|
||||||
|
* **NavigationMenu:** highlight open items on `horizontal` orientation only ([931211a](https://github.com/nuxt/ui/commit/931211a634183a8122ce0be874cc1f9048768d88))
|
||||||
|
* **useToast:** add in queue and improve unique ids ([aafddd8](https://github.com/nuxt/ui/commit/aafddd8eed0f3fc7c7228c2db4718ba54f3fc522)), closes [#2686](https://github.com/nuxt/ui/issues/2686)
|
||||||
|
|
||||||
|
### Code Refactoring
|
||||||
|
|
||||||
|
* **ColorPicker:** migrate from `color` to `colortranslator` ([#3097](https://github.com/nuxt/ui/issues/3097)) ([51e5e65](https://github.com/nuxt/ui/commit/51e5e65be7f834ec226be28d95a1b547b85b329c))
|
||||||
|
|
||||||
## [3.0.0-alpha.11](https://github.com/nuxt/ui/compare/v3.0.0-alpha.10...v3.0.0-alpha.11) (2025-01-13)
|
## [3.0.0-alpha.11](https://github.com/nuxt/ui/compare/v3.0.0-alpha.10...v3.0.0-alpha.11) (2025-01-13)
|
||||||
|
|
||||||
### ⚠ BREAKING CHANGES
|
### ⚠ BREAKING CHANGES
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"citty": "^0.1.6",
|
"citty": "^0.1.6",
|
||||||
"consola": "^3.3.3",
|
"consola": "^3.4.0",
|
||||||
"pathe": "^2.0.1",
|
"pathe": "^2.0.2",
|
||||||
"scule": "^1.3.0"
|
"scule": "^1.3.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,9 +35,9 @@ import _appConfig from '#build/app.config'
|
|||||||
import theme from '#build/${path}/${prose ? 'prose/' : ''}${content ? 'content/' : ''}${kebabName}'
|
import theme from '#build/${path}/${prose ? 'prose/' : ''}${content ? 'content/' : ''}${kebabName}'
|
||||||
import { tv } from '${pro ? '#ui/utils/tv' : '../utils/tv'}'
|
import { tv } from '${pro ? '#ui/utils/tv' : '../utils/tv'}'
|
||||||
|
|
||||||
const appConfig = _appConfig as AppConfig & { ${key}: { ${prose ? 'prose: { ' : ''}${camelName}: Partial<typeof theme> } }${prose ? ' }' : ''}
|
const appConfig${camelName} = _appConfig as AppConfig & { ${key}: { ${prose ? 'prose: { ' : ''}${camelName}: Partial<typeof theme> } }${prose ? ' }' : ''}
|
||||||
|
|
||||||
const ${camelName} = tv({ extend: tv(theme), ...(appConfig.${key}?.${prose ? 'prose?.' : ''}${camelName} || {}) })
|
const ${camelName} = tv({ extend: tv(theme), ...(appConfig${camelName}.${key}?.${prose ? 'prose?.' : ''}${camelName} || {}) })
|
||||||
|
|
||||||
export interface ${upperName}Props {
|
export interface ${upperName}Props {
|
||||||
/**
|
/**
|
||||||
@@ -78,9 +78,9 @@ import _appConfig from '#build/app.config'
|
|||||||
import theme from '#build/${path}/${prose ? 'prose/' : ''}${content ? 'content/' : ''}${kebabName}'
|
import theme from '#build/${path}/${prose ? 'prose/' : ''}${content ? 'content/' : ''}${kebabName}'
|
||||||
import { tv } from '${pro ? '#ui/utils/tv' : '../utils/tv'}'
|
import { tv } from '${pro ? '#ui/utils/tv' : '../utils/tv'}'
|
||||||
|
|
||||||
const appConfig = _appConfig as AppConfig & { ${key}: { ${prose ? 'prose: { ' : ''}${camelName}: Partial<typeof theme> } }${prose ? ' }' : ''}
|
const appConfig${camelName} = _appConfig as AppConfig & { ${key}: { ${prose ? 'prose: { ' : ''}${camelName}: Partial<typeof theme> } }${prose ? ' }' : ''}
|
||||||
|
|
||||||
const ${camelName} = tv({ extend: tv(theme), ...(appConfig.${key}?.${prose ? 'prose?.' : ''}${camelName} || {}) })
|
const ${camelName} = tv({ extend: tv(theme), ...(appConfig${camelName}.${key}?.${prose ? 'prose?.' : ''}${camelName} || {}) })
|
||||||
|
|
||||||
type ${upperName}Variants = VariantProps<typeof ${camelName}>
|
type ${upperName}Variants = VariantProps<typeof ${camelName}>
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nuxt/ui": "latest",
|
"@nuxt/ui": "latest",
|
||||||
"knitwork": "^1.2.0",
|
"knitwork": "^1.2.0",
|
||||||
"nuxt": "^3.15.1",
|
"nuxt": "^3.15.3",
|
||||||
"prettier": "^3.4.2",
|
"prettier": "^3.4.2",
|
||||||
"zod": "^3.24.1"
|
"zod": "^3.24.1"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ provide('navigation', mappedNavigation)
|
|||||||
items: modules
|
items: modules
|
||||||
}]"
|
}]"
|
||||||
:navigation="filteredNavigation"
|
:navigation="filteredNavigation"
|
||||||
:fuse="{ resultLimit: 42 }"
|
:fuse="{ resultLimit: 100 }"
|
||||||
/>
|
/>
|
||||||
</ClientOnly>
|
</ClientOnly>
|
||||||
</template>
|
</template>
|
||||||
@@ -152,5 +152,5 @@ html[data-module="ui"] .ui-pro-only {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Safelist (do not remove): [&>div]:*:my-0 [&>div]:*:w-full */
|
/* Safelist (do not remove): [&>div]:*:my-0 [&>div]:*:w-full h-64 */
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -132,11 +132,18 @@ const optionsValues = ref(props.options?.reduce((acc, option) => {
|
|||||||
return acc
|
return acc
|
||||||
}, {} as Record<string, any>) || {})
|
}, {} as Record<string, any>) || {})
|
||||||
|
|
||||||
const urlSearchParams = computed(() => new URLSearchParams({
|
const urlSearchParams = computed(() => {
|
||||||
...optionsValues.value,
|
const params = {
|
||||||
...componentProps,
|
...optionsValues.value,
|
||||||
width: Math.round(width.value).toString()
|
...componentProps
|
||||||
}).toString())
|
}
|
||||||
|
|
||||||
|
if (!props.iframeMobile) {
|
||||||
|
params.width = Math.round(width.value).toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
return new URLSearchParams(params).toString()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import json5 from 'json5'
|
import json5 from 'json5'
|
||||||
import { camelCase } from 'scule'
|
import { camelCase } from 'scule'
|
||||||
|
import { hash } from 'ohash'
|
||||||
import * as theme from '#build/ui'
|
import * as theme from '#build/ui'
|
||||||
import * as themePro from '#build/ui-pro'
|
import * as themePro from '#build/ui-pro'
|
||||||
|
|
||||||
@@ -77,7 +78,7 @@ const component = computed(() => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const { data: ast } = await useAsyncData(`component-theme-${name}`, async () => {
|
const { data: ast } = await useAsyncData(`component-theme-${name}-${hash({ props })}`, async () => {
|
||||||
const md = `
|
const md = `
|
||||||
::code-collapse{class="nuxt-only"}
|
::code-collapse{class="nuxt-only"}
|
||||||
|
|
||||||
|
|||||||
@@ -15,14 +15,14 @@ function getEmojiFlag(locale: string): string {
|
|||||||
el: 'gr',
|
el: 'gr',
|
||||||
et: 'ee',
|
et: 'ee',
|
||||||
en: 'gb',
|
en: 'gb',
|
||||||
|
hi: 'in',
|
||||||
ja: 'jp',
|
ja: 'jp',
|
||||||
kh: 'km',
|
kh: 'km',
|
||||||
ko: 'kr',
|
ko: 'kr',
|
||||||
nb: 'no',
|
nb: 'no',
|
||||||
sv: 'se',
|
sv: 'se',
|
||||||
uk: 'ua',
|
uk: 'ua',
|
||||||
vi: 'vn',
|
vi: 'vn'
|
||||||
zh: 'cn'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseLanguage = locale.split('-')[0]?.toLowerCase() || locale
|
const baseLanguage = locale.split('-')[0]?.toLowerCase() || locale
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import type { FormSubmitEvent } from '@nuxt/ui'
|
|||||||
|
|
||||||
const schema = z.object({
|
const schema = z.object({
|
||||||
name: z.string().min(2),
|
name: z.string().min(2),
|
||||||
news: z.boolean()
|
news: z.boolean().default(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
type Schema = z.output<typeof schema>
|
type Schema = z.output<typeof schema>
|
||||||
@@ -36,7 +36,7 @@ async function onSubmit(event: FormSubmitEvent<any>) {
|
|||||||
</UFormField>
|
</UFormField>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<UCheckbox v-model="state.news" name="news" label="Register to our newsletter" />
|
<UCheckbox v-model="state.news" name="news" label="Register to our newsletter" @update:model-value="state.email = undefined" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<UForm v-if="state.news" :state="state" :schema="nestedSchema">
|
<UForm v-if="state.news" :state="state" :schema="nestedSchema">
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ const text = computed(() => {
|
|||||||
variant="link"
|
variant="link"
|
||||||
size="sm"
|
size="sm"
|
||||||
:icon="show ? 'i-lucide-eye-off' : 'i-lucide-eye'"
|
:icon="show ? 'i-lucide-eye-off' : 'i-lucide-eye'"
|
||||||
aria-label="show ? 'Hide password' : 'Show password'"
|
:aria-label="show ? 'Hide password' : 'Show password'"
|
||||||
:aria-pressed="show"
|
:aria-pressed="show"
|
||||||
aria-controls="password"
|
aria-controls="password"
|
||||||
@click="show = !show"
|
@click="show = !show"
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const password = ref('password')
|
|||||||
variant="link"
|
variant="link"
|
||||||
size="sm"
|
size="sm"
|
||||||
:icon="show ? 'i-lucide-eye-off' : 'i-lucide-eye'"
|
:icon="show ? 'i-lucide-eye-off' : 'i-lucide-eye'"
|
||||||
aria-label="show ? 'Hide password' : 'Show password'"
|
:aria-label="show ? 'Hide password' : 'Show password'"
|
||||||
:aria-pressed="show"
|
:aria-pressed="show"
|
||||||
aria-controls="password"
|
aria-controls="password"
|
||||||
@click="show = !show"
|
@click="show = !show"
|
||||||
|
|||||||
@@ -51,7 +51,8 @@ const items = [
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'GitHub'
|
label: 'GitHub',
|
||||||
|
icon: 'i-simple-icons-github'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -10,7 +10,13 @@ export function useSharedData() {
|
|||||||
icon: 'i-simple-icons-vuedotjs',
|
icon: 'i-simple-icons-vuedotjs',
|
||||||
value: 'vue',
|
value: 'vue',
|
||||||
disabled: module.value === 'ui-pro',
|
disabled: module.value === 'ui-pro',
|
||||||
onSelect: () => framework.value = 'vue'
|
onSelect: () => {
|
||||||
|
if (module.value === 'ui-pro') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
framework.value = 'vue'
|
||||||
|
}
|
||||||
}].map(f => ({ ...f, active: framework.value === f.value })))
|
}].map(f => ({ ...f, active: framework.value === f.value })))
|
||||||
|
|
||||||
const module = useCookie('nuxt-ui-module', { default: () => 'ui' })
|
const module = useCookie('nuxt-ui-module', { default: () => 'ui' })
|
||||||
@@ -24,7 +30,13 @@ export function useSharedData() {
|
|||||||
icon: 'i-lucide-panels-top-left',
|
icon: 'i-lucide-panels-top-left',
|
||||||
value: 'ui-pro',
|
value: 'ui-pro',
|
||||||
disabled: framework.value === 'vue',
|
disabled: framework.value === 'vue',
|
||||||
onSelect: () => module.value = 'ui-pro'
|
onSelect: () => {
|
||||||
|
if (framework.value === 'vue') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
module.value = 'ui-pro'
|
||||||
|
}
|
||||||
}].map(m => ({ ...m, active: module.value === m.value })))
|
}].map(m => ({ ...m, active: module.value === m.value })))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ provide('navigation', mappedNavigation)
|
|||||||
items: modules
|
items: modules
|
||||||
}]"
|
}]"
|
||||||
:navigation="filteredNavigation"
|
:navigation="filteredNavigation"
|
||||||
:fuse="{ resultLimit: 42 }"
|
:fuse="{ resultLimit: 100 }"
|
||||||
/>
|
/>
|
||||||
</ClientOnly>
|
</ClientOnly>
|
||||||
</UApp>
|
</UApp>
|
||||||
|
|||||||
@@ -87,12 +87,12 @@ const communityLinks = computed(() => [{
|
|||||||
to: `https://github.com/nuxt/${page.value?.module === 'ui-pro' ? 'ui-pro' : 'ui'}`,
|
to: `https://github.com/nuxt/${page.value?.module === 'ui-pro' ? 'ui-pro' : 'ui'}`,
|
||||||
target: '_blank'
|
target: '_blank'
|
||||||
}, {
|
}, {
|
||||||
icon: 'i-heroicons-lifebuoy',
|
icon: 'i-lucide-life-buoy',
|
||||||
label: 'Contribution',
|
label: 'Contribution',
|
||||||
to: '/getting-started/contribution'
|
to: '/getting-started/contribution'
|
||||||
}, {
|
}, {
|
||||||
label: 'Roadmap',
|
label: 'Roadmap',
|
||||||
icon: 'i-heroicons-map',
|
icon: 'i-lucide-map',
|
||||||
to: '/roadmap'
|
to: '/roadmap'
|
||||||
}])
|
}])
|
||||||
|
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ You can now use Nuxt UI in any Vue project without Nuxt by adding the Vite and V
|
|||||||
Learn how to install and configure Nuxt UI in a Vue project in the **Vue installation guide**.
|
Learn how to install and configure Nuxt UI in a Vue project in the **Vue installation guide**.
|
||||||
::
|
::
|
||||||
|
|
||||||
## Nuxt DevTools Integration
|
### Nuxt DevTools Integration
|
||||||
|
|
||||||
Nuxt UI is deeply integrated with Nuxt Devtools, providing a powerful development experience:
|
Nuxt UI is deeply integrated with Nuxt Devtools, providing a powerful development experience:
|
||||||
|
|
||||||
|
|||||||
@@ -214,6 +214,23 @@ export default defineNuxtConfig({
|
|||||||
This option adds the `transition-colors` class on components with hover or active states.
|
This option adds the `transition-colors` class on components with hover or active states.
|
||||||
::
|
::
|
||||||
|
|
||||||
|
### `devtools.enabled`
|
||||||
|
|
||||||
|
Use the `devtools.enabled` option to enable or disable the Nuxt UI devtools.
|
||||||
|
|
||||||
|
- Default: `true`{lang="ts-type"}
|
||||||
|
|
||||||
|
```ts [nuxt.config.ts]
|
||||||
|
export default defineNuxtConfig({
|
||||||
|
modules: ['@nuxt/ui'],
|
||||||
|
ui: {
|
||||||
|
devtools: {
|
||||||
|
enabled: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
## Continuous Releases
|
## Continuous Releases
|
||||||
|
|
||||||
Nuxt UI v3 uses [pkg.pr.new](https://github.com/stackblitz-labs/pkg.pr.new) for continuous preview releases, providing developers with instant access to the latest features and bug fixes without waiting for official releases.
|
Nuxt UI v3 uses [pkg.pr.new](https://github.com/stackblitz-labs/pkg.pr.new) for continuous preview releases, providing developers with instant access to the latest features and bug fixes without waiting for official releases.
|
||||||
|
|||||||
@@ -195,9 +195,13 @@ This will give you access to the following:
|
|||||||
| Name | Type |
|
| Name | Type |
|
||||||
| ---- | ---- |
|
| ---- | ---- |
|
||||||
| `submit()`{lang="ts-type"} | `Promise<void>`{lang="ts-type"} <br> <div class="text-[var(--ui-text-toned)] mt-1"><p>Triggers form submission.</p> |
|
| `submit()`{lang="ts-type"} | `Promise<void>`{lang="ts-type"} <br> <div class="text-[var(--ui-text-toned)] mt-1"><p>Triggers form submission.</p> |
|
||||||
| `validate(opts: { name?: string \| string[], silent?: boolean, nested?: boolean, transform?: boolean })`{lang="ts-type"} | `Promise<T>`{lang="ts-type"} <br> <div class="text-[var(--ui-text-toned)] mt-1"><p>Triggers form validation. Will raise any errors unless `opts.silent` is set to true.</p> |
|
| `validate(opts: { name?: keyof T \| (keyof T)[], silent?: boolean, nested?: boolean, transform?: boolean })`{lang="ts-type"} | `Promise<T>`{lang="ts-type"} <br> <div class="text-[var(--ui-text-toned)] mt-1"><p>Triggers form validation. Will raise any errors unless `opts.silent` is set to true.</p> |
|
||||||
| `clear(path?: string)`{lang="ts-type"} | `void` <br> <div class="text-[var(--ui-text-toned)] mt-1"><p>Clears form errors associated with a specific path. If no path is provided, clears all form errors.</p> |
|
| `clear(path?: keyof T)`{lang="ts-type"} | `void` <br> <div class="text-[var(--ui-text-toned)] mt-1"><p>Clears form errors associated with a specific path. If no path is provided, clears all form errors.</p> |
|
||||||
| `getErrors(path?: string)`{lang="ts-type"} | `FormError[]`{lang="ts-type"} <br> <div class="text-[var(--ui-text-toned)] mt-1"><p>Retrieves form errors associated with a specific path. If no path is provided, returns all form errors.</p></div> |
|
| `getErrors(path?: keyof T)`{lang="ts-type"} | `FormError[]`{lang="ts-type"} <br> <div class="text-[var(--ui-text-toned)] mt-1"><p>Retrieves form errors associated with a specific path. If no path is provided, returns all form errors.</p></div> |
|
||||||
| `setErrors(errors: FormError[], path?: string)`{lang="ts-type"} | `void` <br> <div class="text-[var(--ui-text-toned)] mt-1"><p>Sets form errors for a given path. If no path is provided, overrides all errors.</p> |
|
| `setErrors(errors: FormError[], path?: keyof T)`{lang="ts-type"} | `void` <br> <div class="text-[var(--ui-text-toned)] mt-1"><p>Sets form errors for a given path. If no path is provided, overrides all errors.</p> |
|
||||||
| `errors`{lang="ts-type"} | `Ref<FormError[]>`{lang="ts-type"} <br> <div class="text-[var(--ui-text-toned)] mt-1"><p>A reference to the array containing validation errors. Use this to access or manipulate the error information.</p> |
|
| `errors`{lang="ts-type"} | `Ref<FormError[]>`{lang="ts-type"} <br> <div class="text-[var(--ui-text-toned)] mt-1"><p>A reference to the array containing validation errors. Use this to access or manipulate the error information.</p> |
|
||||||
| `disabled`{lang="ts-type"} | `Ref<boolean>`{lang="ts-type"} |
|
| `disabled`{lang="ts-type"} | `Ref<boolean>`{lang="ts-type"} |
|
||||||
|
| `dirty`{lang="ts-type"} | `Ref<boolean>`{lang="ts-type"} `true` if at least one form field has been updated by the user.|
|
||||||
|
| `dirtyFields`{lang="ts-type"} | `DeepReadonly<Set<keyof T>>`{lang="ts-type"} Tracks fields that have been modified by the user. |
|
||||||
|
| `touchedFields`{lang="ts-type"} | `DeepReadonly<Set<keyof T>>`{lang="ts-type"} Tracks fields that the user interacted with. |
|
||||||
|
| `blurredFields`{lang="ts-type"} | `DeepReadonly<Set<keyof T>>`{lang="ts-type"} Tracks fields blurred by the user. |
|
||||||
|
|||||||
@@ -613,6 +613,85 @@ props:
|
|||||||
The arrow is animated to follow the active item.
|
The arrow is animated to follow the active item.
|
||||||
::
|
::
|
||||||
|
|
||||||
|
### Content Orientation
|
||||||
|
|
||||||
|
Use the `content-orientation` prop to change the orientation of the content.
|
||||||
|
|
||||||
|
::warning
|
||||||
|
This prop only works when `orientation` is `horizontal`.
|
||||||
|
::
|
||||||
|
|
||||||
|
::component-code
|
||||||
|
---
|
||||||
|
collapse: true
|
||||||
|
ignore:
|
||||||
|
- items
|
||||||
|
- arrow
|
||||||
|
- class
|
||||||
|
external:
|
||||||
|
- items
|
||||||
|
props:
|
||||||
|
arrow: true
|
||||||
|
contentOrientation: 'vertical'
|
||||||
|
items:
|
||||||
|
- label: Guide
|
||||||
|
icon: i-lucide-book-open
|
||||||
|
to: /getting-started
|
||||||
|
children:
|
||||||
|
- label: Introduction
|
||||||
|
description: Fully styled and customizable components for Nuxt.
|
||||||
|
icon: i-lucide-house
|
||||||
|
- label: Installation
|
||||||
|
description: Learn how to install and configure Nuxt UI in your application.
|
||||||
|
icon: i-lucide-cloud-download
|
||||||
|
- label: 'Icons'
|
||||||
|
icon: 'i-lucide-smile'
|
||||||
|
description: 'You have nothing to do, @nuxt/icon will handle it automatically.'
|
||||||
|
- label: Composables
|
||||||
|
icon: i-lucide-database
|
||||||
|
to: /composables
|
||||||
|
children:
|
||||||
|
- label: defineShortcuts
|
||||||
|
icon: i-lucide-file-text
|
||||||
|
description: Define shortcuts for your application.
|
||||||
|
to: /composables/define-shortcuts
|
||||||
|
- label: useModal
|
||||||
|
icon: i-lucide-file-text
|
||||||
|
description: Display a modal within your application.
|
||||||
|
to: /composables/use-modal
|
||||||
|
- label: useSlideover
|
||||||
|
icon: i-lucide-file-text
|
||||||
|
description: Display a slideover within your application.
|
||||||
|
to: /composables/use-slideover
|
||||||
|
- label: useToast
|
||||||
|
icon: i-lucide-file-text
|
||||||
|
description: Display a toast within your application.
|
||||||
|
to: /composables/use-toast
|
||||||
|
- label: Components
|
||||||
|
icon: i-lucide-box
|
||||||
|
to: /components
|
||||||
|
active: true
|
||||||
|
children:
|
||||||
|
- label: Link
|
||||||
|
icon: i-lucide-file-text
|
||||||
|
description: Use NuxtLink with superpowers.
|
||||||
|
to: /components/link
|
||||||
|
- label: Modal
|
||||||
|
icon: i-lucide-file-text
|
||||||
|
description: Display a modal within your application.
|
||||||
|
to: /components/modal
|
||||||
|
- label: NavigationMenu
|
||||||
|
icon: i-lucide-file-text
|
||||||
|
description: Display a list of links.
|
||||||
|
to: /components/navigation-menu
|
||||||
|
- label: Pagination
|
||||||
|
icon: i-lucide-file-text
|
||||||
|
description: Display a list of pages.
|
||||||
|
to: /components/pagination
|
||||||
|
class: 'w-full justify-center'
|
||||||
|
---
|
||||||
|
::
|
||||||
|
|
||||||
### Unmount
|
### Unmount
|
||||||
|
|
||||||
Use the `unmount-on-hide` prop to control the content unmounting behavior. Defaults to `true`.
|
Use the `unmount-on-hide` prop to control the content unmounting behavior. Defaults to `true`.
|
||||||
|
|||||||
@@ -77,6 +77,10 @@ Use the `columns` prop as an array of [ColumnDef](https://tanstack.com/table/lat
|
|||||||
- `accessorKey`: [The key of the row object to use when extracting the value for the column.]{class="text-[var(--ui-text-muted)]"}
|
- `accessorKey`: [The key of the row object to use when extracting the value for the column.]{class="text-[var(--ui-text-muted)]"}
|
||||||
- `header`: [The header to display for the column. If a string is passed, it can be used as a default for the column ID. If a function is passed, it will be passed a props object for the header and should return the rendered header value (the exact type depends on the adapter being used).]{class="text-[var(--ui-text-muted)]"}
|
- `header`: [The header to display for the column. If a string is passed, it can be used as a default for the column ID. If a function is passed, it will be passed a props object for the header and should return the rendered header value (the exact type depends on the adapter being used).]{class="text-[var(--ui-text-muted)]"}
|
||||||
- `cell`: [The cell to display each row for the column. If a function is passed, it will be passed a props object for the cell and should return the rendered cell value (the exact type depends on the adapter being used).]{class="text-[var(--ui-text-muted)]"}
|
- `cell`: [The cell to display each row for the column. If a function is passed, it will be passed a props object for the cell and should return the rendered cell value (the exact type depends on the adapter being used).]{class="text-[var(--ui-text-muted)]"}
|
||||||
|
- `meta`: [Extra properties for the column.]{class="text-[var(--ui-text-muted)]"}
|
||||||
|
- `class`:
|
||||||
|
- `td`: [The classes to apply to the `td` element.]{class="text-[var(--ui-text-muted)]"}
|
||||||
|
- `th`: [The classes to apply to the `th` element.]{class="text-[var(--ui-text-muted)]"}
|
||||||
|
|
||||||
In order to render components or other HTML elements, you will need to use the Vue [`h` function](https://vuejs.org/api/render-function.html#h) inside the `header` and `cell` props. This is different from other components that use slots but allows for more flexibility.
|
In order to render components or other HTML elements, you will need to use the Vue [`h` function](https://vuejs.org/api/render-function.html#h) inside the `header` and `cell` props. This is different from other components that use slots but allows for more flexibility.
|
||||||
|
|
||||||
|
|||||||
@@ -4,21 +4,21 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@iconify-json/logos": "^1.2.4",
|
"@iconify-json/logos": "^1.2.4",
|
||||||
"@iconify-json/lucide": "^1.2.22",
|
"@iconify-json/lucide": "^1.2.25",
|
||||||
"@iconify-json/simple-icons": "^1.2.19",
|
"@iconify-json/simple-icons": "^1.2.22",
|
||||||
"@iconify-json/vscode-icons": "^1.2.10",
|
"@iconify-json/vscode-icons": "^1.2.10",
|
||||||
"@nuxt/content": "^3.0.0",
|
"@nuxt/content": "^3.0.1",
|
||||||
"@nuxt/image": "^1.9.0",
|
"@nuxt/image": "^1.9.0",
|
||||||
"@nuxt/ui": "latest",
|
"@nuxt/ui": "latest",
|
||||||
"@nuxt/ui-pro": "https://pkg.pr.new/@nuxt/ui-pro@2dbbff0",
|
"@nuxt/ui-pro": "https://pkg.pr.new/@nuxt/ui-pro@880e49c",
|
||||||
"@nuxthub/core": "^0.8.11",
|
"@nuxthub/core": "^0.8.14",
|
||||||
"@nuxtjs/plausible": "^1.2.0",
|
"@nuxtjs/plausible": "^1.2.0",
|
||||||
"@octokit/rest": "^21.1.0",
|
"@octokit/rest": "^21.1.0",
|
||||||
"@vueuse/nuxt": "^12.4.0",
|
"@vueuse/nuxt": "^12.5.0",
|
||||||
"joi": "^17.13.3",
|
"joi": "^17.13.3",
|
||||||
"nuxt": "^3.15.1",
|
"nuxt": "^3.15.3",
|
||||||
"nuxt-component-meta": "^0.9.0",
|
"nuxt-component-meta": "^0.10.0",
|
||||||
"nuxt-og-image": "^4.0.2",
|
"nuxt-og-image": "^4.1.2",
|
||||||
"prettier": "^3.4.2",
|
"prettier": "^3.4.2",
|
||||||
"shiki-transformer-color-highlight": "^0.2.0",
|
"shiki-transformer-color-highlight": "^0.2.0",
|
||||||
"superstruct": "^2.0.2",
|
"superstruct": "^2.0.2",
|
||||||
@@ -28,6 +28,6 @@
|
|||||||
"zod": "^3.24.1"
|
"zod": "^3.24.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"wrangler": "^3.101.0"
|
"wrangler": "^3.105.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
42
package.json
42
package.json
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "@nuxt/ui",
|
"name": "@nuxt/ui",
|
||||||
"description": "A UI Library for Modern Web Apps, powered by Vue & Tailwind CSS.",
|
"description": "A UI Library for Modern Web Apps, powered by Vue & Tailwind CSS.",
|
||||||
"version": "3.0.0-alpha.11",
|
"version": "3.0.0-alpha.12",
|
||||||
"packageManager": "pnpm@9.15.3",
|
"packageManager": "pnpm@9.15.4",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/nuxt/ui.git"
|
"url": "git+https://github.com/nuxt/ui.git"
|
||||||
@@ -60,7 +60,7 @@
|
|||||||
"dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground && nuxi prepare docs && nuxi prepare devtools && vite build playground-vue",
|
"dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground && nuxi prepare docs && nuxi prepare devtools && vite build playground-vue",
|
||||||
"devtools": "NUXT_UI_DEVTOOLS_LOCAL=true nuxi dev playground",
|
"devtools": "NUXT_UI_DEVTOOLS_LOCAL=true nuxi dev playground",
|
||||||
"devtools:build": "nuxi generate devtools",
|
"devtools:build": "nuxi generate devtools",
|
||||||
"devtools:prepare": "nuxt-component-meta playground --outputDir ../src/devtools/.component-meta/",
|
"devtools:prepare": "DEVTOOLS=true nuxt-component-meta playground --outputDir ../src/devtools/.component-meta/",
|
||||||
"docs": "DEV=true nuxi dev docs",
|
"docs": "DEV=true nuxi dev docs",
|
||||||
"docs:build": "nuxi build docs",
|
"docs:build": "nuxi build docs",
|
||||||
"docs:prepare": "nuxt-component-meta docs",
|
"docs:prepare": "nuxt-component-meta docs",
|
||||||
@@ -74,22 +74,22 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@iconify/vue": "^4.3.0",
|
"@iconify/vue": "^4.3.0",
|
||||||
"@internationalized/date": "^3.6.0",
|
"@internationalized/date": "^3.7.0",
|
||||||
"@internationalized/number": "^3.6.0",
|
"@internationalized/number": "^3.6.0",
|
||||||
"@nuxt/devtools-kit": "^1.7.0",
|
"@nuxt/devtools-kit": "^1.7.0",
|
||||||
"@nuxt/fonts": "^0.10.3",
|
"@nuxt/fonts": "^0.10.3",
|
||||||
"@nuxt/icon": "^1.10.3",
|
"@nuxt/icon": "^1.10.3",
|
||||||
"@nuxt/kit": "^3.15.1",
|
"@nuxt/kit": "^3.15.3",
|
||||||
"@nuxt/schema": "^3.15.1",
|
"@nuxt/schema": "^3.15.3",
|
||||||
"@nuxtjs/color-mode": "^3.5.2",
|
"@nuxtjs/color-mode": "^3.5.2",
|
||||||
"@tailwindcss/postcss": "4.0.0-beta.9",
|
"@tailwindcss/postcss": "4.0.0",
|
||||||
"@tailwindcss/vite": "4.0.0-beta.9",
|
"@tailwindcss/vite": "4.0.0",
|
||||||
"@tanstack/vue-table": "^8.20.5",
|
"@tanstack/vue-table": "^8.20.5",
|
||||||
"@unhead/vue": "^1.11.16",
|
"@unhead/vue": "^1.11.18",
|
||||||
"@vueuse/core": "^12.4.0",
|
"@vueuse/core": "^12.5.0",
|
||||||
"@vueuse/integrations": "^12.4.0",
|
"@vueuse/integrations": "^12.5.0",
|
||||||
"colortranslator": "^4.1.0",
|
"colortranslator": "^4.1.0",
|
||||||
"consola": "^3.3.3",
|
"consola": "^3.4.0",
|
||||||
"defu": "^6.1.4",
|
"defu": "^6.1.4",
|
||||||
"embla-carousel-auto-height": "^8.5.2",
|
"embla-carousel-auto-height": "^8.5.2",
|
||||||
"embla-carousel-auto-scroll": "^8.5.2",
|
"embla-carousel-auto-scroll": "^8.5.2",
|
||||||
@@ -104,12 +104,12 @@
|
|||||||
"magic-string": "^0.30.17",
|
"magic-string": "^0.30.17",
|
||||||
"mlly": "^1.7.4",
|
"mlly": "^1.7.4",
|
||||||
"ohash": "^1.1.4",
|
"ohash": "^1.1.4",
|
||||||
"pathe": "^2.0.1",
|
"pathe": "^2.0.2",
|
||||||
"reka-ui": "1.0.0-alpha.8",
|
"reka-ui": "1.0.0-alpha.8",
|
||||||
"scule": "^1.3.0",
|
"scule": "^1.3.0",
|
||||||
"sirv": "^3.0.0",
|
"sirv": "^3.0.0",
|
||||||
"tailwind-variants": "^0.3.0",
|
"tailwind-variants": "^0.3.1",
|
||||||
"tailwindcss": "4.0.0-beta.9",
|
"tailwindcss": "4.0.0",
|
||||||
"tinyglobby": "^0.2.10",
|
"tinyglobby": "^0.2.10",
|
||||||
"unplugin": "^2.1.2",
|
"unplugin": "^2.1.2",
|
||||||
"unplugin-auto-import": "^19.0.0",
|
"unplugin-auto-import": "^19.0.0",
|
||||||
@@ -121,19 +121,19 @@
|
|||||||
"@nuxt/module-builder": "^0.8.4",
|
"@nuxt/module-builder": "^0.8.4",
|
||||||
"@nuxt/test-utils": "^3.15.4",
|
"@nuxt/test-utils": "^3.15.4",
|
||||||
"@release-it/conventional-changelog": "^10.0.0",
|
"@release-it/conventional-changelog": "^10.0.0",
|
||||||
"@standard-schema/spec": "1.0.0-rc.0",
|
"@standard-schema/spec": "1.0.0",
|
||||||
"@vue/test-utils": "^2.4.6",
|
"@vue/test-utils": "^2.4.6",
|
||||||
"embla-carousel": "^8.5.2",
|
"embla-carousel": "^8.5.2",
|
||||||
"eslint": "^9.18.0",
|
"eslint": "^9.19.0",
|
||||||
"happy-dom": "^15.7.4",
|
"happy-dom": "^15.7.4",
|
||||||
"joi": "^17.13.3",
|
"joi": "^17.13.3",
|
||||||
"knitwork": "^1.2.0",
|
"knitwork": "^1.2.0",
|
||||||
"nuxt": "^3.15.1",
|
"nuxt": "^3.15.3",
|
||||||
"nuxt-component-meta": "^0.9.0",
|
"nuxt-component-meta": "^0.10.0",
|
||||||
"release-it": "^18.1.1",
|
"release-it": "^18.1.2",
|
||||||
"superstruct": "^2.0.2",
|
"superstruct": "^2.0.2",
|
||||||
"valibot": "^0.42.1",
|
"valibot": "^0.42.1",
|
||||||
"vitest": "^2.1.8",
|
"vitest": "^3.0.4",
|
||||||
"vitest-environment-nuxt": "^1.0.1",
|
"vitest-environment-nuxt": "^1.0.1",
|
||||||
"vue-tsc": "^2.2.0",
|
"vue-tsc": "^2.2.0",
|
||||||
"yup": "^1.6.1",
|
"yup": "^1.6.1",
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
"typescript": "^5.7.2",
|
"typescript": "^5.7.2",
|
||||||
"unplugin-auto-import": "^19.0.0",
|
"unplugin-auto-import": "^19.0.0",
|
||||||
"unplugin-vue-components": "^28.0.0",
|
"unplugin-vue-components": "^28.0.0",
|
||||||
"vite": "^6.0.7",
|
"vite": "^6.0.11",
|
||||||
"vue-tsc": "^2.2.0"
|
"vue-tsc": "^2.2.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,13 @@ import theme from '#build/ui/navigation-menu'
|
|||||||
const colors = Object.keys(theme.variants.color)
|
const colors = Object.keys(theme.variants.color)
|
||||||
const variants = Object.keys(theme.variants.variant)
|
const variants = Object.keys(theme.variants.variant)
|
||||||
const orientations = Object.keys(theme.variants.orientation)
|
const orientations = Object.keys(theme.variants.orientation)
|
||||||
|
const contentOrientations = Object.keys(theme.variants.contentOrientation)
|
||||||
|
|
||||||
const color = ref(theme.defaultVariants.color)
|
const color = ref(theme.defaultVariants.color)
|
||||||
const highlightColor = ref()
|
const highlightColor = ref()
|
||||||
const variant = ref(theme.defaultVariants.variant)
|
const variant = ref(theme.defaultVariants.variant)
|
||||||
const orientation = ref('vertical' as const)
|
const orientation = ref('horizontal' as const)
|
||||||
|
const contentOrientation = ref('horizontal' as const)
|
||||||
const highlight = ref(true)
|
const highlight = ref(true)
|
||||||
const collapsed = ref(false)
|
const collapsed = ref(false)
|
||||||
|
|
||||||
@@ -93,6 +95,7 @@ const items = [
|
|||||||
<USelect v-model="color" :items="colors" placeholder="Color" />
|
<USelect v-model="color" :items="colors" placeholder="Color" />
|
||||||
<USelect v-model="variant" :items="variants" placeholder="Variant" />
|
<USelect v-model="variant" :items="variants" placeholder="Variant" />
|
||||||
<USelect v-model="orientation" :items="orientations" placeholder="Orientation" />
|
<USelect v-model="orientation" :items="orientations" placeholder="Orientation" />
|
||||||
|
<USelect v-model="contentOrientation" :items="contentOrientations" placeholder="Content orientation" />
|
||||||
<USwitch v-model="collapsed" label="Collapsed" />
|
<USwitch v-model="collapsed" label="Collapsed" />
|
||||||
<USwitch v-model="highlight" label="Highlight" />
|
<USwitch v-model="highlight" label="Highlight" />
|
||||||
<USelect v-model="highlightColor" :items="colors" placeholder="Highlight color" />
|
<USelect v-model="highlightColor" :items="colors" placeholder="Highlight color" />
|
||||||
@@ -105,6 +108,7 @@ const items = [
|
|||||||
:color="color"
|
:color="color"
|
||||||
:variant="variant"
|
:variant="variant"
|
||||||
:orientation="orientation"
|
:orientation="orientation"
|
||||||
|
:viewport-orientation="contentOrientation"
|
||||||
:highlight="highlight"
|
:highlight="highlight"
|
||||||
:highlight-color="highlightColor"
|
:highlight-color="highlightColor"
|
||||||
:class="highlight && 'data-[orientation=horizontal]:border-b border-[var(--ui-border)]'"
|
:class="highlight && 'data-[orientation=horizontal]:border-b border-[var(--ui-border)]'"
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export default defineNuxtConfig({
|
|||||||
},
|
},
|
||||||
|
|
||||||
ui: {
|
ui: {
|
||||||
fonts: false
|
fonts: !process.env.DEVTOOLS
|
||||||
},
|
},
|
||||||
|
|
||||||
future: {
|
future: {
|
||||||
|
|||||||
@@ -8,10 +8,10 @@
|
|||||||
"generate": "nuxi generate"
|
"generate": "nuxi generate"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@iconify-json/lucide": "^1.2.22",
|
"@iconify-json/lucide": "^1.2.25",
|
||||||
"@iconify-json/simple-icons": "^1.2.19",
|
"@iconify-json/simple-icons": "^1.2.22",
|
||||||
"@nuxt/ui": "latest",
|
"@nuxt/ui": "latest",
|
||||||
"@nuxthub/core": "^0.8.11",
|
"@nuxthub/core": "^0.8.14",
|
||||||
"nuxt": "^3.15.1"
|
"nuxt": "^3.15.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
4510
pnpm-lock.yaml
generated
4510
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -26,5 +26,6 @@
|
|||||||
}, {
|
}, {
|
||||||
"matchDepTypes": ["resolutions"],
|
"matchDepTypes": ["resolutions"],
|
||||||
"enabled": false
|
"enabled": false
|
||||||
}]
|
}],
|
||||||
|
"postUpdateOptions": ["pnpmDedupe"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
|||||||
import { tv } from '../utils/tv'
|
import { tv } from '../utils/tv'
|
||||||
import type { DynamicSlots } from '../types/utils'
|
import type { DynamicSlots } from '../types/utils'
|
||||||
|
|
||||||
const appConfig = _appConfig as AppConfig & { ui: { accordion: Partial<typeof theme> } }
|
const appConfigAccordion = _appConfig as AppConfig & { ui: { accordion: Partial<typeof theme> } }
|
||||||
|
|
||||||
const accordion = tv({ extend: tv(theme), ...(appConfig.ui?.accordion || {}) })
|
const accordion = tv({ extend: tv(theme), ...(appConfigAccordion.ui?.accordion || {}) })
|
||||||
|
|
||||||
export interface AccordionItem {
|
export interface AccordionItem {
|
||||||
label?: string
|
label?: string
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
|||||||
import { tv } from '../utils/tv'
|
import { tv } from '../utils/tv'
|
||||||
import type { AvatarProps, ButtonProps } from '../types'
|
import type { AvatarProps, ButtonProps } from '../types'
|
||||||
|
|
||||||
const appConfig = _appConfig as AppConfig & { ui: { alert: Partial<typeof theme> } }
|
const appConfigAlert = _appConfig as AppConfig & { ui: { alert: Partial<typeof theme> } }
|
||||||
|
|
||||||
const alert = tv({ extend: tv(theme), ...(appConfig.ui?.alert || {}) })
|
const alert = tv({ extend: tv(theme), ...(appConfigAlert.ui?.alert || {}) })
|
||||||
|
|
||||||
type AlertVariants = VariantProps<typeof alert>
|
type AlertVariants = VariantProps<typeof alert>
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import theme from '#build/ui/avatar'
|
|||||||
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
||||||
import { tv } from '../utils/tv'
|
import { tv } from '../utils/tv'
|
||||||
|
|
||||||
const appConfig = _appConfig as AppConfig & { ui: { avatar: Partial<typeof theme> } }
|
const appConfigAvatar = _appConfig as AppConfig & { ui: { avatar: Partial<typeof theme> } }
|
||||||
|
|
||||||
const avatar = tv({ extend: tv(theme), ...(appConfig.ui?.avatar || {}) })
|
const avatar = tv({ extend: tv(theme), ...(appConfigAvatar.ui?.avatar || {}) })
|
||||||
|
|
||||||
type AvatarVariants = VariantProps<typeof avatar>
|
type AvatarVariants = VariantProps<typeof avatar>
|
||||||
|
|
||||||
@@ -36,21 +36,24 @@ extendDevtoolsMeta<AvatarProps>({ defaultProps: { src: 'https://avatars.githubus
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue'
|
import { ref, computed, useAttrs, onMounted } from 'vue'
|
||||||
import { AvatarRoot, AvatarImage, AvatarFallback, useForwardProps } from 'reka-ui'
|
import { AvatarRoot, AvatarFallback, useForwardProps } from 'reka-ui'
|
||||||
import { reactivePick } from '@vueuse/core'
|
import { reactivePick, useImage } from '@vueuse/core'
|
||||||
|
import ImageComponent from '#build/ui-image-component'
|
||||||
import { useAvatarGroup } from '../composables/useAvatarGroup'
|
import { useAvatarGroup } from '../composables/useAvatarGroup'
|
||||||
import UIcon from './Icon.vue'
|
import UIcon from './Icon.vue'
|
||||||
import ImageComponent from '#build/ui-image-component'
|
|
||||||
|
|
||||||
defineOptions({ inheritAttrs: false })
|
defineOptions({ inheritAttrs: false })
|
||||||
|
|
||||||
const props = withDefaults(defineProps<AvatarProps>(), { as: 'span' })
|
const props = withDefaults(defineProps<AvatarProps>(), { as: 'span' })
|
||||||
|
const attrs = useAttrs()
|
||||||
|
|
||||||
const fallbackProps = useForwardProps(reactivePick(props, 'delayMs'))
|
const fallbackProps = useForwardProps(reactivePick(props, 'delayMs'))
|
||||||
|
|
||||||
const fallback = computed(() => props.text || (props.alt || '').split(' ').map(word => word.charAt(0)).join('').substring(0, 2))
|
const fallback = computed(() => props.text || (props.alt || '').split(' ').map(word => word.charAt(0)).join('').substring(0, 2))
|
||||||
|
|
||||||
|
const imageLoaded = ref(false)
|
||||||
|
|
||||||
const { size } = useAvatarGroup(props)
|
const { size } = useAvatarGroup(props)
|
||||||
|
|
||||||
// eslint-disable-next-line vue/no-dupe-keys
|
// eslint-disable-next-line vue/no-dupe-keys
|
||||||
@@ -69,22 +72,40 @@ const sizePx = computed(() => ({
|
|||||||
'2xl': 44,
|
'2xl': 44,
|
||||||
'3xl': 48
|
'3xl': 48
|
||||||
})[props.size || 'md'])
|
})[props.size || 'md'])
|
||||||
|
|
||||||
|
// Reproduces Reka UI's [AvatarImage](https://reka-ui.com/docs/components/avatar#image) component behavior which cannot be used with NuxtImg component
|
||||||
|
onMounted(() => {
|
||||||
|
if (!props.src || (ImageComponent as unknown as string) !== 'img') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { then } = useImage({ ...props, ...attrs, src: props.src! })
|
||||||
|
|
||||||
|
then((img) => {
|
||||||
|
if (img.isReady.value) {
|
||||||
|
imageLoaded.value = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<AvatarRoot :as="as" :class="ui.root({ class: [props.class, props.ui?.root] })">
|
<AvatarRoot :as="as" :class="ui.root({ class: [props.class, props.ui?.root] })">
|
||||||
<AvatarImage
|
<component
|
||||||
|
:is="ImageComponent"
|
||||||
v-if="src"
|
v-if="src"
|
||||||
:as="ImageComponent"
|
v-show="imageLoaded"
|
||||||
|
role="img"
|
||||||
:src="src"
|
:src="src"
|
||||||
:alt="alt"
|
:alt="alt"
|
||||||
:width="sizePx"
|
:width="sizePx"
|
||||||
:height="sizePx"
|
:height="sizePx"
|
||||||
v-bind="$attrs"
|
v-bind="attrs"
|
||||||
:class="ui.image({ class: props.ui?.image })"
|
:class="ui.image({ class: props.ui?.image })"
|
||||||
|
@load="imageLoaded = true"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<AvatarFallback as-child v-bind="{ ...fallbackProps, ...$attrs }">
|
<AvatarFallback v-if="!imageLoaded" as-child v-bind="{ ...fallbackProps, ...$attrs }">
|
||||||
<slot>
|
<slot>
|
||||||
<UIcon v-if="icon" :name="icon" :class="ui.icon({ class: props.ui?.icon })" />
|
<UIcon v-if="icon" :name="icon" :class="ui.icon({ class: props.ui?.icon })" />
|
||||||
<span v-else :class="ui.fallback({ class: props.ui?.fallback })">{{ fallback || ' ' }}</span>
|
<span v-else :class="ui.fallback({ class: props.ui?.fallback })">{{ fallback || ' ' }}</span>
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ import theme from '#build/ui/avatar-group'
|
|||||||
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
||||||
import { tv } from '../utils/tv'
|
import { tv } from '../utils/tv'
|
||||||
|
|
||||||
const appConfig = _appConfig as AppConfig & { ui: { avatarGroup: Partial<typeof theme> } }
|
const appConfigAvatarGroup = _appConfig as AppConfig & { ui: { avatarGroup: Partial<typeof theme> } }
|
||||||
|
|
||||||
const avatarGroup = tv({ extend: tv(theme), ...(appConfig.ui?.avatarGroup || {}) })
|
const avatarGroup = tv({ extend: tv(theme), ...(appConfigAvatarGroup.ui?.avatarGroup || {}) })
|
||||||
|
|
||||||
type AvatarGroupVariants = VariantProps<typeof avatarGroup>
|
type AvatarGroupVariants = VariantProps<typeof avatarGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ import type { UseComponentIconsProps } from '../composables/useComponentIcons'
|
|||||||
import { tv } from '../utils/tv'
|
import { tv } from '../utils/tv'
|
||||||
import type { AvatarProps } from '../types'
|
import type { AvatarProps } from '../types'
|
||||||
|
|
||||||
const appConfig = _appConfig as AppConfig & { ui: { badge: Partial<typeof theme> } }
|
const appConfigBadge = _appConfig as AppConfig & { ui: { badge: Partial<typeof theme> } }
|
||||||
|
|
||||||
const badge = tv({ extend: tv(theme), ...(appConfig.ui?.badge || {}) })
|
const badge = tv({ extend: tv(theme), ...(appConfigBadge.ui?.badge || {}) })
|
||||||
|
|
||||||
type BadgeVariants = VariantProps<typeof badge>
|
type BadgeVariants = VariantProps<typeof badge>
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import { tv } from '../utils/tv'
|
|||||||
import type { AvatarProps, LinkProps } from '../types'
|
import type { AvatarProps, LinkProps } from '../types'
|
||||||
import type { DynamicSlots, PartialString } from '../types/utils'
|
import type { DynamicSlots, PartialString } from '../types/utils'
|
||||||
|
|
||||||
const appConfig = _appConfig as AppConfig & { ui: { breadcrumb: Partial<typeof theme> } }
|
const appConfigBreadcrumb = _appConfig as AppConfig & { ui: { breadcrumb: Partial<typeof theme> } }
|
||||||
|
|
||||||
const breadcrumb = tv({ extend: tv(theme), ...(appConfig.ui?.breadcrumb || {}) })
|
const breadcrumb = tv({ extend: tv(theme), ...(appConfigBreadcrumb.ui?.breadcrumb || {}) })
|
||||||
|
|
||||||
export interface BreadcrumbItem extends Omit<LinkProps, 'raw' | 'custom'> {
|
export interface BreadcrumbItem extends Omit<LinkProps, 'raw' | 'custom'> {
|
||||||
label?: string
|
label?: string
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ import { tv } from '../utils/tv'
|
|||||||
import type { AvatarProps } from '../types'
|
import type { AvatarProps } from '../types'
|
||||||
import type { PartialString } from '../types/utils'
|
import type { PartialString } from '../types/utils'
|
||||||
|
|
||||||
const appConfig = _appConfig as AppConfig & { ui: { button: Partial<typeof theme> } }
|
const appConfigButton = _appConfig as AppConfig & { ui: { button: Partial<typeof theme> } }
|
||||||
|
|
||||||
const button = tv({ extend: tv(theme), ...(appConfig.ui?.button || {}) })
|
const button = tv({ extend: tv(theme), ...(appConfigButton.ui?.button || {}) })
|
||||||
|
|
||||||
type ButtonVariants = VariantProps<typeof button>
|
type ButtonVariants = VariantProps<typeof button>
|
||||||
|
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ import theme from '#build/ui/button-group'
|
|||||||
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
||||||
import { tv } from '../utils/tv'
|
import { tv } from '../utils/tv'
|
||||||
|
|
||||||
const appConfig = _appConfig as AppConfig & { ui: { buttonGroup: Partial<typeof theme> } }
|
const appConfigButtonGroup = _appConfig as AppConfig & { ui: { buttonGroup: Partial<typeof theme> } }
|
||||||
|
|
||||||
const buttonGroup = tv({ extend: tv(theme), ...(appConfig.ui?.buttonGroup) })
|
const buttonGroup = tv({ extend: tv(theme), ...(appConfigButtonGroup.ui?.buttonGroup) })
|
||||||
|
|
||||||
type ButtonGroupVariants = VariantProps<typeof buttonGroup>
|
type ButtonGroupVariants = VariantProps<typeof buttonGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import _appConfig from '#build/app.config'
|
|||||||
import theme from '#build/ui/calendar'
|
import theme from '#build/ui/calendar'
|
||||||
import { tv } from '../utils/tv'
|
import { tv } from '../utils/tv'
|
||||||
|
|
||||||
const appConfig = _appConfig as AppConfig & { ui: { calendar: Partial<typeof theme> } }
|
const appConfigCalendar = _appConfig as AppConfig & { ui: { calendar: Partial<typeof theme> } }
|
||||||
|
|
||||||
const calendar = tv({ extend: tv(theme), ...(appConfig.ui?.calendar || {}) })
|
const calendar = tv({ extend: tv(theme), ...(appConfigCalendar.ui?.calendar || {}) })
|
||||||
|
|
||||||
type CalendarVariants = VariantProps<typeof calendar>
|
type CalendarVariants = VariantProps<typeof calendar>
|
||||||
|
|
||||||
@@ -77,6 +77,7 @@ import { computed } from 'vue'
|
|||||||
import { useForwardPropsEmits } from 'reka-ui'
|
import { useForwardPropsEmits } from 'reka-ui'
|
||||||
import { Calendar as SingleCalendar, RangeCalendar } from 'reka-ui/namespaced'
|
import { Calendar as SingleCalendar, RangeCalendar } from 'reka-ui/namespaced'
|
||||||
import { reactiveOmit } from '@vueuse/core'
|
import { reactiveOmit } from '@vueuse/core'
|
||||||
|
import { useAppConfig } from '#imports'
|
||||||
import { useLocale } from '../composables/useLocale'
|
import { useLocale } from '../composables/useLocale'
|
||||||
import UButton from './Button.vue'
|
import UButton from './Button.vue'
|
||||||
|
|
||||||
@@ -88,6 +89,7 @@ const props = withDefaults(defineProps<CalendarProps<R, M>>(), {
|
|||||||
const emits = defineEmits<CalendarEmits<R, M>>()
|
const emits = defineEmits<CalendarEmits<R, M>>()
|
||||||
defineSlots<CalendarSlots>()
|
defineSlots<CalendarSlots>()
|
||||||
|
|
||||||
|
const appConfig = useAppConfig()
|
||||||
const { code: locale, dir, t } = useLocale()
|
const { code: locale, dir, t } = useLocale()
|
||||||
|
|
||||||
const rootProps = useForwardPropsEmits(reactiveOmit(props, 'range', 'modelValue', 'defaultValue', 'color', 'size', 'monthControls', 'yearControls', 'class', 'ui'), emits)
|
const rootProps = useForwardPropsEmits(reactiveOmit(props, 'range', 'modelValue', 'defaultValue', 'color', 'size', 'monthControls', 'yearControls', 'class', 'ui'), emits)
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import theme from '#build/ui/card'
|
|||||||
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
||||||
import { tv } from '../utils/tv'
|
import { tv } from '../utils/tv'
|
||||||
|
|
||||||
const appConfig = _appConfig as AppConfig & { ui: { card: Partial<typeof theme> } }
|
const appConfigCard = _appConfig as AppConfig & { ui: { card: Partial<typeof theme> } }
|
||||||
|
|
||||||
const card = tv({ extend: tv(theme), ...(appConfig.ui?.card || {}) })
|
const card = tv({ extend: tv(theme), ...(appConfigCard.ui?.card || {}) })
|
||||||
|
|
||||||
export interface CardProps {
|
export interface CardProps {
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -16,9 +16,9 @@ import { tv } from '../utils/tv'
|
|||||||
import type { ButtonProps } from '../types'
|
import type { ButtonProps } from '../types'
|
||||||
import type { PartialString } from '../types/utils'
|
import type { PartialString } from '../types/utils'
|
||||||
|
|
||||||
const appConfig = _appConfig as AppConfig & { ui: { carousel: Partial<typeof theme> } }
|
const appConfigCarousel = _appConfig as AppConfig & { ui: { carousel: Partial<typeof theme> } }
|
||||||
|
|
||||||
const carousel = tv({ extend: tv(theme), ...(appConfig.ui?.carousel || {}) })
|
const carousel = tv({ extend: tv(theme), ...(appConfigCarousel.ui?.carousel || {}) })
|
||||||
|
|
||||||
type CarouselVariants = VariantProps<typeof carousel>
|
type CarouselVariants = VariantProps<typeof carousel>
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import theme from '#build/ui/checkbox'
|
|||||||
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
||||||
import { tv } from '../utils/tv'
|
import { tv } from '../utils/tv'
|
||||||
|
|
||||||
const appConfig = _appConfig as AppConfig & { ui: { checkbox: Partial<typeof theme> } }
|
const appConfigCheckbox = _appConfig as AppConfig & { ui: { checkbox: Partial<typeof theme> } }
|
||||||
|
|
||||||
const checkbox = tv({ extend: tv(theme), ...(appConfig.ui?.checkbox || {}) })
|
const checkbox = tv({ extend: tv(theme), ...(appConfigCheckbox.ui?.checkbox || {}) })
|
||||||
|
|
||||||
type CheckboxVariants = VariantProps<typeof checkbox>
|
type CheckboxVariants = VariantProps<typeof checkbox>
|
||||||
|
|
||||||
@@ -66,7 +66,7 @@ const modelValue = defineModel<boolean | 'indeterminate'>({ default: undefined }
|
|||||||
const rootProps = useForwardProps(reactivePick(props, 'required', 'value', 'defaultValue'))
|
const rootProps = useForwardProps(reactivePick(props, 'required', 'value', 'defaultValue'))
|
||||||
|
|
||||||
const appConfig = useAppConfig()
|
const appConfig = useAppConfig()
|
||||||
const { id: _id, emitFormChange, emitFormInput, size, color, name, disabled } = useFormField<CheckboxProps>(props)
|
const { id: _id, emitFormChange, emitFormInput, size, color, name, disabled, ariaAttrs } = useFormField<CheckboxProps>(props)
|
||||||
const id = _id.value ?? useId()
|
const id = _id.value ?? useId()
|
||||||
|
|
||||||
const ui = computed(() => checkbox({
|
const ui = computed(() => checkbox({
|
||||||
@@ -92,7 +92,7 @@ function onUpdate(value: any) {
|
|||||||
<div :class="ui.container({ class: props.ui?.container })">
|
<div :class="ui.container({ class: props.ui?.container })">
|
||||||
<CheckboxRoot
|
<CheckboxRoot
|
||||||
:id="id"
|
:id="id"
|
||||||
v-bind="rootProps"
|
v-bind="{ ...rootProps, ...ariaAttrs }"
|
||||||
v-model="modelValue"
|
v-model="modelValue"
|
||||||
:name="name"
|
:name="name"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ import theme from '#build/ui/chip'
|
|||||||
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
||||||
import { tv } from '../utils/tv'
|
import { tv } from '../utils/tv'
|
||||||
|
|
||||||
const appConfig = _appConfig as AppConfig & { ui: { chip: Partial<typeof theme> } }
|
const appConfigChip = _appConfig as AppConfig & { ui: { chip: Partial<typeof theme> } }
|
||||||
|
|
||||||
const chip = tv({ extend: tv(theme), ...(appConfig.ui?.chip || {}) })
|
const chip = tv({ extend: tv(theme), ...(appConfigChip.ui?.chip || {}) })
|
||||||
|
|
||||||
type ChipVariants = VariantProps<typeof chip>
|
type ChipVariants = VariantProps<typeof chip>
|
||||||
|
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ import theme from '#build/ui/collapsible'
|
|||||||
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
||||||
import { tv } from '../utils/tv'
|
import { tv } from '../utils/tv'
|
||||||
|
|
||||||
const appConfig = _appConfig as AppConfig & { ui: { collapsible: Partial<typeof theme> } }
|
const appConfigCollapsible = _appConfig as AppConfig & { ui: { collapsible: Partial<typeof theme> } }
|
||||||
|
|
||||||
const collapsible = tv({ extend: tv(theme), ...(appConfig.ui?.collapsible || {}) })
|
const collapsible = tv({ extend: tv(theme), ...(appConfigCollapsible.ui?.collapsible || {}) })
|
||||||
|
|
||||||
export interface CollapsibleProps extends Pick<CollapsibleRootProps, 'defaultOpen' | 'open' | 'disabled' | 'unmountOnHide'> {
|
export interface CollapsibleProps extends Pick<CollapsibleRootProps, 'defaultOpen' | 'open' | 'disabled' | 'unmountOnHide'> {
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import theme from '#build/ui/color-picker'
|
|||||||
import { tv } from '../utils/tv'
|
import { tv } from '../utils/tv'
|
||||||
import type { HSLObject } from 'colortranslator'
|
import type { HSLObject } from 'colortranslator'
|
||||||
|
|
||||||
const appConfig = _appConfig as AppConfig & { ui: { colorPicker: Partial<typeof theme> } }
|
const appConfigColorPicker = _appConfig as AppConfig & { ui: { colorPicker: Partial<typeof theme> } }
|
||||||
|
|
||||||
const colorPicker = tv({ extend: tv(theme), ...(appConfig.ui?.colorPicker || {}) })
|
const colorPicker = tv({ extend: tv(theme), ...(appConfigColorPicker.ui?.colorPicker || {}) })
|
||||||
|
|
||||||
type ColorPickerVariants = VariantProps<typeof colorPicker>
|
type ColorPickerVariants = VariantProps<typeof colorPicker>
|
||||||
|
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ import { tv } from '../utils/tv'
|
|||||||
import type { AvatarProps, ButtonProps, ChipProps, KbdProps, InputProps } from '../types'
|
import type { AvatarProps, ButtonProps, ChipProps, KbdProps, InputProps } from '../types'
|
||||||
import type { DynamicSlots, PartialString } from '../types/utils'
|
import type { DynamicSlots, PartialString } from '../types/utils'
|
||||||
|
|
||||||
const appConfig = _appConfig as AppConfig & { ui: { commandPalette: Partial<typeof theme> } }
|
const appConfigCommandPalette = _appConfig as AppConfig & { ui: { commandPalette: Partial<typeof theme> } }
|
||||||
|
|
||||||
const commandPalette = tv({ extend: tv(theme), ...(appConfig.ui?.commandPalette || {}) })
|
const commandPalette = tv({ extend: tv(theme), ...(appConfigCommandPalette.ui?.commandPalette || {}) })
|
||||||
|
|
||||||
export interface CommandPaletteItem {
|
export interface CommandPaletteItem {
|
||||||
prefix?: string
|
prefix?: string
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import theme from '#build/ui/container'
|
|||||||
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
||||||
import { tv } from '../utils/tv'
|
import { tv } from '../utils/tv'
|
||||||
|
|
||||||
const appConfig = _appConfig as AppConfig & { ui: { container: Partial<typeof theme> } }
|
const appConfigContainer = _appConfig as AppConfig & { ui: { container: Partial<typeof theme> } }
|
||||||
|
|
||||||
const container = tv({ extend: tv(theme), ...(appConfig.ui?.container || {}) })
|
const container = tv({ extend: tv(theme), ...(appConfigContainer.ui?.container || {}) })
|
||||||
|
|
||||||
export interface ContainerProps {
|
export interface ContainerProps {
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ import { tv } from '../utils/tv'
|
|||||||
import type { AvatarProps, KbdProps, LinkProps } from '../types'
|
import type { AvatarProps, KbdProps, LinkProps } from '../types'
|
||||||
import type { DynamicSlots, PartialString } from '../types/utils'
|
import type { DynamicSlots, PartialString } from '../types/utils'
|
||||||
|
|
||||||
const appConfig = _appConfig as AppConfig & { ui: { contextMenu: Partial<typeof theme> } }
|
const appConfigContextMenu = _appConfig as AppConfig & { ui: { contextMenu: Partial<typeof theme> } }
|
||||||
|
|
||||||
const contextMenu = tv({ extend: tv(theme), ...(appConfig.ui?.contextMenu || {}) })
|
const contextMenu = tv({ extend: tv(theme), ...(appConfigContextMenu.ui?.contextMenu || {}) })
|
||||||
|
|
||||||
type ContextMenuVariants = VariantProps<typeof contextMenu>
|
type ContextMenuVariants = VariantProps<typeof contextMenu>
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ const emits = defineEmits<ContextMenuContentEmits>()
|
|||||||
const slots = defineSlots<ContextMenuSlots<T>>()
|
const slots = defineSlots<ContextMenuSlots<T>>()
|
||||||
|
|
||||||
const appConfig = useAppConfig()
|
const appConfig = useAppConfig()
|
||||||
const contentProps = useForwardPropsEmits(reactiveOmit(props, 'sub', 'items', 'portal', 'class', 'ui'), emits)
|
const contentProps = useForwardPropsEmits(reactiveOmit(props, 'sub', 'items', 'portal', 'labelKey', 'checkedIcon', 'loadingIcon', 'class', 'ui', 'uiOverride'), emits)
|
||||||
const proxySlots = omit(slots, ['default']) as Record<string, ContextMenuSlots<T>[string]>
|
const proxySlots = omit(slots, ['default']) as Record<string, ContextMenuSlots<T>[string]>
|
||||||
|
|
||||||
const [DefineItemTemplate, ReuseItemTemplate] = createReusableTemplate<{ item: ContextMenuItem, active?: boolean, index: number }>()
|
const [DefineItemTemplate, ReuseItemTemplate] = createReusableTemplate<{ item: ContextMenuItem, active?: boolean, index: number }>()
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import theme from '#build/ui/drawer'
|
|||||||
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
||||||
import { tv } from '../utils/tv'
|
import { tv } from '../utils/tv'
|
||||||
|
|
||||||
const appConfig = _appConfig as AppConfig & { ui: { drawer: Partial<typeof theme> } }
|
const appConfigDrawer = _appConfig as AppConfig & { ui: { drawer: Partial<typeof theme> } }
|
||||||
|
|
||||||
const drawer = tv({ extend: tv(theme), ...(appConfig.ui?.drawer || {}) })
|
const drawer = tv({ extend: tv(theme), ...(appConfigDrawer.ui?.drawer || {}) })
|
||||||
|
|
||||||
export interface DrawerProps extends Pick<DrawerRootProps, 'activeSnapPoint' | 'closeThreshold' | 'defaultOpen' | 'direction' | 'fadeFromIndex' | 'fixed' | 'modal' | 'nested' | 'direction' | 'open' | 'scrollLockTimeout' | 'shouldScaleBackground' | 'snapPoints'> {
|
export interface DrawerProps extends Pick<DrawerRootProps, 'activeSnapPoint' | 'closeThreshold' | 'defaultOpen' | 'direction' | 'fadeFromIndex' | 'fixed' | 'modal' | 'nested' | 'direction' | 'open' | 'scrollLockTimeout' | 'shouldScaleBackground' | 'snapPoints'> {
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ import { tv } from '../utils/tv'
|
|||||||
import type { AvatarProps, KbdProps, LinkProps } from '../types'
|
import type { AvatarProps, KbdProps, LinkProps } from '../types'
|
||||||
import type { DynamicSlots, PartialString } from '../types/utils'
|
import type { DynamicSlots, PartialString } from '../types/utils'
|
||||||
|
|
||||||
const appConfig = _appConfig as AppConfig & { ui: { dropdownMenu: Partial<typeof theme> } }
|
const appConfigDropdownMenu = _appConfig as AppConfig & { ui: { dropdownMenu: Partial<typeof theme> } }
|
||||||
|
|
||||||
const dropdownMenu = tv({ extend: tv(theme), ...(appConfig.ui?.dropdownMenu || {}) })
|
const dropdownMenu = tv({ extend: tv(theme), ...(appConfigDropdownMenu.ui?.dropdownMenu || {}) })
|
||||||
|
|
||||||
type DropdownMenuVariants = VariantProps<typeof dropdownMenu>
|
type DropdownMenuVariants = VariantProps<typeof dropdownMenu>
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ const emits = defineEmits<DropdownMenuContentEmits>()
|
|||||||
const slots = defineSlots<DropdownMenuContentSlots<T>>()
|
const slots = defineSlots<DropdownMenuContentSlots<T>>()
|
||||||
|
|
||||||
const appConfig = useAppConfig()
|
const appConfig = useAppConfig()
|
||||||
const contentProps = useForwardPropsEmits(reactiveOmit(props, 'sub', 'items', 'portal', 'class', 'ui'), emits)
|
const contentProps = useForwardPropsEmits(reactiveOmit(props, 'sub', 'items', 'portal', 'labelKey', 'checkedIcon', 'loadingIcon', 'class', 'ui', 'uiOverride'), emits)
|
||||||
const proxySlots = omit(slots, ['default']) as Record<string, DropdownMenuContentSlots<T>[string]>
|
const proxySlots = omit(slots, ['default']) as Record<string, DropdownMenuContentSlots<T>[string]>
|
||||||
|
|
||||||
const [DefineItemTemplate, ReuseItemTemplate] = createReusableTemplate<{ item: DropdownMenuItem, active?: boolean, index: number }>()
|
const [DefineItemTemplate, ReuseItemTemplate] = createReusableTemplate<{ item: DropdownMenuItem, active?: boolean, index: number }>()
|
||||||
|
|||||||
@@ -5,10 +5,11 @@ import theme from '#build/ui/form'
|
|||||||
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
||||||
import { tv } from '../utils/tv'
|
import { tv } from '../utils/tv'
|
||||||
import type { FormSchema, FormError, FormInputEvents, FormErrorEvent, FormSubmitEvent, FormEvent, Form, FormErrorWithId } from '../types/form'
|
import type { FormSchema, FormError, FormInputEvents, FormErrorEvent, FormSubmitEvent, FormEvent, Form, FormErrorWithId } from '../types/form'
|
||||||
|
import type { DeepReadonly } from 'vue'
|
||||||
|
|
||||||
const appConfig = _appConfig as AppConfig & { ui: { form: Partial<typeof theme> } }
|
const appConfigForm = _appConfig as AppConfig & { ui: { form: Partial<typeof theme> } }
|
||||||
|
|
||||||
const form = tv({ extend: tv(theme), ...(appConfig.ui?.form || {}) })
|
const form = tv({ extend: tv(theme), ...(appConfigForm.ui?.form || {}) })
|
||||||
|
|
||||||
export interface FormProps<T extends object> {
|
export interface FormProps<T extends object> {
|
||||||
id?: string | number
|
id?: string | number
|
||||||
@@ -52,7 +53,7 @@ defineSlots<FormSlots>()
|
|||||||
|
|
||||||
const formId = props.id ?? useId() as string
|
const formId = props.id ?? useId() as string
|
||||||
|
|
||||||
const bus = useEventBus<FormEvent>(`form-${formId}`)
|
const bus = useEventBus<FormEvent<T>>(`form-${formId}`)
|
||||||
const parentBus = inject(
|
const parentBus = inject(
|
||||||
formBusInjectionKey,
|
formBusInjectionKey,
|
||||||
undefined
|
undefined
|
||||||
@@ -68,8 +69,24 @@ onMounted(async () => {
|
|||||||
nestedForms.value.set(event.formId, { validate: event.validate })
|
nestedForms.value.set(event.formId, { validate: event.validate })
|
||||||
} else if (event.type === 'detach') {
|
} else if (event.type === 'detach') {
|
||||||
nestedForms.value.delete(event.formId)
|
nestedForms.value.delete(event.formId)
|
||||||
} else if (props.validateOn?.includes(event.type as FormInputEvents)) {
|
} else if (props.validateOn?.includes(event.type)) {
|
||||||
await _validate({ name: event.name, silent: true, nested: false })
|
if (event.type !== 'input') {
|
||||||
|
await _validate({ name: event.name, silent: true, nested: false })
|
||||||
|
} else if (event.eager || blurredFields.has(event.name)) {
|
||||||
|
await _validate({ name: event.name, silent: true, nested: false })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.type === 'blur') {
|
||||||
|
blurredFields.add(event.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.type === 'change' || event.type === 'input' || event.type === 'blur' || event.type === 'focus') {
|
||||||
|
touchedFields.add(event.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.type === 'change' || event.type === 'input') {
|
||||||
|
dirtyFields.add(event.name)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -94,8 +111,12 @@ onUnmounted(() => {
|
|||||||
const errors = ref<FormErrorWithId[]>([])
|
const errors = ref<FormErrorWithId[]>([])
|
||||||
provide('form-errors', errors)
|
provide('form-errors', errors)
|
||||||
|
|
||||||
const inputs = ref<Record<string, { id?: string, pattern?: RegExp }>>({})
|
const inputs = ref<{ [P in keyof T]?: { id?: string, pattern?: RegExp } }>({})
|
||||||
provide(formInputsInjectionKey, inputs)
|
provide(formInputsInjectionKey, inputs as any)
|
||||||
|
|
||||||
|
const dirtyFields = new Set<keyof T>()
|
||||||
|
const touchedFields = new Set<keyof T>()
|
||||||
|
const blurredFields = new Set<keyof T>()
|
||||||
|
|
||||||
function resolveErrorIds(errs: FormError[]): FormErrorWithId[] {
|
function resolveErrorIds(errs: FormError[]): FormErrorWithId[] {
|
||||||
return errs.map(err => ({
|
return errs.map(err => ({
|
||||||
@@ -121,8 +142,8 @@ async function getErrors(): Promise<FormErrorWithId[]> {
|
|||||||
return resolveErrorIds(errs)
|
return resolveErrorIds(errs)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function _validate(opts: { name?: string | string[], silent?: boolean, nested?: boolean, transform?: boolean } = { silent: false, nested: true, transform: false }): Promise<T | false> {
|
async function _validate(opts: { name?: keyof T | (keyof T)[], silent?: boolean, nested?: boolean, transform?: boolean } = { silent: false, nested: true, transform: false }): Promise<T | false> {
|
||||||
const names = opts.name && !Array.isArray(opts.name) ? [opts.name] : opts.name as string[]
|
const names = opts.name && !Array.isArray(opts.name) ? [opts.name] : opts.name as (keyof T)[]
|
||||||
|
|
||||||
const nestedValidatePromises = !names && opts.nested
|
const nestedValidatePromises = !names && opts.nested
|
||||||
? Array.from(nestedForms.value.values()).map(
|
? Array.from(nestedForms.value.values()).map(
|
||||||
@@ -203,7 +224,7 @@ defineExpose<Form<T>>({
|
|||||||
validate: _validate,
|
validate: _validate,
|
||||||
errors,
|
errors,
|
||||||
|
|
||||||
setErrors(errs: FormError[], name?: string) {
|
setErrors(errs: FormError[], name?: keyof T) {
|
||||||
if (name) {
|
if (name) {
|
||||||
errors.value = errors.value
|
errors.value = errors.value
|
||||||
.filter(error => error.name !== name)
|
.filter(error => error.name !== name)
|
||||||
@@ -217,7 +238,7 @@ defineExpose<Form<T>>({
|
|||||||
await onSubmitWrapper(new Event('submit'))
|
await onSubmitWrapper(new Event('submit'))
|
||||||
},
|
},
|
||||||
|
|
||||||
getErrors(name?: string) {
|
getErrors(name?: keyof T) {
|
||||||
if (name) {
|
if (name) {
|
||||||
return errors.value.filter(err => err.name === name)
|
return errors.value.filter(err => err.name === name)
|
||||||
}
|
}
|
||||||
@@ -232,7 +253,12 @@ defineExpose<Form<T>>({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
disabled
|
disabled,
|
||||||
|
dirty: computed(() => !!dirtyFields.size),
|
||||||
|
|
||||||
|
dirtyFields: readonly(dirtyFields) as DeepReadonly<Set<keyof T>>,
|
||||||
|
blurredFields: readonly(blurredFields) as DeepReadonly<Set<keyof T>>,
|
||||||
|
touchedFields: readonly(touchedFields) as DeepReadonly<Set<keyof T>>
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ import theme from '#build/ui/form-field'
|
|||||||
import { tv } from '../utils/tv'
|
import { tv } from '../utils/tv'
|
||||||
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
||||||
|
|
||||||
const appConfig = _appConfig as AppConfig & { ui: { formField: Partial<typeof theme> } }
|
const appConfigFormField = _appConfig as AppConfig & { ui: { formField: Partial<typeof theme> } }
|
||||||
|
|
||||||
const formField = tv({ extend: tv(theme), ...(appConfig.ui?.formField || {}) })
|
const formField = tv({ extend: tv(theme), ...(appConfigFormField.ui?.formField || {}) })
|
||||||
|
|
||||||
type FormFieldVariants = VariantProps<typeof formField>
|
type FormFieldVariants = VariantProps<typeof formField>
|
||||||
|
|
||||||
@@ -66,6 +66,9 @@ const formErrors = inject<Ref<FormError[]> | null>('form-errors', null)
|
|||||||
const error = computed(() => props.error || formErrors?.value?.find(error => error.name === props.name || (props.errorPattern && error.name.match(props.errorPattern)))?.message)
|
const error = computed(() => props.error || formErrors?.value?.find(error => error.name === props.name || (props.errorPattern && error.name.match(props.errorPattern)))?.message)
|
||||||
|
|
||||||
const id = ref(useId())
|
const id = ref(useId())
|
||||||
|
// Copies id's initial value to bind aria-attributes such as aria-describedby.
|
||||||
|
// This is required for the RadioGroup component which unsets the id value.
|
||||||
|
const ariaId = id.value
|
||||||
|
|
||||||
provide(inputIdInjectionKey, id)
|
provide(inputIdInjectionKey, id)
|
||||||
|
|
||||||
@@ -75,7 +78,10 @@ provide(formFieldInjectionKey, computed(() => ({
|
|||||||
size: props.size,
|
size: props.size,
|
||||||
eagerValidation: props.eagerValidation,
|
eagerValidation: props.eagerValidation,
|
||||||
validateOnInputDelay: props.validateOnInputDelay,
|
validateOnInputDelay: props.validateOnInputDelay,
|
||||||
errorPattern: props.errorPattern
|
errorPattern: props.errorPattern,
|
||||||
|
hint: props.hint,
|
||||||
|
description: props.description,
|
||||||
|
ariaId
|
||||||
}) as FormFieldInjectedOptions<FormFieldProps>))
|
}) as FormFieldInjectedOptions<FormFieldProps>))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -88,14 +94,14 @@ provide(formFieldInjectionKey, computed(() => ({
|
|||||||
{{ label }}
|
{{ label }}
|
||||||
</slot>
|
</slot>
|
||||||
</Label>
|
</Label>
|
||||||
<span v-if="hint || !!slots.hint" :class="ui.hint({ class: props.ui?.hint })">
|
<span v-if="hint || !!slots.hint" :id="`${ariaId}-hint`" :class="ui.hint({ class: props.ui?.hint })">
|
||||||
<slot name="hint" :hint="hint">
|
<slot name="hint" :hint="hint">
|
||||||
{{ hint }}
|
{{ hint }}
|
||||||
</slot>
|
</slot>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p v-if="description || !!slots.description" :class="ui.description({ class: props.ui?.description })">
|
<p v-if="description || !!slots.description" :id="`${ariaId}-description`" :class="ui.description({ class: props.ui?.description })">
|
||||||
<slot name="description" :description="description">
|
<slot name="description" :description="description">
|
||||||
{{ description }}
|
{{ description }}
|
||||||
</slot>
|
</slot>
|
||||||
@@ -105,7 +111,7 @@ provide(formFieldInjectionKey, computed(() => ({
|
|||||||
<div :class="[(label || !!slots.label || description || !!slots.description) && ui.container({ class: props.ui?.container })]">
|
<div :class="[(label || !!slots.label || description || !!slots.description) && ui.container({ class: props.ui?.container })]">
|
||||||
<slot :error="error" />
|
<slot :error="error" />
|
||||||
|
|
||||||
<p v-if="(typeof error === 'string' && error) || !!slots.error" :class="ui.error({ class: props.ui?.error })">
|
<p v-if="(typeof error === 'string' && error) || !!slots.error" :id="`${ariaId}-error`" :class="ui.error({ class: props.ui?.error })">
|
||||||
<slot name="error" :error="error">
|
<slot name="error" :error="error">
|
||||||
{{ error }}
|
{{ error }}
|
||||||
</slot>
|
</slot>
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ import { tv } from '../utils/tv'
|
|||||||
import type { AvatarProps } from '../types'
|
import type { AvatarProps } from '../types'
|
||||||
import type { PartialString } from '../types/utils'
|
import type { PartialString } from '../types/utils'
|
||||||
|
|
||||||
const appConfig = _appConfig as AppConfig & { ui: { input: Partial<typeof theme> } }
|
const appConfigInput = _appConfig as AppConfig & { ui: { input: Partial<typeof theme> } }
|
||||||
|
|
||||||
const input = tv({ extend: tv(theme), ...(appConfig.ui?.input || {}) })
|
const input = tv({ extend: tv(theme), ...(appConfigInput.ui?.input || {}) })
|
||||||
|
|
||||||
type InputVariants = VariantProps<typeof input>
|
type InputVariants = VariantProps<typeof input>
|
||||||
|
|
||||||
@@ -75,7 +75,7 @@ const slots = defineSlots<InputSlots>()
|
|||||||
|
|
||||||
const [modelValue, modelModifiers] = defineModel<string | number>()
|
const [modelValue, modelModifiers] = defineModel<string | number>()
|
||||||
|
|
||||||
const { emitFormBlur, emitFormInput, emitFormChange, size: formGroupSize, color, id, name, highlight, disabled } = useFormField<InputProps>(props, { deferInputValidation: true })
|
const { emitFormBlur, emitFormInput, emitFormChange, size: formGroupSize, color, id, name, highlight, disabled, emitFormFocus, ariaAttrs } = useFormField<InputProps>(props, { deferInputValidation: true })
|
||||||
const { orientation, size: buttonGroupSize } = useButtonGroup<InputProps>(props)
|
const { orientation, size: buttonGroupSize } = useButtonGroup<InputProps>(props)
|
||||||
const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponentIcons(props)
|
const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponentIcons(props)
|
||||||
|
|
||||||
@@ -166,10 +166,11 @@ onMounted(() => {
|
|||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
:required="required"
|
:required="required"
|
||||||
:autocomplete="autocomplete"
|
:autocomplete="autocomplete"
|
||||||
v-bind="$attrs"
|
v-bind="{ ...$attrs, ...ariaAttrs }"
|
||||||
@input="onInput"
|
@input="onInput"
|
||||||
@blur="onBlur"
|
@blur="onBlur"
|
||||||
@change="onChange"
|
@change="onChange"
|
||||||
|
@focus="emitFormFocus"
|
||||||
>
|
>
|
||||||
|
|
||||||
<slot />
|
<slot />
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ import { tv } from '../utils/tv'
|
|||||||
import type { AvatarProps, ChipProps, InputProps } from '../types'
|
import type { AvatarProps, ChipProps, InputProps } from '../types'
|
||||||
import type { PartialString, MaybeArrayOfArray, MaybeArrayOfArrayItem, SelectModelValue, SelectModelValueEmits, SelectItemKey } from '../types/utils'
|
import type { PartialString, MaybeArrayOfArray, MaybeArrayOfArrayItem, SelectModelValue, SelectModelValueEmits, SelectItemKey } from '../types/utils'
|
||||||
|
|
||||||
const appConfig = _appConfig as AppConfig & { ui: { inputMenu: Partial<typeof theme> } }
|
const appConfigInputMenu = _appConfig as AppConfig & { ui: { inputMenu: Partial<typeof theme> } }
|
||||||
|
|
||||||
const inputMenu = tv({ extend: tv(theme), ...(appConfig.ui?.inputMenu || {}) })
|
const inputMenu = tv({ extend: tv(theme), ...(appConfigInputMenu.ui?.inputMenu || {}) })
|
||||||
|
|
||||||
export interface InputMenuItem {
|
export interface InputMenuItem {
|
||||||
label?: string
|
label?: string
|
||||||
@@ -178,7 +178,7 @@ const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'modelValue', '
|
|||||||
const contentProps = toRef(() => defu(props.content, { side: 'bottom', sideOffset: 8, collisionPadding: 8, position: 'popper' }) as ComboboxContentProps)
|
const contentProps = toRef(() => defu(props.content, { side: 'bottom', sideOffset: 8, collisionPadding: 8, position: 'popper' }) as ComboboxContentProps)
|
||||||
const arrowProps = toRef(() => props.arrow as ComboboxArrowProps)
|
const arrowProps = toRef(() => props.arrow as ComboboxArrowProps)
|
||||||
|
|
||||||
const { emitFormBlur, emitFormChange, emitFormInput, size: formGroupSize, color, id, name, highlight, disabled } = useFormField<InputProps>(props)
|
const { emitFormBlur, emitFormFocus, emitFormChange, emitFormInput, size: formGroupSize, color, id, name, highlight, disabled, ariaAttrs } = useFormField<InputProps>(props)
|
||||||
const { orientation, size: buttonGroupSize } = useButtonGroup<InputProps>(props)
|
const { orientation, size: buttonGroupSize } = useButtonGroup<InputProps>(props)
|
||||||
const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponentIcons(toRef(() => defu(props, { trailingIcon: appConfig.ui.icons.chevronDown })))
|
const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponentIcons(toRef(() => defu(props, { trailingIcon: appConfig.ui.icons.chevronDown })))
|
||||||
|
|
||||||
@@ -279,6 +279,7 @@ function onBlur(event: FocusEvent) {
|
|||||||
|
|
||||||
function onFocus(event: FocusEvent) {
|
function onFocus(event: FocusEvent) {
|
||||||
emits('focus', event)
|
emits('focus', event)
|
||||||
|
emitFormFocus()
|
||||||
}
|
}
|
||||||
|
|
||||||
function onUpdateOpen(value: boolean) {
|
function onUpdateOpen(value: boolean) {
|
||||||
@@ -365,7 +366,7 @@ defineExpose({
|
|||||||
<ComboboxInput v-model="searchTerm" :display-value="displayValue" as-child>
|
<ComboboxInput v-model="searchTerm" :display-value="displayValue" as-child>
|
||||||
<TagsInputInput
|
<TagsInputInput
|
||||||
ref="inputRef"
|
ref="inputRef"
|
||||||
v-bind="$attrs"
|
v-bind="{ ...$attrs, ...ariaAttrs }"
|
||||||
:placeholder="placeholder"
|
:placeholder="placeholder"
|
||||||
:required="required"
|
:required="required"
|
||||||
:class="ui.tagsInput({ class: props.ui?.tagsInput })"
|
:class="ui.tagsInput({ class: props.ui?.tagsInput })"
|
||||||
@@ -379,7 +380,7 @@ defineExpose({
|
|||||||
ref="inputRef"
|
ref="inputRef"
|
||||||
v-model="searchTerm"
|
v-model="searchTerm"
|
||||||
:display-value="displayValue"
|
:display-value="displayValue"
|
||||||
v-bind="$attrs"
|
v-bind="{ ...$attrs, ...ariaAttrs }"
|
||||||
:type="type"
|
:type="type"
|
||||||
:placeholder="placeholder"
|
:placeholder="placeholder"
|
||||||
:required="required"
|
:required="required"
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import theme from '#build/ui/input-number'
|
|||||||
import { tv } from '../utils/tv'
|
import { tv } from '../utils/tv'
|
||||||
import type { ButtonProps } from '../types'
|
import type { ButtonProps } from '../types'
|
||||||
|
|
||||||
const appConfig = _appConfig as AppConfig & { ui: { inputNumber: Partial<typeof theme> } }
|
const appConfigInputNumber = _appConfig as AppConfig & { ui: { inputNumber: Partial<typeof theme> } }
|
||||||
|
|
||||||
const inputNumber = tv({ extend: tv(theme), ...(appConfig.ui?.inputNumber || {}) })
|
const inputNumber = tv({ extend: tv(theme), ...(appConfigInputNumber.ui?.inputNumber || {}) })
|
||||||
|
|
||||||
type InputNumberVariants = VariantProps<typeof inputNumber>
|
type InputNumberVariants = VariantProps<typeof inputNumber>
|
||||||
|
|
||||||
@@ -78,6 +78,7 @@ export interface InputNumberSlots {
|
|||||||
import { onMounted, ref, computed } from 'vue'
|
import { onMounted, ref, computed } from 'vue'
|
||||||
import { NumberFieldRoot, NumberFieldInput, NumberFieldDecrement, NumberFieldIncrement, useForwardPropsEmits } from 'reka-ui'
|
import { NumberFieldRoot, NumberFieldInput, NumberFieldDecrement, NumberFieldIncrement, useForwardPropsEmits } from 'reka-ui'
|
||||||
import { reactivePick } from '@vueuse/core'
|
import { reactivePick } from '@vueuse/core'
|
||||||
|
import { useAppConfig } from '#imports'
|
||||||
import { useFormField } from '../composables/useFormField'
|
import { useFormField } from '../composables/useFormField'
|
||||||
import { useLocale } from '../composables/useLocale'
|
import { useLocale } from '../composables/useLocale'
|
||||||
import UButton from './Button.vue'
|
import UButton from './Button.vue'
|
||||||
@@ -92,7 +93,8 @@ defineSlots<InputNumberSlots>()
|
|||||||
|
|
||||||
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'modelValue', 'defaultValue', 'min', 'max', 'step', 'formatOptions'), emits)
|
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'modelValue', 'defaultValue', 'min', 'max', 'step', 'formatOptions'), emits)
|
||||||
|
|
||||||
const { emitFormBlur, emitFormChange, emitFormInput, id, color, size, name, highlight, disabled } = useFormField<InputNumberProps>(props)
|
const appConfig = useAppConfig()
|
||||||
|
const { emitFormBlur, emitFormFocus, emitFormChange, emitFormInput, id, color, size, name, highlight, disabled, ariaAttrs } = useFormField<InputNumberProps>(props)
|
||||||
|
|
||||||
const { t, code: codeLocale } = useLocale()
|
const { t, code: codeLocale } = useLocale()
|
||||||
const locale = computed(() => props.locale || codeLocale.value)
|
const locale = computed(() => props.locale || codeLocale.value)
|
||||||
@@ -152,12 +154,13 @@ defineExpose({
|
|||||||
@update:model-value="onUpdate"
|
@update:model-value="onUpdate"
|
||||||
>
|
>
|
||||||
<NumberFieldInput
|
<NumberFieldInput
|
||||||
v-bind="$attrs"
|
v-bind="{ ...$attrs, ...ariaAttrs }"
|
||||||
ref="inputRef"
|
ref="inputRef"
|
||||||
:placeholder="placeholder"
|
:placeholder="placeholder"
|
||||||
:required="required"
|
:required="required"
|
||||||
:class="ui.base({ class: props.ui?.base })"
|
:class="ui.base({ class: props.ui?.base })"
|
||||||
@blur="onBlur"
|
@blur="onBlur"
|
||||||
|
@focus="emitFormFocus"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div :class="ui.increment({ class: props.ui?.increment })">
|
<div :class="ui.increment({ class: props.ui?.increment })">
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import type { KbdKey } from '../composables/useKbd'
|
|||||||
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
||||||
import { tv } from '../utils/tv'
|
import { tv } from '../utils/tv'
|
||||||
|
|
||||||
const appConfig = _appConfig as AppConfig & { ui: { kbd: Partial<typeof theme> } }
|
const appConfigKbd = _appConfig as AppConfig & { ui: { kbd: Partial<typeof theme> } }
|
||||||
|
|
||||||
const kbd = tv({ extend: tv(theme), ...(appConfig.ui?.kbd || {}) })
|
const kbd = tv({ extend: tv(theme), ...(appConfigKbd.ui?.kbd || {}) })
|
||||||
|
|
||||||
type KbdVariants = VariantProps<typeof kbd>
|
type KbdVariants = VariantProps<typeof kbd>
|
||||||
|
|
||||||
|
|||||||
@@ -53,9 +53,9 @@ interface NuxtLinkProps extends Omit<RouterLinkProps, 'to'> {
|
|||||||
noPrefetch?: boolean
|
noPrefetch?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const appConfig = _appConfig as AppConfig & { ui: { link: Partial<typeof theme> } }
|
const appConfigLink = _appConfig as AppConfig & { ui: { link: Partial<typeof theme> } }
|
||||||
|
|
||||||
const link = tv({ extend: tv(theme), ...(appConfig.ui?.link || {}) })
|
const link = tv({ extend: tv(theme), ...(appConfigLink.ui?.link || {}) })
|
||||||
|
|
||||||
export interface LinkProps extends NuxtLinkProps {
|
export interface LinkProps extends NuxtLinkProps {
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
|||||||
import { tv } from '../utils/tv'
|
import { tv } from '../utils/tv'
|
||||||
import type { ButtonProps } from '../types'
|
import type { ButtonProps } from '../types'
|
||||||
|
|
||||||
const appConfig = _appConfig as AppConfig & { ui: { modal: Partial<typeof theme> } }
|
const appConfigModal = _appConfig as AppConfig & { ui: { modal: Partial<typeof theme> } }
|
||||||
|
|
||||||
const modal = tv({ extend: tv(theme), ...(appConfig.ui?.modal || {}) })
|
const modal = tv({ extend: tv(theme), ...(appConfigModal.ui?.modal || {}) })
|
||||||
|
|
||||||
export interface ModalProps extends DialogRootProps {
|
export interface ModalProps extends DialogRootProps {
|
||||||
title?: string
|
title?: string
|
||||||
@@ -73,7 +73,7 @@ extendDevtoolsMeta({ example: 'ModalExample' })
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, toRef, provide } from 'vue'
|
import { computed, toRef } from 'vue'
|
||||||
import { DialogRoot, DialogTrigger, DialogPortal, DialogOverlay, DialogContent, DialogTitle, DialogDescription, DialogClose, useForwardPropsEmits } from 'reka-ui'
|
import { DialogRoot, DialogTrigger, DialogPortal, DialogOverlay, DialogContent, DialogTitle, DialogDescription, DialogClose, useForwardPropsEmits } from 'reka-ui'
|
||||||
import { reactivePick } from '@vueuse/core'
|
import { reactivePick } from '@vueuse/core'
|
||||||
import { useAppConfig } from '#imports'
|
import { useAppConfig } from '#imports'
|
||||||
@@ -112,9 +112,6 @@ const ui = computed(() => modal({
|
|||||||
transition: props.transition,
|
transition: props.transition,
|
||||||
fullscreen: props.fullscreen
|
fullscreen: props.fullscreen
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// Blocks ButtonGroup injections to avoid side-effects if the modal is within a button group.
|
|
||||||
provide(buttonGroupInjectionKey, undefined)
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ import { tv } from '../utils/tv'
|
|||||||
import type { AvatarProps, BadgeProps, LinkProps } from '../types'
|
import type { AvatarProps, BadgeProps, LinkProps } from '../types'
|
||||||
import type { DynamicSlots, MaybeArrayOfArray, MaybeArrayOfArrayItem, PartialString } from '../types/utils'
|
import type { DynamicSlots, MaybeArrayOfArray, MaybeArrayOfArrayItem, PartialString } from '../types/utils'
|
||||||
|
|
||||||
const appConfig = _appConfig as AppConfig & { ui: { navigationMenu: Partial<typeof theme> } }
|
const appConfigNavigationMenu = _appConfig as AppConfig & { ui: { navigationMenu: Partial<typeof theme> } }
|
||||||
|
|
||||||
const navigationMenu = tv({ extend: tv(theme), ...(appConfig.ui?.navigationMenu || {}) })
|
const navigationMenu = tv({ extend: tv(theme), ...(appConfigNavigationMenu.ui?.navigationMenu || {}) })
|
||||||
|
|
||||||
export interface NavigationMenuChildItem extends Omit<NavigationMenuItem, 'children' | 'type'> {
|
export interface NavigationMenuChildItem extends Omit<NavigationMenuItem, 'children' | 'type'> {
|
||||||
/** Description is only used when `orientation` is `horizontal`. */
|
/** Description is only used when `orientation` is `horizontal`. */
|
||||||
@@ -72,6 +72,12 @@ export interface NavigationMenuProps<T> extends Pick<NavigationMenuRootProps, 'm
|
|||||||
highlightColor?: NavigationMenuVariants['highlightColor']
|
highlightColor?: NavigationMenuVariants['highlightColor']
|
||||||
/** The content of the menu. */
|
/** The content of the menu. */
|
||||||
content?: Omit<NavigationMenuContentProps, 'as' | 'asChild' | 'forceMount'>
|
content?: Omit<NavigationMenuContentProps, 'as' | 'asChild' | 'forceMount'>
|
||||||
|
/**
|
||||||
|
* The orientation of the content.
|
||||||
|
* Only works when `orientation` is `horizontal`.
|
||||||
|
* @defaultValue 'horizontal'
|
||||||
|
*/
|
||||||
|
contentOrientation?: NavigationMenuVariants['contentOrientation']
|
||||||
/**
|
/**
|
||||||
* Display an arrow alongside the menu.
|
* Display an arrow alongside the menu.
|
||||||
* @defaultValue false
|
* @defaultValue false
|
||||||
@@ -142,6 +148,7 @@ extendDevtoolsMeta({
|
|||||||
import { computed, toRef } from 'vue'
|
import { computed, toRef } from 'vue'
|
||||||
import { NavigationMenuRoot, NavigationMenuList, NavigationMenuItem, NavigationMenuTrigger, NavigationMenuContent, NavigationMenuLink, NavigationMenuIndicator, NavigationMenuViewport, useForwardPropsEmits } from 'reka-ui'
|
import { NavigationMenuRoot, NavigationMenuList, NavigationMenuItem, NavigationMenuTrigger, NavigationMenuContent, NavigationMenuLink, NavigationMenuIndicator, NavigationMenuViewport, useForwardPropsEmits } from 'reka-ui'
|
||||||
import { createReusableTemplate } from '@vueuse/core'
|
import { createReusableTemplate } from '@vueuse/core'
|
||||||
|
import { useAppConfig } from '#imports'
|
||||||
import { get } from '../utils'
|
import { get } from '../utils'
|
||||||
import { pickLinkProps } from '../utils/link'
|
import { pickLinkProps } from '../utils/link'
|
||||||
import ULinkBase from './LinkBase.vue'
|
import ULinkBase from './LinkBase.vue'
|
||||||
@@ -153,6 +160,7 @@ import UCollapsible from './Collapsible.vue'
|
|||||||
|
|
||||||
const props = withDefaults(defineProps<NavigationMenuProps<I>>(), {
|
const props = withDefaults(defineProps<NavigationMenuProps<I>>(), {
|
||||||
orientation: 'horizontal',
|
orientation: 'horizontal',
|
||||||
|
contentOrientation: 'horizontal',
|
||||||
delayDuration: 0,
|
delayDuration: 0,
|
||||||
unmountOnHide: true,
|
unmountOnHide: true,
|
||||||
labelKey: 'label'
|
labelKey: 'label'
|
||||||
@@ -175,10 +183,13 @@ const rootProps = useForwardPropsEmits(computed(() => ({
|
|||||||
|
|
||||||
const contentProps = toRef(() => props.content)
|
const contentProps = toRef(() => props.content)
|
||||||
|
|
||||||
const [DefineItemTemplate, ReuseItemTemplate] = createReusableTemplate<{ item: NavigationMenuItem, active?: boolean, index: number }>()
|
const appConfig = useAppConfig()
|
||||||
|
const [DefineLinkTemplate, ReuseLinkTemplate] = createReusableTemplate<{ item: NavigationMenuItem, index: number, active?: boolean }>()
|
||||||
|
const [DefineItemTemplate, ReuseItemTemplate] = createReusableTemplate<{ item: NavigationMenuItem, index: number }>()
|
||||||
|
|
||||||
const ui = computed(() => navigationMenu({
|
const ui = computed(() => navigationMenu({
|
||||||
orientation: props.orientation,
|
orientation: props.orientation,
|
||||||
|
contentOrientation: props.contentOrientation,
|
||||||
collapsed: props.collapsed,
|
collapsed: props.collapsed,
|
||||||
color: props.color,
|
color: props.color,
|
||||||
variant: props.variant,
|
variant: props.variant,
|
||||||
@@ -190,7 +201,7 @@ const lists = computed(() => props.items?.length ? (Array.isArray(props.items[0]
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<DefineItemTemplate v-slot="{ item, active, index }">
|
<DefineLinkTemplate v-slot="{ item, active, index }">
|
||||||
<slot :name="item.slot || 'item'" :item="(item as T)" :index="index">
|
<slot :name="item.slot || 'item'" :item="(item as T)" :index="index">
|
||||||
<slot :name="item.slot ? `${item.slot}-leading` : 'item-leading'" :item="(item as T)" :active="active" :index="index">
|
<slot :name="item.slot ? `${item.slot}-leading` : 'item-leading'" :item="(item as T)" :active="active" :index="index">
|
||||||
<UAvatar v-if="item.avatar" :size="((props.ui?.linkLeadingAvatarSize || ui.linkLeadingAvatarSize()) as AvatarProps['size'])" v-bind="item.avatar" :class="ui.linkLeadingAvatar({ class: props.ui?.linkLeadingAvatar, active, disabled: !!item.disabled })" />
|
<UAvatar v-if="item.avatar" :size="((props.ui?.linkLeadingAvatarSize || ui.linkLeadingAvatarSize()) as AvatarProps['size'])" v-bind="item.avatar" :class="ui.linkLeadingAvatar({ class: props.ui?.linkLeadingAvatar, active, disabled: !!item.disabled })" />
|
||||||
@@ -224,80 +235,73 @@ const lists = computed(() => props.items?.length ? (Array.isArray(props.items[0]
|
|||||||
</slot>
|
</slot>
|
||||||
</span>
|
</span>
|
||||||
</slot>
|
</slot>
|
||||||
</DefineItemTemplate>
|
</DefineLinkTemplate>
|
||||||
|
|
||||||
<NavigationMenuRoot v-bind="rootProps" :data-collapsed="collapsed" :class="ui.root({ class: [props.class, props.ui?.root] })">
|
<DefineItemTemplate v-slot="{ item, index }">
|
||||||
<template v-for="(list, listIndex) in lists" :key="`list-${listIndex}`">
|
<component
|
||||||
<NavigationMenuList :class="ui.list({ class: props.ui?.list })">
|
:is="(orientation === 'vertical' && item.children?.length) ? UCollapsible : NavigationMenuItem"
|
||||||
|
as="li"
|
||||||
|
:value="item.value || String(index)"
|
||||||
|
:default-open="item.defaultOpen"
|
||||||
|
:unmount-on-hide="(orientation === 'vertical' && item.children?.length) ? unmountOnHide : undefined"
|
||||||
|
:open="item.open"
|
||||||
|
>
|
||||||
|
<div v-if="orientation === 'vertical' && item.type === 'label'" :class="ui.label({ class: props.ui?.label })">
|
||||||
|
<ReuseLinkTemplate :item="(item as T)" :index="index" />
|
||||||
|
</div>
|
||||||
|
<ULink v-else-if="item.type !== 'label'" v-slot="{ active, ...slotProps }" v-bind="(orientation === 'vertical' && item.children?.length) ? {} : pickLinkProps(item as Omit<NavigationMenuItem, 'type'>)" custom>
|
||||||
<component
|
<component
|
||||||
:is="(orientation === 'vertical' && item.children?.length) ? UCollapsible : NavigationMenuItem"
|
:is="(orientation === 'horizontal' && (item.children?.length || !!slots[item.slot ? `${item.slot}-content` : 'item-content'])) ? NavigationMenuTrigger : NavigationMenuLink"
|
||||||
v-for="(item, index) in list"
|
as-child
|
||||||
:key="`list-${listIndex}-${index}`"
|
:active="active"
|
||||||
as="li"
|
:disabled="item.disabled"
|
||||||
:value="item.value || String(index)"
|
@select="item.onSelect"
|
||||||
:default-open="item.defaultOpen"
|
|
||||||
:unmount-on-hide="(orientation === 'vertical' && item.children?.length) ? unmountOnHide : undefined"
|
|
||||||
:open="item.open"
|
|
||||||
:class="ui.item({ class: props.ui?.item })"
|
|
||||||
>
|
>
|
||||||
<div v-if="orientation === 'vertical' && item.type === 'label'" :class="ui.label({ class: props.ui?.label })">
|
<ULinkBase v-bind="slotProps" :class="ui.link({ class: [props.ui?.link, item.class], active: active || item.active, disabled: !!item.disabled, level: !(orientation === 'vertical' && item.children?.length) })">
|
||||||
<ReuseItemTemplate :item="(item as T)" :index="index" />
|
<ReuseLinkTemplate :item="(item as T)" :active="active || item.active" :index="index" />
|
||||||
</div>
|
</ULinkBase>
|
||||||
<ULink v-else-if="item.type !== 'label'" v-slot="{ active, ...slotProps }" v-bind="(orientation === 'vertical' && item.children?.length) ? {} : pickLinkProps(item as Omit<NavigationMenuItem, 'type'>)" custom>
|
</component>
|
||||||
<component
|
|
||||||
:is="(orientation === 'horizontal' && (item.children?.length || !!slots[item.slot ? `${item.slot}-content` : 'item-content'])) ? NavigationMenuTrigger : NavigationMenuLink"
|
|
||||||
as-child
|
|
||||||
:active="active"
|
|
||||||
:disabled="item.disabled"
|
|
||||||
@select="item.onSelect"
|
|
||||||
>
|
|
||||||
<ULinkBase v-bind="slotProps" :class="ui.link({ class: [props.ui?.link, item.class], active: active || item.active, disabled: !!item.disabled, level: !(orientation === 'vertical' && item.children?.length) })">
|
|
||||||
<ReuseItemTemplate :item="(item as T)" :active="active || item.active" :index="index" />
|
|
||||||
</ULinkBase>
|
|
||||||
</component>
|
|
||||||
|
|
||||||
<NavigationMenuContent v-if="orientation === 'horizontal' && (item.children?.length || !!slots[item.slot ? `${item.slot}-content` : 'item-content'])" v-bind="contentProps" :class="ui.content({ class: props.ui?.content })">
|
<NavigationMenuContent v-if="orientation === 'horizontal' && (item.children?.length || !!slots[item.slot ? `${item.slot}-content` : 'item-content'])" v-bind="contentProps" :class="ui.content({ class: props.ui?.content })">
|
||||||
<slot :name="item.slot ? `${item.slot}-content` : 'item-content'" :item="(item as T)" :active="active" :index="index">
|
<slot :name="item.slot ? `${item.slot}-content` : 'item-content'" :item="(item as T)" :active="active" :index="index">
|
||||||
<ul :class="ui.childList({ class: props.ui?.childList })">
|
|
||||||
<li v-for="(childItem, childIndex) in item.children" :key="childIndex" :class="ui.childItem({ class: props.ui?.childItem })">
|
|
||||||
<ULink v-slot="{ active: childActive, ...childSlotProps }" v-bind="pickLinkProps(childItem)" custom>
|
|
||||||
<NavigationMenuLink as-child :active="childActive" @select="childItem.onSelect">
|
|
||||||
<ULinkBase v-bind="childSlotProps" :class="ui.childLink({ class: [props.ui?.childLink, childItem.class], active: childActive })">
|
|
||||||
<UIcon v-if="childItem.icon" :name="childItem.icon" :class="ui.childLinkIcon({ class: props.ui?.childLinkIcon, active: childActive })" />
|
|
||||||
|
|
||||||
<div :class="ui.childLinkWrapper({ class: props.ui?.childLinkWrapper })">
|
|
||||||
<p :class="ui.childLinkLabel({ class: props.ui?.childLinkLabel, active: childActive })">
|
|
||||||
{{ get(childItem, props.labelKey as string) }}
|
|
||||||
|
|
||||||
<UIcon v-if="childItem.target === '_blank'" :name="appConfig.ui.icons.external" :class="ui.childLinkLabelExternalIcon({ class: props.ui?.childLinkLabelExternalIcon, active: childActive })" />
|
|
||||||
</p>
|
|
||||||
<p v-if="childItem.description" :class="ui.childLinkDescription({ class: props.ui?.childLinkDescription, active: childActive })">
|
|
||||||
{{ childItem.description }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</ULinkBase>
|
|
||||||
</NavigationMenuLink>
|
|
||||||
</ULink>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</slot>
|
|
||||||
</NavigationMenuContent>
|
|
||||||
</ULink>
|
|
||||||
|
|
||||||
<template v-if="orientation === 'vertical' && item.children?.length" #content>
|
|
||||||
<ul :class="ui.childList({ class: props.ui?.childList })">
|
<ul :class="ui.childList({ class: props.ui?.childList })">
|
||||||
<li v-for="(childItem, childIndex) in item.children" :key="childIndex" :class="ui.childItem({ class: props.ui?.childItem })">
|
<li v-for="(childItem, childIndex) in item.children" :key="childIndex" :class="ui.childItem({ class: props.ui?.childItem })">
|
||||||
<ULink v-slot="{ active: childActive, ...childSlotProps }" v-bind="pickLinkProps(childItem)" custom>
|
<ULink v-slot="{ active: childActive, ...childSlotProps }" v-bind="pickLinkProps(childItem)" custom>
|
||||||
<NavigationMenuLink as-child :active="childActive" @select="childItem.onSelect">
|
<NavigationMenuLink as-child :active="childActive" @select="childItem.onSelect">
|
||||||
<ULinkBase v-bind="childSlotProps" :class="ui.link({ class: [props.ui?.link, childItem.class], active: childActive, disabled: !!childItem.disabled, level: true })">
|
<ULinkBase v-bind="childSlotProps" :class="ui.childLink({ class: [props.ui?.childLink, childItem.class], active: childActive })">
|
||||||
<ReuseItemTemplate :item="(childItem as T)" :active="childActive" :index="childIndex" />
|
<UIcon v-if="childItem.icon" :name="childItem.icon" :class="ui.childLinkIcon({ class: props.ui?.childLinkIcon, active: childActive })" />
|
||||||
|
|
||||||
|
<div :class="ui.childLinkWrapper({ class: props.ui?.childLinkWrapper })">
|
||||||
|
<p :class="ui.childLinkLabel({ class: props.ui?.childLinkLabel, active: childActive })">
|
||||||
|
{{ get(childItem, props.labelKey as string) }}
|
||||||
|
|
||||||
|
<UIcon v-if="childItem.target === '_blank'" :name="appConfig.ui.icons.external" :class="ui.childLinkLabelExternalIcon({ class: props.ui?.childLinkLabelExternalIcon, active: childActive })" />
|
||||||
|
</p>
|
||||||
|
<p v-if="childItem.description" :class="ui.childLinkDescription({ class: props.ui?.childLinkDescription, active: childActive })">
|
||||||
|
{{ childItem.description }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</ULinkBase>
|
</ULinkBase>
|
||||||
</NavigationMenuLink>
|
</NavigationMenuLink>
|
||||||
</ULink>
|
</ULink>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</template>
|
</slot>
|
||||||
</component>
|
</NavigationMenuContent>
|
||||||
|
</ULink>
|
||||||
|
|
||||||
|
<template v-if="orientation === 'vertical' && item.children?.length" #content>
|
||||||
|
<ul :class="ui.childList({ class: props.ui?.childList })">
|
||||||
|
<ReuseItemTemplate v-for="(childItem, childIndex) in item.children" :key="childIndex" :item="childItem" :index="childIndex" :class="ui.childItem({ class: props.ui?.childItem })" />
|
||||||
|
</ul>
|
||||||
|
</template>
|
||||||
|
</component>
|
||||||
|
</DefineItemTemplate>
|
||||||
|
|
||||||
|
<NavigationMenuRoot v-bind="rootProps" :data-collapsed="collapsed" :class="ui.root({ class: [props.class, props.ui?.root] })">
|
||||||
|
<template v-for="(list, listIndex) in lists" :key="`list-${listIndex}`">
|
||||||
|
<NavigationMenuList :class="ui.list({ class: props.ui?.list })">
|
||||||
|
<ReuseItemTemplate v-for="(item, index) in list" :key="`list-${listIndex}-${index}`" :item="item" :index="index" :class="ui.item({ class: props.ui?.item })" />
|
||||||
</NavigationMenuList>
|
</NavigationMenuList>
|
||||||
|
|
||||||
<div v-if="orientation === 'vertical' && listIndex < lists.length - 1" :class="ui.separator({ class: props.ui?.separator })" />
|
<div v-if="orientation === 'vertical' && listIndex < lists.length - 1" :class="ui.separator({ class: props.ui?.separator })" />
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
|||||||
import { tv } from '../utils/tv'
|
import { tv } from '../utils/tv'
|
||||||
import type { ButtonProps } from '../types'
|
import type { ButtonProps } from '../types'
|
||||||
|
|
||||||
const appConfig = _appConfig as AppConfig & { ui: { pagination: Partial<typeof theme> } }
|
const appConfigPagination = _appConfig as AppConfig & { ui: { pagination: Partial<typeof theme> } }
|
||||||
|
|
||||||
const pagination = tv({ extend: tv(theme), ...(appConfig.ui?.pagination || {}) })
|
const pagination = tv({ extend: tv(theme), ...(appConfigPagination.ui?.pagination || {}) })
|
||||||
|
|
||||||
export interface PaginationProps extends Partial<Pick<PaginationRootProps, 'defaultPage' | 'disabled' | 'itemsPerPage' | 'page' | 'showEdges' | 'siblingCount' | 'total'>> {
|
export interface PaginationProps extends Partial<Pick<PaginationRootProps, 'defaultPage' | 'disabled' | 'itemsPerPage' | 'page' | 'showEdges' | 'siblingCount' | 'total'>> {
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import theme from '#build/ui/pin-input'
|
|||||||
import { tv } from '../utils/tv'
|
import { tv } from '../utils/tv'
|
||||||
import type { PartialString } from '../types/utils'
|
import type { PartialString } from '../types/utils'
|
||||||
|
|
||||||
const appConfig = _appConfig as AppConfig & { ui: { pinInput: Partial<typeof theme> } }
|
const appConfigPinInput = _appConfig as AppConfig & { ui: { pinInput: Partial<typeof theme> } }
|
||||||
|
|
||||||
const pinInput = tv({ extend: tv(theme), ...(appConfig.ui?.pinInput || {}) })
|
const pinInput = tv({ extend: tv(theme), ...(appConfigPinInput.ui?.pinInput || {}) })
|
||||||
|
|
||||||
type PinInputVariants = VariantProps<typeof pinInput>
|
type PinInputVariants = VariantProps<typeof pinInput>
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@ const props = withDefaults(defineProps<PinInputProps>(), {
|
|||||||
const emits = defineEmits<PinInputEmits>()
|
const emits = defineEmits<PinInputEmits>()
|
||||||
|
|
||||||
const rootProps = useForwardPropsEmits(reactivePick(props, 'defaultValue', 'disabled', 'id', 'mask', 'modelValue', 'name', 'otp', 'placeholder', 'required', 'type'), emits)
|
const rootProps = useForwardPropsEmits(reactivePick(props, 'defaultValue', 'disabled', 'id', 'mask', 'modelValue', 'name', 'otp', 'placeholder', 'required', 'type'), emits)
|
||||||
const { emitFormInput, emitFormChange, emitFormBlur, size, color, id, name, highlight, disabled } = useFormField<PinInputProps>(props)
|
const { emitFormInput, emitFormFocus, emitFormChange, emitFormBlur, size, color, id, name, highlight, disabled, ariaAttrs } = useFormField<PinInputProps>(props)
|
||||||
|
|
||||||
const ui = computed(() => pinInput({
|
const ui = computed(() => pinInput({
|
||||||
color: color.value,
|
color: color.value,
|
||||||
@@ -77,7 +77,7 @@ function onBlur(event: FocusEvent) {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<PinInputRoot
|
<PinInputRoot
|
||||||
v-bind="rootProps"
|
v-bind="{ ...rootProps, ...ariaAttrs }"
|
||||||
:id="id"
|
:id="id"
|
||||||
:name="name"
|
:name="name"
|
||||||
:class="ui.root({ class: [props.class, props.ui?.root] })"
|
:class="ui.root({ class: [props.class, props.ui?.root] })"
|
||||||
@@ -92,6 +92,7 @@ function onBlur(event: FocusEvent) {
|
|||||||
v-bind="$attrs"
|
v-bind="$attrs"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
@blur="onBlur"
|
@blur="onBlur"
|
||||||
|
@focus="emitFormFocus"
|
||||||
/>
|
/>
|
||||||
</PinInputRoot>
|
</PinInputRoot>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ import theme from '#build/ui/popover'
|
|||||||
import { tv } from '../utils/tv'
|
import { tv } from '../utils/tv'
|
||||||
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
||||||
|
|
||||||
const appConfig = _appConfig as AppConfig & { ui: { popover: Partial<typeof theme> } }
|
const appConfigPopover = _appConfig as AppConfig & { ui: { popover: Partial<typeof theme> } }
|
||||||
|
|
||||||
const popover = tv({ extend: tv(theme), ...(appConfig.ui?.popover || {}) })
|
const popover = tv({ extend: tv(theme), ...(appConfigPopover.ui?.popover || {}) })
|
||||||
|
|
||||||
export interface PopoverProps extends PopoverRootProps, Pick<HoverCardRootProps, 'openDelay' | 'closeDelay'> {
|
export interface PopoverProps extends PopoverRootProps, Pick<HoverCardRootProps, 'openDelay' | 'closeDelay'> {
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import _appConfig from '#build/app.config'
|
|||||||
import theme from '#build/ui/progress'
|
import theme from '#build/ui/progress'
|
||||||
import { tv } from '../utils/tv'
|
import { tv } from '../utils/tv'
|
||||||
|
|
||||||
const appConfig = _appConfig as AppConfig & { ui: { progress: Partial<typeof theme> } }
|
const appConfigProgress = _appConfig as AppConfig & { ui: { progress: Partial<typeof theme> } }
|
||||||
|
|
||||||
const progress = tv({ extend: tv(theme), ...(appConfig.ui?.progress || {}) })
|
const progress = tv({ extend: tv(theme), ...(appConfigProgress.ui?.progress || {}) })
|
||||||
|
|
||||||
type ProgressVariants = VariantProps<typeof progress>
|
type ProgressVariants = VariantProps<typeof progress>
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import theme from '#build/ui/radio-group'
|
|||||||
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
||||||
import { tv } from '../utils/tv'
|
import { tv } from '../utils/tv'
|
||||||
|
|
||||||
const appConfig = _appConfig as AppConfig & { ui: { radioGroup: Partial<typeof theme> } }
|
const appConfigRadioGroup = _appConfig as AppConfig & { ui: { radioGroup: Partial<typeof theme> } }
|
||||||
|
|
||||||
const radioGroup = tv({ extend: tv(theme), ...(appConfig.ui?.radioGroup || {}) })
|
const radioGroup = tv({ extend: tv(theme), ...(appConfigRadioGroup.ui?.radioGroup || {}) })
|
||||||
|
|
||||||
type RadioGroupVariants = VariantProps<typeof radioGroup>
|
type RadioGroupVariants = VariantProps<typeof radioGroup>
|
||||||
|
|
||||||
@@ -87,7 +87,7 @@ const slots = defineSlots<RadioGroupSlots<T>>()
|
|||||||
|
|
||||||
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'modelValue', 'defaultValue', 'orientation', 'loop', 'required'), emits)
|
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'modelValue', 'defaultValue', 'orientation', 'loop', 'required'), emits)
|
||||||
|
|
||||||
const { emitFormChange, emitFormInput, color, name, size, id: _id, disabled } = useFormField<RadioGroupProps<T>>(props, { bind: false })
|
const { emitFormChange, emitFormInput, color, name, size, id: _id, disabled, ariaAttrs } = useFormField<RadioGroupProps<T>>(props, { bind: false })
|
||||||
const id = _id.value ?? useId()
|
const id = _id.value ?? useId()
|
||||||
|
|
||||||
const ui = computed(() => radioGroup({
|
const ui = computed(() => radioGroup({
|
||||||
@@ -147,7 +147,7 @@ function onUpdate(value: any) {
|
|||||||
:class="ui.root({ class: [props.class, props.ui?.root] })"
|
:class="ui.root({ class: [props.class, props.ui?.root] })"
|
||||||
@update:model-value="onUpdate"
|
@update:model-value="onUpdate"
|
||||||
>
|
>
|
||||||
<fieldset :class="ui.fieldset({ class: props.ui?.fieldset })">
|
<fieldset :class="ui.fieldset({ class: props.ui?.fieldset })" v-bind="ariaAttrs">
|
||||||
<legend v-if="legend || !!slots.legend" :class="ui.legend({ class: props.ui?.legend })">
|
<legend v-if="legend || !!slots.legend" :class="ui.legend({ class: props.ui?.legend })">
|
||||||
<slot name="legend">
|
<slot name="legend">
|
||||||
{{ legend }}
|
{{ legend }}
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ import { tv } from '../utils/tv'
|
|||||||
import type { AvatarProps, ChipProps, InputProps } from '../types'
|
import type { AvatarProps, ChipProps, InputProps } from '../types'
|
||||||
import type { PartialString, MaybeArrayOfArray, MaybeArrayOfArrayItem, SelectModelValue, SelectModelValueEmits, SelectItemKey } from '../types/utils'
|
import type { PartialString, MaybeArrayOfArray, MaybeArrayOfArrayItem, SelectModelValue, SelectModelValueEmits, SelectItemKey } from '../types/utils'
|
||||||
|
|
||||||
const appConfig = _appConfig as AppConfig & { ui: { select: Partial<typeof theme> } }
|
const appConfigSelect = _appConfig as AppConfig & { ui: { select: Partial<typeof theme> } }
|
||||||
|
|
||||||
const select = tv({ extend: tv(theme), ...(appConfig.ui?.select || {}) })
|
const select = tv({ extend: tv(theme), ...(appConfigSelect.ui?.select || {}) })
|
||||||
|
|
||||||
export interface SelectItem {
|
export interface SelectItem {
|
||||||
label?: string
|
label?: string
|
||||||
@@ -129,11 +129,11 @@ const emits = defineEmits<SelectEmits<T, V, M>>()
|
|||||||
const slots = defineSlots<SelectSlots<T, M>>()
|
const slots = defineSlots<SelectSlots<T, M>>()
|
||||||
|
|
||||||
const appConfig = useAppConfig()
|
const appConfig = useAppConfig()
|
||||||
const rootProps = useForwardPropsEmits(reactivePick(props, 'modelValue', 'defaultValue', 'open', 'defaultOpen', 'disabled', 'autocomplete', 'required', 'multiple'), emits)
|
const rootProps = useForwardPropsEmits(reactivePick(props, 'open', 'defaultOpen', 'disabled', 'autocomplete', 'required', 'multiple'), emits)
|
||||||
const contentProps = toRef(() => defu(props.content, { side: 'bottom', sideOffset: 8, collisionPadding: 8, position: 'popper' }) as SelectContentProps)
|
const contentProps = toRef(() => defu(props.content, { side: 'bottom', sideOffset: 8, collisionPadding: 8, position: 'popper' }) as SelectContentProps)
|
||||||
const arrowProps = toRef(() => props.arrow as SelectArrowProps)
|
const arrowProps = toRef(() => props.arrow as SelectArrowProps)
|
||||||
|
|
||||||
const { emitFormChange, emitFormInput, emitFormBlur, size: formGroupSize, color, id, name, highlight, disabled } = useFormField<InputProps>(props)
|
const { emitFormChange, emitFormInput, emitFormBlur, emitFormFocus, size: formGroupSize, color, id, name, highlight, disabled, ariaAttrs } = useFormField<InputProps>(props)
|
||||||
const { orientation, size: buttonGroupSize } = useButtonGroup<InputProps>(props)
|
const { orientation, size: buttonGroupSize } = useButtonGroup<InputProps>(props)
|
||||||
const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponentIcons(toRef(() => defu(props, { trailingIcon: appConfig.ui.icons.chevronDown })))
|
const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponentIcons(toRef(() => defu(props, { trailingIcon: appConfig.ui.icons.chevronDown })))
|
||||||
|
|
||||||
@@ -179,6 +179,7 @@ function onUpdateOpen(value: boolean) {
|
|||||||
} else {
|
} else {
|
||||||
const event = new FocusEvent('focus')
|
const event = new FocusEvent('focus')
|
||||||
emits('focus', event)
|
emits('focus', event)
|
||||||
|
emitFormFocus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -186,16 +187,17 @@ function onUpdateOpen(value: boolean) {
|
|||||||
<!-- eslint-disable vue/no-template-shadow -->
|
<!-- eslint-disable vue/no-template-shadow -->
|
||||||
<template>
|
<template>
|
||||||
<SelectRoot
|
<SelectRoot
|
||||||
:id="id"
|
|
||||||
v-slot="{ modelValue, open }"
|
v-slot="{ modelValue, open }"
|
||||||
v-bind="rootProps"
|
|
||||||
:name="name"
|
:name="name"
|
||||||
|
v-bind="rootProps"
|
||||||
:autocomplete="autocomplete"
|
:autocomplete="autocomplete"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
|
:default-value="(defaultValue as (AcceptableValue | AcceptableValue[] | undefined))"
|
||||||
|
:model-value="(modelValue as (AcceptableValue | AcceptableValue[] | undefined))"
|
||||||
@update:model-value="onUpdate"
|
@update:model-value="onUpdate"
|
||||||
@update:open="onUpdateOpen"
|
@update:open="onUpdateOpen"
|
||||||
>
|
>
|
||||||
<SelectTrigger :class="ui.base({ class: [props.class, props.ui?.base] })">
|
<SelectTrigger :id="id" :class="ui.base({ class: [props.class, props.ui?.base] })" v-bind="ariaAttrs">
|
||||||
<span v-if="isLeading || !!avatar || !!slots.leading" :class="ui.leading({ class: props.ui?.leading })">
|
<span v-if="isLeading || !!avatar || !!slots.leading" :class="ui.leading({ class: props.ui?.leading })">
|
||||||
<slot name="leading" :model-value="(modelValue as M extends true ? AcceptableValue[] : AcceptableValue)" :open="open" :ui="ui">
|
<slot name="leading" :model-value="(modelValue as M extends true ? AcceptableValue[] : AcceptableValue)" :open="open" :ui="ui">
|
||||||
<UIcon v-if="isLeading && leadingIconName" :name="leadingIconName" :class="ui.leadingIcon({ class: props.ui?.leadingIcon })" />
|
<UIcon v-if="isLeading && leadingIconName" :name="leadingIconName" :class="ui.leadingIcon({ class: props.ui?.leadingIcon })" />
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ import { tv } from '../utils/tv'
|
|||||||
import type { AvatarProps, ChipProps, InputProps } from '../types'
|
import type { AvatarProps, ChipProps, InputProps } from '../types'
|
||||||
import type { PartialString, MaybeArrayOfArray, MaybeArrayOfArrayItem, SelectModelValue, SelectModelValueEmits, SelectItemKey } from '../types/utils'
|
import type { PartialString, MaybeArrayOfArray, MaybeArrayOfArrayItem, SelectModelValue, SelectModelValueEmits, SelectItemKey } from '../types/utils'
|
||||||
|
|
||||||
const appConfig = _appConfig as AppConfig & { ui: { selectMenu: Partial<typeof theme> } }
|
const appConfigSelectMenu = _appConfig as AppConfig & { ui: { selectMenu: Partial<typeof theme> } }
|
||||||
|
|
||||||
const selectMenu = tv({ extend: tv(theme), ...(appConfig.ui?.selectMenu || {}) })
|
const selectMenu = tv({ extend: tv(theme), ...(appConfigSelectMenu.ui?.selectMenu || {}) })
|
||||||
|
|
||||||
export interface SelectMenuItem {
|
export interface SelectMenuItem {
|
||||||
label?: string
|
label?: string
|
||||||
@@ -168,7 +168,7 @@ const contentProps = toRef(() => defu(props.content, { side: 'bottom', sideOffse
|
|||||||
const arrowProps = toRef(() => props.arrow as ComboboxArrowProps)
|
const arrowProps = toRef(() => props.arrow as ComboboxArrowProps)
|
||||||
const searchInputProps = toRef(() => defu(props.searchInput, { placeholder: t('selectMenu.search'), variant: 'none' }) as InputProps)
|
const searchInputProps = toRef(() => defu(props.searchInput, { placeholder: t('selectMenu.search'), variant: 'none' }) as InputProps)
|
||||||
|
|
||||||
const { emitFormBlur, emitFormInput, emitFormChange, size: formGroupSize, color, id, name, highlight, disabled } = useFormField<InputProps>(props)
|
const { emitFormBlur, emitFormFocus, emitFormInput, emitFormChange, size: formGroupSize, color, id, name, highlight, disabled, ariaAttrs } = useFormField<InputProps>(props)
|
||||||
const { orientation, size: buttonGroupSize } = useButtonGroup<InputProps>(props)
|
const { orientation, size: buttonGroupSize } = useButtonGroup<InputProps>(props)
|
||||||
const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponentIcons(toRef(() => defu(props, { trailingIcon: appConfig.ui.icons.chevronDown })))
|
const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponentIcons(toRef(() => defu(props, { trailingIcon: appConfig.ui.icons.chevronDown })))
|
||||||
|
|
||||||
@@ -272,6 +272,7 @@ function onUpdateOpen(value: boolean) {
|
|||||||
} else {
|
} else {
|
||||||
const event = new FocusEvent('focus')
|
const event = new FocusEvent('focus')
|
||||||
emits('focus', event)
|
emits('focus', event)
|
||||||
|
emitFormFocus()
|
||||||
clearTimeout(timeoutId)
|
clearTimeout(timeoutId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -298,7 +299,7 @@ function onUpdateOpen(value: boolean) {
|
|||||||
<ComboboxRoot
|
<ComboboxRoot
|
||||||
:id="id"
|
:id="id"
|
||||||
v-slot="{ modelValue, open }"
|
v-slot="{ modelValue, open }"
|
||||||
v-bind="rootProps"
|
v-bind="{ ...rootProps, ...ariaAttrs }"
|
||||||
ignore-filter
|
ignore-filter
|
||||||
as-child
|
as-child
|
||||||
:name="name"
|
:name="name"
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import theme from '#build/ui/separator'
|
|||||||
import { tv } from '../utils/tv'
|
import { tv } from '../utils/tv'
|
||||||
import type { AvatarProps } from '../types'
|
import type { AvatarProps } from '../types'
|
||||||
|
|
||||||
const appConfig = _appConfig as AppConfig & { ui: { separator: Partial<typeof theme> } }
|
const appConfigSeparator = _appConfig as AppConfig & { ui: { separator: Partial<typeof theme> } }
|
||||||
|
|
||||||
const separator = tv({ extend: tv(theme), ...(appConfig.ui?.separator || {}) })
|
const separator = tv({ extend: tv(theme), ...(appConfigSeparator.ui?.separator || {}) })
|
||||||
|
|
||||||
type SeparatorVariants = VariantProps<typeof separator>
|
type SeparatorVariants = VariantProps<typeof separator>
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import theme from '#build/ui/skeleton'
|
|||||||
import { tv } from '../utils/tv'
|
import { tv } from '../utils/tv'
|
||||||
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
||||||
|
|
||||||
const appConfig = _appConfig as AppConfig & { ui: { skeleton: Partial<typeof theme> } }
|
const appConfigSkeleton = _appConfig as AppConfig & { ui: { skeleton: Partial<typeof theme> } }
|
||||||
|
|
||||||
const skeleton = tv({ extend: tv(theme), ...(appConfig.ui?.skeleton || {}) })
|
const skeleton = tv({ extend: tv(theme), ...(appConfigSkeleton.ui?.skeleton || {}) })
|
||||||
|
|
||||||
export interface SkeletonProps {
|
export interface SkeletonProps {
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
|||||||
import { tv } from '../utils/tv'
|
import { tv } from '../utils/tv'
|
||||||
import type { ButtonProps } from '../types'
|
import type { ButtonProps } from '../types'
|
||||||
|
|
||||||
const appConfig = _appConfig as AppConfig & { ui: { slideover: Partial<typeof theme> } }
|
const appConfigSlideover = _appConfig as AppConfig & { ui: { slideover: Partial<typeof theme> } }
|
||||||
|
|
||||||
const slideover = tv({ extend: tv(theme), ...(appConfig.ui?.slideover || {}) })
|
const slideover = tv({ extend: tv(theme), ...(appConfigSlideover.ui?.slideover || {}) })
|
||||||
|
|
||||||
type SlideoverVariants = VariantProps<typeof slideover>
|
type SlideoverVariants = VariantProps<typeof slideover>
|
||||||
|
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ import _appConfig from '#build/app.config'
|
|||||||
import theme from '#build/ui/slider'
|
import theme from '#build/ui/slider'
|
||||||
import { tv } from '../utils/tv'
|
import { tv } from '../utils/tv'
|
||||||
|
|
||||||
const appConfig = _appConfig as AppConfig & { ui: { slider: Partial<typeof theme> } }
|
const appConfigSlider = _appConfig as AppConfig & { ui: { slider: Partial<typeof theme> } }
|
||||||
|
|
||||||
const slider = tv({ extend: tv(theme), ...(appConfig.ui?.slider || {}) })
|
const slider = tv({ extend: tv(theme), ...(appConfigSlider.ui?.slider || {}) })
|
||||||
|
|
||||||
type SliderVariants = VariantProps<typeof slider>
|
type SliderVariants = VariantProps<typeof slider>
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ const modelValue = defineModel<number | number[]>()
|
|||||||
|
|
||||||
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'orientation', 'min', 'max', 'step', 'minStepsBetweenThumbs', 'inverted'), emits)
|
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'orientation', 'min', 'max', 'step', 'minStepsBetweenThumbs', 'inverted'), emits)
|
||||||
|
|
||||||
const { id, emitFormChange, emitFormInput, size, color, name, disabled } = useFormField<SliderProps>(props)
|
const { id, emitFormChange, emitFormInput, size, color, name, disabled, ariaAttrs } = useFormField<SliderProps>(props)
|
||||||
|
|
||||||
const defaultSliderValue = computed(() => {
|
const defaultSliderValue = computed(() => {
|
||||||
if (typeof props.defaultValue === 'number') {
|
if (typeof props.defaultValue === 'number') {
|
||||||
@@ -95,7 +95,7 @@ function onChange(value: any) {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<SliderRoot
|
<SliderRoot
|
||||||
v-bind="rootProps"
|
v-bind="{ ...rootProps, ...ariaAttrs }"
|
||||||
:id="id"
|
:id="id"
|
||||||
v-model="sliderValue"
|
v-model="sliderValue"
|
||||||
:name="name"
|
:name="name"
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
|||||||
import { tv } from '../utils/tv'
|
import { tv } from '../utils/tv'
|
||||||
import type { DynamicSlots } from '../types/utils'
|
import type { DynamicSlots } from '../types/utils'
|
||||||
|
|
||||||
const appConfig = _appConfig as AppConfig & { ui: { stepper: Partial<typeof theme> } }
|
const appConfigStepper = _appConfig as AppConfig & { ui: { stepper: Partial<typeof theme> } }
|
||||||
|
|
||||||
const stepper = tv({ extend: tv(theme), ...(appConfig.ui?.stepper || {}) })
|
const stepper = tv({ extend: tv(theme), ...(appConfigStepper.ui?.stepper || {}) })
|
||||||
|
|
||||||
type StepperVariants = VariantProps<typeof stepper>
|
type StepperVariants = VariantProps<typeof stepper>
|
||||||
|
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
|||||||
import { tv } from '../utils/tv'
|
import { tv } from '../utils/tv'
|
||||||
import type { PartialString } from '../types/utils'
|
import type { PartialString } from '../types/utils'
|
||||||
|
|
||||||
const appConfig = _appConfig as AppConfig & { ui: { switch: Partial<typeof theme> } }
|
const appConfigSwitch = _appConfig as AppConfig & { ui: { switch: Partial<typeof theme> } }
|
||||||
|
|
||||||
const switchTv = tv({ extend: tv(theme), ...(appConfig.ui?.switch || {}) })
|
const switchTv = tv({ extend: tv(theme), ...(appConfigSwitch.ui?.switch || {}) })
|
||||||
|
|
||||||
type SwitchVariants = VariantProps<typeof switchTv>
|
type SwitchVariants = VariantProps<typeof switchTv>
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ const modelValue = defineModel<boolean>({ default: undefined })
|
|||||||
const appConfig = useAppConfig()
|
const appConfig = useAppConfig()
|
||||||
const rootProps = useForwardProps(reactivePick(props, 'required', 'value', 'defaultValue'))
|
const rootProps = useForwardProps(reactivePick(props, 'required', 'value', 'defaultValue'))
|
||||||
|
|
||||||
const { id: _id, emitFormChange, emitFormInput, size, color, name, disabled } = useFormField<SwitchProps>(props)
|
const { id: _id, emitFormChange, emitFormInput, size, color, name, disabled, ariaAttrs } = useFormField<SwitchProps>(props)
|
||||||
const id = _id.value ?? useId()
|
const id = _id.value ?? useId()
|
||||||
|
|
||||||
const ui = computed(() => switchTv({
|
const ui = computed(() => switchTv({
|
||||||
@@ -93,7 +93,7 @@ function onUpdate(value: any) {
|
|||||||
<div :class="ui.container({ class: props.ui?.container })">
|
<div :class="ui.container({ class: props.ui?.container })">
|
||||||
<SwitchRoot
|
<SwitchRoot
|
||||||
:id="id"
|
:id="id"
|
||||||
v-bind="rootProps"
|
v-bind="{ ...rootProps, ...ariaAttrs }"
|
||||||
v-model="modelValue"
|
v-model="modelValue"
|
||||||
:name="name"
|
:name="name"
|
||||||
:disabled="disabled || loading"
|
:disabled="disabled || loading"
|
||||||
|
|||||||
@@ -38,9 +38,9 @@ declare module '@tanstack/table-core' {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const appConfig = _appConfig as AppConfig & { ui: { table: Partial<typeof theme> } }
|
const appConfigTable = _appConfig as AppConfig & { ui: { table: Partial<typeof theme> } }
|
||||||
|
|
||||||
const table = tv({ extend: tv(theme), ...(appConfig.ui?.table || {}) })
|
const table = tv({ extend: tv(theme), ...(appConfigTable.ui?.table || {}) })
|
||||||
|
|
||||||
type TableVariants = VariantProps<typeof table>
|
type TableVariants = VariantProps<typeof table>
|
||||||
|
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ import { tv } from '../utils/tv'
|
|||||||
import type { AvatarProps } from '../types'
|
import type { AvatarProps } from '../types'
|
||||||
import type { DynamicSlots, PartialString } from '../types/utils'
|
import type { DynamicSlots, PartialString } from '../types/utils'
|
||||||
|
|
||||||
const appConfig = _appConfig as AppConfig & { ui: { tabs: Partial<typeof theme> } }
|
const appConfigTabs = _appConfig as AppConfig & { ui: { tabs: Partial<typeof theme> } }
|
||||||
|
|
||||||
const tabs = tv({ extend: tv(theme), ...(appConfig.ui?.tabs || {}) })
|
const tabs = tv({ extend: tv(theme), ...(appConfigTabs.ui?.tabs || {}) })
|
||||||
|
|
||||||
export interface TabsItem {
|
export interface TabsItem {
|
||||||
label?: string
|
label?: string
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import _appConfig from '#build/app.config'
|
|||||||
import theme from '#build/ui/textarea'
|
import theme from '#build/ui/textarea'
|
||||||
import { tv } from '../utils/tv'
|
import { tv } from '../utils/tv'
|
||||||
|
|
||||||
const appConfig = _appConfig as AppConfig & { ui: { textarea: Partial<typeof theme> } }
|
const appConfigTextarea = _appConfig as AppConfig & { ui: { textarea: Partial<typeof theme> } }
|
||||||
|
|
||||||
const textarea = tv({ extend: tv(theme), ...(appConfig.ui?.textarea || {}) })
|
const textarea = tv({ extend: tv(theme), ...(appConfigTextarea.ui?.textarea || {}) })
|
||||||
|
|
||||||
type TextareaVariants = VariantProps<typeof textarea>
|
type TextareaVariants = VariantProps<typeof textarea>
|
||||||
|
|
||||||
@@ -66,7 +66,7 @@ const emits = defineEmits<TextareaEmits>()
|
|||||||
|
|
||||||
const [modelValue, modelModifiers] = defineModel<string | number>()
|
const [modelValue, modelModifiers] = defineModel<string | number>()
|
||||||
|
|
||||||
const { emitFormBlur, emitFormInput, emitFormChange, size, color, id, name, highlight, disabled } = useFormField<TextareaProps>(props, { deferInputValidation: true })
|
const { emitFormFocus, emitFormBlur, emitFormInput, emitFormChange, size, color, id, name, highlight, disabled, ariaAttrs } = useFormField<TextareaProps>(props, { deferInputValidation: true })
|
||||||
|
|
||||||
const ui = computed(() => textarea({
|
const ui = computed(() => textarea({
|
||||||
color: color.value,
|
color: color.value,
|
||||||
@@ -185,10 +185,11 @@ onMounted(() => {
|
|||||||
:class="ui.base({ class: props.ui?.base })"
|
:class="ui.base({ class: props.ui?.base })"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
:required="required"
|
:required="required"
|
||||||
v-bind="$attrs"
|
v-bind="{ ...$attrs, ...ariaAttrs }"
|
||||||
@input="onInput"
|
@input="onInput"
|
||||||
@blur="onBlur"
|
@blur="onBlur"
|
||||||
@change="onChange"
|
@change="onChange"
|
||||||
|
@focus="emitFormFocus"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<slot />
|
<slot />
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
|||||||
import { tv } from '../utils/tv'
|
import { tv } from '../utils/tv'
|
||||||
import type { AvatarProps, ButtonProps } from '../types'
|
import type { AvatarProps, ButtonProps } from '../types'
|
||||||
|
|
||||||
const appConfig = _appConfig as AppConfig & { ui: { toast: Partial<typeof theme> } }
|
const appConfigToast = _appConfig as AppConfig & { ui: { toast: Partial<typeof theme> } }
|
||||||
|
|
||||||
const toast = tv({ extend: tv(theme), ...(appConfig.ui?.toast || {}) })
|
const toast = tv({ extend: tv(theme), ...(appConfigToast.ui?.toast || {}) })
|
||||||
|
|
||||||
type ToastVariants = VariantProps<typeof toast>
|
type ToastVariants = VariantProps<typeof toast>
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import theme from '#build/ui/toaster'
|
|||||||
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
||||||
import { tv } from '../utils/tv'
|
import { tv } from '../utils/tv'
|
||||||
|
|
||||||
const appConfig = _appConfig as AppConfig & { ui: { toaster: Partial<typeof theme> } }
|
const appConfigToaster = _appConfig as AppConfig & { ui: { toaster: Partial<typeof theme> } }
|
||||||
|
|
||||||
const toaster = tv({ extend: tv(theme), ...(appConfig.ui?.toaster || {}) })
|
const toaster = tv({ extend: tv(theme), ...(appConfigToaster.ui?.toaster || {}) })
|
||||||
|
|
||||||
type ToasterVariants = VariantProps<typeof toaster>
|
type ToasterVariants = VariantProps<typeof toaster>
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
|||||||
import { tv } from '../utils/tv'
|
import { tv } from '../utils/tv'
|
||||||
import type { KbdProps } from '../types'
|
import type { KbdProps } from '../types'
|
||||||
|
|
||||||
const appConfig = _appConfig as AppConfig & { ui: { tooltip: Partial<typeof theme> } }
|
const appConfigTooltip = _appConfig as AppConfig & { ui: { tooltip: Partial<typeof theme> } }
|
||||||
|
|
||||||
const tooltip = tv({ extend: tv(theme), ...(appConfig.ui?.tooltip || {}) })
|
const tooltip = tv({ extend: tv(theme), ...(appConfigTooltip.ui?.tooltip || {}) })
|
||||||
|
|
||||||
export interface TooltipProps extends TooltipRootProps {
|
export interface TooltipProps extends TooltipRootProps {
|
||||||
/** The text content of the tooltip. */
|
/** The text content of the tooltip. */
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import type { GetObjectField } from '../types/utils'
|
|||||||
export const buttonGroupInjectionKey: InjectionKey<ComputedRef<{
|
export const buttonGroupInjectionKey: InjectionKey<ComputedRef<{
|
||||||
size: ButtonGroupProps['size']
|
size: ButtonGroupProps['size']
|
||||||
orientation: ButtonGroupProps['orientation']
|
orientation: ButtonGroupProps['orientation']
|
||||||
}> | undefined> = Symbol('nuxt-ui.button-group')
|
}>> = Symbol('nuxt-ui.button-group')
|
||||||
|
|
||||||
type Props<T> = {
|
type Props<T> = {
|
||||||
size?: GetObjectField<T, 'size'>
|
size?: GetObjectField<T, 'size'>
|
||||||
@@ -14,7 +14,6 @@ type Props<T> = {
|
|||||||
|
|
||||||
export function useButtonGroup<T>(props: Props<T>) {
|
export function useButtonGroup<T>(props: Props<T>) {
|
||||||
const buttonGroup = inject(buttonGroupInjectionKey, undefined)
|
const buttonGroup = inject(buttonGroupInjectionKey, undefined)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
orientation: computed(() => buttonGroup?.value.orientation),
|
orientation: computed(() => buttonGroup?.value.orientation),
|
||||||
size: computed(() => props?.size ?? buttonGroup?.value.size)
|
size: computed(() => props?.size ?? buttonGroup?.value.size)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { inject, ref, computed, type InjectionKey, type Ref, type ComputedRef } from 'vue'
|
import { inject, computed, type InjectionKey, type Ref, type ComputedRef } from 'vue'
|
||||||
import { type UseEventBusReturn, useDebounceFn } from '@vueuse/core'
|
import { type UseEventBusReturn, useDebounceFn } from '@vueuse/core'
|
||||||
import type { FormFieldProps } from '../types'
|
import type { FormFieldProps } from '../types'
|
||||||
import type { FormEvent, FormInputEvents, FormFieldInjectedOptions, FormInjectedOptions } from '../types/form'
|
import type { FormEvent, FormInputEvents, FormFieldInjectedOptions, FormInjectedOptions } from '../types/form'
|
||||||
@@ -9,13 +9,12 @@ type Props<T> = {
|
|||||||
name?: string
|
name?: string
|
||||||
size?: GetObjectField<T, 'size'>
|
size?: GetObjectField<T, 'size'>
|
||||||
color?: GetObjectField<T, 'color'>
|
color?: GetObjectField<T, 'color'>
|
||||||
legend?: string
|
|
||||||
highlight?: boolean
|
highlight?: boolean
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const formOptionsInjectionKey: InjectionKey<ComputedRef<FormInjectedOptions>> = Symbol('nuxt-ui.form-options')
|
export const formOptionsInjectionKey: InjectionKey<ComputedRef<FormInjectedOptions>> = Symbol('nuxt-ui.form-options')
|
||||||
export const formBusInjectionKey: InjectionKey<UseEventBusReturn<FormEvent, string>> = Symbol('nuxt-ui.form-events')
|
export const formBusInjectionKey: InjectionKey<UseEventBusReturn<FormEvent<any>, string>> = Symbol('nuxt-ui.form-events')
|
||||||
export const formFieldInjectionKey: InjectionKey<ComputedRef<FormFieldInjectedOptions<FormFieldProps>>> = Symbol('nuxt-ui.form-field')
|
export const formFieldInjectionKey: InjectionKey<ComputedRef<FormFieldInjectedOptions<FormFieldProps>>> = Symbol('nuxt-ui.form-field')
|
||||||
export const inputIdInjectionKey: InjectionKey<Ref<string | undefined>> = Symbol('nuxt-ui.input-id')
|
export const inputIdInjectionKey: InjectionKey<Ref<string | undefined>> = Symbol('nuxt-ui.input-id')
|
||||||
export const formInputsInjectionKey: InjectionKey<Ref<Record<string, { id?: string, pattern?: RegExp }>>> = Symbol('nuxt-ui.form-inputs')
|
export const formInputsInjectionKey: InjectionKey<Ref<Record<string, { id?: string, pattern?: RegExp }>>> = Symbol('nuxt-ui.form-inputs')
|
||||||
@@ -29,41 +28,40 @@ export function useFormField<T>(props?: Props<T>, opts?: { bind?: boolean, defer
|
|||||||
const inputId = inject(inputIdInjectionKey, undefined)
|
const inputId = inject(inputIdInjectionKey, undefined)
|
||||||
|
|
||||||
if (formField && inputId) {
|
if (formField && inputId) {
|
||||||
if (opts?.bind === false || props?.legend) {
|
if (opts?.bind === false) {
|
||||||
// Removes for="..." attribute on label for RadioGroup and alike.
|
// Removes for="..." attribute on label for RadioGroup and alike.
|
||||||
inputId.value = undefined
|
inputId.value = undefined
|
||||||
} else if (props?.id) {
|
} else if (props?.id) {
|
||||||
// Updates for="..." attribute on label if props.id is provided.
|
// Updates for="..." attribute on label if props.id is provided.
|
||||||
inputId.value = props?.id
|
inputId.value = props?.id
|
||||||
}
|
}
|
||||||
|
|
||||||
if (formInputs && formField.value.name && inputId.value) {
|
if (formInputs && formField.value.name && inputId.value) {
|
||||||
formInputs.value[formField.value.name] = { id: inputId.value, pattern: formField.value.errorPattern }
|
formInputs.value[formField.value.name] = { id: inputId.value, pattern: formField.value.errorPattern }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const touched = ref(false)
|
function emitFormEvent(type: FormInputEvents, name?: string, eager?: boolean) {
|
||||||
|
|
||||||
function emitFormEvent(type: FormInputEvents, name?: string) {
|
|
||||||
if (formBus && formField && name) {
|
if (formBus && formField && name) {
|
||||||
formBus.emit({ type, name })
|
formBus.emit({ type, name, eager })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function emitFormBlur() {
|
function emitFormBlur() {
|
||||||
touched.value = true
|
|
||||||
emitFormEvent('blur', formField?.value.name)
|
emitFormEvent('blur', formField?.value.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function emitFormFocus() {
|
||||||
|
emitFormEvent('focus', formField?.value.name)
|
||||||
|
}
|
||||||
|
|
||||||
function emitFormChange() {
|
function emitFormChange() {
|
||||||
touched.value = true
|
|
||||||
emitFormEvent('change', formField?.value.name)
|
emitFormEvent('change', formField?.value.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
const emitFormInput = useDebounceFn(
|
const emitFormInput = useDebounceFn(
|
||||||
() => {
|
() => {
|
||||||
if (!opts?.deferInputValidation || touched.value || formField?.value.eagerValidation) {
|
emitFormEvent('input', formField?.value.name, !opts?.deferInputValidation || formField?.value.eagerValidation)
|
||||||
emitFormEvent('input', formField?.value.name)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
formField?.value.validateOnInputDelay ?? formOptions?.value.validateOnInputDelay ?? 0
|
formField?.value.validateOnInputDelay ?? formOptions?.value.validateOnInputDelay ?? 0
|
||||||
)
|
)
|
||||||
@@ -77,6 +75,19 @@ export function useFormField<T>(props?: Props<T>, opts?: { bind?: boolean, defer
|
|||||||
disabled: computed(() => formOptions?.value.disabled || props?.disabled),
|
disabled: computed(() => formOptions?.value.disabled || props?.disabled),
|
||||||
emitFormBlur,
|
emitFormBlur,
|
||||||
emitFormInput,
|
emitFormInput,
|
||||||
emitFormChange
|
emitFormChange,
|
||||||
|
emitFormFocus,
|
||||||
|
ariaAttrs: computed(() => {
|
||||||
|
if (!formField?.value) return
|
||||||
|
|
||||||
|
const descriptiveAttrs = ['error' as const, 'hint' as const, 'description' as const]
|
||||||
|
.filter(type => formField?.value?.[type])
|
||||||
|
.map(type => `${formField?.value.ariaId}-${type}`) || []
|
||||||
|
|
||||||
|
return {
|
||||||
|
'aria-describedby': descriptiveAttrs.join(' '),
|
||||||
|
'aria-invalid': !!formField?.value.error
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { ref, nextTick } from 'vue'
|
||||||
import { useState } from '#imports'
|
import { useState } from '#imports'
|
||||||
import type { ToastProps } from '../types'
|
import type { ToastProps } from '../types'
|
||||||
|
|
||||||
@@ -8,20 +9,40 @@ export interface Toast extends Omit<ToastProps, 'defaultOpen'> {
|
|||||||
|
|
||||||
export function useToast() {
|
export function useToast() {
|
||||||
const toasts = useState<Toast[]>('toasts', () => [])
|
const toasts = useState<Toast[]>('toasts', () => [])
|
||||||
|
const maxToasts = 5
|
||||||
|
const running = ref(false)
|
||||||
|
const queue: Toast[] = []
|
||||||
|
|
||||||
function add(toast: Partial<Toast>): Toast {
|
const generateId = () => `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`
|
||||||
|
|
||||||
|
async function processQueue() {
|
||||||
|
if (running.value || queue.length === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
running.value = true
|
||||||
|
|
||||||
|
while (queue.length > 0) {
|
||||||
|
const toast = queue.shift()!
|
||||||
|
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
|
toasts.value = [...toasts.value, toast].slice(-maxToasts)
|
||||||
|
}
|
||||||
|
|
||||||
|
running.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
async function add(toast: Partial<Toast>): Promise<Toast> {
|
||||||
const body = {
|
const body = {
|
||||||
id: new Date().getTime().toString(),
|
id: generateId(),
|
||||||
open: true,
|
open: true,
|
||||||
...toast
|
...toast
|
||||||
}
|
}
|
||||||
|
|
||||||
const index = toasts.value.findIndex((t: Toast) => t.id === body.id)
|
queue.push(body)
|
||||||
if (index === -1) {
|
|
||||||
toasts.value.push(body)
|
|
||||||
}
|
|
||||||
|
|
||||||
toasts.value = toasts.value.slice(-5)
|
await processQueue()
|
||||||
|
|
||||||
return body
|
return body
|
||||||
}
|
}
|
||||||
|
|||||||
54
src/runtime/locale/he.ts
Normal file
54
src/runtime/locale/he.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import { defineLocale } from '../composables/defineLocale'
|
||||||
|
|
||||||
|
export default defineLocale({
|
||||||
|
name: 'Hebrew',
|
||||||
|
code: 'he',
|
||||||
|
dir: 'rtl',
|
||||||
|
messages: {
|
||||||
|
inputMenu: {
|
||||||
|
noMatch: 'אין התאמה',
|
||||||
|
noData: 'אין נתונים',
|
||||||
|
create: 'צור "{label}"'
|
||||||
|
},
|
||||||
|
calendar: {
|
||||||
|
prevYear: 'שנה קודמת',
|
||||||
|
nextYear: 'שנה הבאה',
|
||||||
|
prevMonth: 'חודש קודם',
|
||||||
|
nextMonth: 'חודש הבא'
|
||||||
|
},
|
||||||
|
inputNumber: {
|
||||||
|
increment: 'הוסף',
|
||||||
|
decrement: 'הפחת'
|
||||||
|
},
|
||||||
|
commandPalette: {
|
||||||
|
placeholder: 'הקלד פקודה...',
|
||||||
|
noMatch: 'לא נמצאה התאמה',
|
||||||
|
noData: 'אין נתונים זמינים',
|
||||||
|
close: 'סגור'
|
||||||
|
},
|
||||||
|
selectMenu: {
|
||||||
|
noMatch: 'לא נמצאה התאמה',
|
||||||
|
noData: 'אין נתונים',
|
||||||
|
create: 'צור "{label}"',
|
||||||
|
search: 'חפש...'
|
||||||
|
},
|
||||||
|
toast: { close: 'סגור' },
|
||||||
|
carousel: {
|
||||||
|
prev: 'הקודם',
|
||||||
|
next: 'הבא',
|
||||||
|
goto: 'מעבר ל {slide}'
|
||||||
|
},
|
||||||
|
modal: {
|
||||||
|
close: 'סגור'
|
||||||
|
},
|
||||||
|
slideover: {
|
||||||
|
close: 'סגור'
|
||||||
|
},
|
||||||
|
alert: {
|
||||||
|
close: 'סגור'
|
||||||
|
},
|
||||||
|
table: {
|
||||||
|
noData: 'אין נתונים להצגה'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
55
src/runtime/locale/hi.ts
Normal file
55
src/runtime/locale/hi.ts
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import { defineLocale } from '../composables/defineLocale'
|
||||||
|
|
||||||
|
export default defineLocale({
|
||||||
|
name: 'Hindi',
|
||||||
|
code: 'hi',
|
||||||
|
messages: {
|
||||||
|
inputMenu: {
|
||||||
|
noMatch: 'कोई मेल खाता डेटा नहीं',
|
||||||
|
noData: 'कोई डेटा नहीं',
|
||||||
|
create: '"{label}" बनाएँ'
|
||||||
|
},
|
||||||
|
calendar: {
|
||||||
|
prevYear: 'पिछला वर्ष',
|
||||||
|
nextYear: 'अगला वर्ष',
|
||||||
|
prevMonth: 'पिछला महीना',
|
||||||
|
nextMonth: 'अगला महीना'
|
||||||
|
},
|
||||||
|
inputNumber: {
|
||||||
|
increment: 'बढ़ाना',
|
||||||
|
decrement: 'घटाना'
|
||||||
|
},
|
||||||
|
commandPalette: {
|
||||||
|
placeholder: 'एक आदेश या खोज टाइप करें...',
|
||||||
|
noMatch: 'कोई मेल खाता डेटा नहीं',
|
||||||
|
noData: 'कोई डेटा नहीं',
|
||||||
|
close: 'बंद करें'
|
||||||
|
},
|
||||||
|
selectMenu: {
|
||||||
|
noMatch: 'कोई मेल खाता डेटा नहीं',
|
||||||
|
noData: 'कोई डेटा नहीं',
|
||||||
|
create: '"{label}" बनाएँ',
|
||||||
|
search: 'खोजें...'
|
||||||
|
},
|
||||||
|
toast: {
|
||||||
|
close: 'बंद करें'
|
||||||
|
},
|
||||||
|
carousel: {
|
||||||
|
prev: 'पिछला',
|
||||||
|
next: 'अगला',
|
||||||
|
goto: 'स्लाइड {slide} पर जाएँ'
|
||||||
|
},
|
||||||
|
modal: {
|
||||||
|
close: 'बंद करें'
|
||||||
|
},
|
||||||
|
slideover: {
|
||||||
|
close: 'बंद करें'
|
||||||
|
},
|
||||||
|
alert: {
|
||||||
|
close: 'बंद करें'
|
||||||
|
},
|
||||||
|
table: {
|
||||||
|
noData: 'कोई डेटा नहीं'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
55
src/runtime/locale/hu.ts
Normal file
55
src/runtime/locale/hu.ts
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import { defineLocale } from '../composables/defineLocale'
|
||||||
|
|
||||||
|
export default defineLocale({
|
||||||
|
name: 'Magyar',
|
||||||
|
code: 'hu',
|
||||||
|
messages: {
|
||||||
|
inputMenu: {
|
||||||
|
noMatch: 'Nincs találat',
|
||||||
|
noData: 'Nincs adat',
|
||||||
|
create: '"{label}" létrehozása'
|
||||||
|
},
|
||||||
|
calendar: {
|
||||||
|
prevYear: 'Előző év',
|
||||||
|
nextYear: 'Következő év',
|
||||||
|
prevMonth: 'Előző hónap',
|
||||||
|
nextMonth: 'Következő hónap'
|
||||||
|
},
|
||||||
|
inputNumber: {
|
||||||
|
increment: 'Növel',
|
||||||
|
decrement: 'Csökkent'
|
||||||
|
},
|
||||||
|
commandPalette: {
|
||||||
|
placeholder: 'Írjon be egy parancsot vagy keressen...',
|
||||||
|
noMatch: 'Nincs találat',
|
||||||
|
noData: 'Nincs adat',
|
||||||
|
close: 'Bezárás'
|
||||||
|
},
|
||||||
|
selectMenu: {
|
||||||
|
noMatch: 'Nincs találat',
|
||||||
|
noData: 'Nincs adat',
|
||||||
|
create: '"{label}" létrehozása',
|
||||||
|
search: 'Keresés...'
|
||||||
|
},
|
||||||
|
toast: {
|
||||||
|
close: 'Bezárás'
|
||||||
|
},
|
||||||
|
carousel: {
|
||||||
|
prev: 'Előző',
|
||||||
|
next: 'Következő',
|
||||||
|
goto: 'Ugrás ide {slide}'
|
||||||
|
},
|
||||||
|
modal: {
|
||||||
|
close: 'Bezárás'
|
||||||
|
},
|
||||||
|
slideover: {
|
||||||
|
close: 'Bezárás'
|
||||||
|
},
|
||||||
|
alert: {
|
||||||
|
close: 'Bezárás'
|
||||||
|
},
|
||||||
|
table: {
|
||||||
|
noData: 'Nincs adat'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -9,6 +9,7 @@ export { default as es } from './es'
|
|||||||
export { default as fa_ir } from './fa_ir'
|
export { default as fa_ir } from './fa_ir'
|
||||||
export { default as fi } from './fi'
|
export { default as fi } from './fi'
|
||||||
export { default as fr } from './fr'
|
export { default as fr } from './fr'
|
||||||
|
export { default as hi } from './hi'
|
||||||
export { default as id } from './id'
|
export { default as id } from './id'
|
||||||
export { default as it } from './it'
|
export { default as it } from './it'
|
||||||
export { default as ja } from './ja'
|
export { default as ja } from './ja'
|
||||||
@@ -25,5 +26,6 @@ export { default as th } from './th'
|
|||||||
export { default as tr } from './tr'
|
export { default as tr } from './tr'
|
||||||
export { default as uk } from './uk'
|
export { default as uk } from './uk'
|
||||||
export { default as vi } from './vi'
|
export { default as vi } from './vi'
|
||||||
export { default as zh_hans } from './zh_hans'
|
export { default as zh_cn } from './zh_cn'
|
||||||
export { default as zh_hant } from './zh_hant'
|
export { default as zh_tw } from './zh_tw'
|
||||||
|
export { default as he } from './he'
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { defineLocale } from '../composables/defineLocale'
|
|||||||
|
|
||||||
export default defineLocale({
|
export default defineLocale({
|
||||||
name: '简体中文',
|
name: '简体中文',
|
||||||
code: 'zh-Hans',
|
code: 'zh-CN',
|
||||||
messages: {
|
messages: {
|
||||||
inputMenu: {
|
inputMenu: {
|
||||||
noMatch: '没有匹配的数据',
|
noMatch: '没有匹配的数据',
|
||||||
@@ -2,7 +2,7 @@ import { defineLocale } from '../composables/defineLocale'
|
|||||||
|
|
||||||
export default defineLocale({
|
export default defineLocale({
|
||||||
name: '繁體中文',
|
name: '繁體中文',
|
||||||
code: 'zh-Hant',
|
code: 'zh-TW',
|
||||||
messages: {
|
messages: {
|
||||||
inputMenu: {
|
inputMenu: {
|
||||||
noMatch: '沒有相符的資料',
|
noMatch: '沒有相符的資料',
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { StandardSchemaV1 } from '@standard-schema/spec'
|
import type { StandardSchemaV1 } from '@standard-schema/spec'
|
||||||
import type { ComputedRef, Ref } from 'vue'
|
import type { ComputedRef, DeepReadonly, Ref } from 'vue'
|
||||||
import type { ZodSchema } from 'zod'
|
import type { ZodSchema } from 'zod'
|
||||||
import type { Schema as JoiSchema } from 'joi'
|
import type { Schema as JoiSchema } from 'joi'
|
||||||
import type { ObjectSchema as YupObjectSchema } from 'yup'
|
import type { ObjectSchema as YupObjectSchema } from 'yup'
|
||||||
@@ -7,17 +7,22 @@ import type { GenericSchema as ValibotSchema, GenericSchemaAsync as ValibotSchem
|
|||||||
import type { GetObjectField } from './utils'
|
import type { GetObjectField } from './utils'
|
||||||
import type { Struct as SuperstructSchema } from 'superstruct'
|
import type { Struct as SuperstructSchema } from 'superstruct'
|
||||||
|
|
||||||
export interface Form<T> {
|
export interface Form<T extends object> {
|
||||||
validate (opts?: { name?: string | string[], silent?: boolean, nested?: boolean, transform?: boolean }): Promise<T | false>
|
validate (opts?: { name?: keyof T | (keyof T)[], silent?: boolean, nested?: boolean, transform?: boolean }): Promise<T | false>
|
||||||
clear (path?: string): void
|
clear (path?: string): void
|
||||||
errors: Ref<FormError[]>
|
errors: Ref<FormError[]>
|
||||||
setErrors (errs: FormError[], path?: string): void
|
setErrors (errs: FormError[], name?: keyof T): void
|
||||||
getErrors (path?: string): FormError[]
|
getErrors (name?: keyof T): FormError[]
|
||||||
submit (): Promise<void>
|
submit (): Promise<void>
|
||||||
disabled: ComputedRef<boolean>
|
disabled: ComputedRef<boolean>
|
||||||
|
dirty: ComputedRef<boolean>
|
||||||
|
|
||||||
|
dirtyFields: DeepReadonly<Set<keyof T>>
|
||||||
|
touchedFields: DeepReadonly<Set<keyof T>>
|
||||||
|
blurredFields: DeepReadonly<Set<keyof T>>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FormSchema<T extends Record<string, any>> =
|
export type FormSchema<T extends object> =
|
||||||
| ZodSchema
|
| ZodSchema
|
||||||
| YupObjectSchema<T>
|
| YupObjectSchema<T>
|
||||||
| ValibotSchema
|
| ValibotSchema
|
||||||
@@ -28,7 +33,7 @@ export type FormSchema<T extends Record<string, any>> =
|
|||||||
| SuperstructSchema<any, any>
|
| SuperstructSchema<any, any>
|
||||||
| StandardSchemaV1
|
| StandardSchemaV1
|
||||||
|
|
||||||
export type FormInputEvents = 'input' | 'blur' | 'change'
|
export type FormInputEvents = 'input' | 'blur' | 'change' | 'focus'
|
||||||
|
|
||||||
export interface FormError<P extends string = string> {
|
export interface FormError<P extends string = string> {
|
||||||
name: P
|
name: P
|
||||||
@@ -61,13 +66,14 @@ export type FormChildDetachEvent = {
|
|||||||
formId: string | number
|
formId: string | number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FormInputEvent = {
|
export type FormInputEvent<T extends object> = {
|
||||||
type: FormEventType
|
type: FormEventType
|
||||||
name?: string
|
name: keyof T
|
||||||
|
eager?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FormEvent =
|
export type FormEvent<T extends object> =
|
||||||
| FormInputEvent
|
| FormInputEvent<T>
|
||||||
| FormChildAttachEvent
|
| FormChildAttachEvent
|
||||||
| FormChildDetachEvent
|
| FormChildDetachEvent
|
||||||
|
|
||||||
@@ -83,6 +89,9 @@ export interface FormFieldInjectedOptions<T> {
|
|||||||
eagerValidation?: boolean
|
eagerValidation?: boolean
|
||||||
validateOnInputDelay?: number
|
validateOnInputDelay?: number
|
||||||
errorPattern?: RegExp
|
errorPattern?: RegExp
|
||||||
|
hint?: string
|
||||||
|
description?: string
|
||||||
|
ariaId: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ValidateReturnSchema<T> {
|
export interface ValidateReturnSchema<T> {
|
||||||
|
|||||||
@@ -47,9 +47,7 @@ export async function validateStandardSchema(
|
|||||||
state: any,
|
state: any,
|
||||||
schema: StandardSchemaV1
|
schema: StandardSchemaV1
|
||||||
): Promise<ValidateReturnSchema<typeof state>> {
|
): Promise<ValidateReturnSchema<typeof state>> {
|
||||||
const result = await schema['~standard'].validate({
|
const result = await schema['~standard'].validate(state)
|
||||||
value: state
|
|
||||||
})
|
|
||||||
|
|
||||||
if (result.issues) {
|
if (result.issues) {
|
||||||
return {
|
return {
|
||||||
@@ -197,14 +195,14 @@ export function validateSchema<T extends object>(state: T, schema: FormSchema<T>
|
|||||||
return validateZodSchema(state, schema)
|
return validateZodSchema(state, schema)
|
||||||
} else if (isJoiSchema(schema)) {
|
} else if (isJoiSchema(schema)) {
|
||||||
return validateJoiSchema(state, schema)
|
return validateJoiSchema(state, schema)
|
||||||
|
} else if (isStandardSchema(schema)) {
|
||||||
|
return validateStandardSchema(state, schema)
|
||||||
} else if (isValibotSchema(schema)) {
|
} else if (isValibotSchema(schema)) {
|
||||||
return validateValibotSchema(state, schema)
|
return validateValibotSchema(state, schema)
|
||||||
} else if (isYupSchema(schema)) {
|
} else if (isYupSchema(schema)) {
|
||||||
return validateYupSchema(state, schema)
|
return validateYupSchema(state, schema)
|
||||||
} else if (isSuperStructSchema(schema)) {
|
} else if (isSuperStructSchema(schema)) {
|
||||||
return validateSuperstructSchema(state, schema)
|
return validateSuperstructSchema(state, schema)
|
||||||
} else if (isStandardSchema(schema)) {
|
|
||||||
return validateStandardSchema(state, schema)
|
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Form validation failed: Unsupported form schema')
|
throw new Error('Form validation failed: Unsupported form schema')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,6 @@ import { createTV, type defaultConfig } from 'tailwind-variants'
|
|||||||
import type { AppConfig } from '@nuxt/schema'
|
import type { AppConfig } from '@nuxt/schema'
|
||||||
import _appConfig from '#build/app.config'
|
import _appConfig from '#build/app.config'
|
||||||
|
|
||||||
const appConfig = _appConfig as AppConfig & { ui: { tv: typeof defaultConfig } }
|
const appConfigTv = _appConfig as AppConfig & { ui: { tv: typeof defaultConfig } }
|
||||||
|
|
||||||
export const tv = createTV(appConfig.ui?.tv)
|
export const tv = createTV(appConfigTv.ui?.tv)
|
||||||
|
|||||||
@@ -52,9 +52,9 @@ interface NuxtLinkProps extends Omit<RouterLinkProps, 'to'> {
|
|||||||
noPrefetch?: boolean
|
noPrefetch?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const appConfig = _appConfig as AppConfig & { ui: { link: Partial<typeof theme> } }
|
const appConfigLink = _appConfig as AppConfig & { ui: { link: Partial<typeof theme> } }
|
||||||
|
|
||||||
const link = tv({ extend: tv(theme), ...(appConfig.ui?.link || {}) })
|
const link = tv({ extend: tv(theme), ...(appConfigLink.ui?.link || {}) })
|
||||||
|
|
||||||
export interface LinkProps extends NuxtLinkProps {
|
export interface LinkProps extends NuxtLinkProps {
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export default {
|
export default {
|
||||||
slots: {
|
slots: {
|
||||||
root: 'inline-flex items-center justify-center shrink-0 select-none overflow-hidden rounded-full align-middle bg-[var(--ui-bg-elevated)]',
|
root: 'inline-flex items-center justify-center shrink-0 select-none overflow-hidden rounded-full align-middle bg-[var(--ui-bg-elevated)]',
|
||||||
image: 'h-full w-full rounded-[inherit] object-cover',
|
image: 'h-full w-full rounded-[inherit] object-cover data-[error]:hidden',
|
||||||
fallback: 'font-medium leading-none text-[var(--ui-text-muted)] truncate',
|
fallback: 'font-medium leading-none text-[var(--ui-text-muted)] truncate',
|
||||||
icon: 'text-[var(--ui-text-muted)] shrink-0'
|
icon: 'text-[var(--ui-text-muted)] shrink-0'
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -25,9 +25,9 @@ export default (options: Required<ModuleOptions>) => ({
|
|||||||
childLinkLabelExternalIcon: 'inline-block size-3 align-top text-[var(--ui-text-dimmed)]',
|
childLinkLabelExternalIcon: 'inline-block size-3 align-top text-[var(--ui-text-dimmed)]',
|
||||||
childLinkDescription: 'text-sm text-[var(--ui-text-muted)]',
|
childLinkDescription: 'text-sm text-[var(--ui-text-muted)]',
|
||||||
separator: 'px-2 h-px bg-[var(--ui-border)]',
|
separator: 'px-2 h-px bg-[var(--ui-border)]',
|
||||||
viewportWrapper: 'absolute top-full left-0 flex w-full justify-center',
|
viewportWrapper: 'absolute top-full left-0 flex w-full',
|
||||||
viewport: 'relative overflow-hidden bg-[var(--ui-bg)] shadow-lg rounded-[calc(var(--ui-radius)*1.5)] ring ring-[var(--ui-border)] h-[var(--reka-navigation-menu-viewport-height)] w-full transition-[width,height] origin-[top_center] data-[state=open]:animate-[scale-in_100ms_ease-out] data-[state=closed]:animate-[scale-out_100ms_ease-in]',
|
viewport: 'relative overflow-hidden bg-[var(--ui-bg)] shadow-lg rounded-[calc(var(--ui-radius)*1.5)] ring ring-[var(--ui-border)] h-[var(--reka-navigation-menu-viewport-height)] w-full transition-[width,height,left] duration-200 origin-[top_center] data-[state=open]:animate-[scale-in_100ms_ease-out] data-[state=closed]:animate-[scale-out_100ms_ease-in]',
|
||||||
content: 'absolute top-0 left-0 w-full data-[motion=from-start]:animate-[enter-from-left_200ms_ease] data-[motion=from-end]:animate-[enter-from-right_200ms_ease] data-[motion=to-start]:animate-[exit-to-left_200ms_ease] data-[motion=to-end]:animate-[exit-to-right_200ms_ease]',
|
content: 'absolute top-0 left-0 w-full',
|
||||||
indicator: 'absolute data-[state=visible]:animate-[fade-in_100ms_ease-out] data-[state=hidden]:animate-[fade-out_100ms_ease-in] data-[state=hidden]:opacity-0 bottom-0 z-[1] w-[var(--reka-navigation-menu-indicator-size)] translate-x-[var(--reka-navigation-menu-indicator-position)] flex h-2.5 items-end justify-center overflow-hidden transition-[translate,width] duration-200',
|
indicator: 'absolute data-[state=visible]:animate-[fade-in_100ms_ease-out] data-[state=hidden]:animate-[fade-out_100ms_ease-in] data-[state=hidden]:opacity-0 bottom-0 z-[1] w-[var(--reka-navigation-menu-indicator-size)] translate-x-[var(--reka-navigation-menu-indicator-position)] flex h-2.5 items-end justify-center overflow-hidden transition-[translate,width] duration-200',
|
||||||
arrow: 'relative top-[50%] size-2.5 rotate-45 border border-[var(--ui-border)] bg-[var(--ui-bg)] z-[1] rounded-[calc(var(--ui-radius)/2)]'
|
arrow: 'relative top-[50%] size-2.5 rotate-45 border border-[var(--ui-border)] bg-[var(--ui-bg)] z-[1] rounded-[calc(var(--ui-radius)/2)]'
|
||||||
},
|
},
|
||||||
@@ -56,13 +56,24 @@ export default (options: Required<ModuleOptions>) => ({
|
|||||||
list: 'flex items-center',
|
list: 'flex items-center',
|
||||||
item: 'py-2',
|
item: 'py-2',
|
||||||
link: 'px-2.5 py-1.5 before:inset-x-px before:inset-y-0',
|
link: 'px-2.5 py-1.5 before:inset-x-px before:inset-y-0',
|
||||||
childList: 'grid grid-cols-2 gap-2 p-2'
|
childList: 'grid p-2'
|
||||||
},
|
},
|
||||||
vertical: {
|
vertical: {
|
||||||
root: 'flex-col',
|
root: 'flex-col',
|
||||||
link: 'flex-row px-2.5 py-1.5 before:inset-y-px before:inset-x-0'
|
link: 'flex-row px-2.5 py-1.5 before:inset-y-px before:inset-x-0'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
contentOrientation: {
|
||||||
|
horizontal: {
|
||||||
|
viewport: '',
|
||||||
|
viewportWrapper: 'justify-center',
|
||||||
|
content: 'data-[motion=from-start]:animate-[enter-from-left_200ms_ease] data-[motion=from-end]:animate-[enter-from-right_200ms_ease] data-[motion=to-start]:animate-[exit-to-left_200ms_ease] data-[motion=to-end]:animate-[exit-to-right_200ms_ease]'
|
||||||
|
},
|
||||||
|
vertical: {
|
||||||
|
viewport: 'sm:w-[var(--reka-navigation-menu-viewport-width)] left-[var(--reka-navigation-menu-viewport-left)]',
|
||||||
|
content: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
active: {
|
active: {
|
||||||
true: {
|
true: {
|
||||||
childLink: 'bg-[var(--ui-bg-elevated)] text-[var(--ui-text-highlighted)]',
|
childLink: 'bg-[var(--ui-bg-elevated)] text-[var(--ui-text-highlighted)]',
|
||||||
@@ -91,6 +102,19 @@ export default (options: Required<ModuleOptions>) => ({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
compoundVariants: [{
|
compoundVariants: [{
|
||||||
|
orientation: 'horizontal',
|
||||||
|
contentOrientation: 'horizontal',
|
||||||
|
class: {
|
||||||
|
childList: 'grid-cols-2 gap-2'
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
orientation: 'horizontal',
|
||||||
|
contentOrientation: 'vertical',
|
||||||
|
class: {
|
||||||
|
childList: 'gap-1',
|
||||||
|
content: 'w-60'
|
||||||
|
}
|
||||||
|
}, {
|
||||||
orientation: 'horizontal',
|
orientation: 'horizontal',
|
||||||
highlight: true,
|
highlight: true,
|
||||||
class: {
|
class: {
|
||||||
|
|||||||
@@ -278,6 +278,39 @@ describe('Form', () => {
|
|||||||
{ id: 'passwordInput', name: 'password', message: 'Required' }
|
{ id: 'passwordInput', name: 'password', message: 'Required' }
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('touchedFields works', async () => {
|
||||||
|
const emailInput = wrapper.find('#emailInput')
|
||||||
|
|
||||||
|
emailInput.trigger('focus')
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
expect(form.value.touchedFields.has('email')).toBe(true)
|
||||||
|
expect(form.value.touchedFields.has('password')).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('touchedFields works', async () => {
|
||||||
|
const emailInput = wrapper.find('#emailInput')
|
||||||
|
|
||||||
|
emailInput.trigger('change')
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
expect(form.value.dirtyFields.has('email')).toBe(true)
|
||||||
|
expect(form.value.touchedFields.has('email')).toBe(true)
|
||||||
|
|
||||||
|
expect(form.value.dirtyFields.has('password')).toBe(false)
|
||||||
|
expect(form.value.touchedFields.has('password')).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('blurredFields works', async () => {
|
||||||
|
const emailInput = wrapper.find('#emailInput')
|
||||||
|
|
||||||
|
emailInput.trigger('blur')
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
expect(form.value.blurredFields.has('email')).toBe(true)
|
||||||
|
expect(form.value.blurredFields.has('password')).toBe(false)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('nested', async () => {
|
describe('nested', async () => {
|
||||||
@@ -444,6 +477,7 @@ describe('Form', () => {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('form field errorPattern works', async () => {
|
test('form field errorPattern works', async () => {
|
||||||
const wrapper = await mountSuspended({
|
const wrapper = await mountSuspended({
|
||||||
components: {
|
components: {
|
||||||
|
|||||||
@@ -1,16 +1,58 @@
|
|||||||
import { defineComponent } from 'vue'
|
import { defineComponent } from 'vue'
|
||||||
import { describe, it, expect } from 'vitest'
|
import { describe, it, expect, test, vi } from 'vitest'
|
||||||
import FormField, { type FormFieldProps, type FormFieldSlots } from '../../src/runtime/components/FormField.vue'
|
import type { FormFieldProps, FormFieldSlots } from '../../src/runtime/components/FormField.vue'
|
||||||
import ComponentRender from '../component-render'
|
import ComponentRender from '../component-render'
|
||||||
import theme from '#build/ui/form-field'
|
import theme from '#build/ui/form-field'
|
||||||
|
import { mountSuspended } from '@nuxt/test-utils/runtime'
|
||||||
|
|
||||||
|
import {
|
||||||
|
UInput,
|
||||||
|
URadioGroup,
|
||||||
|
UTextarea,
|
||||||
|
UCheckbox,
|
||||||
|
USelect,
|
||||||
|
USelectMenu,
|
||||||
|
UInputMenu,
|
||||||
|
UInputNumber,
|
||||||
|
USwitch,
|
||||||
|
USlider,
|
||||||
|
UPinInput,
|
||||||
|
UFormField
|
||||||
|
|
||||||
|
} from '#components'
|
||||||
|
|
||||||
|
const inputComponents = [UInput, URadioGroup, UTextarea, UCheckbox, USelect, USelectMenu, UInputMenu, UInputNumber, USwitch, USlider, UPinInput]
|
||||||
|
|
||||||
|
async function renderFormField(options: {
|
||||||
|
props: Partial<FormFieldProps>
|
||||||
|
inputComponent: typeof inputComponents[number]
|
||||||
|
}) {
|
||||||
|
return await mountSuspended(UFormField, {
|
||||||
|
props: options.props,
|
||||||
|
slots: {
|
||||||
|
default: {
|
||||||
|
// @ts-expect-error - Object literal may only specify known properties, and setup does not exist in type
|
||||||
|
setup: () => ({ inputComponent: options.inputComponent }),
|
||||||
|
components: {
|
||||||
|
UFormField,
|
||||||
|
...inputComponents
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<component :is="inputComponent" />
|
||||||
|
`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// A wrapper component is needed here because of a conflict with the error prop / expose.
|
// A wrapper component is needed here because of a conflict with the error prop / expose.
|
||||||
// See: https://github.com/nuxt/test-utils/issues/684
|
// See: https://github.com/nuxt/test-utils/issues/684
|
||||||
const FormFieldWrapper = defineComponent({
|
const FormFieldWrapper = defineComponent({
|
||||||
components: {
|
components: {
|
||||||
UFormField: FormField
|
UFormField
|
||||||
},
|
},
|
||||||
template: `<UFormField>
|
template: `
|
||||||
|
<UFormField>
|
||||||
<template v-for="(_, name) in $slots" #[name]="slotData">
|
<template v-for="(_, name) in $slots" #[name]="slotData">
|
||||||
<slot :name="name" v-bind="slotData" />
|
<slot :name="name" v-bind="slotData" />
|
||||||
</template>
|
</template>
|
||||||
@@ -42,4 +84,80 @@ describe('FormField', () => {
|
|||||||
const html = await ComponentRender(nameOrHtml, options, FormFieldWrapper)
|
const html = await ComponentRender(nameOrHtml, options, FormFieldWrapper)
|
||||||
expect(html).toMatchSnapshot()
|
expect(html).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe.each(inputComponents.map(inputComponent => [(inputComponent as any).__name, inputComponent]))('%s integration', async (name: string, inputComponent: any) => {
|
||||||
|
// Mock useId to force a consistent return value in Nuxt and Vue. This is required to test aria attributes.
|
||||||
|
vi.mock('vue', async () => {
|
||||||
|
const actual = await vi.importActual('vue')
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
|
useId: () => 'v-0-0' // Static value matching Nuxt's format
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (name === 'RadioGroup') {
|
||||||
|
test('unbinds label for', async () => {
|
||||||
|
const wrapper = await renderFormField({
|
||||||
|
props: { label: 'Label' },
|
||||||
|
inputComponent
|
||||||
|
})
|
||||||
|
|
||||||
|
const label = wrapper.find('label[for=v-0-0]')
|
||||||
|
expect(label.exists()).toBe(false)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
test('binds label for', async () => {
|
||||||
|
const wrapper = await renderFormField({
|
||||||
|
props: { label: 'Label' },
|
||||||
|
inputComponent
|
||||||
|
})
|
||||||
|
|
||||||
|
const label = wrapper.find('label[for=v-0-0]')
|
||||||
|
expect(label.exists()).toBe(true)
|
||||||
|
|
||||||
|
const input = wrapper.find('[id=v-0-0]')
|
||||||
|
expect(input.exists()).toBe(true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
test('binds hints with aria-describedby', async () => {
|
||||||
|
const wrapper = await renderFormField({
|
||||||
|
props: { hint: 'somehint' },
|
||||||
|
inputComponent
|
||||||
|
})
|
||||||
|
|
||||||
|
const attr = wrapper.find('[aria-describedby=v-0-0-hint]')
|
||||||
|
expect(attr.exists()).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('binds description with aria-describedby', async () => {
|
||||||
|
const wrapper = await renderFormField({
|
||||||
|
props: { description: 'somedescription' },
|
||||||
|
inputComponent
|
||||||
|
})
|
||||||
|
|
||||||
|
const attr = wrapper.find('[aria-describedby=v-0-0-description]')
|
||||||
|
expect(attr.exists()).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('binds error with aria-describedby', async () => {
|
||||||
|
const wrapper = await renderFormField({
|
||||||
|
props: { error: 'someerror' },
|
||||||
|
inputComponent
|
||||||
|
})
|
||||||
|
|
||||||
|
const attr = wrapper.find('[aria-describedby=v-0-0-error]')
|
||||||
|
expect(attr.exists()).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('binds aria-invalid on error', async () => {
|
||||||
|
const wrapper = await renderFormField({
|
||||||
|
props: { error: 'someerror' },
|
||||||
|
inputComponent
|
||||||
|
})
|
||||||
|
|
||||||
|
const attr = wrapper.find('[aria-invalid=true]')
|
||||||
|
expect(attr.exists()).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -84,9 +84,11 @@ describe('NavigationMenu', () => {
|
|||||||
it.each([
|
it.each([
|
||||||
// Props
|
// Props
|
||||||
['with items', { props }],
|
['with items', { props }],
|
||||||
|
['with modelValue', { props: { ...props, modelValue: '0' } }],
|
||||||
['with labelKey', { props: { ...props, labelKey: 'icon' } }],
|
['with labelKey', { props: { ...props, labelKey: 'icon' } }],
|
||||||
['with arrow', { props: { ...props, arrow: true } }],
|
['with arrow', { props: { ...props, arrow: true, modelValue: '0' } }],
|
||||||
['with orientation vertical', { props: { ...props, orientation: 'vertical' as const } }],
|
['with orientation vertical', { props: { ...props, orientation: 'vertical' as const, modelValue: '0' } }],
|
||||||
|
['with content orientation vertical', { props: { ...props, contentOrientation: 'vertical' as const, modelValue: '0' } }],
|
||||||
...variants.map((variant: string) => [`with primary variant ${variant}`, { props: { ...props, variant } }]),
|
...variants.map((variant: string) => [`with primary variant ${variant}`, { props: { ...props, variant } }]),
|
||||||
...variants.map((variant: string) => [`with neutral variant ${variant}`, { props: { ...props, variant, color: 'neutral' } }]),
|
...variants.map((variant: string) => [`with neutral variant ${variant}`, { props: { ...props, variant, color: 'neutral' } }]),
|
||||||
...variants.map((variant: string) => [`with primary variant ${variant} highlight`, { props: { ...props, variant, highlight: true } }]),
|
...variants.map((variant: string) => [`with primary variant ${variant} highlight`, { props: { ...props, variant, highlight: true } }]),
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ exports[`Alert > renders with as correctly 1`] = `
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`Alert > renders with avatar correctly 1`] = `
|
exports[`Alert > renders with avatar correctly 1`] = `
|
||||||
"<div class="relative overflow-hidden w-full rounded-[calc(var(--ui-radius)*2)] p-4 flex gap-2.5 items-center bg-[var(--ui-primary)] text-[var(--ui-bg)]"><span class="inline-flex items-center justify-center select-none overflow-hidden rounded-full align-middle bg-[var(--ui-bg-elevated)] size-11 text-[22px] shrink-0"><img role="img" src="https://github.com/benjamincanac.png" width="44" height="44" class="h-full w-full rounded-[inherit] object-cover" style="display: none;"><span class="font-medium leading-none text-[var(--ui-text-muted)] truncate"> </span></span>
|
"<div class="relative overflow-hidden w-full rounded-[calc(var(--ui-radius)*2)] p-4 flex gap-2.5 items-center bg-[var(--ui-primary)] text-[var(--ui-bg)]"><span class="inline-flex items-center justify-center select-none overflow-hidden rounded-full align-middle bg-[var(--ui-bg-elevated)] size-11 text-[22px] shrink-0"><img role="img" src="https://github.com/benjamincanac.png" width="44" height="44" class="h-full w-full rounded-[inherit] object-cover data-[error]:hidden" style="display: none;"><span class="font-medium leading-none text-[var(--ui-text-muted)] truncate"> </span></span>
|
||||||
<div class="min-w-0 flex-1 flex flex-col gap-1">
|
<div class="min-w-0 flex-1 flex flex-col gap-1">
|
||||||
<div class="text-sm font-medium">Alert</div>
|
<div class="text-sm font-medium">Alert</div>
|
||||||
<!--v-if-->
|
<!--v-if-->
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ exports[`Alert > renders with as correctly 1`] = `
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`Alert > renders with avatar correctly 1`] = `
|
exports[`Alert > renders with avatar correctly 1`] = `
|
||||||
"<div class="relative overflow-hidden w-full rounded-[calc(var(--ui-radius)*2)] p-4 flex gap-2.5 items-center bg-[var(--ui-primary)] text-[var(--ui-bg)]"><span class="inline-flex items-center justify-center select-none overflow-hidden rounded-full align-middle bg-[var(--ui-bg-elevated)] size-11 text-[22px] shrink-0"><img role="img" src="https://github.com/benjamincanac.png" width="44" height="44" class="h-full w-full rounded-[inherit] object-cover" style="display: none;"><span class="font-medium leading-none text-[var(--ui-text-muted)] truncate"> </span></span>
|
"<div class="relative overflow-hidden w-full rounded-[calc(var(--ui-radius)*2)] p-4 flex gap-2.5 items-center bg-[var(--ui-primary)] text-[var(--ui-bg)]"><span class="inline-flex items-center justify-center select-none overflow-hidden rounded-full align-middle bg-[var(--ui-bg-elevated)] size-11 text-[22px] shrink-0"><img role="img" src="https://github.com/benjamincanac.png" width="44" height="44" class="h-full w-full rounded-[inherit] object-cover data-[error]:hidden" style="display: none;"><span class="font-medium leading-none text-[var(--ui-text-muted)] truncate"> </span></span>
|
||||||
<div class="min-w-0 flex-1 flex flex-col gap-1">
|
<div class="min-w-0 flex-1 flex flex-col gap-1">
|
||||||
<div class="text-sm font-medium">Alert</div>
|
<div class="text-sm font-medium">Alert</div>
|
||||||
<!--v-if-->
|
<!--v-if-->
|
||||||
|
|||||||
@@ -14,25 +14,25 @@ exports[`Avatar > renders with default slot correctly 1`] = `"<span class="inlin
|
|||||||
|
|
||||||
exports[`Avatar > renders with icon correctly 1`] = `"<span class="inline-flex items-center justify-center shrink-0 select-none overflow-hidden rounded-full align-middle bg-[var(--ui-bg-elevated)] size-8 text-base"><!--v-if--><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="text-[var(--ui-text-muted)] shrink-0" width="1em" height="1em" viewBox="0 0 16 16"></svg></span>"`;
|
exports[`Avatar > renders with icon correctly 1`] = `"<span class="inline-flex items-center justify-center shrink-0 select-none overflow-hidden rounded-full align-middle bg-[var(--ui-bg-elevated)] size-8 text-base"><!--v-if--><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="text-[var(--ui-text-muted)] shrink-0" width="1em" height="1em" viewBox="0 0 16 16"></svg></span>"`;
|
||||||
|
|
||||||
exports[`Avatar > renders with size 2xl correctly 1`] = `"<span class="inline-flex items-center justify-center shrink-0 select-none overflow-hidden rounded-full align-middle bg-[var(--ui-bg-elevated)] size-11 text-[22px]"><img role="img" src="https://github.com/benjamincanac.png" width="44" height="44" class="h-full w-full rounded-[inherit] object-cover" style="display: none;"><span class="font-medium leading-none text-[var(--ui-text-muted)] truncate"> </span></span>"`;
|
exports[`Avatar > renders with size 2xl correctly 1`] = `"<span class="inline-flex items-center justify-center shrink-0 select-none overflow-hidden rounded-full align-middle bg-[var(--ui-bg-elevated)] size-11 text-[22px]"><img role="img" src="https://github.com/benjamincanac.png" width="44" height="44" class="h-full w-full rounded-[inherit] object-cover data-[error]:hidden" style="display: none;"><span class="font-medium leading-none text-[var(--ui-text-muted)] truncate"> </span></span>"`;
|
||||||
|
|
||||||
exports[`Avatar > renders with size 2xs correctly 1`] = `"<span class="inline-flex items-center justify-center shrink-0 select-none overflow-hidden rounded-full align-middle bg-[var(--ui-bg-elevated)] size-5 text-[10px]"><img role="img" src="https://github.com/benjamincanac.png" width="20" height="20" class="h-full w-full rounded-[inherit] object-cover" style="display: none;"><span class="font-medium leading-none text-[var(--ui-text-muted)] truncate"> </span></span>"`;
|
exports[`Avatar > renders with size 2xs correctly 1`] = `"<span class="inline-flex items-center justify-center shrink-0 select-none overflow-hidden rounded-full align-middle bg-[var(--ui-bg-elevated)] size-5 text-[10px]"><img role="img" src="https://github.com/benjamincanac.png" width="20" height="20" class="h-full w-full rounded-[inherit] object-cover data-[error]:hidden" style="display: none;"><span class="font-medium leading-none text-[var(--ui-text-muted)] truncate"> </span></span>"`;
|
||||||
|
|
||||||
exports[`Avatar > renders with size 3xl correctly 1`] = `"<span class="inline-flex items-center justify-center shrink-0 select-none overflow-hidden rounded-full align-middle bg-[var(--ui-bg-elevated)] size-12 text-2xl"><img role="img" src="https://github.com/benjamincanac.png" width="48" height="48" class="h-full w-full rounded-[inherit] object-cover" style="display: none;"><span class="font-medium leading-none text-[var(--ui-text-muted)] truncate"> </span></span>"`;
|
exports[`Avatar > renders with size 3xl correctly 1`] = `"<span class="inline-flex items-center justify-center shrink-0 select-none overflow-hidden rounded-full align-middle bg-[var(--ui-bg-elevated)] size-12 text-2xl"><img role="img" src="https://github.com/benjamincanac.png" width="48" height="48" class="h-full w-full rounded-[inherit] object-cover data-[error]:hidden" style="display: none;"><span class="font-medium leading-none text-[var(--ui-text-muted)] truncate"> </span></span>"`;
|
||||||
|
|
||||||
exports[`Avatar > renders with size 3xs correctly 1`] = `"<span class="inline-flex items-center justify-center shrink-0 select-none overflow-hidden rounded-full align-middle bg-[var(--ui-bg-elevated)] size-4 text-[8px]"><img role="img" src="https://github.com/benjamincanac.png" width="16" height="16" class="h-full w-full rounded-[inherit] object-cover" style="display: none;"><span class="font-medium leading-none text-[var(--ui-text-muted)] truncate"> </span></span>"`;
|
exports[`Avatar > renders with size 3xs correctly 1`] = `"<span class="inline-flex items-center justify-center shrink-0 select-none overflow-hidden rounded-full align-middle bg-[var(--ui-bg-elevated)] size-4 text-[8px]"><img role="img" src="https://github.com/benjamincanac.png" width="16" height="16" class="h-full w-full rounded-[inherit] object-cover data-[error]:hidden" style="display: none;"><span class="font-medium leading-none text-[var(--ui-text-muted)] truncate"> </span></span>"`;
|
||||||
|
|
||||||
exports[`Avatar > renders with size lg correctly 1`] = `"<span class="inline-flex items-center justify-center shrink-0 select-none overflow-hidden rounded-full align-middle bg-[var(--ui-bg-elevated)] size-9 text-lg"><img role="img" src="https://github.com/benjamincanac.png" width="36" height="36" class="h-full w-full rounded-[inherit] object-cover" style="display: none;"><span class="font-medium leading-none text-[var(--ui-text-muted)] truncate"> </span></span>"`;
|
exports[`Avatar > renders with size lg correctly 1`] = `"<span class="inline-flex items-center justify-center shrink-0 select-none overflow-hidden rounded-full align-middle bg-[var(--ui-bg-elevated)] size-9 text-lg"><img role="img" src="https://github.com/benjamincanac.png" width="36" height="36" class="h-full w-full rounded-[inherit] object-cover data-[error]:hidden" style="display: none;"><span class="font-medium leading-none text-[var(--ui-text-muted)] truncate"> </span></span>"`;
|
||||||
|
|
||||||
exports[`Avatar > renders with size md correctly 1`] = `"<span class="inline-flex items-center justify-center shrink-0 select-none overflow-hidden rounded-full align-middle bg-[var(--ui-bg-elevated)] size-8 text-base"><img role="img" src="https://github.com/benjamincanac.png" width="32" height="32" class="h-full w-full rounded-[inherit] object-cover" style="display: none;"><span class="font-medium leading-none text-[var(--ui-text-muted)] truncate"> </span></span>"`;
|
exports[`Avatar > renders with size md correctly 1`] = `"<span class="inline-flex items-center justify-center shrink-0 select-none overflow-hidden rounded-full align-middle bg-[var(--ui-bg-elevated)] size-8 text-base"><img role="img" src="https://github.com/benjamincanac.png" width="32" height="32" class="h-full w-full rounded-[inherit] object-cover data-[error]:hidden" style="display: none;"><span class="font-medium leading-none text-[var(--ui-text-muted)] truncate"> </span></span>"`;
|
||||||
|
|
||||||
exports[`Avatar > renders with size sm correctly 1`] = `"<span class="inline-flex items-center justify-center shrink-0 select-none overflow-hidden rounded-full align-middle bg-[var(--ui-bg-elevated)] size-7 text-sm"><img role="img" src="https://github.com/benjamincanac.png" width="28" height="28" class="h-full w-full rounded-[inherit] object-cover" style="display: none;"><span class="font-medium leading-none text-[var(--ui-text-muted)] truncate"> </span></span>"`;
|
exports[`Avatar > renders with size sm correctly 1`] = `"<span class="inline-flex items-center justify-center shrink-0 select-none overflow-hidden rounded-full align-middle bg-[var(--ui-bg-elevated)] size-7 text-sm"><img role="img" src="https://github.com/benjamincanac.png" width="28" height="28" class="h-full w-full rounded-[inherit] object-cover data-[error]:hidden" style="display: none;"><span class="font-medium leading-none text-[var(--ui-text-muted)] truncate"> </span></span>"`;
|
||||||
|
|
||||||
exports[`Avatar > renders with size xl correctly 1`] = `"<span class="inline-flex items-center justify-center shrink-0 select-none overflow-hidden rounded-full align-middle bg-[var(--ui-bg-elevated)] size-10 text-xl"><img role="img" src="https://github.com/benjamincanac.png" width="40" height="40" class="h-full w-full rounded-[inherit] object-cover" style="display: none;"><span class="font-medium leading-none text-[var(--ui-text-muted)] truncate"> </span></span>"`;
|
exports[`Avatar > renders with size xl correctly 1`] = `"<span class="inline-flex items-center justify-center shrink-0 select-none overflow-hidden rounded-full align-middle bg-[var(--ui-bg-elevated)] size-10 text-xl"><img role="img" src="https://github.com/benjamincanac.png" width="40" height="40" class="h-full w-full rounded-[inherit] object-cover data-[error]:hidden" style="display: none;"><span class="font-medium leading-none text-[var(--ui-text-muted)] truncate"> </span></span>"`;
|
||||||
|
|
||||||
exports[`Avatar > renders with size xs correctly 1`] = `"<span class="inline-flex items-center justify-center shrink-0 select-none overflow-hidden rounded-full align-middle bg-[var(--ui-bg-elevated)] size-6 text-xs"><img role="img" src="https://github.com/benjamincanac.png" width="24" height="24" class="h-full w-full rounded-[inherit] object-cover" style="display: none;"><span class="font-medium leading-none text-[var(--ui-text-muted)] truncate"> </span></span>"`;
|
exports[`Avatar > renders with size xs correctly 1`] = `"<span class="inline-flex items-center justify-center shrink-0 select-none overflow-hidden rounded-full align-middle bg-[var(--ui-bg-elevated)] size-6 text-xs"><img role="img" src="https://github.com/benjamincanac.png" width="24" height="24" class="h-full w-full rounded-[inherit] object-cover data-[error]:hidden" style="display: none;"><span class="font-medium leading-none text-[var(--ui-text-muted)] truncate"> </span></span>"`;
|
||||||
|
|
||||||
exports[`Avatar > renders with src correctly 1`] = `"<span class="inline-flex items-center justify-center shrink-0 select-none overflow-hidden rounded-full align-middle bg-[var(--ui-bg-elevated)] size-8 text-base"><img role="img" src="https://github.com/benjamincanac.png" width="32" height="32" class="h-full w-full rounded-[inherit] object-cover" style="display: none;"><span class="font-medium leading-none text-[var(--ui-text-muted)] truncate"> </span></span>"`;
|
exports[`Avatar > renders with src correctly 1`] = `"<span class="inline-flex items-center justify-center shrink-0 select-none overflow-hidden rounded-full align-middle bg-[var(--ui-bg-elevated)] size-8 text-base"><img role="img" src="https://github.com/benjamincanac.png" width="32" height="32" class="h-full w-full rounded-[inherit] object-cover data-[error]:hidden" style="display: none;"><span class="font-medium leading-none text-[var(--ui-text-muted)] truncate"> </span></span>"`;
|
||||||
|
|
||||||
exports[`Avatar > renders with text correctly 1`] = `"<span class="inline-flex items-center justify-center shrink-0 select-none overflow-hidden rounded-full align-middle bg-[var(--ui-bg-elevated)] size-8 text-base"><!--v-if--><span class="font-medium leading-none text-[var(--ui-text-muted)] truncate">+1</span></span>"`;
|
exports[`Avatar > renders with text correctly 1`] = `"<span class="inline-flex items-center justify-center shrink-0 select-none overflow-hidden rounded-full align-middle bg-[var(--ui-bg-elevated)] size-8 text-base"><!--v-if--><span class="font-medium leading-none text-[var(--ui-text-muted)] truncate">+1</span></span>"`;
|
||||||
|
|
||||||
|
|||||||
@@ -14,25 +14,25 @@ exports[`Avatar > renders with default slot correctly 1`] = `"<span class="inlin
|
|||||||
|
|
||||||
exports[`Avatar > renders with icon correctly 1`] = `"<span class="inline-flex items-center justify-center shrink-0 select-none overflow-hidden rounded-full align-middle bg-[var(--ui-bg-elevated)] size-8 text-base"><!--v-if--><span class="iconify i-lucide:image text-[var(--ui-text-muted)] shrink-0" aria-hidden="true"></span></span>"`;
|
exports[`Avatar > renders with icon correctly 1`] = `"<span class="inline-flex items-center justify-center shrink-0 select-none overflow-hidden rounded-full align-middle bg-[var(--ui-bg-elevated)] size-8 text-base"><!--v-if--><span class="iconify i-lucide:image text-[var(--ui-text-muted)] shrink-0" aria-hidden="true"></span></span>"`;
|
||||||
|
|
||||||
exports[`Avatar > renders with size 2xl correctly 1`] = `"<span class="inline-flex items-center justify-center shrink-0 select-none overflow-hidden rounded-full align-middle bg-[var(--ui-bg-elevated)] size-11 text-[22px]"><img role="img" src="https://github.com/benjamincanac.png" width="44" height="44" class="h-full w-full rounded-[inherit] object-cover" style="display: none;"><span class="font-medium leading-none text-[var(--ui-text-muted)] truncate"> </span></span>"`;
|
exports[`Avatar > renders with size 2xl correctly 1`] = `"<span class="inline-flex items-center justify-center shrink-0 select-none overflow-hidden rounded-full align-middle bg-[var(--ui-bg-elevated)] size-11 text-[22px]"><img role="img" src="https://github.com/benjamincanac.png" width="44" height="44" class="h-full w-full rounded-[inherit] object-cover data-[error]:hidden" style="display: none;"><span class="font-medium leading-none text-[var(--ui-text-muted)] truncate"> </span></span>"`;
|
||||||
|
|
||||||
exports[`Avatar > renders with size 2xs correctly 1`] = `"<span class="inline-flex items-center justify-center shrink-0 select-none overflow-hidden rounded-full align-middle bg-[var(--ui-bg-elevated)] size-5 text-[10px]"><img role="img" src="https://github.com/benjamincanac.png" width="20" height="20" class="h-full w-full rounded-[inherit] object-cover" style="display: none;"><span class="font-medium leading-none text-[var(--ui-text-muted)] truncate"> </span></span>"`;
|
exports[`Avatar > renders with size 2xs correctly 1`] = `"<span class="inline-flex items-center justify-center shrink-0 select-none overflow-hidden rounded-full align-middle bg-[var(--ui-bg-elevated)] size-5 text-[10px]"><img role="img" src="https://github.com/benjamincanac.png" width="20" height="20" class="h-full w-full rounded-[inherit] object-cover data-[error]:hidden" style="display: none;"><span class="font-medium leading-none text-[var(--ui-text-muted)] truncate"> </span></span>"`;
|
||||||
|
|
||||||
exports[`Avatar > renders with size 3xl correctly 1`] = `"<span class="inline-flex items-center justify-center shrink-0 select-none overflow-hidden rounded-full align-middle bg-[var(--ui-bg-elevated)] size-12 text-2xl"><img role="img" src="https://github.com/benjamincanac.png" width="48" height="48" class="h-full w-full rounded-[inherit] object-cover" style="display: none;"><span class="font-medium leading-none text-[var(--ui-text-muted)] truncate"> </span></span>"`;
|
exports[`Avatar > renders with size 3xl correctly 1`] = `"<span class="inline-flex items-center justify-center shrink-0 select-none overflow-hidden rounded-full align-middle bg-[var(--ui-bg-elevated)] size-12 text-2xl"><img role="img" src="https://github.com/benjamincanac.png" width="48" height="48" class="h-full w-full rounded-[inherit] object-cover data-[error]:hidden" style="display: none;"><span class="font-medium leading-none text-[var(--ui-text-muted)] truncate"> </span></span>"`;
|
||||||
|
|
||||||
exports[`Avatar > renders with size 3xs correctly 1`] = `"<span class="inline-flex items-center justify-center shrink-0 select-none overflow-hidden rounded-full align-middle bg-[var(--ui-bg-elevated)] size-4 text-[8px]"><img role="img" src="https://github.com/benjamincanac.png" width="16" height="16" class="h-full w-full rounded-[inherit] object-cover" style="display: none;"><span class="font-medium leading-none text-[var(--ui-text-muted)] truncate"> </span></span>"`;
|
exports[`Avatar > renders with size 3xs correctly 1`] = `"<span class="inline-flex items-center justify-center shrink-0 select-none overflow-hidden rounded-full align-middle bg-[var(--ui-bg-elevated)] size-4 text-[8px]"><img role="img" src="https://github.com/benjamincanac.png" width="16" height="16" class="h-full w-full rounded-[inherit] object-cover data-[error]:hidden" style="display: none;"><span class="font-medium leading-none text-[var(--ui-text-muted)] truncate"> </span></span>"`;
|
||||||
|
|
||||||
exports[`Avatar > renders with size lg correctly 1`] = `"<span class="inline-flex items-center justify-center shrink-0 select-none overflow-hidden rounded-full align-middle bg-[var(--ui-bg-elevated)] size-9 text-lg"><img role="img" src="https://github.com/benjamincanac.png" width="36" height="36" class="h-full w-full rounded-[inherit] object-cover" style="display: none;"><span class="font-medium leading-none text-[var(--ui-text-muted)] truncate"> </span></span>"`;
|
exports[`Avatar > renders with size lg correctly 1`] = `"<span class="inline-flex items-center justify-center shrink-0 select-none overflow-hidden rounded-full align-middle bg-[var(--ui-bg-elevated)] size-9 text-lg"><img role="img" src="https://github.com/benjamincanac.png" width="36" height="36" class="h-full w-full rounded-[inherit] object-cover data-[error]:hidden" style="display: none;"><span class="font-medium leading-none text-[var(--ui-text-muted)] truncate"> </span></span>"`;
|
||||||
|
|
||||||
exports[`Avatar > renders with size md correctly 1`] = `"<span class="inline-flex items-center justify-center shrink-0 select-none overflow-hidden rounded-full align-middle bg-[var(--ui-bg-elevated)] size-8 text-base"><img role="img" src="https://github.com/benjamincanac.png" width="32" height="32" class="h-full w-full rounded-[inherit] object-cover" style="display: none;"><span class="font-medium leading-none text-[var(--ui-text-muted)] truncate"> </span></span>"`;
|
exports[`Avatar > renders with size md correctly 1`] = `"<span class="inline-flex items-center justify-center shrink-0 select-none overflow-hidden rounded-full align-middle bg-[var(--ui-bg-elevated)] size-8 text-base"><img role="img" src="https://github.com/benjamincanac.png" width="32" height="32" class="h-full w-full rounded-[inherit] object-cover data-[error]:hidden" style="display: none;"><span class="font-medium leading-none text-[var(--ui-text-muted)] truncate"> </span></span>"`;
|
||||||
|
|
||||||
exports[`Avatar > renders with size sm correctly 1`] = `"<span class="inline-flex items-center justify-center shrink-0 select-none overflow-hidden rounded-full align-middle bg-[var(--ui-bg-elevated)] size-7 text-sm"><img role="img" src="https://github.com/benjamincanac.png" width="28" height="28" class="h-full w-full rounded-[inherit] object-cover" style="display: none;"><span class="font-medium leading-none text-[var(--ui-text-muted)] truncate"> </span></span>"`;
|
exports[`Avatar > renders with size sm correctly 1`] = `"<span class="inline-flex items-center justify-center shrink-0 select-none overflow-hidden rounded-full align-middle bg-[var(--ui-bg-elevated)] size-7 text-sm"><img role="img" src="https://github.com/benjamincanac.png" width="28" height="28" class="h-full w-full rounded-[inherit] object-cover data-[error]:hidden" style="display: none;"><span class="font-medium leading-none text-[var(--ui-text-muted)] truncate"> </span></span>"`;
|
||||||
|
|
||||||
exports[`Avatar > renders with size xl correctly 1`] = `"<span class="inline-flex items-center justify-center shrink-0 select-none overflow-hidden rounded-full align-middle bg-[var(--ui-bg-elevated)] size-10 text-xl"><img role="img" src="https://github.com/benjamincanac.png" width="40" height="40" class="h-full w-full rounded-[inherit] object-cover" style="display: none;"><span class="font-medium leading-none text-[var(--ui-text-muted)] truncate"> </span></span>"`;
|
exports[`Avatar > renders with size xl correctly 1`] = `"<span class="inline-flex items-center justify-center shrink-0 select-none overflow-hidden rounded-full align-middle bg-[var(--ui-bg-elevated)] size-10 text-xl"><img role="img" src="https://github.com/benjamincanac.png" width="40" height="40" class="h-full w-full rounded-[inherit] object-cover data-[error]:hidden" style="display: none;"><span class="font-medium leading-none text-[var(--ui-text-muted)] truncate"> </span></span>"`;
|
||||||
|
|
||||||
exports[`Avatar > renders with size xs correctly 1`] = `"<span class="inline-flex items-center justify-center shrink-0 select-none overflow-hidden rounded-full align-middle bg-[var(--ui-bg-elevated)] size-6 text-xs"><img role="img" src="https://github.com/benjamincanac.png" width="24" height="24" class="h-full w-full rounded-[inherit] object-cover" style="display: none;"><span class="font-medium leading-none text-[var(--ui-text-muted)] truncate"> </span></span>"`;
|
exports[`Avatar > renders with size xs correctly 1`] = `"<span class="inline-flex items-center justify-center shrink-0 select-none overflow-hidden rounded-full align-middle bg-[var(--ui-bg-elevated)] size-6 text-xs"><img role="img" src="https://github.com/benjamincanac.png" width="24" height="24" class="h-full w-full rounded-[inherit] object-cover data-[error]:hidden" style="display: none;"><span class="font-medium leading-none text-[var(--ui-text-muted)] truncate"> </span></span>"`;
|
||||||
|
|
||||||
exports[`Avatar > renders with src correctly 1`] = `"<span class="inline-flex items-center justify-center shrink-0 select-none overflow-hidden rounded-full align-middle bg-[var(--ui-bg-elevated)] size-8 text-base"><img role="img" src="https://github.com/benjamincanac.png" width="32" height="32" class="h-full w-full rounded-[inherit] object-cover" style="display: none;"><span class="font-medium leading-none text-[var(--ui-text-muted)] truncate"> </span></span>"`;
|
exports[`Avatar > renders with src correctly 1`] = `"<span class="inline-flex items-center justify-center shrink-0 select-none overflow-hidden rounded-full align-middle bg-[var(--ui-bg-elevated)] size-8 text-base"><img role="img" src="https://github.com/benjamincanac.png" width="32" height="32" class="h-full w-full rounded-[inherit] object-cover data-[error]:hidden" style="display: none;"><span class="font-medium leading-none text-[var(--ui-text-muted)] truncate"> </span></span>"`;
|
||||||
|
|
||||||
exports[`Avatar > renders with text correctly 1`] = `"<span class="inline-flex items-center justify-center shrink-0 select-none overflow-hidden rounded-full align-middle bg-[var(--ui-bg-elevated)] size-8 text-base"><!--v-if--><span class="font-medium leading-none text-[var(--ui-text-muted)] truncate">+1</span></span>"`;
|
exports[`Avatar > renders with text correctly 1`] = `"<span class="inline-flex items-center justify-center shrink-0 select-none overflow-hidden rounded-full align-middle bg-[var(--ui-bg-elevated)] size-8 text-base"><!--v-if--><span class="font-medium leading-none text-[var(--ui-text-muted)] truncate">+1</span></span>"`;
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user