mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-14 20:19:34 +01:00
Merge branch 'v3' into feat/1058
This commit is contained in:
@@ -133,7 +133,6 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.alert || {})
|
||||
<UButton
|
||||
v-if="close"
|
||||
:icon="closeIcon || appConfig.ui.icons.close"
|
||||
size="md"
|
||||
color="neutral"
|
||||
variant="link"
|
||||
:aria-label="t('alert.close')"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import type { AppConfig } from '@nuxt/schema'
|
||||
import theme from '#build/ui/avatar'
|
||||
import type { ChipProps } from '../types'
|
||||
import type { ComponentConfig } from '../types/utils'
|
||||
|
||||
type Avatar = ComponentConfig<typeof theme, AppConfig, 'avatar'>
|
||||
@@ -22,6 +23,7 @@ export interface AvatarProps {
|
||||
* @defaultValue 'md'
|
||||
*/
|
||||
size?: Avatar['variants']['size']
|
||||
chip?: boolean | ChipProps
|
||||
class?: any
|
||||
style?: any
|
||||
ui?: Avatar['slots']
|
||||
@@ -40,6 +42,7 @@ import ImageComponent from '#build/ui-image-component'
|
||||
import { useAvatarGroup } from '../composables/useAvatarGroup'
|
||||
import { tv } from '../utils/tv'
|
||||
import UIcon from './Icon.vue'
|
||||
import UChip from './Chip.vue'
|
||||
|
||||
defineOptions({ inheritAttrs: false })
|
||||
|
||||
@@ -81,7 +84,13 @@ function onError() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Primitive :as="as" :class="ui.root({ class: [props.ui?.root, props.class] })" :style="props.style">
|
||||
<component
|
||||
:is="props.chip ? UChip : Primitive"
|
||||
:as="as"
|
||||
v-bind="props.chip ? (typeof props.chip === 'object' ? { inset: true, ...props.chip } : { inset: true }) : {}"
|
||||
:class="ui.root({ class: [props.ui?.root, props.class] })"
|
||||
:style="props.style"
|
||||
>
|
||||
<component
|
||||
:is="ImageComponent"
|
||||
v-if="src && !error"
|
||||
@@ -101,5 +110,5 @@ function onError() {
|
||||
<span v-else :class="ui.fallback({ class: props.ui?.fallback })">{{ fallback || ' ' }}</span>
|
||||
</slot>
|
||||
</Slot>
|
||||
</Primitive>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
@@ -153,8 +153,8 @@ const Calendar = computed(() => props.range ? RangeCalendar : SingleCalendar)
|
||||
<Calendar.Root
|
||||
v-slot="{ weekDays, grid }"
|
||||
v-bind="rootProps"
|
||||
:model-value="modelValue"
|
||||
:default-value="defaultValue"
|
||||
:model-value="(modelValue as DateValue | DateValue[])"
|
||||
:default-value="(defaultValue as DateValue)"
|
||||
:locale="locale"
|
||||
:dir="dir"
|
||||
:class="ui.root({ class: [props.ui?.root, props.class] })"
|
||||
|
||||
@@ -310,7 +310,6 @@ defineExpose({
|
||||
<UButton
|
||||
:disabled="!canScrollPrev"
|
||||
:icon="prevIcon"
|
||||
size="md"
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
:aria-label="t('carousel.prev')"
|
||||
@@ -321,7 +320,6 @@ defineExpose({
|
||||
<UButton
|
||||
:disabled="!canScrollNext"
|
||||
:icon="nextIcon"
|
||||
size="md"
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
:aria-label="t('carousel.next')"
|
||||
@@ -336,6 +334,7 @@ defineExpose({
|
||||
<button
|
||||
:aria-label="t('carousel.goto', { slide: index + 1 })"
|
||||
:class="ui.dot({ class: props.ui?.dot, active: selectedIndex === index })"
|
||||
:data-state="selectedIndex === index ? 'active' : undefined"
|
||||
@click="scrollTo(index)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -26,13 +26,18 @@ export interface CommandPaletteItem extends Omit<LinkProps, 'type' | 'raw' | 'cu
|
||||
loading?: boolean
|
||||
disabled?: boolean
|
||||
slot?: string
|
||||
/**
|
||||
* The placeholder to display when the item has children.
|
||||
*/
|
||||
placeholder?: string
|
||||
children?: CommandPaletteItem[]
|
||||
onSelect?(e?: Event): void
|
||||
class?: any
|
||||
ui?: Pick<CommandPalette['slots'], 'item' | 'itemLeadingIcon' | 'itemLeadingAvatarSize' | 'itemLeadingAvatar' | 'itemLeadingChipSize' | 'itemLeadingChip' | 'itemLabel' | 'itemLabelPrefix' | 'itemLabelBase' | 'itemLabelSuffix' | 'itemTrailing' | 'itemTrailingKbds' | 'itemTrailingKbdsSize' | 'itemTrailingHighlightedIcon' | 'itemTrailingIcon'>
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
export interface CommandPaletteGroup<T> {
|
||||
export interface CommandPaletteGroup<T extends CommandPaletteItem = CommandPaletteItem> {
|
||||
id: string
|
||||
label?: string
|
||||
slot?: string
|
||||
@@ -52,7 +57,7 @@ export interface CommandPaletteGroup<T> {
|
||||
highlightedIcon?: string
|
||||
}
|
||||
|
||||
export interface CommandPaletteProps<G, T> extends Pick<ListboxRootProps, 'multiple' | 'disabled' | 'modelValue' | 'defaultValue' | 'highlightOnHover'>, Pick<UseComponentIconsProps, 'loading' | 'loadingIcon'> {
|
||||
export interface CommandPaletteProps<G extends CommandPaletteGroup<T> = CommandPaletteGroup<any>, T extends CommandPaletteItem = CommandPaletteItem> extends Pick<ListboxRootProps, 'multiple' | 'disabled' | 'modelValue' | 'defaultValue' | 'highlightOnHover' | 'selectionBehavior'>, Pick<UseComponentIconsProps, 'loading' | 'loadingIcon'> {
|
||||
/**
|
||||
* The element or component this component should render as.
|
||||
* @defaultValue 'div'
|
||||
@@ -70,6 +75,12 @@ export interface CommandPaletteProps<G, T> extends Pick<ListboxRootProps, 'multi
|
||||
* @IconifyIcon
|
||||
*/
|
||||
selectedIcon?: string
|
||||
/**
|
||||
* The icon displayed when an item has children.
|
||||
* @defaultValue appConfig.ui.icons.chevronRight
|
||||
* @IconifyIcon
|
||||
*/
|
||||
trailingIcon?: string
|
||||
/**
|
||||
* The placeholder text for the input.
|
||||
* @defaultValue t('commandPalette.placeholder')
|
||||
@@ -93,6 +104,18 @@ export interface CommandPaletteProps<G, T> extends Pick<ListboxRootProps, 'multi
|
||||
* @IconifyIcon
|
||||
*/
|
||||
closeIcon?: string
|
||||
/**
|
||||
* Display a button to navigate back in history.
|
||||
* `{ size: 'md', color: 'neutral', variant: 'link' }`{lang="ts-type"}
|
||||
* @defaultValue true
|
||||
*/
|
||||
back?: boolean | ButtonProps
|
||||
/**
|
||||
* The icon displayed in the back button.
|
||||
* @defaultValue appConfig.ui.icons.arrowLeft
|
||||
* @IconifyIcon
|
||||
*/
|
||||
backIcon?: string
|
||||
groups?: G[]
|
||||
/**
|
||||
* Options for [useFuse](https://vueuse.org/integrations/useFuse).
|
||||
@@ -116,14 +139,15 @@ export interface CommandPaletteProps<G, T> extends Pick<ListboxRootProps, 'multi
|
||||
ui?: CommandPalette['slots']
|
||||
}
|
||||
|
||||
export type CommandPaletteEmits<T> = ListboxRootEmits<T> & {
|
||||
export type CommandPaletteEmits<T extends CommandPaletteItem = CommandPaletteItem> = ListboxRootEmits<T> & {
|
||||
'update:open': [value: boolean]
|
||||
}
|
||||
|
||||
type SlotProps<T> = (props: { item: T, index: number }) => any
|
||||
|
||||
export type CommandPaletteSlots<G extends { slot?: string }, T extends { slot?: string }> = {
|
||||
export type CommandPaletteSlots<G extends CommandPaletteGroup<T> = CommandPaletteGroup<any>, T extends CommandPaletteItem = CommandPaletteItem> = {
|
||||
'empty'(props: { searchTerm?: string }): any
|
||||
'back'(props: { ui: { [K in keyof Required<CommandPalette['slots']>]: (props?: Record<string, any>) => string } }): any
|
||||
'close'(props: { ui: { [K in keyof Required<CommandPalette['slots']>]: (props?: Record<string, any>) => string } }): any
|
||||
'item': SlotProps<T>
|
||||
'item-leading': SlotProps<T>
|
||||
@@ -134,7 +158,7 @@ export type CommandPaletteSlots<G extends { slot?: string }, T extends { slot?:
|
||||
</script>
|
||||
|
||||
<script setup lang="ts" generic="G extends CommandPaletteGroup<T>, T extends CommandPaletteItem">
|
||||
import { computed } from 'vue'
|
||||
import { computed, ref, useTemplateRef } from 'vue'
|
||||
import { ListboxRoot, ListboxFilter, ListboxContent, ListboxGroup, ListboxGroupLabel, ListboxItem, ListboxItemIndicator, useForwardProps, useForwardPropsEmits } from 'reka-ui'
|
||||
import { defu } from 'defu'
|
||||
import { reactivePick } from '@vueuse/core'
|
||||
@@ -157,7 +181,8 @@ import UKbd from './Kbd.vue'
|
||||
const props = withDefaults(defineProps<CommandPaletteProps<G, T>>(), {
|
||||
modelValue: '',
|
||||
labelKey: 'label',
|
||||
autofocus: true
|
||||
autofocus: true,
|
||||
back: true
|
||||
})
|
||||
const emits = defineEmits<CommandPaletteEmits<T>>()
|
||||
const slots = defineSlots<CommandPaletteSlots<G, T>>()
|
||||
@@ -167,7 +192,7 @@ const searchTerm = defineModel<string>('searchTerm', { default: '' })
|
||||
const { t } = useLocale()
|
||||
const appConfig = useAppConfig() as CommandPalette['AppConfig']
|
||||
|
||||
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'disabled', 'multiple', 'modelValue', 'defaultValue', 'highlightOnHover'), emits)
|
||||
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'disabled', 'multiple', 'modelValue', 'defaultValue', 'highlightOnHover', 'selectionBehavior'), emits)
|
||||
const inputProps = useForwardProps(reactivePick(props, 'loading', 'loadingIcon'))
|
||||
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
@@ -183,18 +208,22 @@ const fuse = computed(() => defu({}, props.fuse, {
|
||||
matchAllWhenSearchEmpty: true
|
||||
}))
|
||||
|
||||
const items = computed(() => props.groups?.filter((group) => {
|
||||
const history = ref<(CommandPaletteGroup & { placeholder?: string })[]>([])
|
||||
|
||||
const placeholder = computed(() => history.value[history.value.length - 1]?.placeholder || props.placeholder || t('commandPalette.placeholder'))
|
||||
|
||||
const groups = computed(() => history.value?.length ? [history.value[history.value.length - 1] as G] : props.groups)
|
||||
|
||||
const items = computed(() => groups.value?.filter((group) => {
|
||||
if (!group.id) {
|
||||
console.warn(`[@nuxt/ui] CommandPalette group is missing an \`id\` property`)
|
||||
return false
|
||||
}
|
||||
|
||||
if (group.ignoreFilter) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}).flatMap(group => group.items?.map(item => ({ ...item, group: group.id })) || []) || [])
|
||||
})?.flatMap(group => group.items?.map(item => ({ ...item, group: group.id })) || []) || [])
|
||||
|
||||
const { results: fuseResults } = useFuse<typeof items.value[number]>(searchTerm, items, fuse)
|
||||
|
||||
@@ -215,7 +244,7 @@ function getGroupWithItems(group: G, items: (T & { matches?: FuseResult<T>['matc
|
||||
}
|
||||
}
|
||||
|
||||
const groups = computed(() => {
|
||||
const filteredGroups = computed(() => {
|
||||
const groupsById = fuseResults.value.reduce((acc, result) => {
|
||||
const { item, matches } = result
|
||||
if (!item.group) {
|
||||
@@ -229,7 +258,7 @@ const groups = computed(() => {
|
||||
}, {} as Record<string, (T & { matches?: FuseResult<T>['matches'] })[]>)
|
||||
|
||||
const fuseGroups = Object.entries(groupsById).map(([id, items]) => {
|
||||
const group = props.groups?.find(group => group.id === id)
|
||||
const group = groups.value?.find(group => group.id === id)
|
||||
if (!group) {
|
||||
return
|
||||
}
|
||||
@@ -237,7 +266,7 @@ const groups = computed(() => {
|
||||
return getGroupWithItems(group, items)
|
||||
}).filter(group => !!group)
|
||||
|
||||
const nonFuseGroups = props.groups
|
||||
const nonFuseGroups = groups.value
|
||||
?.map((group, index) => ({ ...group, index }))
|
||||
?.filter(group => group.ignoreFilter && group.items?.length)
|
||||
?.map(group => ({ ...getGroupWithItems(group, group.items || []), index: group.index })) || []
|
||||
@@ -247,26 +276,88 @@ const groups = computed(() => {
|
||||
return acc
|
||||
}, [...fuseGroups])
|
||||
})
|
||||
|
||||
const listboxRootRef = useTemplateRef('listboxRootRef')
|
||||
|
||||
function navigate(item: T) {
|
||||
if (!item.children?.length) {
|
||||
return
|
||||
}
|
||||
|
||||
history.value.push({
|
||||
id: `history-${history.value.length}`,
|
||||
label: item.label,
|
||||
slot: item.slot,
|
||||
placeholder: item.placeholder,
|
||||
items: item.children
|
||||
} as any)
|
||||
|
||||
searchTerm.value = ''
|
||||
|
||||
listboxRootRef.value?.highlightFirstItem()
|
||||
}
|
||||
|
||||
function navigateBack() {
|
||||
if (!history.value.length) {
|
||||
return
|
||||
}
|
||||
|
||||
history.value.pop()
|
||||
|
||||
searchTerm.value = ''
|
||||
|
||||
listboxRootRef.value?.highlightFirstItem()
|
||||
}
|
||||
|
||||
function onBackspace() {
|
||||
if (!searchTerm.value) {
|
||||
navigateBack()
|
||||
}
|
||||
}
|
||||
|
||||
function onSelect(e: Event, item: T) {
|
||||
if (item.children?.length) {
|
||||
e.preventDefault()
|
||||
|
||||
navigate(item)
|
||||
} else {
|
||||
item.onSelect?.(e)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<template>
|
||||
<ListboxRoot v-bind="rootProps" :class="ui.root({ class: [props.ui?.root, props.class] })">
|
||||
<ListboxRoot v-bind="rootProps" ref="listboxRootRef" :class="ui.root({ class: [props.ui?.root, props.class] })">
|
||||
<ListboxFilter v-model="searchTerm" as-child>
|
||||
<UInput
|
||||
:placeholder="placeholder || t('commandPalette.placeholder')"
|
||||
:placeholder="placeholder"
|
||||
variant="none"
|
||||
:autofocus="autofocus"
|
||||
v-bind="inputProps"
|
||||
:icon="icon || appConfig.ui.icons.search"
|
||||
:class="ui.input({ class: props.ui?.input })"
|
||||
@keydown.backspace="onBackspace"
|
||||
>
|
||||
<template v-if="history?.length && (back || !!slots.back)" #leading>
|
||||
<slot name="back" :ui="ui">
|
||||
<UButton
|
||||
:icon="backIcon || appConfig.ui.icons.arrowLeft"
|
||||
color="neutral"
|
||||
variant="link"
|
||||
:aria-label="t('commandPalette.back')"
|
||||
v-bind="(typeof back === 'object' ? back as Partial<ButtonProps> : {})"
|
||||
:class="ui.back({ class: props.ui?.back })"
|
||||
@click="navigateBack"
|
||||
/>
|
||||
</slot>
|
||||
</template>
|
||||
|
||||
<template v-if="close || !!slots.close" #trailing>
|
||||
<slot name="close" :ui="ui">
|
||||
<UButton
|
||||
v-if="close"
|
||||
:icon="closeIcon || appConfig.ui.icons.close"
|
||||
size="md"
|
||||
color="neutral"
|
||||
variant="ghost"
|
||||
:aria-label="t('commandPalette.close')"
|
||||
@@ -280,8 +371,8 @@ const groups = computed(() => {
|
||||
</ListboxFilter>
|
||||
|
||||
<ListboxContent :class="ui.content({ class: props.ui?.content })">
|
||||
<div v-if="groups?.length" role="presentation" :class="ui.viewport({ class: props.ui?.viewport })">
|
||||
<ListboxGroup v-for="group in groups" :key="`group-${group.id}`" :class="ui.group({ class: props.ui?.group })">
|
||||
<div v-if="filteredGroups?.length" role="presentation" :class="ui.viewport({ class: props.ui?.viewport })">
|
||||
<ListboxGroup v-for="group in filteredGroups" :key="`group-${group.id}`" :class="ui.group({ class: props.ui?.group })">
|
||||
<ListboxGroupLabel v-if="get(group, props.labelKey as string)" :class="ui.label({ class: props.ui?.label })">
|
||||
{{ get(group, props.labelKey as string) }}
|
||||
</ListboxGroupLabel>
|
||||
@@ -289,10 +380,10 @@ const groups = computed(() => {
|
||||
<ListboxItem
|
||||
v-for="(item, index) in group.items"
|
||||
:key="`group-${group.id}-${index}`"
|
||||
:value="omit(item, ['matches' as any, 'group' as any, 'onSelect', 'labelHtml', 'suffixHtml'])"
|
||||
:value="omit(item, ['matches' as any, 'group' as any, 'onSelect', 'labelHtml', 'suffixHtml', 'children'])"
|
||||
:disabled="item.disabled"
|
||||
as-child
|
||||
@select="item.onSelect"
|
||||
@select="onSelect($event, item)"
|
||||
>
|
||||
<ULink v-slot="{ active, ...slotProps }" v-bind="pickLinkProps(item)" custom>
|
||||
<ULinkBase v-bind="slotProps" :class="ui.item({ class: [props.ui?.item, item.ui?.item, item.class], active: active || item.active })">
|
||||
@@ -323,13 +414,20 @@ const groups = computed(() => {
|
||||
|
||||
<span :class="ui.itemTrailing({ class: [props.ui?.itemTrailing, item.ui?.itemTrailing] })">
|
||||
<slot :name="((item.slot ? `${item.slot}-trailing` : group.slot ? `${group.slot}-trailing` : `item-trailing`) as keyof CommandPaletteSlots<G, T>)" :item="(item as any)" :index="index">
|
||||
<span v-if="item.kbds?.length" :class="ui.itemTrailingKbds({ class: [props.ui?.itemTrailingKbds, item.ui?.itemTrailingKbds] })">
|
||||
<UIcon
|
||||
v-if="item.children && item.children.length > 0"
|
||||
:name="trailingIcon || appConfig.ui.icons.chevronRight"
|
||||
:class="ui.itemTrailingIcon({ class: [props.ui?.itemTrailingIcon, item.ui?.itemTrailingIcon] })"
|
||||
/>
|
||||
|
||||
<span v-else-if="item.kbds?.length" :class="ui.itemTrailingKbds({ class: [props.ui?.itemTrailingKbds, item.ui?.itemTrailingKbds] })">
|
||||
<UKbd v-for="(kbd, kbdIndex) in item.kbds" :key="kbdIndex" :size="((item.ui?.itemTrailingKbdsSize || props.ui?.itemTrailingKbdsSize || ui.itemTrailingKbdsSize()) as KbdProps['size'])" v-bind="typeof kbd === 'string' ? { value: kbd } : kbd" />
|
||||
</span>
|
||||
|
||||
<UIcon v-else-if="group.highlightedIcon" :name="group.highlightedIcon" :class="ui.itemTrailingHighlightedIcon({ class: [props.ui?.itemTrailingHighlightedIcon, item.ui?.itemTrailingHighlightedIcon] })" />
|
||||
</slot>
|
||||
|
||||
<ListboxItemIndicator as-child>
|
||||
<ListboxItemIndicator v-if="!item.children?.length" as-child>
|
||||
<UIcon :name="selectedIcon || appConfig.ui.icons.check" :class="ui.itemTrailingIcon({ class: [props.ui?.itemTrailingIcon, item.ui?.itemTrailingIcon] })" />
|
||||
</ListboxItemIndicator>
|
||||
</span>
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
<script lang="ts">
|
||||
import type { DeepReadonly } from 'vue'
|
||||
import type { AppConfig } from '@nuxt/schema'
|
||||
import theme from '#build/ui/form'
|
||||
import type { FormSchema, FormError, FormInputEvents, FormErrorEvent, FormSubmitEvent, FormEvent, Form, FormErrorWithId, InferInput, InferOutput } from '../types/form'
|
||||
import type { FormSchema, FormError, FormInputEvents, FormErrorEvent, FormSubmitEvent, FormEvent, Form, FormErrorWithId, InferInput, InferOutput, FormData } from '../types/form'
|
||||
import type { ComponentConfig } from '../types/utils'
|
||||
|
||||
type FormConfig = ComponentConfig<typeof theme, AppConfig, 'form'>
|
||||
|
||||
export interface FormProps<S extends FormSchema> {
|
||||
export interface FormProps<S extends FormSchema, T extends boolean = true> {
|
||||
id?: string | number
|
||||
/** Schema to validate the form state. Supports Standard Schema objects, Yup, Joi, and Superstructs. */
|
||||
schema?: S
|
||||
@@ -35,7 +34,7 @@ export interface FormProps<S extends FormSchema> {
|
||||
* If true, schema transformations will be applied to the state on submit.
|
||||
* @defaultValue `true`
|
||||
*/
|
||||
transform?: boolean
|
||||
transform?: T
|
||||
|
||||
/**
|
||||
* If true, this form will attach to its parent Form (if any) and validate at the same time.
|
||||
@@ -50,21 +49,21 @@ export interface FormProps<S extends FormSchema> {
|
||||
*/
|
||||
loadingAuto?: boolean
|
||||
class?: any
|
||||
onSubmit?: ((event: FormSubmitEvent<InferOutput<S>>) => void | Promise<void>) | (() => void | Promise<void>)
|
||||
onSubmit?: ((event: FormSubmitEvent<FormData<S, T>>) => void | Promise<void>) | (() => void | Promise<void>)
|
||||
}
|
||||
|
||||
export interface FormEmits<S extends FormSchema> {
|
||||
(e: 'submit', payload: FormSubmitEvent<InferOutput<S>>): void
|
||||
export interface FormEmits<S extends FormSchema, T extends boolean = true> {
|
||||
(e: 'submit', payload: FormSubmitEvent<FormData<S, T>>): void
|
||||
(e: 'error', payload: FormErrorEvent): void
|
||||
}
|
||||
|
||||
export interface FormSlots {
|
||||
default(props?: { errors: FormError[] }): any
|
||||
default(props?: { errors: FormError[], loading: boolean }): any
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup generic="S extends FormSchema">
|
||||
import { provide, inject, nextTick, ref, onUnmounted, onMounted, computed, useId, readonly } from 'vue'
|
||||
<script lang="ts" setup generic="S extends FormSchema, T extends boolean = true">
|
||||
import { provide, inject, nextTick, ref, onUnmounted, onMounted, computed, useId, readonly, reactive } from 'vue'
|
||||
import { useEventBus } from '@vueuse/core'
|
||||
import { useAppConfig } from '#imports'
|
||||
import { formOptionsInjectionKey, formInputsInjectionKey, formBusInjectionKey, formLoadingInjectionKey } from '../composables/useFormField'
|
||||
@@ -75,17 +74,17 @@ import { FormValidationException } from '../types/form'
|
||||
type I = InferInput<S>
|
||||
type O = InferOutput<S>
|
||||
|
||||
const props = withDefaults(defineProps<FormProps<S>>(), {
|
||||
const props = withDefaults(defineProps<FormProps<S, T>>(), {
|
||||
validateOn() {
|
||||
return ['input', 'blur', 'change'] as FormInputEvents[]
|
||||
},
|
||||
validateOnInputDelay: 300,
|
||||
attach: true,
|
||||
transform: true,
|
||||
transform: () => true as T,
|
||||
loadingAuto: true
|
||||
})
|
||||
|
||||
const emits = defineEmits<FormEmits<S>>()
|
||||
const emits = defineEmits<FormEmits<S, T>>()
|
||||
defineSlots<FormSlots>()
|
||||
|
||||
const appConfig = useAppConfig() as FormConfig['AppConfig']
|
||||
@@ -155,9 +154,9 @@ provide('form-errors', errors)
|
||||
const inputs = ref<{ [P in keyof I]?: { id?: string, pattern?: RegExp } }>({})
|
||||
provide(formInputsInjectionKey, inputs as any)
|
||||
|
||||
const dirtyFields = new Set<keyof I>()
|
||||
const touchedFields = new Set<keyof I>()
|
||||
const blurredFields = new Set<keyof I>()
|
||||
const dirtyFields: Set<keyof I> = reactive(new Set<keyof I>())
|
||||
const touchedFields: Set<keyof I> = reactive(new Set<keyof I>())
|
||||
const blurredFields: Set<keyof I> = reactive(new Set<keyof I>())
|
||||
|
||||
function resolveErrorIds(errs: FormError[]): FormErrorWithId[] {
|
||||
return errs.map(err => ({
|
||||
@@ -183,10 +182,10 @@ async function getErrors(): Promise<FormErrorWithId[]> {
|
||||
return resolveErrorIds(errs)
|
||||
}
|
||||
|
||||
type ValidateOpts<Silent extends boolean> = { name?: keyof I | (keyof I)[], silent?: Silent, nested?: boolean, transform?: boolean }
|
||||
async function _validate(opts: ValidateOpts<false>): Promise<O>
|
||||
async function _validate(opts: ValidateOpts<true>): Promise<O | false>
|
||||
async function _validate(opts: ValidateOpts<boolean> = { silent: false, nested: true, transform: false }): Promise<O | false> {
|
||||
type ValidateOpts<Silent extends boolean, Transform extends boolean> = { name?: keyof I | (keyof I)[], silent?: Silent, nested?: boolean, transform?: Transform }
|
||||
async function _validate<T extends boolean>(opts: ValidateOpts<false, T>): Promise<FormData<S, T>>
|
||||
async function _validate<T extends boolean>(opts: ValidateOpts<true, T>): Promise<FormData<S, T> | false>
|
||||
async function _validate<T extends boolean>(opts: ValidateOpts<boolean, boolean> = { silent: false, nested: true, transform: false }): Promise<FormData<S, T> | false> {
|
||||
const names = opts.name && !Array.isArray(opts.name) ? [opts.name] : opts.name as (keyof O)[]
|
||||
|
||||
const nestedValidatePromises = !names && opts.nested
|
||||
@@ -227,7 +226,7 @@ async function _validate(opts: ValidateOpts<boolean> = { silent: false, nested:
|
||||
Object.assign(props.state, transformedState.value)
|
||||
}
|
||||
|
||||
return props.state as O
|
||||
return props.state as FormData<S, T>
|
||||
}
|
||||
|
||||
const loading = ref(false)
|
||||
@@ -236,7 +235,7 @@ provide(formLoadingInjectionKey, readonly(loading))
|
||||
async function onSubmitWrapper(payload: Event) {
|
||||
loading.value = props.loadingAuto && true
|
||||
|
||||
const event = payload as FormSubmitEvent<O>
|
||||
const event = payload as FormSubmitEvent<FormData<S, T>>
|
||||
|
||||
try {
|
||||
event.data = await _validate({ nested: true, transform: props.transform })
|
||||
@@ -265,7 +264,7 @@ provide(formOptionsInjectionKey, computed(() => ({
|
||||
validateOnInputDelay: props.validateOnInputDelay
|
||||
})))
|
||||
|
||||
defineExpose<Form<I>>({
|
||||
defineExpose<Form<S>>({
|
||||
validate: _validate,
|
||||
errors,
|
||||
|
||||
@@ -302,9 +301,9 @@ defineExpose<Form<I>>({
|
||||
loading,
|
||||
dirty: computed(() => !!dirtyFields.size),
|
||||
|
||||
dirtyFields: readonly(dirtyFields) as DeepReadonly<Set<keyof I>>,
|
||||
blurredFields: readonly(blurredFields) as DeepReadonly<Set<keyof I>>,
|
||||
touchedFields: readonly(touchedFields) as DeepReadonly<Set<keyof I>>
|
||||
dirtyFields: readonly(dirtyFields),
|
||||
blurredFields: readonly(blurredFields),
|
||||
touchedFields: readonly(touchedFields)
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -315,6 +314,6 @@ defineExpose<Form<I>>({
|
||||
:class="ui({ class: props.class })"
|
||||
@submit.prevent="onSubmitWrapper"
|
||||
>
|
||||
<slot :errors="errors" />
|
||||
<slot :errors="errors" :loading="loading" />
|
||||
</component>
|
||||
</template>
|
||||
|
||||
@@ -379,6 +379,7 @@ function onSelect(e: Event, item: InputMenuItem) {
|
||||
function isInputItem(item: InputMenuItem): item is _InputMenuItem {
|
||||
return typeof item === 'object' && item !== null
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
inputRef
|
||||
})
|
||||
|
||||
@@ -7,7 +7,7 @@ import type { ComponentConfig } from '../types/utils'
|
||||
|
||||
type InputNumber = ComponentConfig<typeof theme, AppConfig, 'inputNumber'>
|
||||
|
||||
export interface InputNumberProps extends Pick<NumberFieldRootProps, 'modelValue' | 'defaultValue' | 'min' | 'max' | 'step' | 'stepSnapping' | 'disabled' | 'required' | 'id' | 'name' | 'formatOptions' | 'disableWheelChange'> {
|
||||
export interface InputNumberProps extends Pick<NumberFieldRootProps, 'modelValue' | 'defaultValue' | 'min' | 'max' | 'step' | 'stepSnapping' | 'disabled' | 'required' | 'id' | 'name' | 'formatOptions' | 'disableWheelChange' | 'invertWheelChange'> {
|
||||
/**
|
||||
* The element or component this component should render as.
|
||||
* @defaultValue 'div'
|
||||
@@ -98,7 +98,7 @@ defineSlots<InputNumberSlots>()
|
||||
const { t, code: codeLocale } = useLocale()
|
||||
const appConfig = useAppConfig() as InputNumber['AppConfig']
|
||||
|
||||
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'modelValue', 'defaultValue', 'min', 'max', 'step', 'stepSnapping', 'formatOptions', 'disableWheelChange'), emits)
|
||||
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'modelValue', 'defaultValue', 'min', 'max', 'step', 'stepSnapping', 'formatOptions', 'disableWheelChange', 'invertWheelChange'), emits)
|
||||
|
||||
const { emitFormBlur, emitFormFocus, emitFormChange, emitFormInput, id, color, size: formGroupSize, name, highlight, disabled, ariaAttrs } = useFormField<InputNumberProps>(props)
|
||||
const { orientation, size: buttonGroupSize } = useButtonGroup<InputNumberProps>(props)
|
||||
|
||||
203
src/runtime/components/InputTags.vue
Normal file
203
src/runtime/components/InputTags.vue
Normal file
@@ -0,0 +1,203 @@
|
||||
<script lang="ts">
|
||||
import type { AppConfig } from '@nuxt/schema'
|
||||
import type { TagsInputRootProps, TagsInputRootEmits, AcceptableInputValue } from 'reka-ui'
|
||||
import theme from '#build/ui/input-tags'
|
||||
import type { UseComponentIconsProps } from '../composables/useComponentIcons'
|
||||
import type { AvatarProps } from '../types'
|
||||
import type { ComponentConfig } from '../types/utils'
|
||||
|
||||
type InputTags = ComponentConfig<typeof theme, AppConfig, 'inputTags'>
|
||||
|
||||
export type InputTagItem = AcceptableInputValue
|
||||
|
||||
export interface InputTagsProps<T extends InputTagItem = InputTagItem> extends Pick<TagsInputRootProps<T>, 'modelValue' | 'defaultValue' | 'addOnPaste' | 'addOnTab' | 'addOnBlur' | 'duplicate' | 'disabled' | 'delimiter' | 'max' | 'id' | 'convertValue' | 'displayValue' | 'name' | 'required'>, UseComponentIconsProps {
|
||||
/**
|
||||
* The element or component this component should render as.
|
||||
* @defaultValue 'div'
|
||||
*/
|
||||
as?: any
|
||||
/** The placeholder text when the input is empty. */
|
||||
placeholder?: string
|
||||
/**
|
||||
* @defaultValue 'primary'
|
||||
*/
|
||||
color?: InputTags['variants']['color']
|
||||
/**
|
||||
* @defaultValue 'outline'
|
||||
*/
|
||||
variant?: InputTags['variants']['variant']
|
||||
/**
|
||||
* @defaultValue 'md'
|
||||
*/
|
||||
size?: InputTags['variants']['size']
|
||||
autofocus?: boolean
|
||||
autofocusDelay?: number
|
||||
/**
|
||||
* The icon displayed to delete a tag.
|
||||
* @defaultValue appConfig.ui.icons.close
|
||||
* @IconifyIcon
|
||||
*/
|
||||
deleteIcon?: string
|
||||
/** Highlight the ring color like a focus state. */
|
||||
highlight?: boolean
|
||||
class?: any
|
||||
ui?: InputTags['slots']
|
||||
}
|
||||
|
||||
export interface InputTagsEmits<T extends InputTagItem> extends TagsInputRootEmits<T> {
|
||||
change: [event: Event]
|
||||
blur: [event: FocusEvent]
|
||||
focus: [event: FocusEvent]
|
||||
}
|
||||
|
||||
type SlotProps<T extends InputTagItem> = (props: { item: T, index: number }) => any
|
||||
|
||||
export interface InputTagsSlots<T extends InputTagItem = InputTagItem> {
|
||||
'leading'(props?: {}): any
|
||||
'default'(props?: {}): any
|
||||
'trailing'(props?: {}): any
|
||||
'item-text': SlotProps<T>
|
||||
'item-delete': SlotProps<T>
|
||||
}
|
||||
</script>
|
||||
|
||||
<script setup lang="ts" generic="T extends InputTagItem">
|
||||
import { computed, ref, onMounted, toRaw } from 'vue'
|
||||
import { TagsInputRoot, TagsInputItem, TagsInputItemText, TagsInputItemDelete, TagsInputInput, useForwardPropsEmits } from 'reka-ui'
|
||||
import { reactivePick } from '@vueuse/core'
|
||||
import { useAppConfig } from '#imports'
|
||||
import { useButtonGroup } from '../composables/useButtonGroup'
|
||||
import { useComponentIcons } from '../composables/useComponentIcons'
|
||||
import { useFormField } from '../composables/useFormField'
|
||||
import { tv } from '../utils/tv'
|
||||
import UIcon from './Icon.vue'
|
||||
import UAvatar from './Avatar.vue'
|
||||
|
||||
defineOptions({ inheritAttrs: false })
|
||||
|
||||
const props = withDefaults(defineProps<InputTagsProps<T>>(), {
|
||||
type: 'text',
|
||||
autofocusDelay: 0
|
||||
})
|
||||
const emits = defineEmits<InputTagsEmits<T>>()
|
||||
const slots = defineSlots<InputTagsSlots<T>>()
|
||||
|
||||
const appConfig = useAppConfig() as InputTags['AppConfig']
|
||||
|
||||
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'addOnPaste', 'addOnTab', 'addOnBlur', 'duplicate', 'delimiter', 'max', 'convertValue', 'displayValue', 'required'), emits)
|
||||
|
||||
const { emitFormBlur, emitFormFocus, emitFormChange, emitFormInput, size: formGroupSize, color, id, name, highlight, disabled, ariaAttrs } = useFormField<InputTagsProps>(props)
|
||||
const { orientation, size: buttonGroupSize } = useButtonGroup<InputTagsProps>(props)
|
||||
const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponentIcons(props)
|
||||
|
||||
const inputSize = computed(() => buttonGroupSize.value || formGroupSize.value)
|
||||
|
||||
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.inputTags || {}) })({
|
||||
color: color.value,
|
||||
variant: props.variant,
|
||||
size: inputSize?.value,
|
||||
loading: props.loading,
|
||||
highlight: highlight.value,
|
||||
leading: isLeading.value || !!props.avatar || !!slots.leading,
|
||||
trailing: isTrailing.value || !!slots.trailing,
|
||||
buttonGroup: orientation.value
|
||||
}))
|
||||
|
||||
const inputRef = ref<InstanceType<typeof TagsInputInput> | null>(null)
|
||||
|
||||
onMounted(() => {
|
||||
setTimeout(() => {
|
||||
autoFocus()
|
||||
}, props.autofocusDelay)
|
||||
})
|
||||
|
||||
function autoFocus() {
|
||||
if (props.autofocus) {
|
||||
inputRef.value?.$el?.focus()
|
||||
}
|
||||
}
|
||||
|
||||
function onUpdate(value: T[]) {
|
||||
if (toRaw(props.modelValue) === value) {
|
||||
return
|
||||
}
|
||||
// @ts-expect-error - 'target' does not exist in type 'EventInit'
|
||||
const event = new Event('change', { target: { value } })
|
||||
emits('change', event)
|
||||
emitFormChange()
|
||||
emitFormInput()
|
||||
}
|
||||
|
||||
function onBlur(event: FocusEvent) {
|
||||
emits('blur', event)
|
||||
emitFormBlur()
|
||||
}
|
||||
|
||||
function onFocus(event: FocusEvent) {
|
||||
emits('focus', event)
|
||||
emitFormFocus()
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
inputRef
|
||||
})
|
||||
</script>
|
||||
|
||||
<!-- eslint-disable vue/no-template-shadow -->
|
||||
<template>
|
||||
<TagsInputRoot
|
||||
:id="id"
|
||||
v-slot="{ modelValue: tags }"
|
||||
:model-value="modelValue"
|
||||
:default-value="defaultValue"
|
||||
:class="ui.root({ class: [ui.base({ class: props.ui?.base }), props.ui?.root, props.class] })"
|
||||
v-bind="rootProps"
|
||||
:name="name"
|
||||
:disabled="disabled"
|
||||
@update:model-value="onUpdate"
|
||||
@blur="onBlur"
|
||||
@focus="onFocus"
|
||||
>
|
||||
<TagsInputItem
|
||||
v-for="(item, index) in tags"
|
||||
:key="index"
|
||||
:value="item"
|
||||
:class="ui.item({ class: [props.ui?.item] })"
|
||||
>
|
||||
<TagsInputItemText :class="ui.itemText({ class: [props.ui?.itemText] })">
|
||||
<slot v-if="!!slots['item-text']" name="item-text" :item="(item as T)" :index="index" />
|
||||
</TagsInputItemText>
|
||||
|
||||
<TagsInputItemDelete
|
||||
:class="ui.itemDelete({ class: [props.ui?.itemDelete] })"
|
||||
:disabled="disabled"
|
||||
>
|
||||
<slot name="item-delete" :item="(item as T)" :index="index">
|
||||
<UIcon :name="deleteIcon || appConfig.ui.icons.close" :class="ui.itemDeleteIcon({ class: [props.ui?.itemDeleteIcon] })" />
|
||||
</slot>
|
||||
</TagsInputItemDelete>
|
||||
</TagsInputItem>
|
||||
|
||||
<TagsInputInput
|
||||
ref="inputRef"
|
||||
v-bind="{ ...$attrs, ...ariaAttrs }"
|
||||
:placeholder="placeholder"
|
||||
:class="ui.input({ class: props.ui?.input })"
|
||||
/>
|
||||
|
||||
<slot />
|
||||
|
||||
<span v-if="isLeading || !!avatar || !!slots.leading" :class="ui.leading({ class: props.ui?.leading })">
|
||||
<slot name="leading">
|
||||
<UIcon v-if="isLeading && leadingIconName" :name="leadingIconName" :class="ui.leadingIcon({ class: props.ui?.leadingIcon })" />
|
||||
<UAvatar v-else-if="!!avatar" :size="((props.ui?.leadingAvatarSize || ui.leadingAvatarSize()) as AvatarProps['size'])" v-bind="avatar" :class="ui.leadingAvatar({ class: props.ui?.leadingAvatar })" />
|
||||
</slot>
|
||||
</span>
|
||||
|
||||
<span v-if="isTrailing || !!slots.trailing" :class="ui.trailing({ class: props.ui?.trailing })">
|
||||
<slot name="trailing">
|
||||
<UIcon v-if="trailingIconName" :name="trailingIconName" :class="ui.trailingIcon({ class: props.ui?.trailingIcon })" />
|
||||
</slot>
|
||||
</span>
|
||||
</TagsInputRoot>
|
||||
</template>
|
||||
@@ -61,13 +61,14 @@ export interface ModalEmits extends DialogRootEmits {
|
||||
|
||||
export interface ModalSlots {
|
||||
default(props: { open: boolean }): any
|
||||
content(props?: {}): any
|
||||
header(props?: {}): any
|
||||
content(props: { close: () => void }): any
|
||||
header(props: { close: () => void }): any
|
||||
title(props?: {}): any
|
||||
description(props?: {}): any
|
||||
close(props: { ui: { [K in keyof Required<Modal['slots']>]: (props?: Record<string, any>) => string } }): any
|
||||
body(props?: {}): any
|
||||
footer(props?: {}): any
|
||||
actions(props?: {}): any
|
||||
close(props: { close: () => void, ui: { [K in keyof Required<Modal['slots']>]: (props?: Record<string, any>) => string } }): any
|
||||
body(props: { close: () => void }): any
|
||||
footer(props: { close: () => void }): any
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -104,15 +105,15 @@ const contentEvents = computed(() => {
|
||||
}
|
||||
|
||||
if (!props.dismissible) {
|
||||
const events = ['pointerDownOutside', 'interactOutside', 'escapeKeyDown', 'closeAutoFocus'] as const
|
||||
type EventType = typeof events[number]
|
||||
const events = ['pointerDownOutside', 'interactOutside', 'escapeKeyDown']
|
||||
|
||||
return events.reduce((acc, curr) => {
|
||||
acc[curr] = (e: Event) => {
|
||||
e.preventDefault()
|
||||
emits('close:prevent')
|
||||
}
|
||||
return acc
|
||||
}, {} as Record<EventType, (e: Event) => void>)
|
||||
}, defaultEvents as Record<typeof events[number] | keyof typeof defaultEvents, (e: Event) => void>)
|
||||
}
|
||||
|
||||
return defaultEvents
|
||||
@@ -124,8 +125,9 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.modal || {})
|
||||
}))
|
||||
</script>
|
||||
|
||||
<!-- eslint-disable vue/no-template-shadow -->
|
||||
<template>
|
||||
<DialogRoot v-slot="{ open }" v-bind="rootProps">
|
||||
<DialogRoot v-slot="{ open, close }" v-bind="rootProps">
|
||||
<DialogTrigger v-if="!!slots.default" as-child :class="props.class">
|
||||
<slot :open="open" />
|
||||
</DialogTrigger>
|
||||
@@ -148,9 +150,9 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.modal || {})
|
||||
</DialogDescription>
|
||||
</VisuallyHidden>
|
||||
|
||||
<slot name="content">
|
||||
<div v-if="!!slots.header || (title || !!slots.title) || (description || !!slots.description) || (close || !!slots.close)" :class="ui.header({ class: props.ui?.header })">
|
||||
<slot name="header">
|
||||
<slot name="content" :close="close">
|
||||
<div v-if="!!slots.header || (title || !!slots.title) || (description || !!slots.description) || (props.close || !!slots.close)" :class="ui.header({ class: props.ui?.header })">
|
||||
<slot name="header" :close="close">
|
||||
<div :class="ui.wrapper({ class: props.ui?.wrapper })">
|
||||
<DialogTitle v-if="title || !!slots.title" :class="ui.title({ class: props.ui?.title })">
|
||||
<slot name="title">
|
||||
@@ -165,16 +167,17 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.modal || {})
|
||||
</DialogDescription>
|
||||
</div>
|
||||
|
||||
<DialogClose v-if="close || !!slots.close" as-child>
|
||||
<slot name="close" :ui="ui">
|
||||
<slot name="actions" />
|
||||
|
||||
<DialogClose v-if="props.close || !!slots.close" as-child>
|
||||
<slot name="close" :close="close" :ui="ui">
|
||||
<UButton
|
||||
v-if="close"
|
||||
v-if="props.close"
|
||||
:icon="closeIcon || appConfig.ui.icons.close"
|
||||
size="md"
|
||||
color="neutral"
|
||||
variant="ghost"
|
||||
:aria-label="t('modal.close')"
|
||||
v-bind="(typeof close === 'object' ? close as Partial<ButtonProps> : {})"
|
||||
v-bind="(typeof props.close === 'object' ? props.close as Partial<ButtonProps> : {})"
|
||||
:class="ui.close({ class: props.ui?.close })"
|
||||
/>
|
||||
</slot>
|
||||
@@ -183,11 +186,11 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.modal || {})
|
||||
</div>
|
||||
|
||||
<div v-if="!!slots.body" :class="ui.body({ class: props.ui?.body })">
|
||||
<slot name="body" />
|
||||
<slot name="body" :close="close" />
|
||||
</div>
|
||||
|
||||
<div v-if="!!slots.footer" :class="ui.footer({ class: props.ui?.footer })">
|
||||
<slot name="footer" />
|
||||
<slot name="footer" :close="close" />
|
||||
</div>
|
||||
</slot>
|
||||
</DialogContent>
|
||||
|
||||
@@ -246,20 +246,13 @@ const lists = computed<NavigationMenuItem[][]>(() =>
|
||||
: []
|
||||
)
|
||||
|
||||
function getAccordionDefaultValue(list: NavigationMenuItem[]) {
|
||||
function findItemsWithDefaultOpen(items: NavigationMenuItem[], level = 0): string[] {
|
||||
return items.reduce((acc: string[], item, index) => {
|
||||
if (item.defaultOpen || item.open) {
|
||||
acc.push(item.value || (level > 0 ? `item-${level}-${index}` : `item-${index}`))
|
||||
}
|
||||
if (item.children?.length) {
|
||||
acc.push(...findItemsWithDefaultOpen(item.children, level + 1))
|
||||
}
|
||||
return acc
|
||||
}, [])
|
||||
}
|
||||
|
||||
const indexes = findItemsWithDefaultOpen(list)
|
||||
function getAccordionDefaultValue(list: NavigationMenuItem[], level = 0) {
|
||||
const indexes = list.reduce((acc: string[], item, index) => {
|
||||
if (item.defaultOpen || item.open) {
|
||||
acc.push(item.value || (level > 0 ? `item-${level}-${index}` : `item-${index}`))
|
||||
}
|
||||
return acc
|
||||
}, [])
|
||||
|
||||
return props.type === 'single' ? indexes[0] : indexes
|
||||
}
|
||||
@@ -393,7 +386,14 @@ function getAccordionDefaultValue(list: NavigationMenuItem[]) {
|
||||
</ULink>
|
||||
|
||||
<AccordionContent v-if="orientation === 'vertical' && item.children?.length && !collapsed" :class="ui.content({ class: [props.ui?.content, item.ui?.content] })">
|
||||
<ul :class="ui.childList({ class: props.ui?.childList })">
|
||||
<AccordionRoot
|
||||
v-bind="({
|
||||
...accordionProps,
|
||||
defaultValue: getAccordionDefaultValue(item.children, level + 1)
|
||||
} as AccordionRootProps)"
|
||||
as="ul"
|
||||
:class="ui.childList({ class: props.ui?.childList })"
|
||||
>
|
||||
<ReuseItemTemplate
|
||||
v-for="(childItem, childIndex) in item.children"
|
||||
:key="childIndex"
|
||||
@@ -402,7 +402,7 @@ function getAccordionDefaultValue(list: NavigationMenuItem[]) {
|
||||
:level="level + 1"
|
||||
:class="ui.childItem({ class: [props.ui?.childItem, childItem.ui?.childItem] })"
|
||||
/>
|
||||
</ul>
|
||||
</AccordionRoot>
|
||||
</AccordionContent>
|
||||
</component>
|
||||
</DefineItemTemplate>
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
import { computed } from 'vue'
|
||||
import { useOverlay, type Overlay } from '../composables/useOverlay'
|
||||
|
||||
const { overlays, unMount, close } = useOverlay()
|
||||
const { overlays, unmount, close } = useOverlay()
|
||||
|
||||
const mountedOverlays = computed(() => overlays.filter((overlay: Overlay) => overlay.isMounted))
|
||||
|
||||
const onAfterLeave = (id: symbol) => {
|
||||
close(id)
|
||||
unMount(id)
|
||||
unmount(id)
|
||||
}
|
||||
|
||||
const onClose = (id: symbol, value: any) => {
|
||||
|
||||
@@ -111,7 +111,6 @@ import { tv } from '../utils/tv'
|
||||
import UButton from './Button.vue'
|
||||
|
||||
const props = withDefaults(defineProps<PaginationProps>(), {
|
||||
size: 'md',
|
||||
color: 'neutral',
|
||||
variant: 'outline',
|
||||
activeColor: 'primary',
|
||||
|
||||
@@ -7,7 +7,9 @@ import type { ComponentConfig } from '../types/utils'
|
||||
|
||||
type PinInput = ComponentConfig<typeof theme, AppConfig, 'pinInput'>
|
||||
|
||||
export interface PinInputProps extends Pick<PinInputRootProps, 'defaultValue' | 'disabled' | 'id' | 'mask' | 'modelValue' | 'name' | 'otp' | 'placeholder' | 'required' | 'type'> {
|
||||
type PinInputType = 'text' | 'number'
|
||||
|
||||
export interface PinInputProps<T extends PinInputType = 'text'> extends Pick<PinInputRootProps<T>, 'defaultValue' | 'disabled' | 'id' | 'mask' | 'modelValue' | 'name' | 'otp' | 'placeholder' | 'required' | 'type'> {
|
||||
/**
|
||||
* The element or component this component should render as.
|
||||
* @defaultValue 'div'
|
||||
@@ -37,14 +39,14 @@ export interface PinInputProps extends Pick<PinInputRootProps, 'defaultValue' |
|
||||
ui?: PinInput['slots']
|
||||
}
|
||||
|
||||
export type PinInputEmits = PinInputRootEmits & {
|
||||
export type PinInputEmits<T extends PinInputType = 'text'> = PinInputRootEmits<T> & {
|
||||
change: [payload: Event]
|
||||
blur: [payload: Event]
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
<script setup lang="ts" generic="T extends PinInputType = 'text'">
|
||||
import type { ComponentPublicInstance } from 'vue'
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { PinInputInput, PinInputRoot, useForwardPropsEmits } from 'reka-ui'
|
||||
@@ -54,16 +56,16 @@ import { useFormField } from '../composables/useFormField'
|
||||
import { looseToNumber } from '../utils'
|
||||
import { tv } from '../utils/tv'
|
||||
|
||||
const props = withDefaults(defineProps<PinInputProps>(), {
|
||||
type: 'text',
|
||||
const props = withDefaults(defineProps<PinInputProps<T>>(), {
|
||||
type: 'text' as never,
|
||||
length: 5,
|
||||
autofocusDelay: 0
|
||||
})
|
||||
const emits = defineEmits<PinInputEmits>()
|
||||
const emits = defineEmits<PinInputEmits<T>>()
|
||||
|
||||
const appConfig = useAppConfig() as PinInput['AppConfig']
|
||||
|
||||
const rootProps = useForwardPropsEmits(reactivePick(props, 'defaultValue', 'disabled', 'id', 'mask', 'modelValue', 'name', 'otp', 'placeholder', 'required', 'type'), emits)
|
||||
const rootProps = useForwardPropsEmits(reactivePick(props, 'defaultValue', 'disabled', 'id', 'mask', 'modelValue', 'name', 'otp', 'required', 'type'), emits)
|
||||
|
||||
const { emitFormInput, emitFormFocus, emitFormChange, emitFormBlur, size, color, id, name, highlight, disabled, ariaAttrs } = useFormField<PinInputProps>(props)
|
||||
|
||||
@@ -77,7 +79,7 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.pinInput ||
|
||||
const inputsRef = ref<ComponentPublicInstance[]>([])
|
||||
|
||||
const completed = ref(false)
|
||||
function onComplete(value: string[]) {
|
||||
function onComplete(value: string[] | number[]) {
|
||||
// @ts-expect-error - 'target' does not exist in type 'EventInit'
|
||||
const event = new Event('change', { target: { value } })
|
||||
emits('change', event)
|
||||
@@ -113,6 +115,7 @@ defineExpose({
|
||||
v-bind="{ ...rootProps, ...ariaAttrs }"
|
||||
:id="id"
|
||||
:name="name"
|
||||
:placeholder="placeholder"
|
||||
:class="ui.root({ class: [props.ui?.root, props.class] })"
|
||||
@update:model-value="emitFormInput()"
|
||||
@complete="onComplete"
|
||||
|
||||
@@ -75,15 +75,15 @@ const portalProps = usePortal(toRef(() => props.portal))
|
||||
const contentProps = toRef(() => defu(props.content, { side: 'bottom', sideOffset: 8, collisionPadding: 8 }) as PopoverContentProps)
|
||||
const contentEvents = computed(() => {
|
||||
if (!props.dismissible) {
|
||||
const events = ['pointerDownOutside', 'interactOutside', 'escapeKeyDown'] as const
|
||||
type EventType = typeof events[number]
|
||||
const events = ['pointerDownOutside', 'interactOutside', 'escapeKeyDown']
|
||||
|
||||
return events.reduce((acc, curr) => {
|
||||
acc[curr] = (e: Event) => {
|
||||
e.preventDefault()
|
||||
emits('close:prevent')
|
||||
}
|
||||
return acc
|
||||
}, {} as Record<EventType, (e: Event) => void>)
|
||||
}, {} as Record<typeof events[number], (e: Event) => void>)
|
||||
}
|
||||
|
||||
return {}
|
||||
|
||||
@@ -7,7 +7,7 @@ import type { ComponentConfig } from '../types/utils'
|
||||
|
||||
type Progress = ComponentConfig<typeof theme, AppConfig, 'progress'>
|
||||
|
||||
export interface ProgressProps extends Pick<ProgressRootProps, 'getValueLabel' | 'modelValue'> {
|
||||
export interface ProgressProps extends Pick<ProgressRootProps, 'getValueLabel' | 'getValueText' | 'modelValue'> {
|
||||
/**
|
||||
* The element or component this component should render as.
|
||||
* @defaultValue 'div'
|
||||
@@ -70,7 +70,7 @@ const slots = defineSlots<ProgressSlots>()
|
||||
const { dir } = useLocale()
|
||||
const appConfig = useAppConfig() as Progress['AppConfig']
|
||||
|
||||
const rootProps = useForwardPropsEmits(reactivePick(props, 'getValueLabel', 'modelValue'), emits)
|
||||
const rootProps = useForwardPropsEmits(reactivePick(props, 'getValueLabel', 'getValueText', 'modelValue'), emits)
|
||||
|
||||
const isIndeterminate = computed(() => rootProps.value.modelValue === null)
|
||||
const hasSteps = computed(() => Array.isArray(props.max))
|
||||
|
||||
@@ -92,6 +92,8 @@ export interface SelectProps<T extends ArrayOrNested<SelectItem> = ArrayOrNested
|
||||
multiple?: M & boolean
|
||||
/** Highlight the ring color like a focus state. */
|
||||
highlight?: boolean
|
||||
autofocus?: boolean
|
||||
autofocusDelay?: number
|
||||
class?: any
|
||||
ui?: Select['slots']
|
||||
}
|
||||
@@ -134,7 +136,7 @@ export interface SelectSlots<
|
||||
</script>
|
||||
|
||||
<script setup lang="ts" generic="T extends ArrayOrNested<SelectItem>, VK extends GetItemKeys<T> = 'value', M extends boolean = false">
|
||||
import { computed, toRef } from 'vue'
|
||||
import { ref, computed, onMounted, toRef } from 'vue'
|
||||
import { SelectRoot, SelectArrow, SelectTrigger, SelectPortal, SelectContent, SelectLabel, SelectGroup, SelectItem, SelectItemIndicator, SelectItemText, SelectSeparator, useForwardPropsEmits } from 'reka-ui'
|
||||
import { defu } from 'defu'
|
||||
import { reactivePick } from '@vueuse/core'
|
||||
@@ -154,7 +156,8 @@ defineOptions({ inheritAttrs: false })
|
||||
const props = withDefaults(defineProps<SelectProps<T, VK, M>>(), {
|
||||
valueKey: 'value' as never,
|
||||
labelKey: 'label' as never,
|
||||
portal: true
|
||||
portal: true,
|
||||
autofocusDelay: 0
|
||||
})
|
||||
const emits = defineEmits<SelectEmits<T, VK, M>>()
|
||||
const slots = defineSlots<SelectSlots<T, VK, M>>()
|
||||
@@ -193,15 +196,32 @@ const groups = computed<SelectItem[][]>(() =>
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
const items = computed(() => groups.value.flatMap(group => group) as T[])
|
||||
|
||||
function displayValue(value?: GetItemValue<T, VK> | GetItemValue<T, VK>[]): string {
|
||||
function displayValue(value: GetItemValue<T, VK> | GetItemValue<T, VK>[]): string | undefined {
|
||||
if (props.multiple && Array.isArray(value)) {
|
||||
return value.map(v => displayValue(v)).filter(Boolean).join(', ')
|
||||
const values = value.map(v => displayValue(v)).filter(Boolean)
|
||||
return values?.length ? values.join(', ') : undefined
|
||||
}
|
||||
|
||||
const item = items.value.find(item => compare(typeof item === 'object' ? get(item as Record<string, any>, props.valueKey as string) : item, value))
|
||||
return item && (typeof item === 'object' ? get(item, props.labelKey as string) : item)
|
||||
}
|
||||
|
||||
const triggerRef = ref<InstanceType<typeof SelectTrigger> | null>(null)
|
||||
|
||||
function autoFocus() {
|
||||
if (props.autofocus) {
|
||||
triggerRef.value?.$el?.focus({
|
||||
focusVisible: true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
setTimeout(() => {
|
||||
autoFocus()
|
||||
}, props.autofocusDelay)
|
||||
})
|
||||
|
||||
function onUpdate(value: any) {
|
||||
// @ts-expect-error - 'target' does not exist in type 'EventInit'
|
||||
const event = new Event('change', { target: { value } })
|
||||
@@ -225,6 +245,10 @@ function onUpdateOpen(value: boolean) {
|
||||
function isSelectItem(item: SelectItem): item is SelectItemBase {
|
||||
return typeof item === 'object' && item !== null
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
triggerRef
|
||||
})
|
||||
</script>
|
||||
|
||||
<!-- eslint-disable vue/no-template-shadow -->
|
||||
@@ -240,7 +264,7 @@ function isSelectItem(item: SelectItem): item is SelectItemBase {
|
||||
@update:model-value="onUpdate"
|
||||
@update:open="onUpdateOpen"
|
||||
>
|
||||
<SelectTrigger :id="id" :class="ui.base({ class: [props.ui?.base, props.class] })" v-bind="{ ...$attrs, ...ariaAttrs }">
|
||||
<SelectTrigger :id="id" ref="triggerRef" :class="ui.base({ class: [props.ui?.base, props.class] })" v-bind="{ ...$attrs, ...ariaAttrs }">
|
||||
<span v-if="isLeading || !!avatar || !!slots.leading" :class="ui.leading({ class: props.ui?.leading })">
|
||||
<slot name="leading" :model-value="(modelValue as GetModelValue<T, VK, M>)" :open="open" :ui="ui">
|
||||
<UIcon v-if="isLeading && leadingIconName" :name="leadingIconName" :class="ui.leadingIcon({ class: props.ui?.leadingIcon })" />
|
||||
@@ -250,7 +274,7 @@ function isSelectItem(item: SelectItem): item is SelectItemBase {
|
||||
|
||||
<slot :model-value="(modelValue as GetModelValue<T, VK, M>)" :open="open">
|
||||
<template v-for="displayedModelValue in [displayValue(modelValue as GetModelValue<T, VK, M>)]" :key="displayedModelValue">
|
||||
<span v-if="displayedModelValue" :class="ui.value({ class: props.ui?.value })">
|
||||
<span v-if="displayedModelValue !== undefined && displayedModelValue !== null" :class="ui.value({ class: props.ui?.value })">
|
||||
{{ displayedModelValue }}
|
||||
</span>
|
||||
<span v-else :class="ui.placeholder({ class: props.ui?.placeholder })">
|
||||
|
||||
@@ -115,6 +115,8 @@ export interface SelectMenuProps<T extends ArrayOrNested<SelectMenuItem> = Array
|
||||
* @defaultValue false
|
||||
*/
|
||||
ignoreFilter?: boolean
|
||||
autofocus?: boolean
|
||||
autofocusDelay?: number
|
||||
class?: any
|
||||
ui?: SelectMenu['slots']
|
||||
}
|
||||
@@ -165,7 +167,7 @@ export interface SelectMenuSlots<
|
||||
</script>
|
||||
|
||||
<script setup lang="ts" generic="T extends ArrayOrNested<SelectMenuItem>, VK extends GetItemKeys<T> | undefined = undefined, M extends boolean = false">
|
||||
import { computed, toRef, toRaw } from 'vue'
|
||||
import { ref, computed, onMounted, toRef, toRaw } from 'vue'
|
||||
import { ComboboxRoot, ComboboxArrow, ComboboxAnchor, ComboboxInput, ComboboxTrigger, ComboboxPortal, ComboboxContent, ComboboxEmpty, ComboboxGroup, ComboboxLabel, ComboboxSeparator, ComboboxItem, ComboboxItemIndicator, FocusScope, useForwardPropsEmits, useFilter } from 'reka-ui'
|
||||
import { defu } from 'defu'
|
||||
import { reactivePick, createReusableTemplate } from '@vueuse/core'
|
||||
@@ -189,7 +191,8 @@ const props = withDefaults(defineProps<SelectMenuProps<T, VK, M>>(), {
|
||||
searchInput: true,
|
||||
labelKey: 'label' as never,
|
||||
resetSearchTermOnBlur: true,
|
||||
resetSearchTermOnSelect: true
|
||||
resetSearchTermOnSelect: true,
|
||||
autofocusDelay: 0
|
||||
})
|
||||
const emits = defineEmits<SelectMenuEmits<T, VK, M>>()
|
||||
const slots = defineSlots<SelectMenuSlots<T, VK, M>>()
|
||||
@@ -225,9 +228,10 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.selectMenu |
|
||||
buttonGroup: orientation.value
|
||||
}))
|
||||
|
||||
function displayValue(value: GetItemValue<T, VK> | GetItemValue<T, VK>[]): string {
|
||||
function displayValue(value: GetItemValue<T, VK> | GetItemValue<T, VK>[]): string | undefined {
|
||||
if (props.multiple && Array.isArray(value)) {
|
||||
return value.map(v => displayValue(v)).filter(Boolean).join(', ')
|
||||
const values = value.map(v => displayValue(v)).filter(Boolean)
|
||||
return values?.length ? values.join(', ') : undefined
|
||||
}
|
||||
|
||||
if (!props.valueKey) {
|
||||
@@ -286,6 +290,22 @@ const createItem = computed(() => {
|
||||
})
|
||||
const createItemPosition = computed(() => typeof props.createItem === 'object' ? props.createItem.position : 'bottom')
|
||||
|
||||
const triggerRef = ref<InstanceType<typeof ComboboxTrigger> | null>(null)
|
||||
|
||||
function autoFocus() {
|
||||
if (props.autofocus) {
|
||||
triggerRef.value?.$el?.focus({
|
||||
focusVisible: true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
setTimeout(() => {
|
||||
autoFocus()
|
||||
}, props.autofocusDelay)
|
||||
})
|
||||
|
||||
function onUpdate(value: any) {
|
||||
if (toRaw(props.modelValue) === value) {
|
||||
return
|
||||
@@ -343,6 +363,10 @@ function onSelect(e: Event, item: SelectMenuItem) {
|
||||
function isSelectItem(item: SelectMenuItem): item is _SelectMenuItem {
|
||||
return typeof item === 'object' && item !== null
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
triggerRef
|
||||
})
|
||||
</script>
|
||||
|
||||
<!-- eslint-disable vue/no-template-shadow -->
|
||||
@@ -375,7 +399,7 @@ function isSelectItem(item: SelectMenuItem): item is _SelectMenuItem {
|
||||
@update:open="onUpdateOpen"
|
||||
>
|
||||
<ComboboxAnchor as-child>
|
||||
<ComboboxTrigger :class="ui.base({ class: [props.ui?.base, props.class] })" tabindex="0">
|
||||
<ComboboxTrigger ref="triggerRef" :class="ui.base({ class: [props.ui?.base, props.class] })" tabindex="0">
|
||||
<span v-if="isLeading || !!avatar || !!slots.leading" :class="ui.leading({ class: props.ui?.leading })">
|
||||
<slot name="leading" :model-value="(modelValue as GetModelValue<T, VK, M>)" :open="open" :ui="ui">
|
||||
<UIcon v-if="isLeading && leadingIconName" :name="leadingIconName" :class="ui.leadingIcon({ class: props.ui?.leadingIcon })" />
|
||||
@@ -385,7 +409,7 @@ function isSelectItem(item: SelectMenuItem): item is _SelectMenuItem {
|
||||
|
||||
<slot :model-value="(modelValue as GetModelValue<T, VK, M>)" :open="open">
|
||||
<template v-for="displayedModelValue in [displayValue(modelValue as GetModelValue<T, VK, M>)]" :key="displayedModelValue">
|
||||
<span v-if="displayedModelValue" :class="ui.value({ class: props.ui?.value })">
|
||||
<span v-if="displayedModelValue !== undefined && displayedModelValue !== null" :class="ui.value({ class: props.ui?.value })">
|
||||
{{ displayedModelValue }}
|
||||
</span>
|
||||
<span v-else :class="ui.placeholder({ class: props.ui?.placeholder })">
|
||||
|
||||
@@ -61,13 +61,14 @@ export interface SlideoverEmits extends DialogRootEmits {
|
||||
|
||||
export interface SlideoverSlots {
|
||||
default(props: { open: boolean }): any
|
||||
content(props?: {}): any
|
||||
header(props?: {}): any
|
||||
content(props: { close: () => void }): any
|
||||
header(props: { close: () => void }): any
|
||||
title(props?: {}): any
|
||||
description(props?: {}): any
|
||||
close(props: { ui: { [K in keyof Required<Slideover['slots']>]: (props?: Record<string, any>) => string } }): any
|
||||
body(props?: {}): any
|
||||
footer(props?: {}): any
|
||||
actions(props?: {}): any
|
||||
close(props: { close: () => void, ui: { [K in keyof Required<Slideover['slots']>]: (props?: Record<string, any>) => string } }): any
|
||||
body(props: { close: () => void }): any
|
||||
footer(props: { close: () => void }): any
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -103,16 +104,17 @@ const contentEvents = computed(() => {
|
||||
const defaultEvents = {
|
||||
closeAutoFocus: (e: Event) => e.preventDefault()
|
||||
}
|
||||
|
||||
if (!props.dismissible) {
|
||||
const events = ['pointerDownOutside', 'interactOutside', 'escapeKeyDown', 'closeAutoFocus'] as const
|
||||
type EventType = typeof events[number]
|
||||
const events = ['pointerDownOutside', 'interactOutside', 'escapeKeyDown']
|
||||
|
||||
return events.reduce((acc, curr) => {
|
||||
acc[curr] = (e: Event) => {
|
||||
e.preventDefault()
|
||||
emits('close:prevent')
|
||||
}
|
||||
return acc
|
||||
}, {} as Record<EventType, (e: Event) => void>)
|
||||
}, defaultEvents as Record<typeof events[number] | keyof typeof defaultEvents, (e: Event) => void>)
|
||||
}
|
||||
|
||||
return defaultEvents
|
||||
@@ -124,8 +126,9 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.slideover ||
|
||||
}))
|
||||
</script>
|
||||
|
||||
<!-- eslint-disable vue/no-template-shadow -->
|
||||
<template>
|
||||
<DialogRoot v-slot="{ open }" v-bind="rootProps">
|
||||
<DialogRoot v-slot="{ open, close }" v-bind="rootProps">
|
||||
<DialogTrigger v-if="!!slots.default" as-child :class="props.class">
|
||||
<slot :open="open" />
|
||||
</DialogTrigger>
|
||||
@@ -155,9 +158,9 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.slideover ||
|
||||
</DialogDescription>
|
||||
</VisuallyHidden>
|
||||
|
||||
<slot name="content">
|
||||
<div v-if="!!slots.header || (title || !!slots.title) || (description || !!slots.description) || (close || !!slots.close)" :class="ui.header({ class: props.ui?.header })">
|
||||
<slot name="header">
|
||||
<slot name="content" :close="close">
|
||||
<div v-if="!!slots.header || (title || !!slots.title) || (description || !!slots.description) || (props.close || !!slots.close)" :class="ui.header({ class: props.ui?.header })">
|
||||
<slot name="header" :close="close">
|
||||
<div :class="ui.wrapper({ class: props.ui?.wrapper })">
|
||||
<DialogTitle v-if="title || !!slots.title" :class="ui.title({ class: props.ui?.title })">
|
||||
<slot name="title">
|
||||
@@ -172,16 +175,17 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.slideover ||
|
||||
</DialogDescription>
|
||||
</div>
|
||||
|
||||
<DialogClose v-if="close || !!slots.close" as-child>
|
||||
<slot name="close" :ui="ui">
|
||||
<slot name="actions" />
|
||||
|
||||
<DialogClose v-if="props.close || !!slots.close" as-child>
|
||||
<slot name="close" :close="close" :ui="ui">
|
||||
<UButton
|
||||
v-if="close"
|
||||
v-if="props.close"
|
||||
:icon="closeIcon || appConfig.ui.icons.close"
|
||||
size="md"
|
||||
color="neutral"
|
||||
variant="ghost"
|
||||
:aria-label="t('slideover.close')"
|
||||
v-bind="(typeof close === 'object' ? close as Partial<ButtonProps> : {})"
|
||||
v-bind="(typeof props.close === 'object' ? props.close as Partial<ButtonProps> : {})"
|
||||
:class="ui.close({ class: props.ui?.close })"
|
||||
/>
|
||||
</slot>
|
||||
@@ -190,11 +194,11 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.slideover ||
|
||||
</div>
|
||||
|
||||
<div :class="ui.body({ class: props.ui?.body })">
|
||||
<slot name="body" />
|
||||
<slot name="body" :close="close" />
|
||||
</div>
|
||||
|
||||
<div v-if="!!slots.footer" :class="ui.footer({ class: props.ui?.footer })">
|
||||
<slot name="footer" />
|
||||
<slot name="footer" :close="close" />
|
||||
</div>
|
||||
</slot>
|
||||
</DialogContent>
|
||||
|
||||
@@ -61,13 +61,13 @@ export type TableRow<T> = Row<T>
|
||||
export type TableData = RowData
|
||||
export type TableColumn<T extends TableData, D = unknown> = ColumnDef<T, D>
|
||||
|
||||
export interface TableOptions<T extends TableData> extends Omit<CoreOptions<T>, 'data' | 'columns' | 'getCoreRowModel' | 'state' | 'onStateChange' | 'renderFallbackValue'> {
|
||||
export interface TableOptions<T extends TableData = TableData> extends Omit<CoreOptions<T>, 'data' | 'columns' | 'getCoreRowModel' | 'state' | 'onStateChange' | 'renderFallbackValue'> {
|
||||
state?: CoreOptions<T>['state']
|
||||
onStateChange?: CoreOptions<T>['onStateChange']
|
||||
renderFallbackValue?: CoreOptions<T>['renderFallbackValue']
|
||||
}
|
||||
|
||||
export interface TableProps<T extends TableData> extends TableOptions<T> {
|
||||
export interface TableProps<T extends TableData = TableData> extends TableOptions<T> {
|
||||
/**
|
||||
* The element or component this component should render as.
|
||||
* @defaultValue 'div'
|
||||
@@ -172,11 +172,13 @@ export interface TableProps<T extends TableData> extends TableOptions<T> {
|
||||
type DynamicHeaderSlots<T, K = keyof T> = Record<string, (props: HeaderContext<T, unknown>) => any> & Record<`${K extends string ? K : never}-header`, (props: HeaderContext<T, unknown>) => any>
|
||||
type DynamicCellSlots<T, K = keyof T> = Record<string, (props: CellContext<T, unknown>) => any> & Record<`${K extends string ? K : never}-cell`, (props: CellContext<T, unknown>) => any>
|
||||
|
||||
export type TableSlots<T> = {
|
||||
expanded: (props: { row: Row<T> }) => any
|
||||
empty: (props?: {}) => any
|
||||
loading: (props?: {}) => any
|
||||
caption: (props?: {}) => any
|
||||
export type TableSlots<T extends TableData = TableData> = {
|
||||
'expanded': (props: { row: Row<T> }) => any
|
||||
'empty': (props?: {}) => any
|
||||
'loading': (props?: {}) => any
|
||||
'caption': (props?: {}) => any
|
||||
'body-top': (props?: {}) => any
|
||||
'body-bottom': (props?: {}) => any
|
||||
} & DynamicHeaderSlots<T> & DynamicCellSlots<T>
|
||||
|
||||
</script>
|
||||
@@ -226,7 +228,7 @@ const groupingState = defineModel<GroupingState>('grouping', { default: [] })
|
||||
const expandedState = defineModel<ExpandedState>('expanded', { default: {} })
|
||||
const paginationState = defineModel<PaginationState>('pagination', { default: {} })
|
||||
|
||||
const tableRef = ref<HTMLTableElement>()
|
||||
const tableRef = ref<HTMLTableElement | null>(null)
|
||||
|
||||
const tableApi = useVueTable({
|
||||
...reactiveOmit(props, 'as', 'data', 'columns', 'caption', 'sticky', 'loading', 'loadingColor', 'loadingAnimation', 'class', 'ui'),
|
||||
@@ -366,9 +368,13 @@ defineExpose({
|
||||
</slot>
|
||||
</th>
|
||||
</tr>
|
||||
|
||||
<tr :class="ui.separator({ class: [props.ui?.separator] })" />
|
||||
</thead>
|
||||
|
||||
<tbody :class="ui.tbody({ class: [props.ui?.tbody] })">
|
||||
<slot name="body-top" />
|
||||
|
||||
<template v-if="tableApi.getRowModel().rows?.length">
|
||||
<template v-for="row in tableApi.getRowModel().rows" :key="row.id">
|
||||
<tr
|
||||
@@ -423,6 +429,8 @@ defineExpose({
|
||||
</slot>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<slot name="body-bottom" />
|
||||
</tbody>
|
||||
</table>
|
||||
</Primitive>
|
||||
|
||||
@@ -79,7 +79,8 @@ export type TabsSlots<T extends TabsItem = TabsItem> = {
|
||||
</script>
|
||||
|
||||
<script setup lang="ts" generic="T extends TabsItem">
|
||||
import { computed } from 'vue'
|
||||
import type { ComponentPublicInstance } from 'vue'
|
||||
import { ref, computed } from 'vue'
|
||||
import { TabsRoot, TabsList, TabsIndicator, TabsTrigger, TabsContent, useForwardPropsEmits } from 'reka-ui'
|
||||
import { reactivePick } from '@vueuse/core'
|
||||
import { useAppConfig } from '#imports'
|
||||
@@ -108,6 +109,12 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.tabs || {})
|
||||
size: props.size,
|
||||
orientation: props.orientation
|
||||
}))
|
||||
|
||||
const triggersRef = ref<ComponentPublicInstance[]>([])
|
||||
|
||||
defineExpose({
|
||||
triggersRef
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -117,7 +124,14 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.tabs || {})
|
||||
|
||||
<slot name="list-leading" />
|
||||
|
||||
<TabsTrigger v-for="(item, index) of items" :key="index" :value="item.value || String(index)" :disabled="item.disabled" :class="ui.trigger({ class: [props.ui?.trigger, item.ui?.trigger] })">
|
||||
<TabsTrigger
|
||||
v-for="(item, index) of items"
|
||||
:key="index"
|
||||
:ref="el => (triggersRef[index] = el as ComponentPublicInstance)"
|
||||
:value="item.value || String(index)"
|
||||
:disabled="item.disabled"
|
||||
:class="ui.trigger({ class: [props.ui?.trigger, item.ui?.trigger] })"
|
||||
>
|
||||
<slot name="leading" :item="item" :index="index">
|
||||
<UIcon v-if="item.icon" :name="item.icon" :class="ui.leadingIcon({ class: [props.ui?.leadingIcon, item.ui?.leadingIcon] })" />
|
||||
<UAvatar v-else-if="item.avatar" :size="((props.ui?.leadingAvatarSize || ui.leadingAvatarSize()) as AvatarProps['size'])" v-bind="item.avatar" :class="ui.leadingAvatar({ class: [props.ui?.leadingAvatar, item.ui?.leadingAvatar] })" />
|
||||
|
||||
@@ -3,10 +3,12 @@ import type { AppConfig } from '@nuxt/schema'
|
||||
import theme from '#build/ui/textarea'
|
||||
import type { UseComponentIconsProps } from '../composables/useComponentIcons'
|
||||
import type { AvatarProps } from '../types'
|
||||
import type { AcceptableValue, ComponentConfig } from '../types/utils'
|
||||
import type { ComponentConfig } from '../types/utils'
|
||||
|
||||
type Textarea = ComponentConfig<typeof theme, AppConfig, 'textarea'>
|
||||
|
||||
type TextareaValue = string | number | null
|
||||
|
||||
export interface TextareaProps extends UseComponentIconsProps {
|
||||
/**
|
||||
* The element or component this component should render as.
|
||||
@@ -49,7 +51,7 @@ export interface TextareaProps extends UseComponentIconsProps {
|
||||
ui?: Textarea['slots']
|
||||
}
|
||||
|
||||
export interface TextareaEmits<T extends AcceptableValue = AcceptableValue> {
|
||||
export interface TextareaEmits<T extends TextareaValue = TextareaValue> {
|
||||
(e: 'update:modelValue', payload: T): void
|
||||
(e: 'blur', event: FocusEvent): void
|
||||
(e: 'change', event: Event): void
|
||||
@@ -62,7 +64,7 @@ export interface TextareaSlots {
|
||||
}
|
||||
</script>
|
||||
|
||||
<script setup lang="ts" generic="T extends AcceptableValue">
|
||||
<script setup lang="ts" generic="T extends TextareaValue">
|
||||
import { ref, computed, onMounted, nextTick, watch } from 'vue'
|
||||
import { Primitive } from 'reka-ui'
|
||||
import { useAppConfig } from '#imports'
|
||||
|
||||
148
src/runtime/components/Timeline.vue
Normal file
148
src/runtime/components/Timeline.vue
Normal file
@@ -0,0 +1,148 @@
|
||||
<!-- eslint-disable vue/block-tag-newline -->
|
||||
<script lang="ts">
|
||||
import type { AppConfig } from '@nuxt/schema'
|
||||
import theme from '#build/ui/timeline'
|
||||
import type { AvatarProps } from '../types'
|
||||
import type { DynamicSlots, ComponentConfig } from '../types/utils'
|
||||
|
||||
type Timeline = ComponentConfig<typeof theme, AppConfig, 'timeline'>
|
||||
|
||||
export interface TimelineItem {
|
||||
date?: string
|
||||
title?: string
|
||||
description?: string
|
||||
icon?: string
|
||||
avatar?: AvatarProps
|
||||
value?: string | number
|
||||
slot?: string
|
||||
class?: any
|
||||
ui?: Pick<Timeline['slots'], 'item' | 'container' | 'indicator' | 'separator' | 'wrapper' | 'date' | 'title' | 'description'>
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
export interface TimelineProps<T extends TimelineItem = TimelineItem> {
|
||||
/**
|
||||
* The element or component this component should render as.
|
||||
* @defaultValue 'div'
|
||||
*/
|
||||
as?: any
|
||||
items: T[]
|
||||
/**
|
||||
* @defaultValue 'md'
|
||||
*/
|
||||
size?: Timeline['variants']['size']
|
||||
/**
|
||||
* @defaultValue 'primary'
|
||||
*/
|
||||
color?: Timeline['variants']['color']
|
||||
/**
|
||||
* The orientation of the Timeline.
|
||||
* @defaultValue 'vertical'
|
||||
*/
|
||||
orientation?: Timeline['variants']['orientation']
|
||||
defaultValue?: string | number
|
||||
reverse?: boolean
|
||||
class?: any
|
||||
ui?: Timeline['slots']
|
||||
}
|
||||
|
||||
type SlotProps<T extends TimelineItem> = (props: { item: T }) => any
|
||||
|
||||
export type TimelineSlots<T extends TimelineItem = TimelineItem> = {
|
||||
indicator: SlotProps<T>
|
||||
date: SlotProps<T>
|
||||
title: SlotProps<T>
|
||||
description: SlotProps<T>
|
||||
} & DynamicSlots<T, 'indicator' | 'date' | 'title' | 'description', { item: T }>
|
||||
|
||||
</script>
|
||||
|
||||
<script setup lang="ts" generic="T extends TimelineItem">
|
||||
import { computed } from 'vue'
|
||||
import { Primitive, Separator } from 'reka-ui'
|
||||
import { useAppConfig } from '#imports'
|
||||
import { tv } from '../utils/tv'
|
||||
import UAvatar from './Avatar.vue'
|
||||
|
||||
const props = withDefaults(defineProps<TimelineProps<T>>(), {
|
||||
orientation: 'vertical'
|
||||
})
|
||||
const slots = defineSlots<TimelineSlots<T>>()
|
||||
|
||||
const modelValue = defineModel<string | number>()
|
||||
|
||||
const appConfig = useAppConfig() as Timeline['AppConfig']
|
||||
|
||||
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.timeline || {}) })({
|
||||
orientation: props.orientation,
|
||||
size: props.size,
|
||||
color: props.color,
|
||||
reverse: props.reverse
|
||||
}))
|
||||
|
||||
const currentStepIndex = computed(() => {
|
||||
const value = modelValue.value ?? props.defaultValue
|
||||
|
||||
if (typeof value === 'string') {
|
||||
return props.items.findIndex(item => item.value === value) ?? -1
|
||||
}
|
||||
|
||||
if (props.reverse) {
|
||||
return value != null ? props.items.length - 1 - value : -1
|
||||
} else {
|
||||
return value ?? -1
|
||||
}
|
||||
})
|
||||
|
||||
function getItemState(index: number): 'active' | 'completed' | undefined {
|
||||
if (currentStepIndex.value === -1) return undefined
|
||||
if (index === currentStepIndex.value) return 'active'
|
||||
|
||||
if (props.reverse) {
|
||||
return index > currentStepIndex.value ? 'completed' : undefined
|
||||
} else {
|
||||
return index < currentStepIndex.value ? 'completed' : undefined
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Primitive :as="as" :data-orientation="orientation" :class="ui.root({ class: [props.ui?.root, props.class] })">
|
||||
<div
|
||||
v-for="(item, index) in items"
|
||||
:key="item.value ?? index"
|
||||
:class="ui.item({ class: [props.ui?.item, item.ui?.item, item.class] })"
|
||||
:data-state="getItemState(index)"
|
||||
>
|
||||
<div :class="ui.container({ class: [props.ui?.container, item.ui?.container] })">
|
||||
<UAvatar :size="size" :icon="item.icon" v-bind="typeof item.avatar === 'object' ? item.avatar : {}" :class="ui.indicator({ class: [props.ui?.indicator, item.ui?.indicator] })" :ui="{ icon: 'text-inherit', fallback: 'text-inherit' }">
|
||||
<slot :name="((item.slot ? `${item.slot}-indicator` : 'indicator') as keyof TimelineSlots<T>)" :item="(item as Extract<T, { slot: string; }>)" />
|
||||
</UAvatar>
|
||||
|
||||
<Separator
|
||||
v-if="index < items.length - 1"
|
||||
:class="ui.separator({ class: [props.ui?.separator, item.ui?.separator] })"
|
||||
:orientation="props.orientation"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div :class="ui.wrapper({ class: [props.ui?.wrapper, item.ui?.wrapper] })">
|
||||
<div v-if="item.date" :class="ui.date({ class: [props.ui?.date, item.ui?.date] })">
|
||||
<slot :name="((item.slot ? `${item.slot}-date` : 'date') as keyof TimelineSlots<T>)" :item="(item as Extract<T, { slot: string; }>)">
|
||||
{{ item.date }}
|
||||
</slot>
|
||||
</div>
|
||||
<div v-if="item.title || !!slots.title" :class="ui.title({ class: [props.ui?.title, item.ui?.title] })">
|
||||
<slot :name="((item.slot ? `${item.slot}-title` : 'title') as keyof TimelineSlots<T>)" :item="(item as Extract<T, { slot: string; }>)">
|
||||
{{ item.title }}
|
||||
</slot>
|
||||
</div>
|
||||
<div v-if="item.description || !!slots.description" :class="ui.description({ class: [props.ui?.description, item.ui?.description] })">
|
||||
<slot :name="((item.slot ? `${item.slot}-description` : 'description') as keyof TimelineSlots<T>)" :item="(item as Extract<T, { slot: string; }>)">
|
||||
{{ item.description }}
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Primitive>
|
||||
</template>
|
||||
@@ -69,7 +69,7 @@ export interface ToastSlots {
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { ref, computed, onMounted, nextTick } from 'vue'
|
||||
import { ToastRoot, ToastTitle, ToastDescription, ToastAction, ToastClose, useForwardPropsEmits } from 'reka-ui'
|
||||
import { reactivePick } from '@vueuse/core'
|
||||
import { useAppConfig } from '#imports'
|
||||
@@ -106,9 +106,9 @@ onMounted(() => {
|
||||
return
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
height.value = el.value.$el.getBoundingClientRect()?.height
|
||||
}, 0)
|
||||
nextTick(() => {
|
||||
height.value = el.value?.$el?.getBoundingClientRect()?.height
|
||||
})
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
@@ -173,7 +173,6 @@ defineExpose({
|
||||
<UButton
|
||||
v-if="close"
|
||||
:icon="closeIcon || appConfig.ui.icons.close"
|
||||
size="md"
|
||||
color="neutral"
|
||||
variant="link"
|
||||
:aria-label="t('toast.close')"
|
||||
|
||||
@@ -29,7 +29,7 @@ export type TreeItem = {
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
export interface TreeProps<T extends TreeItem[] = TreeItem[], VK extends GetItemKeys<T> = 'value', M extends boolean = false> extends Pick<TreeRootProps<T>, 'expanded' | 'defaultExpanded' | 'selectionBehavior' | 'propagateSelect' | 'disabled'> {
|
||||
export interface TreeProps<T extends TreeItem[] = TreeItem[], VK extends GetItemKeys<T> = 'value', M extends boolean = false> extends Pick<TreeRootProps<T>, 'expanded' | 'defaultExpanded' | 'selectionBehavior' | 'propagateSelect' | 'disabled' | 'bubbleSelect'> {
|
||||
/**
|
||||
* The element or component this component should render as.
|
||||
* @defaultValue 'ul'
|
||||
@@ -116,7 +116,7 @@ const slots = defineSlots<TreeSlots<T>>()
|
||||
|
||||
const appConfig = useAppConfig() as Tree['AppConfig']
|
||||
|
||||
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'modelValue', 'defaultValue', 'items', 'multiple', 'expanded', 'disabled', 'propagateSelect'), emits)
|
||||
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'modelValue', 'defaultValue', 'items', 'multiple', 'expanded', 'disabled', 'propagateSelect', 'bubbleSelect'), emits)
|
||||
|
||||
const [DefineTreeTemplate, ReuseTreeTemplate] = createReusableTemplate<{ items?: TreeItem[], level: number }, TreeSlots<T>>()
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { defu } from 'defu'
|
||||
import type { Locale, Direction } from '../types/locale'
|
||||
import type { DeepPartial } from '../types/utils'
|
||||
|
||||
interface DefineLocaleOptions<M> {
|
||||
name: string
|
||||
@@ -12,3 +13,8 @@ interface DefineLocaleOptions<M> {
|
||||
export function defineLocale<M>(options: DefineLocaleOptions<M>): Locale<M> {
|
||||
return defu<DefineLocaleOptions<M>, [{ dir: Direction }]>(options, { dir: 'ltr' })
|
||||
}
|
||||
|
||||
/* @__NO_SIDE_EFFECTS__ */
|
||||
export function extendLocale<M>(locale: Locale<M>, options: Partial<DefineLocaleOptions<DeepPartial<M>>>): Locale<M> {
|
||||
return defu<Locale<M>, [DefineLocaleOptions<M>]>(options, locale)
|
||||
}
|
||||
|
||||
@@ -151,7 +151,7 @@ export function defineShortcuts(config: MaybeRef<ShortcutsConfig>, options: Shor
|
||||
// Parse key and modifiers
|
||||
let shortcut: Partial<Shortcut>
|
||||
|
||||
if (key.includes('-') && key !== '-' && !key.match(chainedShortcutRegex)?.length) {
|
||||
if (key.includes('-') && key !== '-' && !key.includes('_') && !key.match(chainedShortcutRegex)?.length) {
|
||||
console.trace(`[Shortcut] Invalid key: "${key}"`)
|
||||
}
|
||||
|
||||
@@ -159,7 +159,7 @@ export function defineShortcuts(config: MaybeRef<ShortcutsConfig>, options: Shor
|
||||
console.trace(`[Shortcut] Invalid key: "${key}"`)
|
||||
}
|
||||
|
||||
const chained = key.includes('-') && key !== '-'
|
||||
const chained = key.includes('-') && key !== '-' && !key.includes('_')
|
||||
if (chained) {
|
||||
shortcut = {
|
||||
key: key.toLowerCase(),
|
||||
|
||||
@@ -17,6 +17,7 @@ interface ManagedOverlayOptionsPrivate<T extends Component> {
|
||||
id: symbol
|
||||
isMounted: boolean
|
||||
isOpen: boolean
|
||||
originalProps?: ComponentProps<T>
|
||||
resolvePromise?: (value: any) => void
|
||||
}
|
||||
export type Overlay = OverlayOptions<Component> & ManagedOverlayOptionsPrivate<Component>
|
||||
@@ -26,7 +27,6 @@ type OverlayInstance<T extends Component> = Omit<ManagedOverlayOptionsPrivate<T>
|
||||
open: (props?: ComponentProps<T>) => OpenedOverlay<T>
|
||||
close: (value?: any) => void
|
||||
patch: (props: Partial<ComponentProps<T>>) => void
|
||||
|
||||
}
|
||||
|
||||
type OpenedOverlay<T extends Component> = Omit<OverlayInstance<T>, 'open' | 'close' | 'patch' | 'modelValue' | 'resolvePromise'> & {
|
||||
@@ -37,7 +37,7 @@ function _useOverlay() {
|
||||
const overlays = shallowReactive<Overlay[]>([])
|
||||
|
||||
const create = <T extends Component>(component: T, _options?: OverlayOptions<ComponentProps<T>>): OverlayInstance<T> => {
|
||||
const { props: props, defaultOpen, destroyOnClose } = _options || {}
|
||||
const { props, defaultOpen, destroyOnClose } = _options || {}
|
||||
|
||||
const options = reactive<Overlay>({
|
||||
id: Symbol(import.meta.dev ? 'useOverlay' : ''),
|
||||
@@ -45,7 +45,8 @@ function _useOverlay() {
|
||||
component: markRaw(component!),
|
||||
isMounted: !!defaultOpen,
|
||||
destroyOnClose: !!destroyOnClose,
|
||||
props: props || {}
|
||||
originalProps: props || {},
|
||||
props: { ...(props || {}) }
|
||||
})
|
||||
|
||||
overlays.push(options)
|
||||
@@ -64,6 +65,8 @@ function _useOverlay() {
|
||||
// If props are provided, update the overlay's props
|
||||
if (props) {
|
||||
patch(overlay.id, props)
|
||||
} else {
|
||||
patch(overlay.id, overlay.originalProps)
|
||||
}
|
||||
|
||||
overlay.isOpen = true
|
||||
@@ -93,7 +96,7 @@ function _useOverlay() {
|
||||
overlays.forEach(overlay => close(overlay.id))
|
||||
}
|
||||
|
||||
const unMount = (id: symbol): void => {
|
||||
const unmount = (id: symbol): void => {
|
||||
const overlay = getOverlay(id)
|
||||
|
||||
overlay.isMounted = false
|
||||
@@ -107,9 +110,7 @@ function _useOverlay() {
|
||||
const patch = <T extends Component>(id: symbol, props: Partial<ComponentProps<T>>): void => {
|
||||
const overlay = getOverlay(id)
|
||||
|
||||
Object.entries(props!).forEach(([key, value]) => {
|
||||
(overlay.props as any)[key] = value
|
||||
})
|
||||
overlay.props = { ...props }
|
||||
}
|
||||
|
||||
const getOverlay = (id: symbol): Overlay => {
|
||||
@@ -135,7 +136,7 @@ function _useOverlay() {
|
||||
closeAll,
|
||||
create,
|
||||
patch,
|
||||
unMount,
|
||||
unmount,
|
||||
isOpen
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,8 @@ export default defineLocale<Messages>({
|
||||
placeholder: 'اكتب أمرًا أو ابحث...',
|
||||
noMatch: 'لا توجد نتائج مطابقة',
|
||||
noData: 'لا توجد بيانات',
|
||||
close: 'إغلاق'
|
||||
close: 'إغلاق',
|
||||
back: 'رجوع'
|
||||
},
|
||||
selectMenu: {
|
||||
noMatch: 'لا توجد نتائج مطابقة',
|
||||
|
||||
@@ -24,7 +24,8 @@ export default defineLocale<Messages>({
|
||||
placeholder: 'Əmr daxil edin və ya axtarın...',
|
||||
noMatch: 'Uyğun məlumat tapılmadı',
|
||||
noData: 'Məlumat yoxdur',
|
||||
close: 'Bağla'
|
||||
close: 'Bağla',
|
||||
back: 'Geri'
|
||||
},
|
||||
selectMenu: {
|
||||
noMatch: 'Uyğun məlumat tapılmadı',
|
||||
|
||||
@@ -24,7 +24,8 @@ export default defineLocale<Messages>({
|
||||
placeholder: 'Въведете команда или потърсете...',
|
||||
noMatch: 'Няма съвпадение на данни',
|
||||
noData: 'Няма данни',
|
||||
close: 'Затворете'
|
||||
close: 'Затворете',
|
||||
back: 'Назад'
|
||||
},
|
||||
selectMenu: {
|
||||
noMatch: 'Няма съвпадение на данни',
|
||||
|
||||
@@ -24,7 +24,8 @@ export default defineLocale<Messages>({
|
||||
placeholder: 'কমান্ড টাইপ করুন বা অনুসন্ধান করুন...',
|
||||
noMatch: 'কোন মিল পাওয়া যায়নি',
|
||||
noData: 'কোন তথ্য নেই',
|
||||
close: 'বন্ধ করুন'
|
||||
close: 'বন্ধ করুন',
|
||||
back: 'পেছনে'
|
||||
},
|
||||
selectMenu: {
|
||||
noMatch: 'কোন মিল পাওয়া যায়নি',
|
||||
|
||||
@@ -24,7 +24,8 @@ export default defineLocale<Messages>({
|
||||
placeholder: 'Escriu una ordre o cerca...',
|
||||
noMatch: 'No hi ha dades coincidents',
|
||||
noData: 'Sense dades',
|
||||
close: 'Tancar'
|
||||
close: 'Tancar',
|
||||
back: 'Enrere'
|
||||
},
|
||||
selectMenu: {
|
||||
noMatch: 'No hi ha dades coincidents',
|
||||
|
||||
@@ -25,7 +25,8 @@ export default defineLocale<Messages>({
|
||||
placeholder: 'فەرمانێک بنووسە یان بگەڕێ...',
|
||||
noMatch: 'هیچ ئەنجامێک نەدۆزرایەوە',
|
||||
noData: 'هیچ داتایەک نییە',
|
||||
close: 'داخستن'
|
||||
close: 'داخستن',
|
||||
back: 'گەڕانەوە'
|
||||
},
|
||||
selectMenu: {
|
||||
noMatch: 'هیچ ئەنجامێک نەدۆزرایەوە',
|
||||
|
||||
@@ -24,7 +24,8 @@ export default defineLocale<Messages>({
|
||||
placeholder: 'Zadejte příkaz nebo hledejte...',
|
||||
noMatch: 'Žádná shoda',
|
||||
noData: 'Žádná data',
|
||||
close: 'Zavřít'
|
||||
close: 'Zavřít',
|
||||
back: 'Zpět'
|
||||
},
|
||||
selectMenu: {
|
||||
noMatch: 'Žádná shoda',
|
||||
|
||||
@@ -24,7 +24,8 @@ export default defineLocale<Messages>({
|
||||
placeholder: 'Skriv en kommando eller søg...',
|
||||
noMatch: 'Ingen matchende data',
|
||||
noData: 'Ingen data',
|
||||
close: 'Luk'
|
||||
close: 'Luk',
|
||||
back: 'Tilbage'
|
||||
},
|
||||
selectMenu: {
|
||||
noMatch: 'Ingen matchende data',
|
||||
|
||||
@@ -24,7 +24,8 @@ export default defineLocale<Messages>({
|
||||
placeholder: 'Geben Sie einen Befehl ein oder suchen Sie...',
|
||||
noMatch: 'Nichts gefunden',
|
||||
noData: 'Keine Daten',
|
||||
close: 'Schließen'
|
||||
close: 'Schließen',
|
||||
back: 'Zurück'
|
||||
},
|
||||
selectMenu: {
|
||||
noMatch: 'Nichts gefunden',
|
||||
|
||||
@@ -24,7 +24,8 @@ export default defineLocale<Messages>({
|
||||
placeholder: 'Πληκτρολογήστε μια εντολή ή αναζητήστε...',
|
||||
noMatch: 'Δεν βρέθηκαν δεδομένα',
|
||||
noData: 'Δεν υπάρχουν δεδομένα',
|
||||
close: 'Κλείσιμο'
|
||||
close: 'Κλείσιμο',
|
||||
back: 'Πίσω'
|
||||
},
|
||||
selectMenu: {
|
||||
noMatch: 'Δεν βρέθηκαν δεδομένα',
|
||||
|
||||
@@ -24,7 +24,8 @@ export default defineLocale<Messages>({
|
||||
placeholder: 'Type a command or search...',
|
||||
noMatch: 'No matching data',
|
||||
noData: 'No data',
|
||||
close: 'Close'
|
||||
close: 'Close',
|
||||
back: 'Back'
|
||||
},
|
||||
selectMenu: {
|
||||
noMatch: 'No matching data',
|
||||
|
||||
@@ -24,7 +24,8 @@ export default defineLocale<Messages>({
|
||||
placeholder: 'Escribe un comando o busca...',
|
||||
noMatch: 'No hay datos coincidentes',
|
||||
noData: 'Sin datos',
|
||||
close: 'Cerrar'
|
||||
close: 'Cerrar',
|
||||
back: 'Atrás'
|
||||
},
|
||||
selectMenu: {
|
||||
noMatch: 'No hay datos coincidentes',
|
||||
|
||||
@@ -24,7 +24,8 @@ export default defineLocale<Messages>({
|
||||
placeholder: 'Sisesta käsk või otsi...',
|
||||
noMatch: 'Pole vastavaid andmeid',
|
||||
noData: 'Pole andmeid',
|
||||
close: 'Sulge'
|
||||
close: 'Sulge',
|
||||
back: 'Tagasi'
|
||||
},
|
||||
selectMenu: {
|
||||
noMatch: 'Pole vastavaid andmeid',
|
||||
|
||||
@@ -25,7 +25,8 @@ export default defineLocale<Messages>({
|
||||
placeholder: 'یک دستور وارد کنید یا جستجو کنید...',
|
||||
noMatch: 'دادهای یافت نشد',
|
||||
noData: 'دادهای موجود نیست',
|
||||
close: 'بستن'
|
||||
close: 'بستن',
|
||||
back: 'بازگشت'
|
||||
},
|
||||
selectMenu: {
|
||||
noMatch: 'دادهای یافت نشد',
|
||||
|
||||
@@ -24,7 +24,8 @@ export default defineLocale<Messages>({
|
||||
placeholder: 'Kirjoita komento tai hae...',
|
||||
noMatch: 'Ei vastaavia tietoja',
|
||||
noData: 'Ei tietoja',
|
||||
close: 'Sulje'
|
||||
close: 'Sulje',
|
||||
back: 'Takaisin'
|
||||
},
|
||||
selectMenu: {
|
||||
noMatch: 'Ei vastaavia tietoja',
|
||||
|
||||
@@ -24,7 +24,8 @@ export default defineLocale<Messages>({
|
||||
placeholder: 'Tapez une commande ou recherchez...',
|
||||
noMatch: 'Aucune donnée correspondante',
|
||||
noData: 'Aucune donnée',
|
||||
close: 'Fermer'
|
||||
close: 'Fermer',
|
||||
back: 'Retour'
|
||||
},
|
||||
selectMenu: {
|
||||
noMatch: 'Aucune donnée correspondante',
|
||||
|
||||
@@ -25,7 +25,8 @@ export default defineLocale<Messages>({
|
||||
placeholder: 'הקלד פקודה...',
|
||||
noMatch: 'לא נמצאה התאמה',
|
||||
noData: 'אין נתונים זמינים',
|
||||
close: 'סגור'
|
||||
close: 'סגור',
|
||||
back: 'חזור'
|
||||
},
|
||||
selectMenu: {
|
||||
noMatch: 'לא נמצאה התאמה',
|
||||
|
||||
@@ -24,7 +24,8 @@ export default defineLocale<Messages>({
|
||||
placeholder: 'एक आदेश या खोज टाइप करें...',
|
||||
noMatch: 'कोई मेल खाता डेटा नहीं',
|
||||
noData: 'कोई डेटा नहीं',
|
||||
close: 'बंद करें'
|
||||
close: 'बंद करें',
|
||||
back: 'वापस'
|
||||
},
|
||||
selectMenu: {
|
||||
noMatch: 'कोई मेल खाता डेटा नहीं',
|
||||
|
||||
@@ -24,7 +24,8 @@ export default defineLocale<Messages>({
|
||||
placeholder: 'Írjon be egy parancsot vagy keressen...',
|
||||
noMatch: 'Nincs találat',
|
||||
noData: 'Nincs adat',
|
||||
close: 'Bezárás'
|
||||
close: 'Bezárás',
|
||||
back: 'Vissza'
|
||||
},
|
||||
selectMenu: {
|
||||
noMatch: 'Nincs találat',
|
||||
|
||||
@@ -24,7 +24,8 @@ export default defineLocale<Messages>({
|
||||
placeholder: 'Մուտքագրեք հրաման կամ որոնեք...',
|
||||
noMatch: 'Համընկնումներ չեն գտնվել',
|
||||
noData: 'Տվյալներ չկան',
|
||||
close: 'Փակել'
|
||||
close: 'Փակել',
|
||||
back: 'Հետ'
|
||||
},
|
||||
selectMenu: {
|
||||
noMatch: 'Համընկնումներ չեն գտնվել',
|
||||
|
||||
@@ -24,7 +24,8 @@ export default defineLocale<Messages>({
|
||||
placeholder: 'Ketik perintah atau cari...',
|
||||
noMatch: 'Tidak ada data yang cocok',
|
||||
noData: 'Tidak ada data',
|
||||
close: 'Tutup'
|
||||
close: 'Tutup',
|
||||
back: 'Kembali'
|
||||
},
|
||||
selectMenu: {
|
||||
noMatch: 'Tidak ada data yang cocok',
|
||||
|
||||
@@ -25,6 +25,7 @@ export { default as kk } from './kk'
|
||||
export { default as km } from './km'
|
||||
export { default as ko } from './ko'
|
||||
export { default as ky } from './ky'
|
||||
export { default as lb } from './lb'
|
||||
export { default as lt } from './lt'
|
||||
export { default as mn } from './mn'
|
||||
export { default as ms } from './ms'
|
||||
|
||||
@@ -24,7 +24,8 @@ export default defineLocale<Messages>({
|
||||
placeholder: 'Digita un comando o cerca...',
|
||||
noMatch: 'Nessun dato corrispondente',
|
||||
noData: 'Nessun dato',
|
||||
close: 'Chiudi'
|
||||
close: 'Chiudi',
|
||||
back: 'Indietro'
|
||||
},
|
||||
selectMenu: {
|
||||
noMatch: 'Nessun dato corrispondente',
|
||||
|
||||
@@ -24,7 +24,8 @@ export default defineLocale<Messages>({
|
||||
placeholder: 'コマンドを入力するか検索...',
|
||||
noMatch: '一致するデータがありません',
|
||||
noData: 'データがありません',
|
||||
close: '閉じる'
|
||||
close: '閉じる',
|
||||
back: '戻る'
|
||||
},
|
||||
selectMenu: {
|
||||
noMatch: '一致するデータがありません',
|
||||
|
||||
@@ -24,7 +24,8 @@ export default defineLocale<Messages>({
|
||||
placeholder: 'Команда енгізіңіз немесе іздеңіз...',
|
||||
noMatch: 'Сәйкес келетін деректер жоқ',
|
||||
noData: 'Деректер жоқ',
|
||||
close: 'Жабу'
|
||||
close: 'Жабу',
|
||||
back: 'Артқа'
|
||||
},
|
||||
selectMenu: {
|
||||
noMatch: 'Сәйкес келетін деректер жоқ',
|
||||
|
||||
@@ -24,7 +24,8 @@ export default defineLocale<Messages>({
|
||||
placeholder: 'វាយពាក្យបញ្ជា ឬស្វែងរក...',
|
||||
noMatch: 'មិនមានទិន្នន័យដែលត្រូវគ្នាទេ',
|
||||
noData: 'មិនមានទិន្នន័យ',
|
||||
close: 'បិទ'
|
||||
close: 'បិទ',
|
||||
back: 'ត្រឡប់'
|
||||
},
|
||||
selectMenu: {
|
||||
noMatch: 'មិនមានទិន្នន័យដែលត្រូវគ្នាទេ',
|
||||
|
||||
@@ -24,7 +24,8 @@ export default defineLocale<Messages>({
|
||||
placeholder: '명령을 입력하거나 검색...',
|
||||
noMatch: '일치하는 데이터가 없습니다.',
|
||||
noData: '데이터가 없습니다.',
|
||||
close: '닫기'
|
||||
close: '닫기',
|
||||
back: '뒤로'
|
||||
},
|
||||
selectMenu: {
|
||||
noMatch: '일치하는 데이터가 없습니다.',
|
||||
|
||||
@@ -24,7 +24,8 @@ export default defineLocale<Messages>({
|
||||
placeholder: 'Буйрук киргизиңиз же издөө…',
|
||||
noMatch: 'Эч нерсе табылган жок',
|
||||
noData: 'Маалымат жок',
|
||||
close: 'Жабуу'
|
||||
close: 'Жабуу',
|
||||
back: 'Артка'
|
||||
},
|
||||
selectMenu: {
|
||||
noMatch: 'Сүйлөшкөн маалыматтар жок',
|
||||
|
||||
57
src/runtime/locale/lb.ts
Normal file
57
src/runtime/locale/lb.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import type { Messages } from '../types'
|
||||
import { defineLocale } from '../composables/defineLocale'
|
||||
|
||||
export default defineLocale<Messages>({
|
||||
name: 'Lëtzebuergesch',
|
||||
code: 'lb',
|
||||
messages: {
|
||||
inputMenu: {
|
||||
noMatch: 'Keng entspriechend Donnéeën',
|
||||
noData: 'Keng Donnéeën',
|
||||
create: '"{label}" erstellen'
|
||||
},
|
||||
calendar: {
|
||||
prevYear: 'Viregt Joer',
|
||||
nextYear: 'Nächst Joer',
|
||||
prevMonth: 'Virege Mount',
|
||||
nextMonth: 'Nächste Mount'
|
||||
},
|
||||
inputNumber: {
|
||||
increment: 'Inkrementéieren',
|
||||
decrement: 'Dekrementéieren'
|
||||
},
|
||||
commandPalette: {
|
||||
placeholder: 'Tippt e Befeel oder sicht...',
|
||||
noMatch: 'Keng entspriechend Donnéeën',
|
||||
noData: 'Keng Donnéeën',
|
||||
close: 'Zoumaachen',
|
||||
back: 'Zréck'
|
||||
},
|
||||
selectMenu: {
|
||||
noMatch: 'Keng entspriechend Donnéeën',
|
||||
noData: 'Keng Donnéeën',
|
||||
create: '"{label}" erstellen',
|
||||
search: 'Sichen..'
|
||||
},
|
||||
toast: {
|
||||
close: 'Zoumaachen'
|
||||
},
|
||||
carousel: {
|
||||
prev: 'Präz.',
|
||||
next: 'Näch.',
|
||||
goto: 'Gitt op d\'Slide {Slide}'
|
||||
},
|
||||
modal: {
|
||||
close: 'Zoumaachen'
|
||||
},
|
||||
slideover: {
|
||||
close: 'Zoumaachen'
|
||||
},
|
||||
alert: {
|
||||
close: 'Zoumaachen'
|
||||
},
|
||||
table: {
|
||||
noData: 'Keng Donnéeën'
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -24,7 +24,8 @@ export default defineLocale<Messages>({
|
||||
placeholder: 'Įveskite komandą arba ieškokite...',
|
||||
noMatch: 'Nėra atitinkančių duomenų',
|
||||
noData: 'Nėra duomenų',
|
||||
close: 'Uždaryti'
|
||||
close: 'Uždaryti',
|
||||
back: 'Atgal'
|
||||
},
|
||||
selectMenu: {
|
||||
noMatch: 'Nėra atitinkančių duomenų',
|
||||
|
||||
@@ -24,7 +24,8 @@ export default defineLocale<Messages>({
|
||||
placeholder: 'Комманд бичих эсвэл хайлт хийх...',
|
||||
noMatch: 'Тохирох мэдээлэл олдсонгүй',
|
||||
noData: 'Мэдээлэл байхгүй',
|
||||
close: 'Хаах'
|
||||
close: 'Хаах',
|
||||
back: 'Буцах'
|
||||
},
|
||||
selectMenu: {
|
||||
noMatch: 'Тохирох мэдээлэл олдсонгүй',
|
||||
|
||||
@@ -24,7 +24,8 @@ export default defineLocale<Messages>({
|
||||
placeholder: 'Taip arahan atau carian...',
|
||||
noMatch: 'Tiada data yang sepadan',
|
||||
noData: 'Tiada data',
|
||||
close: 'Tutup'
|
||||
close: 'Tutup',
|
||||
back: 'Kembali'
|
||||
},
|
||||
selectMenu: {
|
||||
noMatch: 'Tiada data yang sepadan',
|
||||
|
||||
@@ -24,7 +24,8 @@ export default defineLocale<Messages>({
|
||||
placeholder: 'Skriv inn en kommando eller søk...',
|
||||
noMatch: 'Ingen samsvarende data',
|
||||
noData: 'Ingen data',
|
||||
close: 'Lukk'
|
||||
close: 'Lukk',
|
||||
back: 'Tilbake'
|
||||
},
|
||||
selectMenu: {
|
||||
noMatch: 'Ingen samsvarende data',
|
||||
|
||||
@@ -24,7 +24,8 @@ export default defineLocale<Messages>({
|
||||
placeholder: 'Typ een commando of zoek...',
|
||||
noMatch: 'Geen overeenkomende gegevens',
|
||||
noData: 'Geen gegevens',
|
||||
close: 'Sluiten'
|
||||
close: 'Sluiten',
|
||||
back: 'Terug'
|
||||
},
|
||||
selectMenu: {
|
||||
noMatch: 'Geen overeenkomende gegevens',
|
||||
|
||||
@@ -24,7 +24,8 @@ export default defineLocale<Messages>({
|
||||
placeholder: 'Wpisz polecenie lub wyszukaj...',
|
||||
noMatch: 'Brak pasujących danych',
|
||||
noData: 'Brak danych',
|
||||
close: 'Zamknij'
|
||||
close: 'Zamknij',
|
||||
back: 'Wstecz'
|
||||
},
|
||||
selectMenu: {
|
||||
noMatch: 'Brak pasujących danych',
|
||||
|
||||
@@ -24,7 +24,8 @@ export default defineLocale<Messages>({
|
||||
placeholder: 'Digite um comando ou pesquise...',
|
||||
noMatch: 'Nenhum dado correspondente',
|
||||
noData: 'Sem dados',
|
||||
close: 'Fechar'
|
||||
close: 'Fechar',
|
||||
back: 'Voltar'
|
||||
},
|
||||
selectMenu: {
|
||||
noMatch: 'Nenhum dado correspondente',
|
||||
|
||||
@@ -24,7 +24,8 @@ export default defineLocale<Messages>({
|
||||
placeholder: 'Digite um comando ou pesquise...',
|
||||
noMatch: 'Nenhum dado correspondente',
|
||||
noData: 'Nenhum dado',
|
||||
close: 'Fechar'
|
||||
close: 'Fechar',
|
||||
back: 'Voltar'
|
||||
},
|
||||
selectMenu: {
|
||||
noMatch: 'Nenhum dado correspondente',
|
||||
|
||||
@@ -24,7 +24,8 @@ export default defineLocale<Messages>({
|
||||
placeholder: 'Tastează o comandă sau caută...',
|
||||
noMatch: 'Nu există date corespunzătoare',
|
||||
noData: 'Nu există date',
|
||||
close: 'Închide'
|
||||
close: 'Închide',
|
||||
back: 'Înapoi'
|
||||
},
|
||||
selectMenu: {
|
||||
noMatch: 'Nu există date corespunzătoare',
|
||||
|
||||
@@ -24,7 +24,8 @@ export default defineLocale<Messages>({
|
||||
placeholder: 'Введите команду или выполните поиск...',
|
||||
noMatch: 'Совпадений не найдено',
|
||||
noData: 'Нет данных',
|
||||
close: 'Закрыть'
|
||||
close: 'Закрыть',
|
||||
back: 'Назад'
|
||||
},
|
||||
selectMenu: {
|
||||
noMatch: 'Совпадений не найдено',
|
||||
|
||||
@@ -24,7 +24,8 @@ export default defineLocale<Messages>({
|
||||
placeholder: 'Zadajte príkaz alebo vyhľadajte...',
|
||||
noMatch: 'Žiadna zhoda',
|
||||
noData: 'Žiadne dáta',
|
||||
close: 'Zatvoriť'
|
||||
close: 'Zavrieť',
|
||||
back: 'Späť'
|
||||
},
|
||||
selectMenu: {
|
||||
noMatch: 'Žiadna zhoda',
|
||||
|
||||
@@ -24,7 +24,8 @@ export default defineLocale<Messages>({
|
||||
placeholder: 'Vpiši ukaz ali išči...',
|
||||
noMatch: 'Ni ujemanj',
|
||||
noData: 'Ni podatkov',
|
||||
close: 'Zapri'
|
||||
close: 'Zapri',
|
||||
back: 'Nazaj'
|
||||
},
|
||||
selectMenu: {
|
||||
noMatch: 'Ni ujemanj',
|
||||
|
||||
@@ -24,7 +24,8 @@ export default defineLocale<Messages>({
|
||||
placeholder: 'Skriv ett kommando eller sök...',
|
||||
noMatch: 'Inga matchande data',
|
||||
noData: 'Inga data',
|
||||
close: 'Stäng'
|
||||
close: 'Stäng',
|
||||
back: 'Tillbaka'
|
||||
},
|
||||
selectMenu: {
|
||||
noMatch: 'Inga matchande data',
|
||||
|
||||
@@ -24,7 +24,8 @@ export default defineLocale<Messages>({
|
||||
placeholder: 'พิมพ์คำสั่งหรือค้นหา...',
|
||||
noMatch: 'ไม่พบข้อมูลที่ตรงกัน',
|
||||
noData: 'ไม่มีข้อมูล',
|
||||
close: 'ปิด'
|
||||
close: 'ปิด',
|
||||
back: 'ย้อนกลับ'
|
||||
},
|
||||
selectMenu: {
|
||||
noMatch: 'ไม่พบข้อมูลที่ตรงกัน',
|
||||
|
||||
@@ -24,7 +24,8 @@ export default defineLocale<Messages>({
|
||||
placeholder: 'Фармонро нависед ё ҷустуҷӯ кунед...',
|
||||
noMatch: 'Маълумоти мувофиқ ёфт нашуд',
|
||||
noData: 'Маълумот нест',
|
||||
close: 'Бастан'
|
||||
close: 'Бастан',
|
||||
back: 'Бозгашт'
|
||||
},
|
||||
selectMenu: {
|
||||
noMatch: 'Маълумоти мувофиқ ёфт нашуд',
|
||||
|
||||
@@ -24,7 +24,8 @@ export default defineLocale<Messages>({
|
||||
placeholder: 'Bir komut yazın veya arama yapın...',
|
||||
noMatch: 'Eşleşen veri yok',
|
||||
noData: 'Veri yok',
|
||||
close: 'Kapat'
|
||||
close: 'Kapat',
|
||||
back: 'Geri'
|
||||
},
|
||||
selectMenu: {
|
||||
noMatch: 'Eşleşen veri yok',
|
||||
|
||||
@@ -25,7 +25,8 @@ export default defineLocale<Messages>({
|
||||
placeholder: 'بۇيرۇق كىرگۈزۈڭ ياكى ئىزدەڭ...',
|
||||
noMatch: 'ماس كېلىدىغان سانلىق مەلۇمات يوق',
|
||||
noData: 'سانلىق مەلۇمات يوق',
|
||||
close: 'تاقاش'
|
||||
close: 'تاقاش',
|
||||
back: 'قايتىش'
|
||||
},
|
||||
selectMenu: {
|
||||
noMatch: 'ماس كېلىدىغان سانلىق مەلۇمات يوق',
|
||||
|
||||
@@ -24,7 +24,8 @@ export default defineLocale<Messages>({
|
||||
placeholder: 'Введіть команду або шукайте...',
|
||||
noMatch: 'Збігів не знайдено',
|
||||
noData: 'Немає даних',
|
||||
close: 'Закрити'
|
||||
close: 'Закрити',
|
||||
back: 'Назад'
|
||||
},
|
||||
selectMenu: {
|
||||
noMatch: 'Збігів не знайдено',
|
||||
|
||||
@@ -25,7 +25,8 @@ export default defineLocale<Messages>({
|
||||
placeholder: 'کمانڈ ٹائپ کریں یا تلاش کریں...',
|
||||
noMatch: 'کوئی ملتا جلتا ڈیٹا نہیں ملا',
|
||||
noData: 'کوئی ڈیٹا نہیں',
|
||||
close: 'بند کریں'
|
||||
close: 'بند کریں',
|
||||
back: 'واپس'
|
||||
},
|
||||
selectMenu: {
|
||||
noMatch: 'کوئی ملتا جلتا ڈیٹا نہیں ملا',
|
||||
|
||||
@@ -24,7 +24,8 @@ export default defineLocale<Messages>({
|
||||
placeholder: 'Buyruq kiriting yoki qidiring...',
|
||||
noMatch: 'Mos keluvchi natija topilmadi',
|
||||
noData: 'Maʼlumot yoʻq',
|
||||
close: 'Yopish'
|
||||
close: 'Yopish',
|
||||
back: 'Orqaga'
|
||||
},
|
||||
selectMenu: {
|
||||
noMatch: 'Mos keluvchi natija topilmadi',
|
||||
|
||||
@@ -24,7 +24,8 @@ export default defineLocale<Messages>({
|
||||
placeholder: 'Nhập lệnh hoặc tìm kiếm...',
|
||||
noMatch: 'Không có kết quả phù hợp',
|
||||
noData: 'Không có dữ liệu',
|
||||
close: 'Đóng'
|
||||
close: 'Đóng',
|
||||
back: 'Quay lại'
|
||||
},
|
||||
selectMenu: {
|
||||
noMatch: 'Không có kết quả phù hợp',
|
||||
|
||||
@@ -24,7 +24,8 @@ export default defineLocale<Messages>({
|
||||
placeholder: '输入命令或搜索...',
|
||||
noMatch: '没有匹配的数据',
|
||||
noData: '没有数据',
|
||||
close: '关闭'
|
||||
close: '关闭',
|
||||
back: '返回'
|
||||
},
|
||||
selectMenu: {
|
||||
noMatch: '没有匹配的数据',
|
||||
|
||||
@@ -24,7 +24,8 @@ export default defineLocale<Messages>({
|
||||
placeholder: '輸入命令或搜尋...',
|
||||
noMatch: '沒有相符的資料',
|
||||
noData: '沒有資料',
|
||||
close: '關閉'
|
||||
close: '關閉',
|
||||
back: '返回'
|
||||
},
|
||||
selectMenu: {
|
||||
noMatch: '沒有相符的資料',
|
||||
|
||||
@@ -5,20 +5,20 @@ import type { ObjectSchema as YupObjectSchema } from 'yup'
|
||||
import type { GetObjectField } from './utils'
|
||||
import type { Struct as SuperstructSchema } from 'superstruct'
|
||||
|
||||
export interface Form<T extends object> {
|
||||
validate (opts?: { name?: keyof T | (keyof T)[], silent?: boolean, nested?: boolean, transform?: boolean }): Promise<T | false>
|
||||
export interface Form<S extends FormSchema> {
|
||||
validate<T extends boolean>(opts?: { name?: keyof FormData<S, false> | (keyof FormData<S, false>)[], silent?: boolean, nested?: boolean, transform?: T }): Promise<FormData<S, T> | false>
|
||||
clear (path?: string): void
|
||||
errors: Ref<FormError[]>
|
||||
setErrors (errs: FormError[], name?: keyof T): void
|
||||
getErrors (name?: keyof T): FormError[]
|
||||
setErrors (errs: FormError[], name?: keyof FormData<S, false>): void
|
||||
getErrors (name?: keyof FormData<S, false>): FormError[]
|
||||
submit (): Promise<void>
|
||||
disabled: ComputedRef<boolean>
|
||||
dirty: ComputedRef<boolean>
|
||||
loading: Ref<boolean>
|
||||
|
||||
dirtyFields: DeepReadonly<Set<keyof T>>
|
||||
touchedFields: DeepReadonly<Set<keyof T>>
|
||||
blurredFields: DeepReadonly<Set<keyof T>>
|
||||
dirtyFields: ReadonlySet<DeepReadonly<keyof FormData<S, false>>>
|
||||
touchedFields: ReadonlySet<DeepReadonly<keyof FormData<S, false>>>
|
||||
blurredFields: ReadonlySet<DeepReadonly<keyof FormData<S, false>>>
|
||||
}
|
||||
|
||||
export type FormSchema<I extends object = object, O extends object = I> =
|
||||
@@ -42,6 +42,8 @@ export type InferOutput<Schema> = Schema extends StandardSchemaV1 ? StandardSche
|
||||
: Schema extends SuperstructSchema<infer O, any> ? O
|
||||
: never
|
||||
|
||||
export type FormData<S extends FormSchema, T extends boolean = true> = T extends true ? InferOutput<S> : InferInput<S>
|
||||
|
||||
export type FormInputEvents = 'input' | 'blur' | 'change' | 'focus'
|
||||
|
||||
export interface FormError<P extends string = string> {
|
||||
|
||||
@@ -26,6 +26,7 @@ export * from '../components/Icon.vue'
|
||||
export * from '../components/Input.vue'
|
||||
export * from '../components/InputMenu.vue'
|
||||
export * from '../components/InputNumber.vue'
|
||||
export * from '../components/InputTags.vue'
|
||||
export * from '../components/Kbd.vue'
|
||||
export * from '../components/Link.vue'
|
||||
export * from '../components/Modal.vue'
|
||||
@@ -46,6 +47,7 @@ export * from '../components/Switch.vue'
|
||||
export * from '../components/Table.vue'
|
||||
export * from '../components/Tabs.vue'
|
||||
export * from '../components/Textarea.vue'
|
||||
export * from '../components/Timeline.vue'
|
||||
export * from '../components/Toast.vue'
|
||||
export * from '../components/Toaster.vue'
|
||||
export * from '../components/Tooltip.vue'
|
||||
|
||||
@@ -19,6 +19,7 @@ export type Messages = {
|
||||
noMatch: string
|
||||
noData: string
|
||||
close: string
|
||||
back: string
|
||||
}
|
||||
selectMenu: {
|
||||
noMatch: string
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import type { VNode } from 'vue'
|
||||
import type { AcceptableValue as _AcceptableValue } from 'reka-ui'
|
||||
|
||||
export type DeepPartial<T> = {
|
||||
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P] | undefined
|
||||
}
|
||||
|
||||
export type DynamicSlotsKeys<Name extends string | undefined, Suffix extends string | undefined = undefined> = (
|
||||
Name extends string
|
||||
? Suffix extends string
|
||||
|
||||
Reference in New Issue
Block a user