mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-15 12:39:35 +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
|
||||
|
||||
## [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)
|
||||
|
||||
### ⚠ BREAKING CHANGES
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"citty": "^0.1.6",
|
||||
"consola": "^3.3.3",
|
||||
"pathe": "^2.0.1",
|
||||
"consola": "^3.4.0",
|
||||
"pathe": "^2.0.2",
|
||||
"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 { 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 {
|
||||
/**
|
||||
@@ -78,9 +78,9 @@ import _appConfig from '#build/app.config'
|
||||
import theme from '#build/${path}/${prose ? 'prose/' : ''}${content ? 'content/' : ''}${kebabName}'
|
||||
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}>
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"dependencies": {
|
||||
"@nuxt/ui": "latest",
|
||||
"knitwork": "^1.2.0",
|
||||
"nuxt": "^3.15.1",
|
||||
"nuxt": "^3.15.3",
|
||||
"prettier": "^3.4.2",
|
||||
"zod": "^3.24.1"
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ provide('navigation', mappedNavigation)
|
||||
items: modules
|
||||
}]"
|
||||
:navigation="filteredNavigation"
|
||||
:fuse="{ resultLimit: 42 }"
|
||||
:fuse="{ resultLimit: 100 }"
|
||||
/>
|
||||
</ClientOnly>
|
||||
</template>
|
||||
@@ -152,5 +152,5 @@ html[data-module="ui"] .ui-pro-only {
|
||||
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>
|
||||
|
||||
@@ -132,11 +132,18 @@ const optionsValues = ref(props.options?.reduce((acc, option) => {
|
||||
return acc
|
||||
}, {} as Record<string, any>) || {})
|
||||
|
||||
const urlSearchParams = computed(() => new URLSearchParams({
|
||||
...optionsValues.value,
|
||||
...componentProps,
|
||||
width: Math.round(width.value).toString()
|
||||
}).toString())
|
||||
const urlSearchParams = computed(() => {
|
||||
const params = {
|
||||
...optionsValues.value,
|
||||
...componentProps
|
||||
}
|
||||
|
||||
if (!props.iframeMobile) {
|
||||
params.width = Math.round(width.value).toString()
|
||||
}
|
||||
|
||||
return new URLSearchParams(params).toString()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import json5 from 'json5'
|
||||
import { camelCase } from 'scule'
|
||||
import { hash } from 'ohash'
|
||||
import * as theme from '#build/ui'
|
||||
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 = `
|
||||
::code-collapse{class="nuxt-only"}
|
||||
|
||||
|
||||
@@ -15,14 +15,14 @@ function getEmojiFlag(locale: string): string {
|
||||
el: 'gr',
|
||||
et: 'ee',
|
||||
en: 'gb',
|
||||
hi: 'in',
|
||||
ja: 'jp',
|
||||
kh: 'km',
|
||||
ko: 'kr',
|
||||
nb: 'no',
|
||||
sv: 'se',
|
||||
uk: 'ua',
|
||||
vi: 'vn',
|
||||
zh: 'cn'
|
||||
vi: 'vn'
|
||||
}
|
||||
|
||||
const baseLanguage = locale.split('-')[0]?.toLowerCase() || locale
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { FormSubmitEvent } from '@nuxt/ui'
|
||||
|
||||
const schema = z.object({
|
||||
name: z.string().min(2),
|
||||
news: z.boolean()
|
||||
news: z.boolean().default(false)
|
||||
})
|
||||
|
||||
type Schema = z.output<typeof schema>
|
||||
@@ -36,7 +36,7 @@ async function onSubmit(event: FormSubmitEvent<any>) {
|
||||
</UFormField>
|
||||
|
||||
<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>
|
||||
|
||||
<UForm v-if="state.news" :state="state" :schema="nestedSchema">
|
||||
|
||||
@@ -51,7 +51,7 @@ const text = computed(() => {
|
||||
variant="link"
|
||||
size="sm"
|
||||
: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-controls="password"
|
||||
@click="show = !show"
|
||||
|
||||
@@ -16,7 +16,7 @@ const password = ref('password')
|
||||
variant="link"
|
||||
size="sm"
|
||||
: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-controls="password"
|
||||
@click="show = !show"
|
||||
|
||||
@@ -51,7 +51,8 @@ const items = [
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'GitHub'
|
||||
label: 'GitHub',
|
||||
icon: 'i-simple-icons-github'
|
||||
}
|
||||
]
|
||||
</script>
|
||||
|
||||
@@ -10,7 +10,13 @@ export function useSharedData() {
|
||||
icon: 'i-simple-icons-vuedotjs',
|
||||
value: 'vue',
|
||||
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 })))
|
||||
|
||||
const module = useCookie('nuxt-ui-module', { default: () => 'ui' })
|
||||
@@ -24,7 +30,13 @@ export function useSharedData() {
|
||||
icon: 'i-lucide-panels-top-left',
|
||||
value: 'ui-pro',
|
||||
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 })))
|
||||
|
||||
return {
|
||||
|
||||
@@ -113,7 +113,7 @@ provide('navigation', mappedNavigation)
|
||||
items: modules
|
||||
}]"
|
||||
:navigation="filteredNavigation"
|
||||
:fuse="{ resultLimit: 42 }"
|
||||
:fuse="{ resultLimit: 100 }"
|
||||
/>
|
||||
</ClientOnly>
|
||||
</UApp>
|
||||
|
||||
@@ -87,12 +87,12 @@ const communityLinks = computed(() => [{
|
||||
to: `https://github.com/nuxt/${page.value?.module === 'ui-pro' ? 'ui-pro' : 'ui'}`,
|
||||
target: '_blank'
|
||||
}, {
|
||||
icon: 'i-heroicons-lifebuoy',
|
||||
icon: 'i-lucide-life-buoy',
|
||||
label: 'Contribution',
|
||||
to: '/getting-started/contribution'
|
||||
}, {
|
||||
label: 'Roadmap',
|
||||
icon: 'i-heroicons-map',
|
||||
icon: 'i-lucide-map',
|
||||
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**.
|
||||
::
|
||||
|
||||
## Nuxt DevTools Integration
|
||||
### Nuxt DevTools Integration
|
||||
|
||||
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.
|
||||
::
|
||||
|
||||
### `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
|
||||
|
||||
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 |
|
||||
| ---- | ---- |
|
||||
| `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> |
|
||||
| `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> |
|
||||
| `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> |
|
||||
| `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> |
|
||||
| `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?: 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?: 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?: 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> |
|
||||
| `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.
|
||||
::
|
||||
|
||||
### 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
|
||||
|
||||
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)]"}
|
||||
- `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)]"}
|
||||
- `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.
|
||||
|
||||
|
||||
@@ -4,21 +4,21 @@
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@iconify-json/logos": "^1.2.4",
|
||||
"@iconify-json/lucide": "^1.2.22",
|
||||
"@iconify-json/simple-icons": "^1.2.19",
|
||||
"@iconify-json/lucide": "^1.2.25",
|
||||
"@iconify-json/simple-icons": "^1.2.22",
|
||||
"@iconify-json/vscode-icons": "^1.2.10",
|
||||
"@nuxt/content": "^3.0.0",
|
||||
"@nuxt/content": "^3.0.1",
|
||||
"@nuxt/image": "^1.9.0",
|
||||
"@nuxt/ui": "latest",
|
||||
"@nuxt/ui-pro": "https://pkg.pr.new/@nuxt/ui-pro@2dbbff0",
|
||||
"@nuxthub/core": "^0.8.11",
|
||||
"@nuxt/ui-pro": "https://pkg.pr.new/@nuxt/ui-pro@880e49c",
|
||||
"@nuxthub/core": "^0.8.14",
|
||||
"@nuxtjs/plausible": "^1.2.0",
|
||||
"@octokit/rest": "^21.1.0",
|
||||
"@vueuse/nuxt": "^12.4.0",
|
||||
"@vueuse/nuxt": "^12.5.0",
|
||||
"joi": "^17.13.3",
|
||||
"nuxt": "^3.15.1",
|
||||
"nuxt-component-meta": "^0.9.0",
|
||||
"nuxt-og-image": "^4.0.2",
|
||||
"nuxt": "^3.15.3",
|
||||
"nuxt-component-meta": "^0.10.0",
|
||||
"nuxt-og-image": "^4.1.2",
|
||||
"prettier": "^3.4.2",
|
||||
"shiki-transformer-color-highlight": "^0.2.0",
|
||||
"superstruct": "^2.0.2",
|
||||
@@ -28,6 +28,6 @@
|
||||
"zod": "^3.24.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"wrangler": "^3.101.0"
|
||||
"wrangler": "^3.105.1"
|
||||
}
|
||||
}
|
||||
|
||||
42
package.json
42
package.json
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "@nuxt/ui",
|
||||
"description": "A UI Library for Modern Web Apps, powered by Vue & Tailwind CSS.",
|
||||
"version": "3.0.0-alpha.11",
|
||||
"packageManager": "pnpm@9.15.3",
|
||||
"version": "3.0.0-alpha.12",
|
||||
"packageManager": "pnpm@9.15.4",
|
||||
"repository": {
|
||||
"type": "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",
|
||||
"devtools": "NUXT_UI_DEVTOOLS_LOCAL=true nuxi dev playground",
|
||||
"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:build": "nuxi build docs",
|
||||
"docs:prepare": "nuxt-component-meta docs",
|
||||
@@ -74,22 +74,22 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@iconify/vue": "^4.3.0",
|
||||
"@internationalized/date": "^3.6.0",
|
||||
"@internationalized/date": "^3.7.0",
|
||||
"@internationalized/number": "^3.6.0",
|
||||
"@nuxt/devtools-kit": "^1.7.0",
|
||||
"@nuxt/fonts": "^0.10.3",
|
||||
"@nuxt/icon": "^1.10.3",
|
||||
"@nuxt/kit": "^3.15.1",
|
||||
"@nuxt/schema": "^3.15.1",
|
||||
"@nuxt/kit": "^3.15.3",
|
||||
"@nuxt/schema": "^3.15.3",
|
||||
"@nuxtjs/color-mode": "^3.5.2",
|
||||
"@tailwindcss/postcss": "4.0.0-beta.9",
|
||||
"@tailwindcss/vite": "4.0.0-beta.9",
|
||||
"@tailwindcss/postcss": "4.0.0",
|
||||
"@tailwindcss/vite": "4.0.0",
|
||||
"@tanstack/vue-table": "^8.20.5",
|
||||
"@unhead/vue": "^1.11.16",
|
||||
"@vueuse/core": "^12.4.0",
|
||||
"@vueuse/integrations": "^12.4.0",
|
||||
"@unhead/vue": "^1.11.18",
|
||||
"@vueuse/core": "^12.5.0",
|
||||
"@vueuse/integrations": "^12.5.0",
|
||||
"colortranslator": "^4.1.0",
|
||||
"consola": "^3.3.3",
|
||||
"consola": "^3.4.0",
|
||||
"defu": "^6.1.4",
|
||||
"embla-carousel-auto-height": "^8.5.2",
|
||||
"embla-carousel-auto-scroll": "^8.5.2",
|
||||
@@ -104,12 +104,12 @@
|
||||
"magic-string": "^0.30.17",
|
||||
"mlly": "^1.7.4",
|
||||
"ohash": "^1.1.4",
|
||||
"pathe": "^2.0.1",
|
||||
"pathe": "^2.0.2",
|
||||
"reka-ui": "1.0.0-alpha.8",
|
||||
"scule": "^1.3.0",
|
||||
"sirv": "^3.0.0",
|
||||
"tailwind-variants": "^0.3.0",
|
||||
"tailwindcss": "4.0.0-beta.9",
|
||||
"tailwind-variants": "^0.3.1",
|
||||
"tailwindcss": "4.0.0",
|
||||
"tinyglobby": "^0.2.10",
|
||||
"unplugin": "^2.1.2",
|
||||
"unplugin-auto-import": "^19.0.0",
|
||||
@@ -121,19 +121,19 @@
|
||||
"@nuxt/module-builder": "^0.8.4",
|
||||
"@nuxt/test-utils": "^3.15.4",
|
||||
"@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",
|
||||
"embla-carousel": "^8.5.2",
|
||||
"eslint": "^9.18.0",
|
||||
"eslint": "^9.19.0",
|
||||
"happy-dom": "^15.7.4",
|
||||
"joi": "^17.13.3",
|
||||
"knitwork": "^1.2.0",
|
||||
"nuxt": "^3.15.1",
|
||||
"nuxt-component-meta": "^0.9.0",
|
||||
"release-it": "^18.1.1",
|
||||
"nuxt": "^3.15.3",
|
||||
"nuxt-component-meta": "^0.10.0",
|
||||
"release-it": "^18.1.2",
|
||||
"superstruct": "^2.0.2",
|
||||
"valibot": "^0.42.1",
|
||||
"vitest": "^2.1.8",
|
||||
"vitest": "^3.0.4",
|
||||
"vitest-environment-nuxt": "^1.0.1",
|
||||
"vue-tsc": "^2.2.0",
|
||||
"yup": "^1.6.1",
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"typescript": "^5.7.2",
|
||||
"unplugin-auto-import": "^19.0.0",
|
||||
"unplugin-vue-components": "^28.0.0",
|
||||
"vite": "^6.0.7",
|
||||
"vite": "^6.0.11",
|
||||
"vue-tsc": "^2.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,11 +4,13 @@ import theme from '#build/ui/navigation-menu'
|
||||
const colors = Object.keys(theme.variants.color)
|
||||
const variants = Object.keys(theme.variants.variant)
|
||||
const orientations = Object.keys(theme.variants.orientation)
|
||||
const contentOrientations = Object.keys(theme.variants.contentOrientation)
|
||||
|
||||
const color = ref(theme.defaultVariants.color)
|
||||
const highlightColor = ref()
|
||||
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 collapsed = ref(false)
|
||||
|
||||
@@ -93,6 +95,7 @@ const items = [
|
||||
<USelect v-model="color" :items="colors" placeholder="Color" />
|
||||
<USelect v-model="variant" :items="variants" placeholder="Variant" />
|
||||
<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="highlight" label="Highlight" />
|
||||
<USelect v-model="highlightColor" :items="colors" placeholder="Highlight color" />
|
||||
@@ -105,6 +108,7 @@ const items = [
|
||||
:color="color"
|
||||
:variant="variant"
|
||||
:orientation="orientation"
|
||||
:viewport-orientation="contentOrientation"
|
||||
:highlight="highlight"
|
||||
:highlight-color="highlightColor"
|
||||
:class="highlight && 'data-[orientation=horizontal]:border-b border-[var(--ui-border)]'"
|
||||
|
||||
@@ -13,7 +13,7 @@ export default defineNuxtConfig({
|
||||
},
|
||||
|
||||
ui: {
|
||||
fonts: false
|
||||
fonts: !process.env.DEVTOOLS
|
||||
},
|
||||
|
||||
future: {
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
"generate": "nuxi generate"
|
||||
},
|
||||
"dependencies": {
|
||||
"@iconify-json/lucide": "^1.2.22",
|
||||
"@iconify-json/simple-icons": "^1.2.19",
|
||||
"@iconify-json/lucide": "^1.2.25",
|
||||
"@iconify-json/simple-icons": "^1.2.22",
|
||||
"@nuxt/ui": "latest",
|
||||
"@nuxthub/core": "^0.8.11",
|
||||
"nuxt": "^3.15.1"
|
||||
"@nuxthub/core": "^0.8.14",
|
||||
"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"],
|
||||
"enabled": false
|
||||
}]
|
||||
}],
|
||||
"postUpdateOptions": ["pnpmDedupe"]
|
||||
}
|
||||
|
||||
@@ -7,9 +7,9 @@ import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
||||
import { tv } from '../utils/tv'
|
||||
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 {
|
||||
label?: string
|
||||
|
||||
@@ -7,9 +7,9 @@ import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
||||
import { tv } from '../utils/tv'
|
||||
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>
|
||||
|
||||
|
||||
@@ -7,9 +7,9 @@ import theme from '#build/ui/avatar'
|
||||
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
||||
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>
|
||||
|
||||
@@ -36,21 +36,24 @@ extendDevtoolsMeta<AvatarProps>({ defaultProps: { src: 'https://avatars.githubus
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { AvatarRoot, AvatarImage, AvatarFallback, useForwardProps } from 'reka-ui'
|
||||
import { reactivePick } from '@vueuse/core'
|
||||
import { ref, computed, useAttrs, onMounted } from 'vue'
|
||||
import { AvatarRoot, AvatarFallback, useForwardProps } from 'reka-ui'
|
||||
import { reactivePick, useImage } from '@vueuse/core'
|
||||
import ImageComponent from '#build/ui-image-component'
|
||||
import { useAvatarGroup } from '../composables/useAvatarGroup'
|
||||
import UIcon from './Icon.vue'
|
||||
import ImageComponent from '#build/ui-image-component'
|
||||
|
||||
defineOptions({ inheritAttrs: false })
|
||||
|
||||
const props = withDefaults(defineProps<AvatarProps>(), { as: 'span' })
|
||||
const attrs = useAttrs()
|
||||
|
||||
const fallbackProps = useForwardProps(reactivePick(props, 'delayMs'))
|
||||
|
||||
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)
|
||||
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
@@ -69,22 +72,40 @@ const sizePx = computed(() => ({
|
||||
'2xl': 44,
|
||||
'3xl': 48
|
||||
})[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>
|
||||
|
||||
<template>
|
||||
<AvatarRoot :as="as" :class="ui.root({ class: [props.class, props.ui?.root] })">
|
||||
<AvatarImage
|
||||
<component
|
||||
:is="ImageComponent"
|
||||
v-if="src"
|
||||
:as="ImageComponent"
|
||||
v-show="imageLoaded"
|
||||
role="img"
|
||||
:src="src"
|
||||
:alt="alt"
|
||||
:width="sizePx"
|
||||
:height="sizePx"
|
||||
v-bind="$attrs"
|
||||
v-bind="attrs"
|
||||
: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>
|
||||
<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>
|
||||
|
||||
@@ -6,9 +6,9 @@ import theme from '#build/ui/avatar-group'
|
||||
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
||||
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>
|
||||
|
||||
|
||||
@@ -8,9 +8,9 @@ import type { UseComponentIconsProps } from '../composables/useComponentIcons'
|
||||
import { tv } from '../utils/tv'
|
||||
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>
|
||||
|
||||
|
||||
@@ -7,9 +7,9 @@ import { tv } from '../utils/tv'
|
||||
import type { AvatarProps, LinkProps } from '../types'
|
||||
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'> {
|
||||
label?: string
|
||||
|
||||
@@ -10,9 +10,9 @@ import { tv } from '../utils/tv'
|
||||
import type { AvatarProps } from '../types'
|
||||
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>
|
||||
|
||||
|
||||
@@ -6,9 +6,9 @@ import theme from '#build/ui/button-group'
|
||||
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
||||
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>
|
||||
|
||||
|
||||
@@ -7,9 +7,9 @@ import _appConfig from '#build/app.config'
|
||||
import theme from '#build/ui/calendar'
|
||||
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>
|
||||
|
||||
@@ -77,6 +77,7 @@ import { computed } from 'vue'
|
||||
import { useForwardPropsEmits } from 'reka-ui'
|
||||
import { Calendar as SingleCalendar, RangeCalendar } from 'reka-ui/namespaced'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { useAppConfig } from '#imports'
|
||||
import { useLocale } from '../composables/useLocale'
|
||||
import UButton from './Button.vue'
|
||||
|
||||
@@ -88,6 +89,7 @@ const props = withDefaults(defineProps<CalendarProps<R, M>>(), {
|
||||
const emits = defineEmits<CalendarEmits<R, M>>()
|
||||
defineSlots<CalendarSlots>()
|
||||
|
||||
const appConfig = useAppConfig()
|
||||
const { code: locale, dir, t } = useLocale()
|
||||
|
||||
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 { 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 {
|
||||
/**
|
||||
|
||||
@@ -16,9 +16,9 @@ import { tv } from '../utils/tv'
|
||||
import type { ButtonProps } from '../types'
|
||||
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>
|
||||
|
||||
|
||||
@@ -7,9 +7,9 @@ import theme from '#build/ui/checkbox'
|
||||
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
||||
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>
|
||||
|
||||
@@ -66,7 +66,7 @@ const modelValue = defineModel<boolean | 'indeterminate'>({ default: undefined }
|
||||
const rootProps = useForwardProps(reactivePick(props, 'required', 'value', 'defaultValue'))
|
||||
|
||||
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 ui = computed(() => checkbox({
|
||||
@@ -92,7 +92,7 @@ function onUpdate(value: any) {
|
||||
<div :class="ui.container({ class: props.ui?.container })">
|
||||
<CheckboxRoot
|
||||
:id="id"
|
||||
v-bind="rootProps"
|
||||
v-bind="{ ...rootProps, ...ariaAttrs }"
|
||||
v-model="modelValue"
|
||||
:name="name"
|
||||
:disabled="disabled"
|
||||
|
||||
@@ -6,9 +6,9 @@ import theme from '#build/ui/chip'
|
||||
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
||||
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>
|
||||
|
||||
|
||||
@@ -6,9 +6,9 @@ import theme from '#build/ui/collapsible'
|
||||
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
||||
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'> {
|
||||
/**
|
||||
|
||||
@@ -7,9 +7,9 @@ import theme from '#build/ui/color-picker'
|
||||
import { tv } from '../utils/tv'
|
||||
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>
|
||||
|
||||
|
||||
@@ -11,9 +11,9 @@ import { tv } from '../utils/tv'
|
||||
import type { AvatarProps, ButtonProps, ChipProps, KbdProps, InputProps } from '../types'
|
||||
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 {
|
||||
prefix?: string
|
||||
|
||||
@@ -5,9 +5,9 @@ import theme from '#build/ui/container'
|
||||
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
||||
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 {
|
||||
/**
|
||||
|
||||
@@ -9,9 +9,9 @@ import { tv } from '../utils/tv'
|
||||
import type { AvatarProps, KbdProps, LinkProps } from '../types'
|
||||
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>
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ const emits = defineEmits<ContextMenuContentEmits>()
|
||||
const slots = defineSlots<ContextMenuSlots<T>>()
|
||||
|
||||
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 [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 { 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'> {
|
||||
/**
|
||||
|
||||
@@ -9,9 +9,9 @@ import { tv } from '../utils/tv'
|
||||
import type { AvatarProps, KbdProps, LinkProps } from '../types'
|
||||
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>
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ const emits = defineEmits<DropdownMenuContentEmits>()
|
||||
const slots = defineSlots<DropdownMenuContentSlots<T>>()
|
||||
|
||||
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 [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 { tv } from '../utils/tv'
|
||||
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> {
|
||||
id?: string | number
|
||||
@@ -52,7 +53,7 @@ defineSlots<FormSlots>()
|
||||
|
||||
const formId = props.id ?? useId() as string
|
||||
|
||||
const bus = useEventBus<FormEvent>(`form-${formId}`)
|
||||
const bus = useEventBus<FormEvent<T>>(`form-${formId}`)
|
||||
const parentBus = inject(
|
||||
formBusInjectionKey,
|
||||
undefined
|
||||
@@ -68,8 +69,24 @@ onMounted(async () => {
|
||||
nestedForms.value.set(event.formId, { validate: event.validate })
|
||||
} else if (event.type === 'detach') {
|
||||
nestedForms.value.delete(event.formId)
|
||||
} else if (props.validateOn?.includes(event.type as FormInputEvents)) {
|
||||
await _validate({ name: event.name, silent: true, nested: false })
|
||||
} else if (props.validateOn?.includes(event.type)) {
|
||||
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[]>([])
|
||||
provide('form-errors', errors)
|
||||
|
||||
const inputs = ref<Record<string, { id?: string, pattern?: RegExp }>>({})
|
||||
provide(formInputsInjectionKey, inputs)
|
||||
const inputs = ref<{ [P in keyof T]?: { id?: string, pattern?: RegExp } }>({})
|
||||
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[] {
|
||||
return errs.map(err => ({
|
||||
@@ -121,8 +142,8 @@ async function getErrors(): Promise<FormErrorWithId[]> {
|
||||
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> {
|
||||
const names = opts.name && !Array.isArray(opts.name) ? [opts.name] : opts.name as string[]
|
||||
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 (keyof T)[]
|
||||
|
||||
const nestedValidatePromises = !names && opts.nested
|
||||
? Array.from(nestedForms.value.values()).map(
|
||||
@@ -203,7 +224,7 @@ defineExpose<Form<T>>({
|
||||
validate: _validate,
|
||||
errors,
|
||||
|
||||
setErrors(errs: FormError[], name?: string) {
|
||||
setErrors(errs: FormError[], name?: keyof T) {
|
||||
if (name) {
|
||||
errors.value = errors.value
|
||||
.filter(error => error.name !== name)
|
||||
@@ -217,7 +238,7 @@ defineExpose<Form<T>>({
|
||||
await onSubmitWrapper(new Event('submit'))
|
||||
},
|
||||
|
||||
getErrors(name?: string) {
|
||||
getErrors(name?: keyof T) {
|
||||
if (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>
|
||||
|
||||
|
||||
@@ -6,9 +6,9 @@ import theme from '#build/ui/form-field'
|
||||
import { tv } from '../utils/tv'
|
||||
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>
|
||||
|
||||
@@ -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 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)
|
||||
|
||||
@@ -75,7 +78,10 @@ provide(formFieldInjectionKey, computed(() => ({
|
||||
size: props.size,
|
||||
eagerValidation: props.eagerValidation,
|
||||
validateOnInputDelay: props.validateOnInputDelay,
|
||||
errorPattern: props.errorPattern
|
||||
errorPattern: props.errorPattern,
|
||||
hint: props.hint,
|
||||
description: props.description,
|
||||
ariaId
|
||||
}) as FormFieldInjectedOptions<FormFieldProps>))
|
||||
</script>
|
||||
|
||||
@@ -88,14 +94,14 @@ provide(formFieldInjectionKey, computed(() => ({
|
||||
{{ label }}
|
||||
</slot>
|
||||
</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">
|
||||
{{ hint }}
|
||||
</slot>
|
||||
</span>
|
||||
</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">
|
||||
{{ description }}
|
||||
</slot>
|
||||
@@ -105,7 +111,7 @@ provide(formFieldInjectionKey, computed(() => ({
|
||||
<div :class="[(label || !!slots.label || description || !!slots.description) && ui.container({ class: props.ui?.container })]">
|
||||
<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">
|
||||
{{ error }}
|
||||
</slot>
|
||||
|
||||
@@ -9,9 +9,9 @@ import { tv } from '../utils/tv'
|
||||
import type { AvatarProps } from '../types'
|
||||
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>
|
||||
|
||||
@@ -75,7 +75,7 @@ const slots = defineSlots<InputSlots>()
|
||||
|
||||
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 { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponentIcons(props)
|
||||
|
||||
@@ -166,10 +166,11 @@ onMounted(() => {
|
||||
:disabled="disabled"
|
||||
:required="required"
|
||||
:autocomplete="autocomplete"
|
||||
v-bind="$attrs"
|
||||
v-bind="{ ...$attrs, ...ariaAttrs }"
|
||||
@input="onInput"
|
||||
@blur="onBlur"
|
||||
@change="onChange"
|
||||
@focus="emitFormFocus"
|
||||
>
|
||||
|
||||
<slot />
|
||||
|
||||
@@ -11,9 +11,9 @@ import { tv } from '../utils/tv'
|
||||
import type { AvatarProps, ChipProps, InputProps } from '../types'
|
||||
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 {
|
||||
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 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 { 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) {
|
||||
emits('focus', event)
|
||||
emitFormFocus()
|
||||
}
|
||||
|
||||
function onUpdateOpen(value: boolean) {
|
||||
@@ -365,7 +366,7 @@ defineExpose({
|
||||
<ComboboxInput v-model="searchTerm" :display-value="displayValue" as-child>
|
||||
<TagsInputInput
|
||||
ref="inputRef"
|
||||
v-bind="$attrs"
|
||||
v-bind="{ ...$attrs, ...ariaAttrs }"
|
||||
:placeholder="placeholder"
|
||||
:required="required"
|
||||
:class="ui.tagsInput({ class: props.ui?.tagsInput })"
|
||||
@@ -379,7 +380,7 @@ defineExpose({
|
||||
ref="inputRef"
|
||||
v-model="searchTerm"
|
||||
:display-value="displayValue"
|
||||
v-bind="$attrs"
|
||||
v-bind="{ ...$attrs, ...ariaAttrs }"
|
||||
:type="type"
|
||||
:placeholder="placeholder"
|
||||
:required="required"
|
||||
|
||||
@@ -7,9 +7,9 @@ import theme from '#build/ui/input-number'
|
||||
import { tv } from '../utils/tv'
|
||||
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>
|
||||
|
||||
@@ -78,6 +78,7 @@ export interface InputNumberSlots {
|
||||
import { onMounted, ref, computed } from 'vue'
|
||||
import { NumberFieldRoot, NumberFieldInput, NumberFieldDecrement, NumberFieldIncrement, useForwardPropsEmits } from 'reka-ui'
|
||||
import { reactivePick } from '@vueuse/core'
|
||||
import { useAppConfig } from '#imports'
|
||||
import { useFormField } from '../composables/useFormField'
|
||||
import { useLocale } from '../composables/useLocale'
|
||||
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 { 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 locale = computed(() => props.locale || codeLocale.value)
|
||||
@@ -152,12 +154,13 @@ defineExpose({
|
||||
@update:model-value="onUpdate"
|
||||
>
|
||||
<NumberFieldInput
|
||||
v-bind="$attrs"
|
||||
v-bind="{ ...$attrs, ...ariaAttrs }"
|
||||
ref="inputRef"
|
||||
:placeholder="placeholder"
|
||||
:required="required"
|
||||
:class="ui.base({ class: props.ui?.base })"
|
||||
@blur="onBlur"
|
||||
@focus="emitFormFocus"
|
||||
/>
|
||||
|
||||
<div :class="ui.increment({ class: props.ui?.increment })">
|
||||
|
||||
@@ -7,9 +7,9 @@ import type { KbdKey } from '../composables/useKbd'
|
||||
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
||||
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>
|
||||
|
||||
|
||||
@@ -53,9 +53,9 @@ interface NuxtLinkProps extends Omit<RouterLinkProps, 'to'> {
|
||||
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 {
|
||||
/**
|
||||
|
||||
@@ -7,9 +7,9 @@ import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
||||
import { tv } from '../utils/tv'
|
||||
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 {
|
||||
title?: string
|
||||
@@ -73,7 +73,7 @@ extendDevtoolsMeta({ example: 'ModalExample' })
|
||||
</script>
|
||||
|
||||
<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 { reactivePick } from '@vueuse/core'
|
||||
import { useAppConfig } from '#imports'
|
||||
@@ -112,9 +112,6 @@ const ui = computed(() => modal({
|
||||
transition: props.transition,
|
||||
fullscreen: props.fullscreen
|
||||
}))
|
||||
|
||||
// Blocks ButtonGroup injections to avoid side-effects if the modal is within a button group.
|
||||
provide(buttonGroupInjectionKey, undefined)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -9,9 +9,9 @@ import { tv } from '../utils/tv'
|
||||
import type { AvatarProps, BadgeProps, LinkProps } from '../types'
|
||||
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'> {
|
||||
/** Description is only used when `orientation` is `horizontal`. */
|
||||
@@ -72,6 +72,12 @@ export interface NavigationMenuProps<T> extends Pick<NavigationMenuRootProps, 'm
|
||||
highlightColor?: NavigationMenuVariants['highlightColor']
|
||||
/** The content of the menu. */
|
||||
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.
|
||||
* @defaultValue false
|
||||
@@ -142,6 +148,7 @@ extendDevtoolsMeta({
|
||||
import { computed, toRef } from 'vue'
|
||||
import { NavigationMenuRoot, NavigationMenuList, NavigationMenuItem, NavigationMenuTrigger, NavigationMenuContent, NavigationMenuLink, NavigationMenuIndicator, NavigationMenuViewport, useForwardPropsEmits } from 'reka-ui'
|
||||
import { createReusableTemplate } from '@vueuse/core'
|
||||
import { useAppConfig } from '#imports'
|
||||
import { get } from '../utils'
|
||||
import { pickLinkProps } from '../utils/link'
|
||||
import ULinkBase from './LinkBase.vue'
|
||||
@@ -153,6 +160,7 @@ import UCollapsible from './Collapsible.vue'
|
||||
|
||||
const props = withDefaults(defineProps<NavigationMenuProps<I>>(), {
|
||||
orientation: 'horizontal',
|
||||
contentOrientation: 'horizontal',
|
||||
delayDuration: 0,
|
||||
unmountOnHide: true,
|
||||
labelKey: 'label'
|
||||
@@ -175,10 +183,13 @@ const rootProps = useForwardPropsEmits(computed(() => ({
|
||||
|
||||
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({
|
||||
orientation: props.orientation,
|
||||
contentOrientation: props.contentOrientation,
|
||||
collapsed: props.collapsed,
|
||||
color: props.color,
|
||||
variant: props.variant,
|
||||
@@ -190,7 +201,7 @@ const lists = computed(() => props.items?.length ? (Array.isArray(props.items[0]
|
||||
</script>
|
||||
|
||||
<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.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 })" />
|
||||
@@ -224,80 +235,73 @@ const lists = computed(() => props.items?.length ? (Array.isArray(props.items[0]
|
||||
</slot>
|
||||
</span>
|
||||
</slot>
|
||||
</DefineItemTemplate>
|
||||
</DefineLinkTemplate>
|
||||
|
||||
<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 })">
|
||||
<DefineItemTemplate v-slot="{ item, index }">
|
||||
<component
|
||||
: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
|
||||
:is="(orientation === 'vertical' && item.children?.length) ? UCollapsible : NavigationMenuItem"
|
||||
v-for="(item, index) in list"
|
||||
:key="`list-${listIndex}-${index}`"
|
||||
as="li"
|
||||
:value="item.value || String(index)"
|
||||
:default-open="item.defaultOpen"
|
||||
:unmount-on-hide="(orientation === 'vertical' && item.children?.length) ? unmountOnHide : undefined"
|
||||
:open="item.open"
|
||||
:class="ui.item({ class: props.ui?.item })"
|
||||
: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"
|
||||
>
|
||||
<div v-if="orientation === 'vertical' && item.type === 'label'" :class="ui.label({ class: props.ui?.label })">
|
||||
<ReuseItemTemplate :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
|
||||
: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>
|
||||
<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) })">
|
||||
<ReuseLinkTemplate :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 })">
|
||||
<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>
|
||||
<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">
|
||||
<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.link({ class: [props.ui?.link, childItem.class], active: childActive, disabled: !!childItem.disabled, level: true })">
|
||||
<ReuseItemTemplate :item="(childItem as T)" :active="childActive" :index="childIndex" />
|
||||
<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>
|
||||
</template>
|
||||
</component>
|
||||
</slot>
|
||||
</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>
|
||||
|
||||
<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 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'>> {
|
||||
/**
|
||||
|
||||
@@ -7,9 +7,9 @@ import theme from '#build/ui/pin-input'
|
||||
import { tv } from '../utils/tv'
|
||||
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>
|
||||
|
||||
@@ -50,7 +50,7 @@ const props = withDefaults(defineProps<PinInputProps>(), {
|
||||
const emits = defineEmits<PinInputEmits>()
|
||||
|
||||
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({
|
||||
color: color.value,
|
||||
@@ -77,7 +77,7 @@ function onBlur(event: FocusEvent) {
|
||||
|
||||
<template>
|
||||
<PinInputRoot
|
||||
v-bind="rootProps"
|
||||
v-bind="{ ...rootProps, ...ariaAttrs }"
|
||||
:id="id"
|
||||
:name="name"
|
||||
:class="ui.root({ class: [props.class, props.ui?.root] })"
|
||||
@@ -92,6 +92,7 @@ function onBlur(event: FocusEvent) {
|
||||
v-bind="$attrs"
|
||||
:disabled="disabled"
|
||||
@blur="onBlur"
|
||||
@focus="emitFormFocus"
|
||||
/>
|
||||
</PinInputRoot>
|
||||
</template>
|
||||
|
||||
@@ -6,9 +6,9 @@ import theme from '#build/ui/popover'
|
||||
import { tv } from '../utils/tv'
|
||||
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'> {
|
||||
/**
|
||||
|
||||
@@ -7,9 +7,9 @@ import _appConfig from '#build/app.config'
|
||||
import theme from '#build/ui/progress'
|
||||
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>
|
||||
|
||||
|
||||
@@ -7,9 +7,9 @@ import theme from '#build/ui/radio-group'
|
||||
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
||||
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>
|
||||
|
||||
@@ -87,7 +87,7 @@ const slots = defineSlots<RadioGroupSlots<T>>()
|
||||
|
||||
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 ui = computed(() => radioGroup({
|
||||
@@ -147,7 +147,7 @@ function onUpdate(value: any) {
|
||||
:class="ui.root({ class: [props.class, props.ui?.root] })"
|
||||
@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 })">
|
||||
<slot name="legend">
|
||||
{{ legend }}
|
||||
|
||||
@@ -10,9 +10,9 @@ import { tv } from '../utils/tv'
|
||||
import type { AvatarProps, ChipProps, InputProps } from '../types'
|
||||
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 {
|
||||
label?: string
|
||||
@@ -129,11 +129,11 @@ const emits = defineEmits<SelectEmits<T, V, M>>()
|
||||
const slots = defineSlots<SelectSlots<T, M>>()
|
||||
|
||||
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 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 { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponentIcons(toRef(() => defu(props, { trailingIcon: appConfig.ui.icons.chevronDown })))
|
||||
|
||||
@@ -179,6 +179,7 @@ function onUpdateOpen(value: boolean) {
|
||||
} else {
|
||||
const event = new FocusEvent('focus')
|
||||
emits('focus', event)
|
||||
emitFormFocus()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -186,16 +187,17 @@ function onUpdateOpen(value: boolean) {
|
||||
<!-- eslint-disable vue/no-template-shadow -->
|
||||
<template>
|
||||
<SelectRoot
|
||||
:id="id"
|
||||
v-slot="{ modelValue, open }"
|
||||
v-bind="rootProps"
|
||||
:name="name"
|
||||
v-bind="rootProps"
|
||||
:autocomplete="autocomplete"
|
||||
:disabled="disabled"
|
||||
:default-value="(defaultValue as (AcceptableValue | AcceptableValue[] | undefined))"
|
||||
:model-value="(modelValue as (AcceptableValue | AcceptableValue[] | undefined))"
|
||||
@update:model-value="onUpdate"
|
||||
@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 })">
|
||||
<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 })" />
|
||||
|
||||
@@ -10,9 +10,9 @@ import { tv } from '../utils/tv'
|
||||
import type { AvatarProps, ChipProps, InputProps } from '../types'
|
||||
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 {
|
||||
label?: string
|
||||
@@ -168,7 +168,7 @@ const contentProps = toRef(() => defu(props.content, { side: 'bottom', sideOffse
|
||||
const arrowProps = toRef(() => props.arrow as ComboboxArrowProps)
|
||||
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 { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponentIcons(toRef(() => defu(props, { trailingIcon: appConfig.ui.icons.chevronDown })))
|
||||
|
||||
@@ -272,6 +272,7 @@ function onUpdateOpen(value: boolean) {
|
||||
} else {
|
||||
const event = new FocusEvent('focus')
|
||||
emits('focus', event)
|
||||
emitFormFocus()
|
||||
clearTimeout(timeoutId)
|
||||
}
|
||||
}
|
||||
@@ -298,7 +299,7 @@ function onUpdateOpen(value: boolean) {
|
||||
<ComboboxRoot
|
||||
:id="id"
|
||||
v-slot="{ modelValue, open }"
|
||||
v-bind="rootProps"
|
||||
v-bind="{ ...rootProps, ...ariaAttrs }"
|
||||
ignore-filter
|
||||
as-child
|
||||
:name="name"
|
||||
|
||||
@@ -7,9 +7,9 @@ import theme from '#build/ui/separator'
|
||||
import { tv } from '../utils/tv'
|
||||
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>
|
||||
|
||||
|
||||
@@ -5,9 +5,9 @@ import theme from '#build/ui/skeleton'
|
||||
import { tv } from '../utils/tv'
|
||||
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 {
|
||||
/**
|
||||
|
||||
@@ -8,9 +8,9 @@ import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
||||
import { tv } from '../utils/tv'
|
||||
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>
|
||||
|
||||
|
||||
@@ -6,9 +6,9 @@ import _appConfig from '#build/app.config'
|
||||
import theme from '#build/ui/slider'
|
||||
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>
|
||||
|
||||
@@ -55,7 +55,7 @@ const modelValue = defineModel<number | number[]>()
|
||||
|
||||
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(() => {
|
||||
if (typeof props.defaultValue === 'number') {
|
||||
@@ -95,7 +95,7 @@ function onChange(value: any) {
|
||||
|
||||
<template>
|
||||
<SliderRoot
|
||||
v-bind="rootProps"
|
||||
v-bind="{ ...rootProps, ...ariaAttrs }"
|
||||
:id="id"
|
||||
v-model="sliderValue"
|
||||
:name="name"
|
||||
|
||||
@@ -8,9 +8,9 @@ import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
||||
import { tv } from '../utils/tv'
|
||||
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>
|
||||
|
||||
|
||||
@@ -8,9 +8,9 @@ import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
||||
import { tv } from '../utils/tv'
|
||||
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>
|
||||
|
||||
@@ -68,7 +68,7 @@ const modelValue = defineModel<boolean>({ default: undefined })
|
||||
const appConfig = useAppConfig()
|
||||
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 ui = computed(() => switchTv({
|
||||
@@ -93,7 +93,7 @@ function onUpdate(value: any) {
|
||||
<div :class="ui.container({ class: props.ui?.container })">
|
||||
<SwitchRoot
|
||||
:id="id"
|
||||
v-bind="rootProps"
|
||||
v-bind="{ ...rootProps, ...ariaAttrs }"
|
||||
v-model="modelValue"
|
||||
:name="name"
|
||||
: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>
|
||||
|
||||
|
||||
@@ -9,9 +9,9 @@ import { tv } from '../utils/tv'
|
||||
import type { AvatarProps } from '../types'
|
||||
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 {
|
||||
label?: string
|
||||
|
||||
@@ -5,9 +5,9 @@ import _appConfig from '#build/app.config'
|
||||
import theme from '#build/ui/textarea'
|
||||
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>
|
||||
|
||||
@@ -66,7 +66,7 @@ const emits = defineEmits<TextareaEmits>()
|
||||
|
||||
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({
|
||||
color: color.value,
|
||||
@@ -185,10 +185,11 @@ onMounted(() => {
|
||||
:class="ui.base({ class: props.ui?.base })"
|
||||
:disabled="disabled"
|
||||
:required="required"
|
||||
v-bind="$attrs"
|
||||
v-bind="{ ...$attrs, ...ariaAttrs }"
|
||||
@input="onInput"
|
||||
@blur="onBlur"
|
||||
@change="onChange"
|
||||
@focus="emitFormFocus"
|
||||
/>
|
||||
|
||||
<slot />
|
||||
|
||||
@@ -8,9 +8,9 @@ import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
||||
import { tv } from '../utils/tv'
|
||||
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>
|
||||
|
||||
|
||||
@@ -7,9 +7,9 @@ import theme from '#build/ui/toaster'
|
||||
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
||||
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>
|
||||
|
||||
|
||||
@@ -7,9 +7,9 @@ import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
||||
import { tv } from '../utils/tv'
|
||||
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 {
|
||||
/** The text content of the tooltip. */
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { GetObjectField } from '../types/utils'
|
||||
export const buttonGroupInjectionKey: InjectionKey<ComputedRef<{
|
||||
size: ButtonGroupProps['size']
|
||||
orientation: ButtonGroupProps['orientation']
|
||||
}> | undefined> = Symbol('nuxt-ui.button-group')
|
||||
}>> = Symbol('nuxt-ui.button-group')
|
||||
|
||||
type Props<T> = {
|
||||
size?: GetObjectField<T, 'size'>
|
||||
@@ -14,7 +14,6 @@ type Props<T> = {
|
||||
|
||||
export function useButtonGroup<T>(props: Props<T>) {
|
||||
const buttonGroup = inject(buttonGroupInjectionKey, undefined)
|
||||
|
||||
return {
|
||||
orientation: computed(() => buttonGroup?.value.orientation),
|
||||
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 { FormFieldProps } from '../types'
|
||||
import type { FormEvent, FormInputEvents, FormFieldInjectedOptions, FormInjectedOptions } from '../types/form'
|
||||
@@ -9,13 +9,12 @@ type Props<T> = {
|
||||
name?: string
|
||||
size?: GetObjectField<T, 'size'>
|
||||
color?: GetObjectField<T, 'color'>
|
||||
legend?: string
|
||||
highlight?: boolean
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
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 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')
|
||||
@@ -29,41 +28,40 @@ export function useFormField<T>(props?: Props<T>, opts?: { bind?: boolean, defer
|
||||
const inputId = inject(inputIdInjectionKey, undefined)
|
||||
|
||||
if (formField && inputId) {
|
||||
if (opts?.bind === false || props?.legend) {
|
||||
if (opts?.bind === false) {
|
||||
// Removes for="..." attribute on label for RadioGroup and alike.
|
||||
inputId.value = undefined
|
||||
} else if (props?.id) {
|
||||
// Updates for="..." attribute on label if props.id is provided.
|
||||
inputId.value = props?.id
|
||||
}
|
||||
|
||||
if (formInputs && formField.value.name && inputId.value) {
|
||||
formInputs.value[formField.value.name] = { id: inputId.value, pattern: formField.value.errorPattern }
|
||||
}
|
||||
}
|
||||
|
||||
const touched = ref(false)
|
||||
|
||||
function emitFormEvent(type: FormInputEvents, name?: string) {
|
||||
function emitFormEvent(type: FormInputEvents, name?: string, eager?: boolean) {
|
||||
if (formBus && formField && name) {
|
||||
formBus.emit({ type, name })
|
||||
formBus.emit({ type, name, eager })
|
||||
}
|
||||
}
|
||||
|
||||
function emitFormBlur() {
|
||||
touched.value = true
|
||||
emitFormEvent('blur', formField?.value.name)
|
||||
}
|
||||
|
||||
function emitFormFocus() {
|
||||
emitFormEvent('focus', formField?.value.name)
|
||||
}
|
||||
|
||||
function emitFormChange() {
|
||||
touched.value = true
|
||||
emitFormEvent('change', formField?.value.name)
|
||||
}
|
||||
|
||||
const emitFormInput = useDebounceFn(
|
||||
() => {
|
||||
if (!opts?.deferInputValidation || touched.value || formField?.value.eagerValidation) {
|
||||
emitFormEvent('input', formField?.value.name)
|
||||
}
|
||||
emitFormEvent('input', formField?.value.name, !opts?.deferInputValidation || formField?.value.eagerValidation)
|
||||
},
|
||||
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),
|
||||
emitFormBlur,
|
||||
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 type { ToastProps } from '../types'
|
||||
|
||||
@@ -8,20 +9,40 @@ export interface Toast extends Omit<ToastProps, 'defaultOpen'> {
|
||||
|
||||
export function useToast() {
|
||||
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 = {
|
||||
id: new Date().getTime().toString(),
|
||||
id: generateId(),
|
||||
open: true,
|
||||
...toast
|
||||
}
|
||||
|
||||
const index = toasts.value.findIndex((t: Toast) => t.id === body.id)
|
||||
if (index === -1) {
|
||||
toasts.value.push(body)
|
||||
}
|
||||
queue.push(body)
|
||||
|
||||
toasts.value = toasts.value.slice(-5)
|
||||
await processQueue()
|
||||
|
||||
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 fi } from './fi'
|
||||
export { default as fr } from './fr'
|
||||
export { default as hi } from './hi'
|
||||
export { default as id } from './id'
|
||||
export { default as it } from './it'
|
||||
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 uk } from './uk'
|
||||
export { default as vi } from './vi'
|
||||
export { default as zh_hans } from './zh_hans'
|
||||
export { default as zh_hant } from './zh_hant'
|
||||
export { default as zh_cn } from './zh_cn'
|
||||
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({
|
||||
name: '简体中文',
|
||||
code: 'zh-Hans',
|
||||
code: 'zh-CN',
|
||||
messages: {
|
||||
inputMenu: {
|
||||
noMatch: '没有匹配的数据',
|
||||
@@ -2,7 +2,7 @@ import { defineLocale } from '../composables/defineLocale'
|
||||
|
||||
export default defineLocale({
|
||||
name: '繁體中文',
|
||||
code: 'zh-Hant',
|
||||
code: 'zh-TW',
|
||||
messages: {
|
||||
inputMenu: {
|
||||
noMatch: '沒有相符的資料',
|
||||
@@ -1,5 +1,5 @@
|
||||
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 { Schema as JoiSchema } from 'joi'
|
||||
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 { Struct as SuperstructSchema } from 'superstruct'
|
||||
|
||||
export interface Form<T> {
|
||||
validate (opts?: { name?: string | string[], silent?: boolean, nested?: boolean, transform?: boolean }): Promise<T | false>
|
||||
export interface Form<T extends object> {
|
||||
validate (opts?: { name?: keyof T | (keyof T)[], silent?: boolean, nested?: boolean, transform?: boolean }): Promise<T | false>
|
||||
clear (path?: string): void
|
||||
errors: Ref<FormError[]>
|
||||
setErrors (errs: FormError[], path?: string): void
|
||||
getErrors (path?: string): FormError[]
|
||||
setErrors (errs: FormError[], name?: keyof T): void
|
||||
getErrors (name?: keyof T): FormError[]
|
||||
submit (): Promise<void>
|
||||
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
|
||||
| YupObjectSchema<T>
|
||||
| ValibotSchema
|
||||
@@ -28,7 +33,7 @@ export type FormSchema<T extends Record<string, any>> =
|
||||
| SuperstructSchema<any, any>
|
||||
| StandardSchemaV1
|
||||
|
||||
export type FormInputEvents = 'input' | 'blur' | 'change'
|
||||
export type FormInputEvents = 'input' | 'blur' | 'change' | 'focus'
|
||||
|
||||
export interface FormError<P extends string = string> {
|
||||
name: P
|
||||
@@ -61,13 +66,14 @@ export type FormChildDetachEvent = {
|
||||
formId: string | number
|
||||
}
|
||||
|
||||
export type FormInputEvent = {
|
||||
export type FormInputEvent<T extends object> = {
|
||||
type: FormEventType
|
||||
name?: string
|
||||
name: keyof T
|
||||
eager?: boolean
|
||||
}
|
||||
|
||||
export type FormEvent =
|
||||
| FormInputEvent
|
||||
export type FormEvent<T extends object> =
|
||||
| FormInputEvent<T>
|
||||
| FormChildAttachEvent
|
||||
| FormChildDetachEvent
|
||||
|
||||
@@ -83,6 +89,9 @@ export interface FormFieldInjectedOptions<T> {
|
||||
eagerValidation?: boolean
|
||||
validateOnInputDelay?: number
|
||||
errorPattern?: RegExp
|
||||
hint?: string
|
||||
description?: string
|
||||
ariaId: string
|
||||
}
|
||||
|
||||
export interface ValidateReturnSchema<T> {
|
||||
|
||||
@@ -47,9 +47,7 @@ export async function validateStandardSchema(
|
||||
state: any,
|
||||
schema: StandardSchemaV1
|
||||
): Promise<ValidateReturnSchema<typeof state>> {
|
||||
const result = await schema['~standard'].validate({
|
||||
value: state
|
||||
})
|
||||
const result = await schema['~standard'].validate(state)
|
||||
|
||||
if (result.issues) {
|
||||
return {
|
||||
@@ -197,14 +195,14 @@ export function validateSchema<T extends object>(state: T, schema: FormSchema<T>
|
||||
return validateZodSchema(state, schema)
|
||||
} else if (isJoiSchema(schema)) {
|
||||
return validateJoiSchema(state, schema)
|
||||
} else if (isStandardSchema(schema)) {
|
||||
return validateStandardSchema(state, schema)
|
||||
} else if (isValibotSchema(schema)) {
|
||||
return validateValibotSchema(state, schema)
|
||||
} else if (isYupSchema(schema)) {
|
||||
return validateYupSchema(state, schema)
|
||||
} else if (isSuperStructSchema(schema)) {
|
||||
return validateSuperstructSchema(state, schema)
|
||||
} else if (isStandardSchema(schema)) {
|
||||
return validateStandardSchema(state, schema)
|
||||
} else {
|
||||
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 _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
|
||||
}
|
||||
|
||||
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 {
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export default {
|
||||
slots: {
|
||||
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',
|
||||
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)]',
|
||||
childLinkDescription: 'text-sm text-[var(--ui-text-muted)]',
|
||||
separator: 'px-2 h-px bg-[var(--ui-border)]',
|
||||
viewportWrapper: 'absolute top-full left-0 flex w-full justify-center',
|
||||
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]',
|
||||
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]',
|
||||
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,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',
|
||||
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)]'
|
||||
},
|
||||
@@ -56,13 +56,24 @@ export default (options: Required<ModuleOptions>) => ({
|
||||
list: 'flex items-center',
|
||||
item: 'py-2',
|
||||
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: {
|
||||
root: 'flex-col',
|
||||
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: {
|
||||
true: {
|
||||
childLink: 'bg-[var(--ui-bg-elevated)] text-[var(--ui-text-highlighted)]',
|
||||
@@ -91,6 +102,19 @@ export default (options: Required<ModuleOptions>) => ({
|
||||
}
|
||||
},
|
||||
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',
|
||||
highlight: true,
|
||||
class: {
|
||||
|
||||
@@ -278,6 +278,39 @@ describe('Form', () => {
|
||||
{ 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 () => {
|
||||
@@ -444,6 +477,7 @@ describe('Form', () => {
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test('form field errorPattern works', async () => {
|
||||
const wrapper = await mountSuspended({
|
||||
components: {
|
||||
|
||||
@@ -1,16 +1,58 @@
|
||||
import { defineComponent } from 'vue'
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import FormField, { type FormFieldProps, type FormFieldSlots } from '../../src/runtime/components/FormField.vue'
|
||||
import { describe, it, expect, test, vi } from 'vitest'
|
||||
import type { FormFieldProps, FormFieldSlots } from '../../src/runtime/components/FormField.vue'
|
||||
import ComponentRender from '../component-render'
|
||||
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.
|
||||
// See: https://github.com/nuxt/test-utils/issues/684
|
||||
const FormFieldWrapper = defineComponent({
|
||||
components: {
|
||||
UFormField: FormField
|
||||
UFormField
|
||||
},
|
||||
template: `<UFormField>
|
||||
template: `
|
||||
<UFormField>
|
||||
<template v-for="(_, name) in $slots" #[name]="slotData">
|
||||
<slot :name="name" v-bind="slotData" />
|
||||
</template>
|
||||
@@ -42,4 +84,80 @@ describe('FormField', () => {
|
||||
const html = await ComponentRender(nameOrHtml, options, FormFieldWrapper)
|
||||
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([
|
||||
// Props
|
||||
['with items', { props }],
|
||||
['with modelValue', { props: { ...props, modelValue: '0' } }],
|
||||
['with labelKey', { props: { ...props, labelKey: 'icon' } }],
|
||||
['with arrow', { props: { ...props, arrow: true } }],
|
||||
['with orientation vertical', { props: { ...props, orientation: 'vertical' as const } }],
|
||||
['with arrow', { props: { ...props, arrow: true, modelValue: '0' } }],
|
||||
['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 neutral variant ${variant}`, { props: { ...props, variant, color: 'neutral' } }]),
|
||||
...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`] = `
|
||||
"<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="text-sm font-medium">Alert</div>
|
||||
<!--v-if-->
|
||||
|
||||
@@ -13,7 +13,7 @@ exports[`Alert > renders with as 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="text-sm font-medium">Alert</div>
|
||||
<!--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 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>"`;
|
||||
|
||||
|
||||
@@ -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 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>"`;
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user