feat(Select): new component (#92)

This commit is contained in:
Benjamin Canac
2024-05-07 22:58:56 +02:00
committed by GitHub
parent 4a123906d0
commit 1942b8e117
60 changed files with 3068 additions and 692 deletions

View File

@@ -49,7 +49,7 @@ const props = withDefaults(defineProps<AccordionProps<T>>(), {
collapsible: true
})
const emits = defineEmits<AccordionEmits>()
defineSlots<AccordionSlots<T>>()
const slots = defineSlots<AccordionSlots<T>>()
const appConfig = useAppConfig()
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'collapsible', 'defaultValue', 'disabled', 'modelValue', 'type'), emits)
@@ -67,7 +67,7 @@ const ui = computed(() => tv({ extend: accordion, slots: props.ui })({ disabled:
<UIcon v-if="item.icon" :name="item.icon" :class="ui.leadingIcon()" />
</slot>
<span v-if="item.label || $slots.default" :class="ui.label()">
<span v-if="item.label || !!slots.default" :class="ui.label()">
<slot :item="item" :index="index">{{ item.label }}</slot>
</span>
@@ -77,7 +77,7 @@ const ui = computed(() => tv({ extend: accordion, slots: props.ui })({ disabled:
</AccordionTrigger>
</AccordionHeader>
<AccordionContent v-if="item.content || $slots.content || (item.slot && $slots[item.slot])" v-bind="contentProps" :class="ui.content()">
<AccordionContent v-if="item.content || !!slots.content || (item.slot && !!slots[item.slot])" v-bind="contentProps" :class="ui.content()">
<slot :name="item.slot || 'content'" :item="item" :index="index">
{{ item.content }}
</slot>

View File

@@ -47,7 +47,7 @@ import { UIcon, UAvatar } from '#components'
const props = withDefaults(defineProps<AlertProps>(), { as: 'div' })
const emits = defineEmits<AlertEmits>()
defineSlots<AlertSlots>()
const slots = defineSlots<AlertSlots>()
const appConfig = useAppConfig()
@@ -67,12 +67,12 @@ const ui = computed(() => tv({ extend: alert, slots: props.ui })({
</slot>
<div :class="ui.wrapper()">
<div v-if="title || $slots.title" :class="ui.title()">
<div v-if="title || !!slots.title" :class="ui.title()">
<slot name="title">
{{ title }}
</slot>
</div>
<template v-if="description || $slots.description">
<template v-if="description || !!slots.description">
<component :is="description" v-if="description && isVNode(description)" />
<div v-else :class="ui.description()">
<slot name="description">

View File

@@ -44,7 +44,7 @@ import { ULink, UIcon, UAvatar } from '#components'
import { omit } from '#ui/utils'
const props = defineProps<BreadcrumbProps<T>>()
defineSlots<BreadcrumbSlots<T>>()
const slots = defineSlots<BreadcrumbSlots<T>>()
const appConfig = useAppConfig()
@@ -63,7 +63,7 @@ const ui = computed(() => tv({ extend: breadcrumb, slots: props.ui })())
<UIcon v-else-if="item.icon" :name="item.icon" :class="ui.itemLeadingIcon({ active: index === items!.length - 1 })" />
</slot>
<span v-if="item.label || $slots[item.slot ? `${item.slot}-label`: 'item-label']" :class="ui.itemLabel()">
<span v-if="item.label || !!slots[item.slot ? `${item.slot}-label`: 'item-label']" :class="ui.itemLabel()">
<slot :name="item.slot ? `${item.slot}-label`: 'item-label'" :item="item" :active="index === items!.length - 1" :index="index">
{{ item.label }}
</slot>

View File

@@ -36,7 +36,7 @@ import { computed } from 'vue'
import { useForwardProps } from 'radix-vue'
import { reactiveOmit } from '@vueuse/core'
import { useComponentIcons, useButtonGroup } from '#imports'
import { UIcon, UAvatar, ULink } from '#components'
import { UIcon, ULink } from '#components'
const props = defineProps<ButtonProps>()
const slots = defineSlots<ButtonSlots>()
@@ -44,7 +44,7 @@ const slots = defineSlots<ButtonSlots>()
const linkProps = useForwardProps(reactiveOmit(props, 'type', 'label', 'color', 'variant', 'size', 'icon', 'leading', 'leadingIcon', 'trailing', 'trailingIcon', 'loading', 'loadingIcon', 'square', 'block', 'disabled', 'truncate', 'class', 'ui'))
const { orientation, size: buttonSize } = useButtonGroup<ButtonProps>(props)
const { isLeading, isTrailing, leadingIconName, trailingIconName, avatarSize } = useComponentIcons<ButtonProps>(props)
const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponentIcons<ButtonProps>(props)
const ui = computed(() => tv({ extend: button, slots: props.ui })({
color: props.color,
@@ -64,10 +64,9 @@ const ui = computed(() => tv({ extend: button, slots: props.ui })({
<ULink :type="type" :disabled="disabled || loading" :class="ui.base({ class: props.class })" v-bind="linkProps" raw>
<slot name="leading">
<UIcon v-if="isLeading && leadingIconName" :name="leadingIconName" :class="ui.leadingIcon()" />
<UAvatar v-else-if="avatar" :size="avatarSize" v-bind="avatar" :class="ui.leadingAvatar()" />
</slot>
<span v-if="label || $slots.default" :class="ui.label()">
<span v-if="label || !!slots.default" :class="ui.label()">
<slot>
{{ label }}
</slot>

View File

@@ -26,22 +26,22 @@ import { computed } from 'vue'
import { Primitive } from 'radix-vue'
const props = withDefaults(defineProps<CardProps>(), { as: 'div' })
defineSlots<CardSlots>()
const slots = defineSlots<CardSlots>()
const ui = computed(() => tv({ extend: card, slots: props.ui })())
</script>
<template>
<Primitive :as="as" :class="ui.root({ class: props.class })">
<div v-if="$slots.header" :class="ui.header()">
<div v-if="!!slots.header" :class="ui.header()">
<slot name="header" />
</div>
<div v-if="$slots.default" :class="ui.body()">
<div v-if="!!slots.default" :class="ui.body()">
<slot />
</div>
<div v-if="$slots.footer" :class="ui.footer()">
<div v-if="!!slots.footer" :class="ui.footer()">
<slot name="footer" />
</div>
</Primitive>

View File

@@ -46,7 +46,7 @@ import { reactivePick } from '@vueuse/core'
import { useId, useAppConfig, useFormField } from '#imports'
const props = defineProps<CheckboxProps>()
defineSlots<CheckboxSlots>()
const slots = defineSlots<CheckboxSlots>()
const modelValue = defineModel<boolean | undefined>({ default: undefined })
@@ -104,13 +104,13 @@ function onChecked() {
</CheckboxRoot>
</div>
<div v-if="(label || $slots.label) || (description || $slots.description)" :class="ui.wrapper()">
<Label v-if="label || $slots.label" :for="id" :class="ui.label()">
<div v-if="(label || !!slots.label) || (description || !!slots.description)" :class="ui.wrapper()">
<Label v-if="label || !!slots.label" :for="id" :class="ui.label()">
<slot name="label" :label="label">
{{ label }}
</slot>
</Label>
<p v-if="description || $slots.description" :class="ui.description()">
<p v-if="description || !!slots.description" :class="ui.description()">
<slot name="description" :description="description">
{{ description }}
</slot>

View File

@@ -29,7 +29,7 @@ import { reactivePick } from '@vueuse/core'
const props = defineProps<CollapsibleProps>()
const emits = defineEmits<CollapsibleEmits>()
defineSlots<CollapsibleSlots>()
const slots = defineSlots<CollapsibleSlots>()
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'defaultOpen', 'open', 'disabled'), emits)
@@ -38,7 +38,7 @@ const ui = computed(() => tv({ extend: collapsible, slots: props.ui })())
<template>
<CollapsibleRoot v-bind="rootProps" :class="ui.root({ class: props.class })">
<CollapsibleTrigger v-if="$slots.default" as-child>
<CollapsibleTrigger v-if="!!slots.default" as-child>
<slot />
</CollapsibleTrigger>

View File

@@ -35,7 +35,7 @@ export interface CommandPaletteGroup<T> {
highlightedIcon?: string
}
export interface CommandPaletteProps<G, T> extends Pick<ComboboxRootProps, 'as' | 'multiple' | 'disabled' | 'modelValue' | 'defaultValue'>, Omit<UseComponentIconsProps, 'leading' | 'trailing' | 'icon' | 'avatar'> {
export interface CommandPaletteProps<G, T> extends Pick<ComboboxRootProps, 'as' | 'multiple' | 'disabled' | 'modelValue' | 'defaultValue'>, Pick<UseComponentIconsProps, 'loading' | 'loadingIcon'> {
/**
* The icon displayed in the input.
* @defaultValue `appConfig.ui.icons.search`
@@ -55,9 +55,7 @@ export interface CommandPaletteProps<G, T> extends Pick<ComboboxRootProps, 'as'
ui?: Partial<typeof commandPalette.slots>
}
export type CommandPaletteEmits<T> = {
close: []
} & Omit<ComboboxRootEmits<T>, 'update:open'>
export type CommandPaletteEmits<T> = ComboboxRootEmits<T>
type SlotProps<T> = (props: { item: T, index: number }) => any
@@ -87,7 +85,7 @@ const props = withDefaults(defineProps<CommandPaletteProps<G, T>>(), {
placeholder: 'Type a command or search...'
})
const emits = defineEmits<CommandPaletteEmits<T>>()
defineSlots<CommandPaletteSlots<G, T>>()
const slots = defineSlots<CommandPaletteSlots<G, T>>()
const searchTerm = defineModel<string>('searchTerm', { default: '' })
@@ -150,7 +148,7 @@ const groups = computed(() => {
:icon="icon || appConfig.ui.icons.search"
:class="ui.input()"
>
<template v-if="close || $slots.close" #trailing>
<template v-if="close || !!slots.close" #trailing>
<slot name="close" :class="ui.close()">
<UButton
v-if="close"
@@ -161,7 +159,7 @@ const groups = computed(() => {
aria-label="Close"
v-bind="typeof close === 'object' ? close : {}"
:class="ui.close()"
@click="emits('close')"
@click="emits('update:open', false)"
/>
</slot>
</template>
@@ -204,7 +202,7 @@ const groups = computed(() => {
/>
</slot>
<span v-if="item.label || $slots[item.slot ? `${item.slot}-label` : group.slot ? `${group.slot}-label` : `item-label`]" :class="ui.itemLabel()">
<span v-if="item.label || !!slots[item.slot ? `${item.slot}-label` : group.slot ? `${group.slot}-label` : `item-label`]" :class="ui.itemLabel()">
<slot :name="item.slot ? `${item.slot}-label` : group.slot ? `${group.slot}-label` : `item-label`" :item="item" :index="index">
<span v-if="item.prefix" :class="ui.itemLabelPrefix()">{{ item.prefix }}</span>

View File

@@ -73,7 +73,7 @@ const ui = computed(() => tv({ extend: contextMenu, slots: props.ui })())
<template>
<ContextMenuRoot v-bind="rootProps">
<ContextMenuTrigger v-if="$slots.default" as-child :disabled="disabled">
<ContextMenuTrigger v-if="!!slots.default" as-child :disabled="disabled">
<slot />
</ContextMenuTrigger>

View File

@@ -49,13 +49,13 @@ const groups = computed(() => props.items?.length ? (Array.isArray(props.items[0
<UIcon v-else-if="item.icon" :name="item.icon" :class="ui.itemLeadingIcon({ active })" />
</slot>
<span v-if="item.label || $slots[item.slot ? `${item.slot}-label`: 'item-label']" :class="ui.itemLabel()">
<span v-if="item.label || !!slots[item.slot ? `${item.slot}-label`: 'item-label']" :class="ui.itemLabel()">
<slot :name="item.slot ? `${item.slot}-label`: 'item-label'" :item="item" :active="active" :index="index">
{{ item.label }}
</slot>
</span>
<span v-if="item.children?.length || item.kbds?.length || $slots[item.slot ? `${item.slot}-trailing`: 'item-trailing']" :class="ui.itemTrailing()">
<span v-if="item.children?.length || item.kbds?.length || !!slots[item.slot ? `${item.slot}-trailing`: 'item-trailing']" :class="ui.itemTrailing()">
<slot :name="item.slot ? `${item.slot}-trailing`: 'item-trailing'" :item="item" :active="active" :index="index">
<UIcon v-if="item.children?.length" :name="appConfig.ui.icons.chevronRight" :class="ui.itemTrailingIcon()" />
<span v-else-if="item.kbds?.length" :class="ui.itemTrailingKbds()">

View File

@@ -45,7 +45,7 @@ const props = withDefaults(defineProps<DrawerProps>(), {
overlay: true
})
const emits = defineEmits<DrawerEmits>()
defineSlots<DrawerSlots>()
const slots = defineSlots<DrawerSlots>()
const rootProps = useForwardPropsEmits(reactivePick(props, 'activeSnapPoint', 'closeThreshold', 'defaultOpen', 'dismissible', 'fadeFromIndex', 'fixed', 'modal', 'nested', 'open', 'scrollLockTimeout', 'shouldScaleBackground', 'snapPoints'), emits)
const contentProps = toRef(() => props.content)
@@ -55,7 +55,7 @@ const ui = computed(() => tv({ extend: drawer, slots: props.ui })())
<template>
<DrawerRoot v-bind="rootProps">
<DrawerTrigger v-if="$slots.default" as-child>
<DrawerTrigger v-if="!!slots.default" as-child>
<slot />
</DrawerTrigger>
@@ -69,15 +69,15 @@ const ui = computed(() => tv({ extend: drawer, slots: props.ui })())
<slot name="content">
<div :class="ui.container()">
<div v-if="$slots.header || (title || $slots.title) || (description || $slots.description)" :class="ui.header()">
<div v-if="!!slots.header || (title || !!slots.title) || (description || !!slots.description)" :class="ui.header()">
<slot name="header">
<DrawerTitle v-if="title || $slots.title" :class="ui.title()">
<DrawerTitle v-if="title || !!slots.title" :class="ui.title()">
<slot name="title">
{{ title }}
</slot>
</DrawerTitle>
<DrawerDescription v-if="description || $slots.description" :class="ui.description()">
<DrawerDescription v-if="description || !!slots.description" :class="ui.description()">
<slot name="description">
{{ description }}
</slot>
@@ -85,11 +85,11 @@ const ui = computed(() => tv({ extend: drawer, slots: props.ui })())
</slot>
</div>
<div v-if="$slots.body" :class="ui.body()">
<div v-if="!!slots.body" :class="ui.body()">
<slot name="body" />
</div>
<div v-if="$slots.footer" :class="ui.footer()">
<div v-if="!!slots.footer" :class="ui.footer()">
<slot name="footer" />
</div>
</div>

View File

@@ -76,7 +76,7 @@ const ui = computed(() => tv({ extend: dropdownMenu, slots: props.ui })())
<template>
<DropdownMenuRoot v-bind="rootProps">
<DropdownMenuTrigger v-if="$slots.default" as-child :disabled="disabled">
<DropdownMenuTrigger v-if="!!slots.default" as-child :disabled="disabled">
<slot />
</DropdownMenuTrigger>

View File

@@ -49,13 +49,13 @@ const groups = computed(() => props.items?.length ? (Array.isArray(props.items[0
<UIcon v-else-if="item.icon" :name="item.icon" :class="ui.itemLeadingIcon({ active })" />
</slot>
<span v-if="item.label || $slots[item.slot ? `${item.slot}-label`: 'item-label']" :class="ui.itemLabel()">
<span v-if="item.label || !!slots[item.slot ? `${item.slot}-label`: 'item-label']" :class="ui.itemLabel()">
<slot :name="item.slot ? `${item.slot}-label`: 'item-label'" :item="item" :active="active" :index="index">
{{ item.label }}
</slot>
</span>
<span v-if="item.children?.length || item.kbds?.length || $slots[item.slot ? `${item.slot}-trailing`: 'item-trailing']" :class="ui.itemTrailing()">
<span v-if="item.children?.length || item.kbds?.length || !!slots[item.slot ? `${item.slot}-trailing`: 'item-trailing']" :class="ui.itemTrailing()">
<slot :name="item.slot ? `${item.slot}-trailing`: 'item-trailing'" :item="item" :active="active" :index="index">
<UIcon v-if="item.children?.length" :name="appConfig.ui.icons.chevronRight" :class="ui.itemTrailingIcon()" />
<span v-else-if="item.kbds?.length" :class="ui.itemTrailingKbds()">

View File

@@ -42,7 +42,7 @@ import type { FormError } from '#ui/types/form'
import { useId, formFieldInjectionKey } from '#imports'
const props = defineProps<FormFieldProps>()
defineSlots<FormFieldSlots>()
const slots = defineSlots<FormFieldSlots>()
const ui = computed(() => tv({ extend: formField, slots: props.ui })({
size: props.size,
@@ -73,20 +73,20 @@ provide(formFieldInjectionKey, computed(() => ({
<template>
<div :class="ui.root({ class: props.class })">
<div :class="ui.wrapper()">
<div v-if="label || $slots.label" :class="ui.labelWrapper()">
<div v-if="label || !!slots.label" :class="ui.labelWrapper()">
<Label :for="id" :class="ui.label()">
<slot name="label" :label="label">
{{ label }}
</slot>
</Label>
<span v-if="hint || $slots.hint" :class="ui.hint()">
<span v-if="hint || !!slots.hint" :class="ui.hint()">
<slot name="hint" :hint="hint">
{{ hint }}
</slot>
</span>
</div>
<p v-if="description || $slots.description" :class="ui.description()">
<p v-if="description || !!slots.description" :class="ui.description()">
<slot name="description" :description="description">
{{ description }}
</slot>
@@ -96,12 +96,12 @@ provide(formFieldInjectionKey, computed(() => ({
<div :class="[label && ui.container()]">
<slot :error="error" />
<p v-if="(typeof error === 'string' && error) || $slots.error" :class="ui.error()">
<p v-if="(typeof error === 'string' && error) || !!slots.error" :class="ui.error()">
<slot name="error" :error="error">
{{ error }}
</slot>
</p>
<p v-else-if="help || $slots.help" :class="ui.help()">
<p v-else-if="help || !!slots.help" :class="ui.help()">
<slot name="help" :help="help">
{{ help }}
</slot>

View File

@@ -16,10 +16,7 @@ export interface InputProps extends UseComponentIconsProps {
id?: string
name?: string
type?: InputHTMLAttributes['type']
/**
* The placeholder text when the input is empty.
* @defaultValue `'Type a command or search...'`
*/
/** The placeholder text when the input is empty. */
placeholder?: string
color?: InputVariants['color']
variant?: InputVariants['variant']
@@ -39,21 +36,21 @@ export interface InputEmits {
export interface InputSlots {
leading(): any
default(): any
trailing(props: { iconClass: string }): any
trailing(): any
}
</script>
<script lang="ts" setup>
import { ref, computed, onMounted } from 'vue'
import { useComponentIcons, useFormField, useButtonGroup } from '#imports'
import { UIcon, UAvatar } from '#components'
import { UIcon } from '#components'
import { looseToNumber } from '#ui/utils'
defineOptions({ inheritAttrs: false })
const props = withDefaults(defineProps<InputProps>(), {
type: 'text',
autofocusDelay: 100
autofocusDelay: 0
})
const emits = defineEmits<InputEmits>()
const slots = defineSlots<InputSlots>()
@@ -62,7 +59,7 @@ const [modelValue, modelModifiers] = defineModel<string | number>()
const { emitFormBlur, emitFormInput, size: formGroupSize, color, id, name, disabled } = useFormField<InputProps>(props)
const { orientation, size: buttonGroupSize } = useButtonGroup<InputProps>(props)
const { isLeading, isTrailing, leadingIconName, trailingIconName, avatarSize } = useComponentIcons<InputProps>(props)
const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponentIcons<InputProps>(props)
const inputSize = computed(() => buttonGroupSize.value || formGroupSize.value)
@@ -150,15 +147,14 @@ onMounted(() => {
<slot />
<span v-if="isLeading || $slots.leading" :class="ui.leading()">
<span v-if="isLeading || !!slots.leading" :class="ui.leading()">
<slot name="leading">
<UAvatar v-if="avatar" :size="avatarSize" v-bind="avatar" :class="ui.leadingAvatar()" />
<UIcon v-else-if="leadingIconName" :name="leadingIconName" :class="ui.leadingIcon()" />
<UIcon v-if="leadingIconName" :name="leadingIconName" :class="ui.leadingIcon()" />
</slot>
</span>
<span v-if="isTrailing || $slots.trailing" :class="ui.trailing()">
<slot name="trailing" :icon-class="ui.trailingIcon()">
<span v-if="isTrailing || !!slots.trailing" :class="ui.trailing()">
<slot name="trailing">
<UIcon v-if="trailingIconName" :name="trailingIconName" :class="ui.trailingIcon()" />
</slot>
</span>

View File

@@ -1,14 +1,13 @@
<script lang="ts">
import { tv } from 'tailwind-variants'
import type { InputHTMLAttributes } from 'vue'
import { tv, type VariantProps } from 'tailwind-variants'
import type { ComboboxRootProps, ComboboxRootEmits, ComboboxContentProps, ComboboxItemProps, ComboboxArrowProps } from 'radix-vue'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/input-menu'
import type { UseComponentIconsProps } from '#ui/composables/useComponentIcons'
import type { AvatarProps, ChipProps, InputProps } from '#ui/types'
type AcceptableValue = string | number | boolean | Record<string, any>
type ArrayOrWrapped<T> = T extends any[] ? T : Array<T>
import type { AcceptableValue, ArrayOrWrapped } from '#ui/types/utils'
const appConfig = _appConfig as AppConfig & { ui: { inputMenu: Partial<typeof theme> } }
@@ -26,9 +25,21 @@ export interface InputMenuItem extends Pick<ComboboxItemProps, 'disabled'> {
type?: 'label' | 'separator' | 'item'
}
export interface InputMenuProps<T> extends Omit<ComboboxRootProps, 'asChild' | 'dir' | 'filterFunction' | 'displayValue' | 'multiple'>, Omit<UseComponentIconsProps, 'leading' | 'trailing' | 'trailingIcon'> {
type InputMenuVariants = VariantProps<typeof inputMenu>
export interface InputMenuProps<T> extends Omit<ComboboxRootProps, 'asChild' | 'dir' | 'filterFunction' | 'displayValue' | 'multiple'>, UseComponentIconsProps {
id?: string
type?: InputHTMLAttributes['type']
/** The placeholder text when the input is empty. */
placeholder?: string
color?: InputMenuVariants['color']
variant?: InputMenuVariants['variant']
size?: InputMenuVariants['size']
required?: boolean
autofocus?: boolean
autofocusDelay?: number
/**
* The icon displayed in the input.
* The icon displayed to open the menu.
* @defaultValue `appConfig.ui.icons.chevronDown`
*/
trailingIcon?: string
@@ -37,12 +48,6 @@ export interface InputMenuProps<T> extends Omit<ComboboxRootProps, 'asChild' | '
* @defaultValue `appConfig.ui.icons.check`
*/
selectedIcon?: string
placeholder?: InputProps['placeholder']
required?: InputProps['required']
avatar?: InputProps['avatar']
color?: InputProps['color']
variant?: InputProps['variant']
size?: InputProps['size']
content?: Omit<ComboboxContentProps, 'asChild' | 'forceMount'>
arrow?: boolean | Omit<ComboboxArrowProps, 'asChild'>
portal?: boolean
@@ -63,6 +68,8 @@ type SlotProps<T> = (props: { item: T, index: number }) => any
export type InputMenuSlots<T> = {
'leading'(): any
'default'(): any
'trailing'(): any
'empty'(props: { searchTerm?: string }): any
'item': SlotProps<T>
'item-leading': SlotProps<T>
@@ -72,29 +79,44 @@ export type InputMenuSlots<T> = {
</script>
<script setup lang="ts" generic="T extends InputMenuItem | AcceptableValue">
import { computed, toRef } from 'vue'
import { ComboboxRoot, ComboboxAnchor, ComboboxInput, ComboboxTrigger, ComboboxPortal, ComboboxContent, ComboboxViewport, ComboboxEmpty, ComboboxGroup, ComboboxLabel, ComboboxSeparator, ComboboxItem, ComboboxItemIndicator, useForwardProps, useForwardPropsEmits } from 'radix-vue'
import { computed, ref, toRef, onMounted } from 'vue'
import { ComboboxRoot, ComboboxAnchor, ComboboxInput, ComboboxTrigger, ComboboxPortal, ComboboxContent, ComboboxViewport, ComboboxEmpty, ComboboxGroup, ComboboxLabel, ComboboxSeparator, ComboboxItem, ComboboxItemIndicator, useForwardPropsEmits } from 'radix-vue'
import { defu } from 'defu'
import { reactivePick } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { useAppConfig, useFormField, useButtonGroup, useComponentIcons } from '#imports'
import { UIcon, UChip, UAvatar } from '#components'
import { get } from '#ui/utils'
defineOptions({ inheritAttrs: false })
const props = withDefaults(defineProps<InputMenuProps<T>>(), {
type: 'text',
autofocusDelay: 0,
portal: true,
filter: () => ['label']
})
const emits = defineEmits<InputMenuEmits<T>>()
defineSlots<InputMenuSlots<T>>()
const slots = defineSlots<InputMenuSlots<T>>()
const appConfig = useAppConfig()
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'modelValue', 'defaultValue', 'open', 'defaultOpen', 'disabled', 'name'), emits)
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'modelValue', 'defaultValue', 'open', 'defaultOpen'), emits)
const contentProps = toRef(() => defu(props.content, { side: 'bottom', sideOffset: 8, position: 'popper' }) as ComboboxContentProps)
const inputProps = useForwardProps(reactivePick(props, 'name', 'loading', 'loadingIcon', 'placeholder', 'required', 'color', 'variant', 'size'))
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { emitFormBlur, emitFormInput, size: formGroupSize, color, id, name, disabled } = useFormField<InputProps>(props)
const { orientation, size: buttonGroupSize } = useButtonGroup<InputProps>(props)
const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponentIcons<InputProps>(defu(props, { trailingIcon: appConfig.ui.icons.chevronDown }))
const ui = computed(() => tv({ extend: inputMenu, slots: props.ui })())
const inputSize = computed(() => buttonGroupSize.value || formGroupSize.value)
const ui = computed(() => tv({ extend: inputMenu, slots: props.ui })({
color: color.value,
variant: props.variant,
size: inputSize?.value,
loading: props.loading,
leading: isLeading.value || !!slots.leading,
trailing: isTrailing.value || !!slots.trailing,
buttonGroup: orientation.value
}))
function displayValue(val: AcceptableValue) {
if (typeof val === 'object') {
@@ -125,24 +147,53 @@ function filterFunction(items: ArrayOrWrapped<AcceptableValue>, searchTerm: stri
}
const groups = computed(() => props.items?.length ? (Array.isArray(props.items[0]) ? props.items : [props.items]) as InputMenuItem[][] : [])
const inputRef = ref<InstanceType<typeof ComboboxInput> | null>(null)
function autoFocus() {
if (props.autofocus) {
inputRef.value?.$el?.focus()
}
}
onMounted(() => {
setTimeout(() => {
autoFocus()
}, props.autofocusDelay)
})
</script>
<template>
<ComboboxRoot v-slot="{ modelValue }" v-bind="rootProps" :display-value="displayValue" :filter-function="filterFunction" :class="ui.root({ class: props.class })">
<ComboboxRoot
:id="id"
:name="name"
:disabled="disabled"
v-bind="rootProps"
:display-value="displayValue"
:filter-function="filterFunction"
:class="ui.root({ class: props.class })"
>
<ComboboxAnchor as-child>
<ComboboxInput as-child>
<UInput v-bind="{ ...inputProps, ...$attrs }" :icon="(modelValue as InputMenuItem)?.icon || icon" :avatar="(modelValue as InputMenuItem)?.avatar || avatar" :class="ui.input()">
<template v-if="$slots.leading" #leading>
<slot name="leading" />
</template>
<ComboboxInput
ref="inputRef"
v-bind="$attrs"
:type="type"
:placeholder="placeholder"
:required="required"
:class="ui.base()"
/>
<template #trailing="{ iconClass }">
<ComboboxTrigger :class="ui.trigger()">
<UIcon :name="trailingIcon || appConfig.ui.icons.chevronDown" :class="iconClass" />
</ComboboxTrigger>
</template>
</UInput>
</ComboboxInput>
<span v-if="isLeading || !!slots.leading" :class="ui.leading()">
<slot name="leading">
<UIcon v-if="leadingIconName" :name="leadingIconName" :class="ui.leadingIcon()" />
</slot>
</span>
<ComboboxTrigger v-if="isTrailing || !!slots.trailing" :class="ui.trailing()">
<slot name="trailing">
<UIcon v-if="trailingIconName" :name="trailingIconName" :class="ui.trailingIcon()" />
</slot>
</ComboboxTrigger>
</ComboboxAnchor>
<ComboboxPortal :disabled="!portal">

View File

@@ -51,7 +51,7 @@ const props = withDefaults(defineProps<ModalProps>(), {
transition: true
})
const emits = defineEmits<ModalEmits>()
defineSlots<ModalSlots>()
const slots = defineSlots<ModalSlots>()
const rootProps = useForwardPropsEmits(reactivePick(props, 'open', 'defaultOpen', 'modal'), emits)
const contentProps = toRef(() => props.content)
@@ -76,7 +76,7 @@ const ui = computed(() => tv({ extend: modal, slots: props.ui })({
<template>
<DialogRoot v-bind="rootProps">
<DialogTrigger v-if="$slots.default" as-child>
<DialogTrigger v-if="!!slots.default" as-child>
<slot />
</DialogTrigger>
@@ -85,15 +85,15 @@ const ui = computed(() => tv({ extend: modal, slots: props.ui })({
<DialogContent :class="ui.content({ class: props.class })" v-bind="contentProps" v-on="contentEvents">
<slot name="content">
<div v-if="$slots.header || (title || $slots.title) || (description || $slots.description) || (close !== null || $slots.close)" :class="ui.header()">
<div v-if="!!slots.header || (title || !!slots.title) || (description || !!slots.description) || (close !== null || !!slots.close)" :class="ui.header()">
<slot name="header">
<DialogTitle v-if="title || $slots.title" :class="ui.title()">
<DialogTitle v-if="title || !!slots.title" :class="ui.title()">
<slot name="title">
{{ title }}
</slot>
</DialogTitle>
<DialogDescription v-if="description || $slots.description" :class="ui.description()">
<DialogDescription v-if="description || !!slots.description" :class="ui.description()">
<slot name="description">
{{ description }}
</slot>
@@ -116,11 +116,11 @@ const ui = computed(() => tv({ extend: modal, slots: props.ui })({
</slot>
</div>
<div v-if="$slots.body" :class="ui.body()">
<div v-if="!!slots.body" :class="ui.body()">
<slot name="body" />
</div>
<div v-if="$slots.footer" :class="ui.footer()">
<div v-if="!!slots.footer" :class="ui.footer()">
<slot name="footer" />
</div>
</slot>

View File

@@ -48,7 +48,7 @@ import { omit } from '#ui/utils'
const props = withDefaults(defineProps<NavigationMenuProps<T>>(), { orientation: 'horizontal' })
const emits = defineEmits<NavigationMenuEmits>()
defineSlots<NavigationMenuSlots<T>>()
const slots = defineSlots<NavigationMenuSlots<T>>()
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'modelValue', 'defaultValue', 'delayDuration', 'skipDelayDuration', 'orientation'), emits)
@@ -71,13 +71,13 @@ const lists = computed(() => props.items?.length ? (Array.isArray(props.items[0]
<UIcon v-else-if="item.icon" :name="item.icon" :class="ui.itemLeadingIcon({ active, disabled: !!item.disabled })" />
</slot>
<span v-if="item.label || $slots[item.slot ? `${item.slot}-label`: 'item-label']" :class="ui.itemLabel()">
<span v-if="item.label || !!slots[item.slot ? `${item.slot}-label`: 'item-label']" :class="ui.itemLabel()">
<slot :name="item.slot ? `${item.slot}-label`: 'item-label'" :item="item" :active="active" :index="index">
{{ item.label }}
</slot>
</span>
<span v-if="item.badge || $slots[item.slot ? `${item.slot}-trailing`: 'item-trailing']" :class="ui.itemTrailing()">
<span v-if="item.badge || !!slots[item.slot ? `${item.slot}-trailing`: 'item-trailing']" :class="ui.itemTrailing()">
<slot :name="item.slot ? `${item.slot}-trailing`: 'item-trailing'" :item="item" :active="active" :index="index">
<UBadge
v-if="item.badge"

View File

@@ -62,7 +62,7 @@ const props = withDefaults(defineProps<PaginationProps>(), {
showControls: true
})
const emits = defineEmits<PaginationEmits>()
defineSlots<PaginationSlots>()
const slots = defineSlots<PaginationSlots>()
const appConfig = useAppConfig()
@@ -74,12 +74,12 @@ const ui = computed(() => tv({ extend: pagination, slots: props.ui })())
<template>
<PaginationRoot v-slot="{ page, pageCount }" v-bind="rootProps" :class="ui.root({ class: props.class })">
<PaginationList v-slot="{ items }" :class="ui.list()">
<PaginationFirst v-if="showControls || $slots.first" as-child>
<PaginationFirst v-if="showControls || !!slots.first" as-child>
<slot name="first">
<UButton :color="color" :variant="variant" :size="size" :icon="firstIcon || appConfig.ui.icons.chevronDoubleLeft" />
</slot>
</PaginationFirst>
<PaginationPrev v-if="showControls || $slots.prev" as-child>
<PaginationPrev v-if="showControls || !!slots.prev" as-child>
<slot name="prev">
<UButton :color="color" :variant="variant" :size="size" :icon="prevIcon || appConfig.ui.icons.chevronLeft" />
</slot>
@@ -106,12 +106,12 @@ const ui = computed(() => tv({ extend: pagination, slots: props.ui })())
</PaginationEllipsis>
</template>
<PaginationNext v-if="showControls || $slots.next" as-child>
<PaginationNext v-if="showControls || !!slots.next" as-child>
<slot name="next">
<UButton :color="color" :variant="variant" :size="size" :icon="nextIcon || appConfig.ui.icons.chevronRight" />
</slot>
</PaginationNext>
<PaginationLast v-if="showControls || $slots.last" as-child>
<PaginationLast v-if="showControls || !!slots.last" as-child>
<slot name="last">
<UButton :color="color" :variant="variant" :size="size" :icon="lastIcon || appConfig.ui.icons.chevronDoubleRight" />
</slot>

View File

@@ -44,7 +44,7 @@ const props = withDefaults(defineProps<PopoverProps>(), {
closeDelay: 0
})
const emits = defineEmits<PopoverEmits>()
defineSlots<PopoverSlots>()
const slots = defineSlots<PopoverSlots>()
const pick = props.mode === 'hover' ? reactivePick(props, 'defaultOpen', 'open', 'openDelay', 'closeDelay') : reactivePick(props, 'defaultOpen', 'open', 'modal')
const rootProps = useForwardPropsEmits(pick, emits)
@@ -58,7 +58,7 @@ const Component = computed(() => props.mode === 'hover' ? HoverCard : Popover)
<template>
<Component.Root v-bind="rootProps">
<Component.Trigger v-if="$slots.default" as-child>
<Component.Trigger v-if="!!slots.default" as-child>
<slot />
</Component.Trigger>

View File

@@ -45,7 +45,7 @@ import { useId, useFormField } from '#imports'
const props = withDefaults(defineProps<RadioGroupProps<T>>(), { orientation: 'vertical' })
const emits = defineEmits<RadioGroupEmits>()
defineSlots<RadioGroupSlots<T>>()
const slots = defineSlots<RadioGroupSlots<T>>()
const modelValue = defineModel<T>({
set(value) {
@@ -106,7 +106,7 @@ function onUpdate() {
@update:model-value="onUpdate"
>
<fieldset :class="ui.fieldset()">
<legend v-if="legend || $slots.legend" :class="ui.legend()">
<legend v-if="legend || !!slots.legend" :class="ui.legend()">
<slot name="legend">
{{ legend }}
</slot>
@@ -127,7 +127,7 @@ function onUpdate() {
<Label :class="ui.label()" :for="option.id">
<slot name="label" v-bind="{ option }">{{ option.label }}</slot>
</Label>
<p v-if="option.description || $slots.description" :class="ui.description()">
<p v-if="option.description || !!slots.description" :class="ui.description()">
<slot name="description" v-bind="{ option }">
{{ option.description }}
</slot>

View File

@@ -0,0 +1,173 @@
<script lang="ts">
import { tv, type VariantProps } from 'tailwind-variants'
import type { SelectRootProps, SelectRootEmits, SelectContentProps, SelectArrowProps, SelectItemProps } from 'radix-vue'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/select'
import type { UseComponentIconsProps } from '#ui/composables/useComponentIcons'
import type { AvatarProps, ChipProps, InputProps } from '#ui/types'
import type { AcceptableValue } from '#ui/types/utils'
const appConfig = _appConfig as AppConfig & { ui: { select: Partial<typeof theme> } }
const select = tv({ extend: tv(theme), ...(appConfig.ui?.select || {}) })
export interface SelectItem extends Pick<SelectItemProps, 'disabled' | 'value'> {
label?: string
icon?: string
avatar?: AvatarProps
chip?: ChipProps
/**
* The item type.
* @defaultValue `'item'`
*/
type?: 'label' | 'separator' | 'item'
}
type SelectVariants = VariantProps<typeof select>
export interface SelectProps<T> extends Omit<SelectRootProps, 'asChild' | 'dir'>, UseComponentIconsProps {
id?: string
/** The placeholder text when the select is empty. */
placeholder?: string
color?: SelectVariants['color']
variant?: SelectVariants['variant']
size?: SelectVariants['size']
/**
* The icon displayed to open the menu.
* @defaultValue `appConfig.ui.icons.chevronDown`
*/
trailingIcon?: string
/**
* The icon displayed when an item is selected.
* @defaultValue `appConfig.ui.icons.check`
*/
selectedIcon?: string
content?: Omit<SelectContentProps, 'asChild' | 'forceMount'>
arrow?: boolean | Omit<SelectArrowProps, 'asChild'>
portal?: boolean
items?: T[] | T[][]
class?: any
ui?: Partial<typeof select.slots>
}
export interface SelectEmits extends SelectRootEmits {}
type SlotProps<T> = (props: { item: T, index: number }) => any
export interface SelectSlots<T> {
'leading'(): any
'trailing'(): any
'item': SlotProps<T>
'item-leading': SlotProps<T>
'item-label': SlotProps<T>
'item-trailing': SlotProps<T>
}
</script>
<script setup lang="ts" generic="T extends SelectItem | AcceptableValue">
import { computed, toRef } from 'vue'
import { SelectRoot, SelectTrigger, SelectValue, SelectPortal, SelectContent, SelectViewport, SelectLabel, SelectGroup, SelectItem, SelectItemIndicator, SelectItemText, SelectSeparator, useForwardPropsEmits } from 'radix-vue'
import { defu } from 'defu'
import { reactivePick } from '@vueuse/core'
import { useAppConfig, useComponentIcons, useFormField, useButtonGroup } from '#imports'
import { UIcon, UChip, UAvatar } from '#components'
const props = withDefaults(defineProps<SelectProps<T>>(), {
portal: true
})
const emits = defineEmits<SelectEmits>()
const slots = defineSlots<SelectSlots<T>>()
const appConfig = useAppConfig()
const rootProps = useForwardPropsEmits(reactivePick(props, 'modelValue', 'defaultValue', 'open', 'defaultOpen', 'disabled', 'autocomplete', 'required'), emits)
const contentProps = toRef(() => defu(props.content, { side: 'bottom', sideOffset: 8, position: 'popper' }) as SelectContentProps)
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { emitFormBlur, emitFormInput, size: formGroupSize, color, id, name, disabled } = useFormField<InputProps>(props)
const { orientation, size: buttonGroupSize } = useButtonGroup<InputProps>(props)
const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponentIcons<InputProps>(defu(props, { trailingIcon: appConfig.ui.icons.chevronDown }))
const selectSize = computed(() => buttonGroupSize.value || formGroupSize.value)
const ui = computed(() => tv({ extend: select, slots: props.ui })({
color: color.value,
variant: props.variant,
size: selectSize?.value,
loading: props.loading,
leading: isLeading.value || !!slots.leading,
trailing: isTrailing.value || !!slots.trailing,
buttonGroup: orientation.value
}))
const groups = computed(() => props.items?.length ? (Array.isArray(props.items[0]) ? props.items : [props.items]) as SelectItem[][] : [])
</script>
<template>
<SelectRoot v-bind="rootProps" :id="id" :name="name" :disabled="disabled">
<SelectTrigger :class="ui.base({ class: props.class })">
<span v-if="isLeading || !!slots.leading" :class="ui.leading()">
<slot name="leading">
<UIcon v-if="leadingIconName" :name="leadingIconName" :class="ui.leadingIcon()" />
</slot>
</span>
<SelectValue :placeholder="placeholder" :class="ui.placeholder()" />
<span v-if="isTrailing || !!slots.trailing" :class="ui.trailing()">
<slot name="trailing">
<UIcon v-if="trailingIconName" :name="trailingIconName" :class="ui.trailingIcon()" />
</slot>
</span>
</SelectTrigger>
<SelectPortal :disabled="!portal">
<SelectContent :class="ui.content()" v-bind="contentProps">
<SelectViewport :class="ui.viewport()">
<SelectGroup v-for="(group, groupIndex) in groups" :key="`group-${groupIndex}`" :class="ui.group()">
<template v-for="(item, index) in group" :key="`group-${groupIndex}-${index}`">
<SelectLabel v-if="item?.type === 'label'" :class="ui.label()">
{{ item.label }}
</SelectLabel>
<SelectSeparator v-else-if="item?.type === 'separator'" :class="ui.separator()" />
<SelectItem
v-else
:class="ui.item()"
:disabled="item.disabled"
:value="typeof item === 'object' ? item.value : item"
>
<slot name="item" :item="(item as T)" :index="index">
<slot name="item-leading" :item="(item as T)" :index="index">
<UAvatar v-if="item.avatar" size="2xs" v-bind="item.avatar" :class="ui.itemLeadingAvatar()" />
<UIcon v-else-if="item.icon" :name="item.icon" :class="ui.itemLeadingIcon()" />
<UChip
v-else-if="item.chip"
size="md"
inset
standalone
v-bind="item.chip"
:class="ui.itemLeadingChip()"
/>
</slot>
<SelectItemText :class="ui.itemLabel()">
<slot name="item-label" :item="(item as T)" :index="index">
{{ typeof item === 'object' ? item.label : item }}
</slot>
</SelectItemText>
<span :class="ui.itemTrailing()">
<slot name="item-trailing" :item="(item as T)" :index="index" />
<SelectItemIndicator as-child>
<UIcon :name="selectedIcon || appConfig.ui.icons.check" :class="ui.itemTrailingSelectedIcon()" />
</SelectItemIndicator>
</span>
</slot>
</SelectItem>
</template>
</SelectGroup>
</SelectViewport>
</SelectContent>
</SelectPortal>
</SelectRoot>
</template>

View File

@@ -38,7 +38,7 @@ const props = withDefaults(defineProps<SeparatorProps>(), {
as: 'div',
orientation: 'horizontal'
})
defineSlots<SeparatorSlots>()
const slots = defineSlots<SeparatorSlots>()
const rootProps = useForwardProps(reactivePick(props, 'as', 'decorative', 'orientation'))
@@ -54,7 +54,7 @@ const ui = computed(() => tv({ extend: separator, slots: props.ui })({
<Separator v-bind="rootProps" :class="ui.root({ class: props.class })">
<div :class="ui.border()" />
<template v-if="label || icon || avatar || $slots.default">
<template v-if="label || icon || avatar || !!slots.default">
<div :class="ui.container()">
<slot>
<span v-if="label" :class="ui.label()">{{ label }}</span>

View File

@@ -54,7 +54,7 @@ const props = withDefaults(defineProps<SlideoverProps>(), {
side: 'right'
})
const emits = defineEmits<SlideoverEmits>()
defineSlots<SlideoverSlots>()
const slots = defineSlots<SlideoverSlots>()
const rootProps = useForwardPropsEmits(reactivePick(props, 'open', 'defaultOpen', 'modal'), emits)
const contentProps = toRef(() => props.content)
@@ -79,7 +79,7 @@ const ui = computed(() => tv({ extend: slideover, slots: props.ui })({
<template>
<DialogRoot v-bind="rootProps">
<DialogTrigger v-if="$slots.default" as-child>
<DialogTrigger v-if="!!slots.default" as-child>
<slot />
</DialogTrigger>
@@ -88,15 +88,15 @@ const ui = computed(() => tv({ extend: slideover, slots: props.ui })({
<DialogContent :data-side="side" :class="ui.content({ class: props.class })" v-bind="contentProps" v-on="contentEvents">
<slot name="content">
<div v-if="$slots.header || (title || $slots.title) || (description || $slots.description) || (close !== null || $slots.close)" :class="ui.header()">
<div v-if="!!slots.header || (title || !!slots.title) || (description || !!slots.description) || (close !== null || !!slots.close)" :class="ui.header()">
<slot name="header">
<DialogTitle v-if="title || $slots.title" :class="ui.title()">
<DialogTitle v-if="title || !!slots.title" :class="ui.title()">
<slot name="title">
{{ title }}
</slot>
</DialogTitle>
<DialogDescription v-if="description || $slots.description" :class="ui.description()">
<DialogDescription v-if="description || !!slots.description" :class="ui.description()">
<slot name="description">
{{ description }}
</slot>
@@ -123,7 +123,7 @@ const ui = computed(() => tv({ extend: slideover, slots: props.ui })({
<slot name="body" />
</div>
<div v-if="$slots.footer" :class="ui.footer()">
<div v-if="!!slots.footer" :class="ui.footer()">
<slot name="footer" />
</div>
</slot>

View File

@@ -11,7 +11,7 @@ const slider = tv({ extend: tv(theme), ...(appConfig.ui?.slider || {}) })
type SliderVariants = VariantProps<typeof slider>
export interface SliderProps extends Omit<SliderRootProps, 'asChild' | 'defaultValue' | 'dir'> {
export interface SliderProps extends Omit<SliderRootProps, 'asChild' | 'modelValue' | 'defaultValue' | 'dir'> {
size?: SliderVariants['size']
color?: SliderVariants['color']
defaultValue?: number | number[]

View File

@@ -38,7 +38,7 @@ import { reactivePick } from '@vueuse/core'
import { useId, useAppConfig, useFormField } from '#imports'
const props = defineProps<SwitchProps>()
defineSlots<SwitchSlots>()
const slots = defineSlots<SwitchSlots>()
const modelValue = defineModel<boolean | undefined>({ default: undefined })
@@ -86,13 +86,13 @@ async function onChecked() {
</SwitchThumb>
</SwitchRoot>
</div>
<div v-if="(label || $slots.label) || (description || $slots.description)" :class="ui.wrapper()">
<Label v-if="label || $slots.label" :for="id" :class="ui.label()">
<div v-if="(label || !!slots.label) || (description || !!slots.description)" :class="ui.wrapper()">
<Label v-if="label || !!slots.label" :for="id" :class="ui.label()">
<slot name="label" :label="label">
{{ label }}
</slot>
</Label>
<p v-if="description || $slots.description" :class="ui.description()">
<p v-if="description || !!slots.description" :class="ui.description()">
<slot name="description" :description="description">
{{ description }}
</slot>

View File

@@ -49,7 +49,7 @@ const props = withDefaults(defineProps<TabsProps<T>>(), {
orientation: 'horizontal'
})
const emits = defineEmits<TabsEmits>()
defineSlots<TabsSlots<T>>()
const slots = defineSlots<TabsSlots<T>>()
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'defaultValue', 'orientation', 'activationMode', 'modelValue'), emits)
const contentProps = toRef(() => defu(props.content, { forceMount: true }) as TabsContentProps)
@@ -68,7 +68,7 @@ const ui = computed(() => tv({ extend: tabs, slots: props.ui })({ orientation: p
<UIcon v-else-if="item.icon" :name="item.icon" :class="ui.leadingIcon()" />
</slot>
<span v-if="item.label || $slots.default" :class="ui.label()">
<span v-if="item.label || !!slots.default" :class="ui.label()">
<slot :item="item" :index="index">{{ item.label }}</slot>
</span>

View File

@@ -47,7 +47,7 @@ defineOptions({ inheritAttrs: false })
const props = withDefaults(defineProps<TextareaProps>(), {
rows: 3,
maxrows: 0,
autofocusDelay: 100
autofocusDelay: 0
})
defineSlots<TextareaSlots>()
const emits = defineEmits<TextareaEmits>()

View File

@@ -45,7 +45,7 @@ import { UIcon, UAvatar } from '#components'
const props = defineProps<ToastProps>()
const emits = defineEmits<ToastEmits>()
defineSlots<ToastSlots>()
const slots = defineSlots<ToastSlots>()
const toaster = inject<ToasterContext>('Toaster')
@@ -89,12 +89,12 @@ defineExpose({
</slot>
<div :class="ui.wrapper()">
<ToastTitle v-if="title || $slots.title" :class="ui.title()">
<ToastTitle v-if="title || !!slots.title" :class="ui.title()">
<slot name="title">
{{ title }}
</slot>
</ToastTitle>
<template v-if="description || $slots.description">
<template v-if="description || !!slots.description">
<ToastDescription v-if="description && isVNode(description)" :as="description" />
<ToastDescription v-else :class="ui.description()">
<slot name="description">

View File

@@ -37,7 +37,7 @@ import { UKbd } from '#components'
const props = withDefaults(defineProps<TooltipProps>(), { portal: true })
const emits = defineEmits<TooltipEmits>()
defineSlots<TooltipSlots>()
const slots = defineSlots<TooltipSlots>()
const rootProps = useForwardPropsEmits(reactivePick(props, 'defaultOpen', 'open', 'delayDuration'), emits)
const contentProps = toRef(() => defu(props.content, { side: 'bottom', sideOffset: 8 }) as TooltipContentProps)
@@ -48,7 +48,7 @@ const ui = computed(() => tv({ extend: tooltip, slots: props.ui })({ side: conte
<template>
<TooltipRoot v-bind="rootProps">
<TooltipTrigger v-if="$slots.default" as-child>
<TooltipTrigger v-if="!!slots.default" as-child>
<slot />
</TooltipTrigger>