mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-14 20:19:34 +01:00
feat(module)!: migrate to reka-ui (#2448)
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { tv } from 'tailwind-variants'
|
||||
import type { AccordionRootProps, AccordionRootEmits } from 'radix-vue'
|
||||
import type { AccordionRootProps, AccordionRootEmits } from 'reka-ui'
|
||||
import type { AppConfig } from '@nuxt/schema'
|
||||
import _appConfig from '#build/app.config'
|
||||
import theme from '#build/ui/accordion'
|
||||
@@ -22,7 +22,7 @@ export interface AccordionItem {
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
export interface AccordionProps<T> extends Pick<AccordionRootProps, 'collapsible' | 'defaultValue' | 'modelValue' | 'type' | 'disabled'> {
|
||||
export interface AccordionProps<T> extends Pick<AccordionRootProps, 'collapsible' | 'defaultValue' | 'modelValue' | 'type' | 'disabled' | 'unmountOnHide'> {
|
||||
/**
|
||||
* The element or component this component should render as.
|
||||
* @defaultValue 'div'
|
||||
@@ -89,7 +89,7 @@ extendDevtoolsMeta({
|
||||
|
||||
<script setup lang="ts" generic="T extends AccordionItem">
|
||||
import { computed } from 'vue'
|
||||
import { AccordionRoot, AccordionItem, AccordionHeader, AccordionTrigger, AccordionContent, useForwardPropsEmits } from 'radix-vue'
|
||||
import { AccordionRoot, AccordionItem, AccordionHeader, AccordionTrigger, AccordionContent, useForwardPropsEmits } from 'reka-ui'
|
||||
import { reactivePick } from '@vueuse/core'
|
||||
import { useAppConfig } from '#imports'
|
||||
import { get } from '../utils'
|
||||
@@ -104,7 +104,7 @@ const emits = defineEmits<AccordionEmits>()
|
||||
const slots = defineSlots<AccordionSlots<T>>()
|
||||
|
||||
const appConfig = useAppConfig()
|
||||
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'collapsible', 'defaultValue', 'disabled', 'modelValue', 'type'), emits)
|
||||
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'collapsible', 'defaultValue', 'disabled', 'modelValue', 'type', 'unmountOnHide'), emits)
|
||||
|
||||
const ui = computed(() => accordion({
|
||||
disabled: props.disabled
|
||||
|
||||
@@ -64,7 +64,7 @@ extendDevtoolsMeta<AlertProps>({ defaultProps: { title: 'Heads up!' } })
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { Primitive } from 'radix-vue'
|
||||
import { Primitive } from 'reka-ui'
|
||||
import { useAppConfig } from '#imports'
|
||||
import { useLocale } from '../composables/useLocale'
|
||||
import UIcon from './Icon.vue'
|
||||
@@ -75,8 +75,8 @@ const props = defineProps<AlertProps>()
|
||||
const emits = defineEmits<AlertEmits>()
|
||||
const slots = defineSlots<AlertSlots>()
|
||||
|
||||
const appConfig = useAppConfig()
|
||||
const { t } = useLocale()
|
||||
const appConfig = useAppConfig()
|
||||
|
||||
const multiline = computed(() => !!props.title && !!props.description)
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<script lang="ts">
|
||||
import type { ConfigProviderProps, TooltipProviderProps } from 'radix-vue'
|
||||
import type { ConfigProviderProps, TooltipProviderProps } from 'reka-ui'
|
||||
import { localeContextInjectionKey } from '../composables/useLocale'
|
||||
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
||||
import type { ToasterProps, Locale } from '../types'
|
||||
|
||||
export interface AppProps extends Omit<ConfigProviderProps, 'useId' | 'dir'> {
|
||||
export interface AppProps extends Omit<ConfigProviderProps, 'useId' | 'dir' | 'locale'> {
|
||||
tooltip?: TooltipProviderProps
|
||||
toaster?: ToasterProps | null
|
||||
locale?: Locale
|
||||
@@ -23,7 +23,7 @@ extendDevtoolsMeta({ ignore: true })
|
||||
|
||||
<script setup lang="ts">
|
||||
import { toRef, useId, provide } from 'vue'
|
||||
import { ConfigProvider, TooltipProvider, useForwardProps } from 'radix-vue'
|
||||
import { ConfigProvider, TooltipProvider, useForwardProps } from 'reka-ui'
|
||||
import { reactivePick } from '@vueuse/core'
|
||||
import UToaster from './Toaster.vue'
|
||||
import UModalProvider from './ModalProvider.vue'
|
||||
@@ -41,7 +41,7 @@ provide(localeContextInjectionKey, locale)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ConfigProvider :use-id="() => (useId() as string)" :dir="locale?.dir" v-bind="configProviderProps">
|
||||
<ConfigProvider :use-id="() => (useId() as string)" :dir="locale?.dir" :locale="locale?.code" v-bind="configProviderProps">
|
||||
<TooltipProvider v-bind="tooltipProps">
|
||||
<UToaster v-if="toaster !== null" v-bind="toasterProps">
|
||||
<slot />
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { tv, type VariantProps } from 'tailwind-variants'
|
||||
import type { AvatarFallbackProps } from 'radix-vue'
|
||||
import type { AvatarFallbackProps } from 'reka-ui'
|
||||
import type { AppConfig } from '@nuxt/schema'
|
||||
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
||||
import _appConfig from '#build/app.config'
|
||||
@@ -32,7 +32,7 @@ extendDevtoolsMeta<AvatarProps>({ defaultProps: { src: 'https://avatars.githubus
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { AvatarRoot, AvatarImage, AvatarFallback, useForwardProps } from 'radix-vue'
|
||||
import { AvatarRoot, AvatarImage, AvatarFallback, useForwardProps } from 'reka-ui'
|
||||
import { reactivePick } from '@vueuse/core'
|
||||
import { useAvatarGroup } from '../composables/useAvatarGroup'
|
||||
import UIcon from './Icon.vue'
|
||||
|
||||
@@ -35,7 +35,7 @@ extendDevtoolsMeta({ example: 'AvatarGroupExample' })
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, provide } from 'vue'
|
||||
import { Primitive } from 'radix-vue'
|
||||
import { Primitive } from 'reka-ui'
|
||||
import { avatarGroupInjectionKey } from '../composables/useAvatarGroup'
|
||||
import UAvatar from './Avatar.vue'
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ export interface BadgeSlots {
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { Primitive } from 'radix-vue'
|
||||
import { Primitive } from 'reka-ui'
|
||||
import { useComponentIcons } from '../composables/useComponentIcons'
|
||||
import UIcon from './Icon.vue'
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ extendDevtoolsMeta({
|
||||
|
||||
<script setup lang="ts" generic="T extends BreadcrumbItem">
|
||||
import { computed } from 'vue'
|
||||
import { Primitive } from 'radix-vue'
|
||||
import { Primitive } from 'reka-ui'
|
||||
import { useAppConfig } from '#imports'
|
||||
import { useLocale } from '../composables/useLocale'
|
||||
import { get } from '../utils'
|
||||
|
||||
@@ -43,7 +43,7 @@ export interface ButtonSlots {
|
||||
|
||||
<script setup lang="ts">
|
||||
import { type Ref, computed, ref, inject } from 'vue'
|
||||
import { useForwardProps } from 'radix-vue'
|
||||
import { useForwardProps } from 'reka-ui'
|
||||
import { useComponentIcons } from '../composables/useComponentIcons'
|
||||
import { useButtonGroup } from '../composables/useButtonGroup'
|
||||
import { formLoadingInjectionKey } from '../composables/useFormField'
|
||||
|
||||
@@ -35,7 +35,7 @@ extendDevtoolsMeta({ example: 'ButtonGroupExample' })
|
||||
|
||||
<script setup lang="ts">
|
||||
import { provide, computed } from 'vue'
|
||||
import { Primitive } from 'radix-vue'
|
||||
import { Primitive } from 'reka-ui'
|
||||
import { buttonGroupInjectionKey } from '../composables/useButtonGroup'
|
||||
|
||||
const props = withDefaults(defineProps<ButtonGroupProps>(), {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { tv, type VariantProps } from 'tailwind-variants'
|
||||
import type { CalendarRootProps, CalendarRootEmits, RangeCalendarRootEmits, DateRange, CalendarCellTriggerProps } from 'radix-vue'
|
||||
import type { CalendarRootProps, CalendarRootEmits, RangeCalendarRootEmits, DateRange, CalendarCellTriggerProps } from 'reka-ui'
|
||||
import type { DateValue } from '@internationalized/date'
|
||||
import type { AppConfig } from '@nuxt/schema'
|
||||
import _appConfig from '#build/app.config'
|
||||
@@ -73,8 +73,8 @@ export interface CalendarSlots {
|
||||
|
||||
<script setup lang="ts" generic="R extends boolean = false, M extends boolean = false">
|
||||
import { computed } from 'vue'
|
||||
import { useForwardPropsEmits } from 'radix-vue'
|
||||
import { Calendar as SingleCalendar, RangeCalendar } from 'radix-vue/namespaced'
|
||||
import { useForwardPropsEmits } from 'reka-ui'
|
||||
import { Calendar as SingleCalendar, RangeCalendar } from 'reka-ui/namespaced'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { useLocale } from '../composables/useLocale'
|
||||
import UButton from './Button.vue'
|
||||
|
||||
@@ -29,7 +29,7 @@ extendDevtoolsMeta({ example: 'CardExample' })
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Primitive } from 'radix-vue'
|
||||
import { Primitive } from 'reka-ui'
|
||||
|
||||
const props = defineProps<CardProps>()
|
||||
const slots = defineSlots<CardSlots>()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { tv, type VariantProps } from 'tailwind-variants'
|
||||
import type { AppConfig } from '@nuxt/schema'
|
||||
import type { AcceptableValue } from 'reka-ui'
|
||||
import type { EmblaCarouselType, EmblaOptionsType, EmblaPluginType } from 'embla-carousel'
|
||||
import type { AutoplayOptionsType } from 'embla-carousel-autoplay'
|
||||
import type { AutoScrollOptionsType } from 'embla-carousel-auto-scroll'
|
||||
@@ -12,7 +13,7 @@ import _appConfig from '#build/app.config'
|
||||
import theme from '#build/ui/carousel'
|
||||
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
||||
import type { ButtonProps } from '../types'
|
||||
import type { AcceptableValue, PartialString } from '../types/utils'
|
||||
import type { PartialString } from '../types/utils'
|
||||
|
||||
const appConfig = _appConfig as AppConfig & { ui: { carousel: Partial<typeof theme> } }
|
||||
|
||||
@@ -21,6 +22,11 @@ const carousel = tv({ extend: tv(theme), ...(appConfig.ui?.carousel || {}) })
|
||||
type CarouselVariants = VariantProps<typeof carousel>
|
||||
|
||||
export interface CarouselProps<T> extends Omit<EmblaOptionsType, 'axis' | 'container' | 'slides' | 'direction'> {
|
||||
/**
|
||||
* The element or component this component should render as.
|
||||
* @defaultValue 'div'
|
||||
*/
|
||||
as?: any
|
||||
/**
|
||||
* Configure the prev button when arrows are enabled.
|
||||
* @defaultValue { size: 'md', color: 'neutral', variant: 'link' }
|
||||
@@ -97,7 +103,7 @@ extendDevtoolsMeta({ example: 'CarouselExample' })
|
||||
<script setup lang="ts" generic="T extends AcceptableValue">
|
||||
import { computed, ref, watch, onMounted } from 'vue'
|
||||
import useEmblaCarousel from 'embla-carousel-vue'
|
||||
import { useForwardProps } from 'radix-vue'
|
||||
import { Primitive, useForwardProps } from 'reka-ui'
|
||||
import { reactivePick, computedAsync } from '@vueuse/core'
|
||||
import { useAppConfig } from '#imports'
|
||||
import { useLocale } from '../composables/useLocale'
|
||||
@@ -254,7 +260,8 @@ defineExpose({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
<Primitive
|
||||
:as="as"
|
||||
role="region"
|
||||
aria-roledescription="carousel"
|
||||
tabindex="0"
|
||||
@@ -311,5 +318,5 @@ defineExpose({
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Primitive>
|
||||
</template>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { tv, type VariantProps } from 'tailwind-variants'
|
||||
import type { CheckboxRootProps } from 'radix-vue'
|
||||
import type { CheckboxRootProps } from 'reka-ui'
|
||||
import type { AppConfig } from '@nuxt/schema'
|
||||
import _appConfig from '#build/app.config'
|
||||
import theme from '#build/ui/checkbox'
|
||||
@@ -12,7 +12,12 @@ const checkbox = tv({ extend: tv(theme), ...(appConfig.ui?.checkbox || {}) })
|
||||
|
||||
type CheckboxVariants = VariantProps<typeof checkbox>
|
||||
|
||||
export interface CheckboxProps extends Pick<CheckboxRootProps, 'disabled' | 'required' | 'name' | 'value' | 'id'> {
|
||||
export interface CheckboxProps extends Pick<CheckboxRootProps, 'disabled' | 'required' | 'name' | 'value' | 'id' | 'defaultValue'> {
|
||||
/**
|
||||
* The element or component this component should render as.
|
||||
* @defaultValue 'div'
|
||||
*/
|
||||
as?: any
|
||||
label?: string
|
||||
description?: string
|
||||
color?: CheckboxVariants['color']
|
||||
@@ -22,21 +27,17 @@ export interface CheckboxProps extends Pick<CheckboxRootProps, 'disabled' | 'req
|
||||
* @defaultValue appConfig.ui.icons.check
|
||||
*/
|
||||
icon?: string
|
||||
indeterminate?: boolean
|
||||
/**
|
||||
* The icon displayed when the checkbox is indeterminate.
|
||||
* @defaultValue appConfig.ui.icons.minus
|
||||
*/
|
||||
indeterminateIcon?: string
|
||||
/** The checked state of the checkbox when it is initially rendered. Use when you do not need to control its checked state. */
|
||||
defaultValue?: boolean
|
||||
class?: any
|
||||
ui?: Partial<typeof checkbox.slots>
|
||||
}
|
||||
|
||||
export interface CheckboxEmits {
|
||||
(e: 'update:modelValue', payload: boolean): void
|
||||
(e: 'change', payload: Event): void
|
||||
export type CheckboxEmits = {
|
||||
change: [payload: Event]
|
||||
}
|
||||
|
||||
export interface CheckboxSlots {
|
||||
@@ -49,7 +50,7 @@ extendDevtoolsMeta({ defaultProps: { label: 'Check me!' } })
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, useId } from 'vue'
|
||||
import { CheckboxRoot, CheckboxIndicator, Label, useForwardProps } from 'radix-vue'
|
||||
import { Primitive, CheckboxRoot, CheckboxIndicator, Label, useForwardProps } from 'reka-ui'
|
||||
import { reactivePick } from '@vueuse/core'
|
||||
import { useAppConfig } from '#imports'
|
||||
import { useFormField } from '../composables/useFormField'
|
||||
@@ -59,29 +60,20 @@ const props = defineProps<CheckboxProps>()
|
||||
const slots = defineSlots<CheckboxSlots>()
|
||||
const emits = defineEmits<CheckboxEmits>()
|
||||
|
||||
const modelValue = defineModel<boolean | undefined>({ default: undefined })
|
||||
const modelValue = defineModel<boolean | 'indeterminate'>({ default: undefined })
|
||||
|
||||
const rootProps = useForwardProps(reactivePick(props, 'required', 'value'))
|
||||
const rootProps = useForwardProps(reactivePick(props, 'required', 'value', 'defaultValue'))
|
||||
|
||||
const appConfig = useAppConfig()
|
||||
const { id: _id, emitFormChange, emitFormInput, size, color, name, disabled } = useFormField<CheckboxProps>(props)
|
||||
const id = _id.value ?? useId()
|
||||
|
||||
const checked = computed({
|
||||
get() {
|
||||
return props.indeterminate ? 'indeterminate' : modelValue.value
|
||||
},
|
||||
set(value) {
|
||||
modelValue.value = value === 'indeterminate' ? undefined : value
|
||||
}
|
||||
})
|
||||
|
||||
const ui = computed(() => checkbox({
|
||||
size: size.value,
|
||||
color: color.value,
|
||||
required: props.required,
|
||||
disabled: disabled.value,
|
||||
checked: (modelValue.value ?? props.defaultValue) || props.indeterminate
|
||||
checked: Boolean(modelValue.value ?? props.defaultValue)
|
||||
}))
|
||||
|
||||
function onUpdate(value: any) {
|
||||
@@ -93,23 +85,25 @@ function onUpdate(value: any) {
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- eslint-disable vue/no-template-shadow -->
|
||||
<template>
|
||||
<div :class="ui.root({ class: [props.class, props.ui?.root] })">
|
||||
<Primitive :as="as" :class="ui.root({ class: [props.class, props.ui?.root] })">
|
||||
<div :class="ui.container({ class: props.ui?.container })">
|
||||
<CheckboxRoot
|
||||
:id="id"
|
||||
v-model:checked="checked"
|
||||
:default-checked="defaultValue"
|
||||
v-bind="rootProps"
|
||||
v-model="modelValue"
|
||||
:name="name"
|
||||
:disabled="disabled"
|
||||
:class="ui.base({ class: props.ui?.base })"
|
||||
@update:checked="onUpdate"
|
||||
@update:model-value="onUpdate"
|
||||
>
|
||||
<CheckboxIndicator as-child>
|
||||
<UIcon v-if="indeterminate" :name="indeterminateIcon || appConfig.ui.icons.minus" :class="ui.icon({ class: props.ui?.icon })" />
|
||||
<UIcon v-else :name="icon || appConfig.ui.icons.check" :class="ui.icon({ class: props.ui?.icon })" />
|
||||
</CheckboxIndicator>
|
||||
<template #default="{ modelValue }">
|
||||
<CheckboxIndicator as-child>
|
||||
<UIcon v-if="modelValue === 'indeterminate'" :name="indeterminateIcon || appConfig.ui.icons.minus" :class="ui.icon({ class: props.ui?.icon })" />
|
||||
<UIcon v-else :name="icon || appConfig.ui.icons.check" :class="ui.icon({ class: props.ui?.icon })" />
|
||||
</CheckboxIndicator>
|
||||
</template>
|
||||
</CheckboxRoot>
|
||||
</div>
|
||||
|
||||
@@ -125,5 +119,5 @@ function onUpdate(value: any) {
|
||||
</slot>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Primitive>
|
||||
</template>
|
||||
|
||||
@@ -44,7 +44,7 @@ extendDevtoolsMeta({ example: 'ChipExample' })
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { Primitive, Slot } from 'radix-vue'
|
||||
import { Primitive, Slot } from 'reka-ui'
|
||||
import { useAvatarGroup } from '../composables/useAvatarGroup'
|
||||
|
||||
defineOptions({ inheritAttrs: false })
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { tv } from 'tailwind-variants'
|
||||
import type { CollapsibleRootProps, CollapsibleRootEmits } from 'radix-vue'
|
||||
import type { CollapsibleRootProps, CollapsibleRootEmits } from 'reka-ui'
|
||||
import type { AppConfig } from '@nuxt/schema'
|
||||
import _appConfig from '#build/app.config'
|
||||
import theme from '#build/ui/collapsible'
|
||||
@@ -10,7 +10,7 @@ const appConfig = _appConfig as AppConfig & { ui: { collapsible: Partial<typeof
|
||||
|
||||
const collapsible = tv({ extend: tv(theme), ...(appConfig.ui?.collapsible || {}) })
|
||||
|
||||
export interface CollapsibleProps extends Pick<CollapsibleRootProps, 'defaultOpen' | 'open' | 'disabled'> {
|
||||
export interface CollapsibleProps extends Pick<CollapsibleRootProps, 'defaultOpen' | 'open' | 'disabled' | 'unmountOnHide'> {
|
||||
/**
|
||||
* The element or component this component should render as.
|
||||
* @defaultValue 'div'
|
||||
@@ -31,14 +31,14 @@ extendDevtoolsMeta({ example: 'CollapsibleExample' })
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { CollapsibleRoot, CollapsibleTrigger, CollapsibleContent, useForwardPropsEmits } from 'radix-vue'
|
||||
import { CollapsibleRoot, CollapsibleTrigger, CollapsibleContent, useForwardPropsEmits } from 'reka-ui'
|
||||
import { reactivePick } from '@vueuse/core'
|
||||
|
||||
const props = defineProps<CollapsibleProps>()
|
||||
const emits = defineEmits<CollapsibleEmits>()
|
||||
const slots = defineSlots<CollapsibleSlots>()
|
||||
|
||||
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'defaultOpen', 'open', 'disabled'), emits)
|
||||
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'defaultOpen', 'open', 'disabled', 'unmountOnHide'), emits)
|
||||
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
const ui = collapsible()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { tv } from 'tailwind-variants'
|
||||
import type { ComboboxRootProps, ComboboxRootEmits } from 'radix-vue'
|
||||
import type { ListboxRootProps, ListboxRootEmits } from 'reka-ui'
|
||||
import type { FuseResult } from 'fuse.js'
|
||||
import type { AppConfig } from '@nuxt/schema'
|
||||
import type { UseFuseOptions } from '@vueuse/integrations/useFuse'
|
||||
@@ -37,17 +37,17 @@ export interface CommandPaletteGroup<T> {
|
||||
items?: T[]
|
||||
/**
|
||||
* Whether to filter group items with [useFuse](https://vueuse.org/integrations/useFuse).
|
||||
* When `false`, items will not be filtered which is useful for custom filtering (useAsyncData, useFetch, etc.).
|
||||
* @defaultValue true
|
||||
* When `true`, items will not be filtered which is useful for custom filtering (useAsyncData, useFetch, etc.).
|
||||
* @defaultValue false
|
||||
*/
|
||||
filter?: boolean
|
||||
ignoreFilter?: boolean
|
||||
/** Filter group items after the search happened. */
|
||||
postFilter?: (searchTerm: string, items: T[]) => T[]
|
||||
/** The icon displayed when an item is highlighted. */
|
||||
highlightedIcon?: string
|
||||
}
|
||||
|
||||
export interface CommandPaletteProps<G, T> extends Pick<ComboboxRootProps, 'multiple' | 'disabled' | 'modelValue' | 'defaultValue' | 'selectedValue' | 'resetSearchTermOnBlur'>, Pick<UseComponentIconsProps, 'loading' | 'loadingIcon'> {
|
||||
export interface CommandPaletteProps<G, T> extends Pick<ListboxRootProps, 'multiple' | 'disabled' | 'modelValue' | 'defaultValue' | 'highlightOnHover'>, Pick<UseComponentIconsProps, 'loading' | 'loadingIcon'> {
|
||||
/**
|
||||
* The element or component this component should render as.
|
||||
* @defaultValue 'div'
|
||||
@@ -102,7 +102,9 @@ export interface CommandPaletteProps<G, T> extends Pick<ComboboxRootProps, 'mult
|
||||
ui?: PartialString<typeof commandPalette.slots>
|
||||
}
|
||||
|
||||
export type CommandPaletteEmits<T> = ComboboxRootEmits<T>
|
||||
export type CommandPaletteEmits<T> = ListboxRootEmits<T> & {
|
||||
'update:open': [value: boolean]
|
||||
}
|
||||
|
||||
type SlotProps<T> = (props: { item: T, index: number }) => any
|
||||
|
||||
@@ -120,7 +122,7 @@ extendDevtoolsMeta({ example: 'CommandPaletteExample', ignoreProps: ['groups'] }
|
||||
|
||||
<script setup lang="ts" generic="G extends CommandPaletteGroup<T>, T extends CommandPaletteItem">
|
||||
import { computed } from 'vue'
|
||||
import { ComboboxRoot, ComboboxInput, ComboboxPortal, ComboboxContent, ComboboxEmpty, ComboboxViewport, ComboboxGroup, ComboboxLabel, ComboboxItem, ComboboxItemIndicator, useForwardProps, useForwardPropsEmits } from 'radix-vue'
|
||||
import { ListboxRoot, ListboxFilter, ListboxContent, ListboxGroup, ListboxGroupLabel, ListboxItem, ListboxItemIndicator, useForwardProps, useForwardPropsEmits } from 'reka-ui'
|
||||
import { defu } from 'defu'
|
||||
import { reactivePick } from '@vueuse/core'
|
||||
import { useFuse } from '@vueuse/integrations/useFuse'
|
||||
@@ -145,9 +147,10 @@ const slots = defineSlots<CommandPaletteSlots<G, T>>()
|
||||
|
||||
const searchTerm = defineModel<string>('searchTerm', { default: '' })
|
||||
|
||||
const appConfig = useAppConfig()
|
||||
const { t } = useLocale()
|
||||
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'disabled', 'multiple', 'modelValue', 'defaultValue', 'selectedValue', 'resetSearchTermOnBlur'), emits)
|
||||
const appConfig = useAppConfig()
|
||||
|
||||
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'disabled', 'multiple', 'modelValue', 'defaultValue', 'highlightOnHover'), emits)
|
||||
const inputProps = useForwardProps(reactivePick(props, 'loading', 'loadingIcon', 'placeholder'))
|
||||
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
@@ -169,7 +172,7 @@ const items = computed(() => props.groups?.filter((group) => {
|
||||
return false
|
||||
}
|
||||
|
||||
if (group.filter === false) {
|
||||
if (group.ignoreFilter) {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -217,7 +220,7 @@ const groups = computed(() => {
|
||||
return getGroupWithItems(group, items)
|
||||
}).filter(group => !!group)
|
||||
|
||||
const nonFuseGroups = props.groups?.filter(group => group.filter === false && group.items?.length).map((group) => {
|
||||
const nonFuseGroups = props.groups?.filter(group => group.ignoreFilter && group.items?.length).map((group) => {
|
||||
return getGroupWithItems(group, group.items || [])
|
||||
}) || []
|
||||
|
||||
@@ -230,8 +233,8 @@ const groups = computed(() => {
|
||||
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<template>
|
||||
<ComboboxRoot v-bind="rootProps" v-model:search-term="searchTerm" open :class="ui.root({ class: [props.class, props.ui?.root] })">
|
||||
<ComboboxInput as-child>
|
||||
<ListboxRoot v-bind="rootProps" :class="ui.root({ class: [props.class, props.ui?.root] })">
|
||||
<ListboxFilter v-model="searchTerm" as-child>
|
||||
<UInput
|
||||
variant="none"
|
||||
autofocus
|
||||
@@ -256,72 +259,70 @@ const groups = computed(() => {
|
||||
</slot>
|
||||
</template>
|
||||
</UInput>
|
||||
</ComboboxInput>
|
||||
</ListboxFilter>
|
||||
|
||||
<ComboboxPortal disabled>
|
||||
<ComboboxContent :class="ui.content({ class: props.ui?.content })" :dismissable="false">
|
||||
<ComboboxEmpty :class="ui.empty({ class: props.ui?.empty })">
|
||||
<slot name="empty" :search-term="searchTerm">
|
||||
{{ searchTerm ? t('commandPalette.noMatch', { searchTerm }) : t('commandPalette.noData') }}
|
||||
</slot>
|
||||
</ComboboxEmpty>
|
||||
<ListboxContent :class="ui.content({ class: props.ui?.content })">
|
||||
<div v-if="groups?.length" :class="ui.viewport({ class: props.ui?.viewport })">
|
||||
<ListboxGroup v-for="(group, groupIndex) in groups" :key="`group-${groupIndex}`" :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>
|
||||
|
||||
<ComboboxViewport :class="ui.viewport({ class: props.ui?.viewport })">
|
||||
<ComboboxGroup v-for="(group, groupIndex) in groups" :key="`group-${groupIndex}`" :class="ui.group({ class: props.ui?.group })">
|
||||
<ComboboxLabel v-if="get(group, props.labelKey as string)" :class="ui.label({ class: props.ui?.label })">
|
||||
{{ get(group, props.labelKey as string) }}
|
||||
</ComboboxLabel>
|
||||
<ListboxItem
|
||||
v-for="(item, index) in group.items"
|
||||
:key="`group-${groupIndex}-${index}`"
|
||||
:value="omit(item, ['matches' as any, 'group' as any, 'onSelect', 'labelHtml', 'suffixHtml'])"
|
||||
:disabled="item.disabled"
|
||||
:class="ui.item({ class: props.ui?.item, active: item.active })"
|
||||
@select="item.onSelect"
|
||||
>
|
||||
<slot :name="item.slot || group.slot || 'item'" :item="item" :index="index">
|
||||
<slot :name="item.slot ? `${item.slot}-leading` : group.slot ? `${group.slot}-leading` : `item-leading`" :item="item" :index="index">
|
||||
<UIcon v-if="item.loading" :name="loadingIcon || appConfig.ui.icons.loading" :class="ui.itemLeadingIcon({ class: props.ui?.itemLeadingIcon, loading: true })" />
|
||||
<UIcon v-else-if="item.icon" :name="item.icon" :class="ui.itemLeadingIcon({ class: props.ui?.itemLeadingIcon, active: item.active })" />
|
||||
<UAvatar v-else-if="item.avatar" :size="((props.ui?.itemLeadingAvatarSize || ui.itemLeadingAvatarSize()) as AvatarProps['size'])" v-bind="item.avatar" :class="ui.itemLeadingAvatar({ class: props.ui?.itemLeadingAvatar, active: item.active })" />
|
||||
<UChip
|
||||
v-else-if="item.chip"
|
||||
:size="((props.ui?.itemLeadingChipSize || ui.itemLeadingChipSize()) as ChipProps['size'])"
|
||||
inset
|
||||
standalone
|
||||
v-bind="item.chip"
|
||||
:class="ui.itemLeadingChip({ class: props.ui?.itemLeadingChip, active: item.active })"
|
||||
/>
|
||||
</slot>
|
||||
|
||||
<ComboboxItem
|
||||
v-for="(item, index) in group.items"
|
||||
:key="`group-${groupIndex}-${index}`"
|
||||
:value="omit(item, ['matches' as any, 'group' as any, 'onSelect', 'labelHtml', 'suffixHtml'])"
|
||||
:disabled="item.disabled"
|
||||
:class="ui.item({ class: props.ui?.item, active: item.active })"
|
||||
@select="item.onSelect"
|
||||
>
|
||||
<slot :name="item.slot || group.slot || 'item'" :item="item" :index="index">
|
||||
<slot :name="item.slot ? `${item.slot}-leading` : group.slot ? `${group.slot}-leading` : `item-leading`" :item="item" :index="index">
|
||||
<UIcon v-if="item.loading" :name="loadingIcon || appConfig.ui.icons.loading" :class="ui.itemLeadingIcon({ class: props.ui?.itemLeadingIcon, loading: true })" />
|
||||
<UIcon v-else-if="item.icon" :name="item.icon" :class="ui.itemLeadingIcon({ class: props.ui?.itemLeadingIcon, active: item.active })" />
|
||||
<UAvatar v-else-if="item.avatar" :size="((props.ui?.itemLeadingAvatarSize || ui.itemLeadingAvatarSize()) as AvatarProps['size'])" v-bind="item.avatar" :class="ui.itemLeadingAvatar({ class: props.ui?.itemLeadingAvatar, active: item.active })" />
|
||||
<UChip
|
||||
v-else-if="item.chip"
|
||||
:size="((props.ui?.itemLeadingChipSize || ui.itemLeadingChipSize()) as ChipProps['size'])"
|
||||
inset
|
||||
standalone
|
||||
v-bind="item.chip"
|
||||
:class="ui.itemLeadingChip({ class: props.ui?.itemLeadingChip, active: item.active })"
|
||||
/>
|
||||
<span v-if="item.labelHtml || get(item, props.labelKey as string) || !!slots[item.slot ? `${item.slot}-label` : group.slot ? `${group.slot}-label` : `item-label`]" :class="ui.itemLabel({ class: props.ui?.itemLabel, active: item.active })">
|
||||
<slot :name="item.slot ? `${item.slot}-label` : group.slot ? `${group.slot}-label` : `item-label`" :item="item" :index="index">
|
||||
<span v-if="item.prefix" :class="ui.itemLabelPrefix({ class: props.ui?.itemLabelPrefix })">{{ item.prefix }}</span>
|
||||
|
||||
<span :class="ui.itemLabelBase({ class: props.ui?.itemLabelBase, active: item.active })" v-html="item.labelHtml || get(item, props.labelKey as string)" />
|
||||
|
||||
<span :class="ui.itemLabelSuffix({ class: props.ui?.itemLabelSuffix, active: item.active })" v-html="item.suffixHtml || item.suffix" />
|
||||
</slot>
|
||||
</span>
|
||||
|
||||
<span :class="ui.itemTrailing({ class: props.ui?.itemTrailing })">
|
||||
<slot :name="item.slot ? `${item.slot}-trailing` : group.slot ? `${group.slot}-trailing` : `item-trailing`" :item="item" :index="index">
|
||||
<span v-if="item.kbds?.length" :class="ui.itemTrailingKbds({ class: props.ui?.itemTrailingKbds })">
|
||||
<UKbd v-for="(kbd, kbdIndex) in item.kbds" :key="kbdIndex" :size="((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 })" />
|
||||
</slot>
|
||||
|
||||
<span v-if="item.labelHtml || get(item, props.labelKey as string) || !!slots[item.slot ? `${item.slot}-label` : group.slot ? `${group.slot}-label` : `item-label`]" :class="ui.itemLabel({ class: props.ui?.itemLabel, active: item.active })">
|
||||
<slot :name="item.slot ? `${item.slot}-label` : group.slot ? `${group.slot}-label` : `item-label`" :item="item" :index="index">
|
||||
<span v-if="item.prefix" :class="ui.itemLabelPrefix({ class: props.ui?.itemLabelPrefix })">{{ item.prefix }}</span>
|
||||
<ListboxItemIndicator as-child>
|
||||
<UIcon :name="selectedIcon || appConfig.ui.icons.check" :class="ui.itemTrailingIcon({ class: props.ui?.itemTrailingIcon })" />
|
||||
</ListboxItemIndicator>
|
||||
</span>
|
||||
</slot>
|
||||
</ListboxItem>
|
||||
</ListboxGroup>
|
||||
</div>
|
||||
|
||||
<span :class="ui.itemLabelBase({ class: props.ui?.itemLabelBase, active: item.active })" v-html="item.labelHtml || get(item, props.labelKey as string)" />
|
||||
|
||||
<span :class="ui.itemLabelSuffix({ class: props.ui?.itemLabelSuffix, active: item.active })" v-html="item.suffixHtml || item.suffix" />
|
||||
</slot>
|
||||
</span>
|
||||
|
||||
<span :class="ui.itemTrailing({ class: props.ui?.itemTrailing })">
|
||||
<slot :name="item.slot ? `${item.slot}-trailing` : group.slot ? `${group.slot}-trailing` : `item-trailing`" :item="item" :index="index">
|
||||
<span v-if="item.kbds?.length" :class="ui.itemTrailingKbds({ class: props.ui?.itemTrailingKbds })">
|
||||
<UKbd v-for="(kbd, kbdIndex) in item.kbds" :key="kbdIndex" :size="((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 })" />
|
||||
</slot>
|
||||
|
||||
<ComboboxItemIndicator as-child>
|
||||
<UIcon :name="selectedIcon || appConfig.ui.icons.check" :class="ui.itemTrailingIcon({ class: props.ui?.itemTrailingIcon })" />
|
||||
</ComboboxItemIndicator>
|
||||
</span>
|
||||
</slot>
|
||||
</ComboboxItem>
|
||||
</ComboboxGroup>
|
||||
</ComboboxViewport>
|
||||
</ComboboxContent>
|
||||
</ComboboxPortal>
|
||||
</ComboboxRoot>
|
||||
<div v-else :class="ui.empty({ class: props.ui?.empty })">
|
||||
<slot name="empty" :search-term="searchTerm">
|
||||
{{ searchTerm ? t('commandPalette.noMatch', { searchTerm }) : t('commandPalette.noData') }}
|
||||
</slot>
|
||||
</div>
|
||||
</ListboxContent>
|
||||
</ListboxRoot>
|
||||
</template>
|
||||
|
||||
@@ -26,7 +26,7 @@ extendDevtoolsMeta({ example: 'ContainerExample' })
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Primitive } from 'radix-vue'
|
||||
import { Primitive } from 'reka-ui'
|
||||
|
||||
const props = defineProps<ContainerProps>()
|
||||
defineSlots<ContainerSlots>()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { tv, type VariantProps } from 'tailwind-variants'
|
||||
import type { ContextMenuRootProps, ContextMenuRootEmits, ContextMenuContentProps } from 'radix-vue'
|
||||
import type { ContextMenuRootProps, ContextMenuRootEmits, ContextMenuContentProps } from 'reka-ui'
|
||||
import type { AppConfig } from '@nuxt/schema'
|
||||
import _appConfig from '#build/app.config'
|
||||
import theme from '#build/ui/context-menu'
|
||||
@@ -143,7 +143,7 @@ extendDevtoolsMeta({
|
||||
|
||||
<script setup lang="ts" generic="T extends ContextMenuItem">
|
||||
import { computed, toRef } from 'vue'
|
||||
import { ContextMenuRoot, ContextMenuTrigger, useForwardPropsEmits } from 'radix-vue'
|
||||
import { ContextMenuRoot, ContextMenuTrigger, useForwardPropsEmits } from 'reka-ui'
|
||||
import { reactivePick } from '@vueuse/core'
|
||||
import { omit } from '../utils'
|
||||
import UContextMenuContent from './ContextMenuContent.vue'
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<script lang="ts">
|
||||
import { tv } from 'tailwind-variants'
|
||||
import type { ContextMenuContentProps as RadixContextMenuContentProps, ContextMenuContentEmits as RadixContextMenuContentEmits } from 'radix-vue'
|
||||
import type { ContextMenuContentProps as RekaContextMenuContentProps, ContextMenuContentEmits as RekaContextMenuContentEmits } from 'reka-ui'
|
||||
import theme from '#build/ui/context-menu'
|
||||
import type { KbdProps, AvatarProps, ContextMenuItem, ContextMenuSlots } from '../types'
|
||||
|
||||
const _contextMenu = tv(theme)()
|
||||
|
||||
interface ContextMenuContentProps<T> extends Omit<RadixContextMenuContentProps, 'as' | 'asChild' | 'forceMount'> {
|
||||
interface ContextMenuContentProps<T> extends Omit<RekaContextMenuContentProps, 'as' | 'asChild' | 'forceMount'> {
|
||||
items?: T[] | T[][]
|
||||
portal?: boolean
|
||||
sub?: boolean
|
||||
@@ -18,13 +18,13 @@ interface ContextMenuContentProps<T> extends Omit<RadixContextMenuContentProps,
|
||||
uiOverride?: any
|
||||
}
|
||||
|
||||
interface ContextMenuContentEmits extends RadixContextMenuContentEmits {}
|
||||
interface ContextMenuContentEmits extends RekaContextMenuContentEmits {}
|
||||
</script>
|
||||
|
||||
<script setup lang="ts" generic="T extends ContextMenuItem">
|
||||
import { computed } from 'vue'
|
||||
import { ContextMenu } from 'radix-vue/namespaced'
|
||||
import { useForwardPropsEmits } from 'radix-vue'
|
||||
import { ContextMenu } from 'reka-ui/namespaced'
|
||||
import { useForwardPropsEmits } from 'reka-ui'
|
||||
import { reactiveOmit, createReusableTemplate } from '@vueuse/core'
|
||||
import { useAppConfig } from '#imports'
|
||||
import { omit, get } from '../utils'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { tv } from 'tailwind-variants'
|
||||
import type { DrawerRootProps, DrawerRootEmits } from 'vaul-vue'
|
||||
import type { DialogContentProps } from 'radix-vue'
|
||||
import type { DialogContentProps } from 'reka-ui'
|
||||
import type { AppConfig } from '@nuxt/schema'
|
||||
import _appConfig from '#build/app.config'
|
||||
import theme from '#build/ui/drawer'
|
||||
@@ -58,7 +58,7 @@ extendDevtoolsMeta({ example: 'DrawerExample' })
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, toRef } from 'vue'
|
||||
import { useForwardPropsEmits } from 'radix-vue'
|
||||
import { useForwardPropsEmits } from 'reka-ui'
|
||||
import { DrawerRoot, DrawerTrigger, DrawerPortal, DrawerOverlay, DrawerContent, DrawerTitle, DrawerDescription } from 'vaul-vue'
|
||||
import { reactivePick } from '@vueuse/core'
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { tv, type VariantProps } from 'tailwind-variants'
|
||||
import type { DropdownMenuRootProps, DropdownMenuRootEmits, DropdownMenuContentProps, DropdownMenuArrowProps } from 'radix-vue'
|
||||
import type { DropdownMenuRootProps, DropdownMenuRootEmits, DropdownMenuContentProps, DropdownMenuArrowProps } from 'reka-ui'
|
||||
import type { AppConfig } from '@nuxt/schema'
|
||||
import _appConfig from '#build/app.config'
|
||||
import theme from '#build/ui/dropdown-menu'
|
||||
@@ -140,7 +140,7 @@ extendDevtoolsMeta({
|
||||
<script setup lang="ts" generic="T extends DropdownMenuItem">
|
||||
import { computed, toRef } from 'vue'
|
||||
import { defu } from 'defu'
|
||||
import { DropdownMenuRoot, DropdownMenuTrigger, DropdownMenuArrow, useForwardPropsEmits } from 'radix-vue'
|
||||
import { DropdownMenuRoot, DropdownMenuTrigger, DropdownMenuArrow, useForwardPropsEmits } from 'reka-ui'
|
||||
import { reactivePick } from '@vueuse/core'
|
||||
import { omit } from '../utils'
|
||||
import UDropdownMenuContent from './DropdownMenuContent.vue'
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<!-- eslint-disable vue/block-tag-newline -->
|
||||
<script lang="ts">
|
||||
import { tv } from 'tailwind-variants'
|
||||
import type { DropdownMenuContentProps as RadixDropdownMenuContentProps, DropdownMenuContentEmits as RadixDropdownMenuContentEmits } from 'radix-vue'
|
||||
import type { DropdownMenuContentProps as RekaDropdownMenuContentProps, DropdownMenuContentEmits as RekaDropdownMenuContentEmits } from 'reka-ui'
|
||||
import theme from '#build/ui/dropdown-menu'
|
||||
import type { KbdProps, AvatarProps, DropdownMenuItem, DropdownMenuSlots } from '../types'
|
||||
|
||||
const _dropdownMenu = tv(theme)()
|
||||
|
||||
interface DropdownMenuContentProps<T> extends Omit<RadixDropdownMenuContentProps, 'as' | 'asChild' | 'forceMount'> {
|
||||
interface DropdownMenuContentProps<T> extends Omit<RekaDropdownMenuContentProps, 'as' | 'asChild' | 'forceMount'> {
|
||||
items?: T[] | T[][]
|
||||
portal?: boolean
|
||||
sub?: boolean
|
||||
@@ -19,7 +19,7 @@ interface DropdownMenuContentProps<T> extends Omit<RadixDropdownMenuContentProps
|
||||
uiOverride?: any
|
||||
}
|
||||
|
||||
interface DropdownMenuContentEmits extends RadixDropdownMenuContentEmits {}
|
||||
interface DropdownMenuContentEmits extends RekaDropdownMenuContentEmits {}
|
||||
|
||||
type DropdownMenuContentSlots<T extends { slot?: string }> = Omit<DropdownMenuSlots<T>, 'default'> & {
|
||||
default(props?: {}): any
|
||||
@@ -29,8 +29,8 @@ type DropdownMenuContentSlots<T extends { slot?: string }> = Omit<DropdownMenuSl
|
||||
|
||||
<script setup lang="ts" generic="T extends DropdownMenuItem">
|
||||
import { computed } from 'vue'
|
||||
import { DropdownMenu } from 'radix-vue/namespaced'
|
||||
import { useForwardPropsEmits } from 'radix-vue'
|
||||
import { DropdownMenu } from 'reka-ui/namespaced'
|
||||
import { useForwardPropsEmits } from 'reka-ui'
|
||||
import { reactiveOmit, createReusableTemplate } from '@vueuse/core'
|
||||
import { useAppConfig } from '#imports'
|
||||
import { omit, get } from '../utils'
|
||||
|
||||
@@ -12,6 +12,11 @@ const formField = tv({ extend: tv(theme), ...(appConfig.ui?.formField || {}) })
|
||||
type FormFieldVariants = VariantProps<typeof formField>
|
||||
|
||||
export interface FormFieldProps {
|
||||
/**
|
||||
* The element or component this component should render as.
|
||||
* @defaultValue 'div'
|
||||
*/
|
||||
as?: any
|
||||
/** The name of the FormField. Also used to match form errors. */
|
||||
name?: string
|
||||
/** A regular expression to match form error names. */
|
||||
@@ -43,7 +48,7 @@ extendDevtoolsMeta({ example: 'FormFieldExample', defaultProps: { label: 'Label'
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, inject, provide, type Ref, useId } from 'vue'
|
||||
import { Label } from 'radix-vue'
|
||||
import { Primitive, Label } from 'reka-ui'
|
||||
import { formFieldInjectionKey, inputIdInjectionKey } from '../composables/useFormField'
|
||||
import type { FormError, FormFieldInjectedOptions } from '../types/form'
|
||||
|
||||
@@ -74,7 +79,7 @@ provide(formFieldInjectionKey, computed(() => ({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="ui.root({ class: [props.class, props.ui?.root] })">
|
||||
<Primitive :as="as" :class="ui.root({ class: [props.class, props.ui?.root] })">
|
||||
<div :class="ui.wrapper({ class: props.ui?.wrapper })">
|
||||
<div v-if="label || !!slots.label" :class="ui.labelWrapper({ class: props.ui?.labelWrapper })">
|
||||
<Label :for="id" :class="ui.label({ class: props.ui?.label })">
|
||||
@@ -110,5 +115,5 @@ provide(formFieldInjectionKey, computed(() => ({
|
||||
</slot>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Primitive>
|
||||
</template>
|
||||
|
||||
@@ -13,7 +13,7 @@ export interface IconProps {
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useForwardProps } from 'radix-vue'
|
||||
import { useForwardProps } from 'reka-ui'
|
||||
import { reactivePick } from '@vueuse/core'
|
||||
|
||||
const props = defineProps<IconProps>()
|
||||
|
||||
@@ -15,6 +15,11 @@ const input = tv({ extend: tv(theme), ...(appConfig.ui?.input || {}) })
|
||||
type InputVariants = VariantProps<typeof input>
|
||||
|
||||
export interface InputProps extends UseComponentIconsProps {
|
||||
/**
|
||||
* The element or component this component should render as.
|
||||
* @defaultValue 'div'
|
||||
*/
|
||||
as?: any
|
||||
id?: string
|
||||
name?: string
|
||||
type?: InputHTMLAttributes['type']
|
||||
@@ -49,6 +54,7 @@ export interface InputSlots {
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { Primitive } from 'reka-ui'
|
||||
import { useButtonGroup } from '../composables/useButtonGroup'
|
||||
import { useComponentIcons } from '../composables/useComponentIcons'
|
||||
import { useFormField } from '../composables/useFormField'
|
||||
@@ -147,7 +153,7 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="ui.root({ class: [props.class, props.ui?.root] })">
|
||||
<Primitive :as="as" :class="ui.root({ class: [props.class, props.ui?.root] })">
|
||||
<input
|
||||
:id="id"
|
||||
ref="inputRef"
|
||||
@@ -179,5 +185,5 @@ onMounted(() => {
|
||||
<UIcon v-if="trailingIconName" :name="trailingIconName" :class="ui.trailingIcon({ class: props.ui?.trailingIcon })" />
|
||||
</slot>
|
||||
</span>
|
||||
</div>
|
||||
</Primitive>
|
||||
</template>
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<script lang="ts">
|
||||
import type { InputHTMLAttributes } from 'vue'
|
||||
import { tv, type VariantProps } from 'tailwind-variants'
|
||||
import type { ComboboxRootProps, ComboboxRootEmits, ComboboxContentProps, ComboboxArrowProps } from 'radix-vue'
|
||||
import type { ComboboxRootProps, ComboboxRootEmits, ComboboxContentProps, ComboboxArrowProps, AcceptableValue } from 'reka-ui'
|
||||
import type { AppConfig } from '@nuxt/schema'
|
||||
import _appConfig from '#build/app.config'
|
||||
import theme from '#build/ui/input-menu'
|
||||
import type { UseComponentIconsProps } from '../composables/useComponentIcons'
|
||||
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
||||
import type { AvatarProps, ChipProps, InputProps } from '../types'
|
||||
import type { AcceptableValue, ArrayOrWrapped, PartialString, MaybeArrayOfArray, MaybeArrayOfArrayItem, SelectModelValue, SelectModelValueEmits, SelectItemKey } from '../types/utils'
|
||||
import type { PartialString, MaybeArrayOfArray, MaybeArrayOfArrayItem, SelectModelValue, SelectModelValueEmits, SelectItemKey } from '../types/utils'
|
||||
|
||||
const appConfig = _appConfig as AppConfig & { ui: { inputMenu: Partial<typeof theme> } }
|
||||
|
||||
@@ -30,7 +30,7 @@ export interface InputMenuItem {
|
||||
|
||||
type InputMenuVariants = VariantProps<typeof inputMenu>
|
||||
|
||||
export interface InputMenuProps<T extends MaybeArrayOfArrayItem<I>, I extends MaybeArrayOfArray<InputMenuItem | AcceptableValue> = MaybeArrayOfArray<InputMenuItem | AcceptableValue>, V extends SelectItemKey<T> | undefined = undefined, M extends boolean = false> extends Pick<ComboboxRootProps<T>, 'defaultValue' | 'selectedValue' | 'open' | 'defaultOpen' | 'searchTerm' | 'disabled' | 'name' | 'resetSearchTermOnBlur'>, UseComponentIconsProps {
|
||||
export interface InputMenuProps<T extends MaybeArrayOfArrayItem<I>, I extends MaybeArrayOfArray<InputMenuItem | AcceptableValue | boolean> = MaybeArrayOfArray<InputMenuItem | AcceptableValue | boolean>, V extends SelectItemKey<T> | undefined = undefined, M extends boolean = false> extends Pick<ComboboxRootProps<T>, 'open' | 'defaultOpen' | 'disabled' | 'name' | 'resetSearchTermOnBlur' | 'highlightOnHover'>, UseComponentIconsProps {
|
||||
/**
|
||||
* The element or component this component should render as.
|
||||
* @defaultValue 'div'
|
||||
@@ -77,13 +77,6 @@ export interface InputMenuProps<T extends MaybeArrayOfArrayItem<I>, I extends Ma
|
||||
* @defaultValue true
|
||||
*/
|
||||
portal?: boolean
|
||||
/**
|
||||
* Whether to filter items or not, can be an array of fields to filter. Defaults to `[labelKey]`.
|
||||
* When `false`, items will not be filtered which is useful for custom filtering (useAsyncData, useFetch, etc.).
|
||||
* `['label']`{lang="ts-type"}
|
||||
* @defaultValue true
|
||||
*/
|
||||
filter?: boolean | string[]
|
||||
/**
|
||||
* When `items` is an array of objects, select the field to use as the value instead of the object itself.
|
||||
* @defaultValue undefined
|
||||
@@ -95,33 +88,45 @@ export interface InputMenuProps<T extends MaybeArrayOfArrayItem<I>, I extends Ma
|
||||
*/
|
||||
labelKey?: V
|
||||
items?: I
|
||||
/** The value of the InputMenu when initially rendered. Use when you do not need to control the state of the InputMenu. */
|
||||
defaultValue?: SelectModelValue<T, V, M>
|
||||
/** The controlled value of the InputMenu. Can be binded-with with `v-model`. */
|
||||
modelValue?: SelectModelValue<T, V, M>
|
||||
/** Whether multiple options can be selected or not. */
|
||||
multiple?: M & boolean
|
||||
/** Highlight the ring color like a focus state. */
|
||||
highlight?: boolean
|
||||
/**
|
||||
* Determines if custom user input that does not exist in options can be added.
|
||||
* @defaultValue false
|
||||
*/
|
||||
createItem?: boolean | 'always' | { placement?: 'top' | 'bottom', when?: 'empty' | 'always' }
|
||||
createItem?: boolean | 'always' | { position?: 'top' | 'bottom', when?: 'empty' | 'always' }
|
||||
/**
|
||||
* Fields to filter items by.
|
||||
* @defaultValue [labelKey]
|
||||
*/
|
||||
filterFields?: string[]
|
||||
/**
|
||||
* When `true`, disable the default filters, useful for custom filtering (useAsyncData, useFetch, etc.).
|
||||
* @defaultValue false
|
||||
*/
|
||||
ignoreFilter?: boolean
|
||||
class?: any
|
||||
ui?: PartialString<typeof inputMenu.slots>
|
||||
/** The controlled value of the Combobox. Can be binded-with with `v-model`. */
|
||||
modelValue?: SelectModelValue<T, V, M>
|
||||
/** Whether multiple options can be selected or not. */
|
||||
multiple?: M & boolean
|
||||
}
|
||||
|
||||
export type InputMenuEmits<T, V, M extends boolean> = Omit<ComboboxRootEmits<T>, 'update:modelValue'> & {
|
||||
change: [payload: Event]
|
||||
blur: [payload: FocusEvent]
|
||||
focus: [payload: FocusEvent]
|
||||
create: [payload: Event, item: T]
|
||||
create: [item: string]
|
||||
} & SelectModelValueEmits<T, V, M>
|
||||
|
||||
type SlotProps<T> = (props: { item: T, index: number }) => any
|
||||
|
||||
export interface InputMenuSlots<T> {
|
||||
'leading'(props: { modelValue: T, open: boolean, ui: any }): any
|
||||
'trailing'(props: { modelValue: T, open: boolean, ui: any }): any
|
||||
export interface InputMenuSlots<T, M extends boolean> {
|
||||
'leading'(props: { modelValue?: M extends true ? T[] : T, open: boolean, ui: any }): any
|
||||
'trailing'(props: { modelValue?: M extends true ? T[] : T, open: boolean, ui: any }): any
|
||||
'empty'(props: { searchTerm?: string }): any
|
||||
'item': SlotProps<T>
|
||||
'item-leading': SlotProps<T>
|
||||
@@ -129,24 +134,23 @@ export interface InputMenuSlots<T> {
|
||||
'item-trailing': SlotProps<T>
|
||||
'tags-item-text': SlotProps<T>
|
||||
'tags-item-delete': SlotProps<T>
|
||||
'create-item-label'(props: { item: T }): any
|
||||
'create-item-label'(props: { item: string }): any
|
||||
}
|
||||
|
||||
extendDevtoolsMeta({ defaultProps: { items: ['Option 1', 'Option 2', 'Option 3'] } })
|
||||
</script>
|
||||
|
||||
<script setup lang="ts" generic="T extends MaybeArrayOfArrayItem<I>, I extends MaybeArrayOfArray<InputMenuItem | AcceptableValue> = MaybeArrayOfArray<InputMenuItem | AcceptableValue>, V extends SelectItemKey<T> | undefined = undefined, M extends boolean = false">
|
||||
<script setup lang="ts" generic="T extends MaybeArrayOfArrayItem<I>, I extends MaybeArrayOfArray<InputMenuItem | AcceptableValue | boolean> = MaybeArrayOfArray<InputMenuItem | AcceptableValue | boolean>, V extends SelectItemKey<T> | undefined = undefined, M extends boolean = false">
|
||||
import { computed, ref, toRef, onMounted, toRaw } from 'vue'
|
||||
import { ComboboxRoot, ComboboxArrow, ComboboxAnchor, ComboboxInput, ComboboxTrigger, ComboboxPortal, ComboboxContent, ComboboxViewport, ComboboxEmpty, ComboboxGroup, ComboboxLabel, ComboboxSeparator, ComboboxItem, ComboboxItemIndicator, TagsInputRoot, TagsInputItem, TagsInputItemText, TagsInputItemDelete, TagsInputInput, useForwardPropsEmits } from 'radix-vue'
|
||||
import { ComboboxRoot, ComboboxArrow, ComboboxAnchor, ComboboxInput, ComboboxTrigger, ComboboxPortal, ComboboxContent, ComboboxViewport, ComboboxEmpty, ComboboxGroup, ComboboxLabel, ComboboxSeparator, ComboboxItem, ComboboxItemIndicator, TagsInputRoot, TagsInputItem, TagsInputItemText, TagsInputItemDelete, TagsInputInput, useForwardPropsEmits, useFilter } from 'reka-ui'
|
||||
import { defu } from 'defu'
|
||||
import { isEqual } from 'ohash'
|
||||
import { reactivePick, createReusableTemplate } from '@vueuse/core'
|
||||
import { useAppConfig } from '#imports'
|
||||
import { useButtonGroup } from '../composables/useButtonGroup'
|
||||
import { useComponentIcons } from '../composables/useComponentIcons'
|
||||
import { useFormField } from '../composables/useFormField'
|
||||
import { useLocale } from '../composables/useLocale'
|
||||
import { get, escapeRegExp } from '../utils'
|
||||
import { get, compare } from '../utils'
|
||||
import UIcon from './Icon.vue'
|
||||
import UAvatar from './Avatar.vue'
|
||||
import UChip from './Chip.vue'
|
||||
@@ -157,17 +161,18 @@ const props = withDefaults(defineProps<InputMenuProps<T, I, V, M>>(), {
|
||||
type: 'text',
|
||||
autofocusDelay: 0,
|
||||
portal: true,
|
||||
filter: true,
|
||||
labelKey: 'label' as never
|
||||
})
|
||||
const emits = defineEmits<InputMenuEmits<T, V, M>>()
|
||||
const slots = defineSlots<InputMenuSlots<T>>()
|
||||
const slots = defineSlots<InputMenuSlots<T, M>>()
|
||||
|
||||
const searchTerm = defineModel<string>('searchTerm', { default: '' })
|
||||
|
||||
const appConfig = useAppConfig()
|
||||
const { t } = useLocale()
|
||||
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'modelValue', 'defaultValue', 'selectedValue', 'open', 'defaultOpen', 'multiple', 'resetSearchTermOnBlur'), emits)
|
||||
const appConfig = useAppConfig()
|
||||
const { contains } = useFilter({ sensitivity: 'base' })
|
||||
|
||||
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'modelValue', 'defaultValue', 'open', 'defaultOpen', 'multiple', 'resetSearchTermOnBlur', 'highlightOnHover', 'ignoreFilter'), emits)
|
||||
const contentProps = toRef(() => defu(props.content, { side: 'bottom', sideOffset: 8, collisionPadding: 8, position: 'popper' }) as ComboboxContentProps)
|
||||
const arrowProps = toRef(() => props.arrow as ComboboxArrowProps)
|
||||
|
||||
@@ -175,10 +180,10 @@ const { emitFormBlur, emitFormChange, emitFormInput, size: formGroupSize, color,
|
||||
const { orientation, size: buttonGroupSize } = useButtonGroup<InputProps>(props)
|
||||
const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponentIcons(toRef(() => defu(props, { trailingIcon: appConfig.ui.icons.chevronDown })))
|
||||
|
||||
const [DefineCreateItemTemplate, ReuseCreateItemTemplate] = createReusableTemplate()
|
||||
|
||||
const inputSize = computed(() => buttonGroupSize.value || formGroupSize.value)
|
||||
|
||||
const [DefineCreateItemTemplate, ReuseCreateItemTemplate] = createReusableTemplate()
|
||||
|
||||
const ui = computed(() => inputMenu({
|
||||
color: color.value,
|
||||
variant: props.variant,
|
||||
@@ -196,69 +201,49 @@ function displayValue(value: T): string {
|
||||
return value && (typeof value === 'object' ? get(value, props.labelKey as string) : value)
|
||||
}
|
||||
|
||||
const item = items.value.find(item => isEqual(get(item as Record<string, any>, props.valueKey as string), value))
|
||||
|
||||
const item = items.value.find(item => compare(typeof item === 'object' ? get(item, props.valueKey as string) : item, value))
|
||||
return item && (typeof item === 'object' ? get(item, props.labelKey as string) : item)
|
||||
}
|
||||
|
||||
function filterFunction(
|
||||
inputItems: ArrayOrWrapped<T> = items.value as ArrayOrWrapped<T>,
|
||||
filterSearchTerm: string = searchTerm.value,
|
||||
comparator = (item: any, term: string) => String(item).search(new RegExp(term, 'i')) !== -1
|
||||
): ArrayOrWrapped<T> {
|
||||
if (props.filter === false) {
|
||||
return inputItems
|
||||
}
|
||||
|
||||
const fields = Array.isArray(props.filter) ? props.filter : [props.labelKey]
|
||||
const escapedSearchTerm = escapeRegExp(filterSearchTerm ?? '')
|
||||
|
||||
return inputItems.filter((item) => {
|
||||
if (typeof item !== 'object') {
|
||||
return comparator(item, escapedSearchTerm)
|
||||
}
|
||||
|
||||
return fields.some((field) => {
|
||||
const child = get(item, field as string)
|
||||
|
||||
return child !== null && child !== undefined && comparator(child, escapedSearchTerm)
|
||||
})
|
||||
}) as ArrayOrWrapped<T>
|
||||
}
|
||||
|
||||
const groups = computed(() => props.items?.length ? (Array.isArray(props.items[0]) ? props.items : [props.items]) as InputMenuItem[][] : [])
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
const items = computed(() => groups.value.flatMap(group => group) as T[])
|
||||
|
||||
const creatable = computed(() => {
|
||||
if (!props.createItem) {
|
||||
const filteredGroups = computed(() => {
|
||||
if (props.ignoreFilter || !searchTerm.value) {
|
||||
return groups.value
|
||||
}
|
||||
|
||||
const fields = Array.isArray(props.filterFields) ? props.filterFields : [props.labelKey] as string[]
|
||||
|
||||
return groups.value.map(items => items.filter((item) => {
|
||||
if (typeof item !== 'object') {
|
||||
return contains(item, searchTerm.value)
|
||||
}
|
||||
|
||||
if (item.type && ['label', 'separator'].includes(item.type)) {
|
||||
return true
|
||||
}
|
||||
|
||||
return fields.some(field => contains(get(item, field), searchTerm.value))
|
||||
})).filter(group => group.filter(item => !item.type || !['label', 'separator'].includes(item.type)).length > 0)
|
||||
})
|
||||
const filteredItems = computed(() => filteredGroups.value.flatMap(group => group) as T[])
|
||||
|
||||
const createItem = computed(() => {
|
||||
if (!props.createItem || !searchTerm.value) {
|
||||
return false
|
||||
}
|
||||
|
||||
const isModelValueCustom = props.modelValue && filterFunction((props.multiple && Array.isArray(props.modelValue) ? props.modelValue : [props.modelValue]) as ArrayOrWrapped<T>, searchTerm.value, (item, term) => String(item) === term).length === 1
|
||||
|
||||
if (isModelValueCustom) {
|
||||
return false
|
||||
}
|
||||
|
||||
const filteredItems = filterFunction()
|
||||
const newItem = searchTerm.value && {
|
||||
item: props.valueKey ? { [props.valueKey]: searchTerm.value, [props.labelKey ?? 'label']: searchTerm.value } : searchTerm.value,
|
||||
position: ((typeof props.createItem === 'object' && props.createItem.placement) || 'bottom') as 'top' | 'bottom'
|
||||
}
|
||||
const newItem = props.valueKey ? { [props.valueKey]: searchTerm.value } as T : searchTerm.value
|
||||
|
||||
if ((typeof props.createItem === 'object' && props.createItem.when === 'always') || props.createItem === 'always') {
|
||||
return (filteredItems.length === 1 && filterFunction(filteredItems, searchTerm.value, (item, term) => String(item) === term).length === 1) ? false : newItem
|
||||
return !filteredItems.value.find(item => compare(item, newItem, props.valueKey))
|
||||
}
|
||||
|
||||
return filteredItems.length > 0 ? false : newItem
|
||||
return !filteredItems.value.length
|
||||
})
|
||||
|
||||
const rootItems = computed(() => [
|
||||
...(creatable.value && creatable.value.position === 'top' ? [creatable.value.item] : []),
|
||||
...filterFunction(),
|
||||
...(creatable.value && creatable.value.position === 'bottom' ? [creatable.value.item] : [])
|
||||
] as ArrayOrWrapped<T>)
|
||||
const createItemPosition = computed(() => typeof props.createItem === 'object' ? props.createItem.position : 'bottom')
|
||||
|
||||
const inputRef = ref<InstanceType<typeof ComboboxInput> | null>(null)
|
||||
|
||||
@@ -310,17 +295,18 @@ defineExpose({
|
||||
})
|
||||
</script>
|
||||
|
||||
<!-- eslint-disable vue/no-template-shadow -->
|
||||
<template>
|
||||
<DefineCreateItemTemplate>
|
||||
<ComboboxGroup v-if="creatable" :class="ui.group({ class: props.ui?.group })">
|
||||
<ComboboxGroup :class="ui.group({ class: props.ui?.group })">
|
||||
<ComboboxItem
|
||||
:class="ui.item({ class: props.ui?.item })"
|
||||
:value="valueKey && typeof creatable.item === 'object' ? get(creatable.item, props.valueKey as string) : creatable.item"
|
||||
@select="e => emits('create', e, (creatable as any).item as T)"
|
||||
:value="searchTerm"
|
||||
@select.prevent="emits('create', searchTerm)"
|
||||
>
|
||||
<span :class="ui.itemLabel({ class: props.ui?.itemLabel })">
|
||||
<slot name="create-item-label" :item="(creatable.item as T)">
|
||||
{{ t('inputMenu.create', { label: typeof creatable.item === 'object' ? get(creatable.item, props.labelKey as string) : creatable.item }) }}
|
||||
<slot name="create-item-label" :item="searchTerm">
|
||||
{{ t('inputMenu.create', { label: searchTerm }) }}
|
||||
</slot>
|
||||
</span>
|
||||
</ComboboxItem>
|
||||
@@ -331,13 +317,11 @@ defineExpose({
|
||||
:id="id"
|
||||
v-slot="{ modelValue, open }"
|
||||
v-bind="rootProps"
|
||||
v-model:search-term="searchTerm"
|
||||
:name="name"
|
||||
:disabled="disabled"
|
||||
:display-value="displayValue"
|
||||
:filter-function="() => rootItems"
|
||||
:class="ui.root({ class: [props.class, props.ui?.root] })"
|
||||
:as-child="!!multiple"
|
||||
ignore-filter
|
||||
@update:model-value="onUpdate"
|
||||
@update:open="onUpdateOpen"
|
||||
@keydown.enter="$event.preventDefault()"
|
||||
@@ -345,7 +329,7 @@ defineExpose({
|
||||
<ComboboxAnchor :as-child="!multiple" :class="ui.base({ class: props.ui?.base })">
|
||||
<TagsInputRoot
|
||||
v-if="multiple"
|
||||
v-slot="{ modelValue: tags }: { modelValue: AcceptableValue[] }"
|
||||
v-slot="{ modelValue: tags }"
|
||||
:model-value="(modelValue as string[])"
|
||||
:disabled="disabled"
|
||||
delimiter=""
|
||||
@@ -367,7 +351,7 @@ defineExpose({
|
||||
</TagsInputItemDelete>
|
||||
</TagsInputItem>
|
||||
|
||||
<ComboboxInput as-child>
|
||||
<ComboboxInput v-model="searchTerm" :display-value="displayValue" as-child>
|
||||
<TagsInputInput
|
||||
ref="inputRef"
|
||||
v-bind="$attrs"
|
||||
@@ -382,24 +366,25 @@ defineExpose({
|
||||
<ComboboxInput
|
||||
v-else
|
||||
ref="inputRef"
|
||||
v-model="searchTerm"
|
||||
:display-value="displayValue"
|
||||
v-bind="$attrs"
|
||||
:type="type"
|
||||
:placeholder="placeholder"
|
||||
:required="required"
|
||||
:class="ui.base({ class: props.ui?.base })"
|
||||
@blur="onBlur"
|
||||
@focus="onFocus"
|
||||
/>
|
||||
|
||||
<span v-if="isLeading || !!avatar || !!slots.leading" :class="ui.leading({ class: props.ui?.leading })">
|
||||
<slot name="leading" :model-value="(modelValue as T)" :open="open" :ui="ui">
|
||||
<slot name="leading" :model-value="(modelValue as M extends true ? T[] : T)" :open="open" :ui="ui">
|
||||
<UIcon v-if="isLeading && leadingIconName" :name="leadingIconName" :class="ui.leadingIcon({ class: props.ui?.leadingIcon })" />
|
||||
<UAvatar v-else-if="!!avatar" :size="((props.ui?.itemLeadingAvatarSize || ui.itemLeadingAvatarSize()) as AvatarProps['size'])" v-bind="avatar" :class="ui.itemLeadingAvatar({ class: props.ui?.itemLeadingAvatar })" />
|
||||
</slot>
|
||||
</span>
|
||||
|
||||
<ComboboxTrigger v-if="isTrailing || !!slots.trailing" :class="ui.trailing({ class: props.ui?.trailing })">
|
||||
<slot name="trailing" :model-value="(modelValue as T)" :open="open" :ui="ui">
|
||||
<slot name="trailing" :model-value="(modelValue as M extends true ? T[] : T)" :open="open" :ui="ui">
|
||||
<UIcon v-if="trailingIconName" :name="trailingIconName" :class="ui.trailingIcon({ class: props.ui?.trailingIcon })" />
|
||||
</slot>
|
||||
</ComboboxTrigger>
|
||||
@@ -414,9 +399,9 @@ defineExpose({
|
||||
</ComboboxEmpty>
|
||||
|
||||
<ComboboxViewport :class="ui.viewport({ class: props.ui?.viewport })">
|
||||
<ReuseCreateItemTemplate v-if="creatable && creatable.position === 'top'" />
|
||||
<ReuseCreateItemTemplate v-if="createItem && createItemPosition === 'top'" />
|
||||
|
||||
<ComboboxGroup v-for="(group, groupIndex) in groups" :key="`group-${groupIndex}`" :class="ui.group({ class: props.ui?.group })">
|
||||
<ComboboxGroup v-for="(group, groupIndex) in filteredGroups" :key="`group-${groupIndex}`" :class="ui.group({ class: props.ui?.group })">
|
||||
<template v-for="(item, index) in group" :key="`group-${groupIndex}-${index}`">
|
||||
<ComboboxLabel v-if="item?.type === 'label'" :class="ui.label({ class: props.ui?.label })">
|
||||
{{ get(item, props.labelKey as string) }}
|
||||
@@ -463,7 +448,7 @@ defineExpose({
|
||||
</template>
|
||||
</ComboboxGroup>
|
||||
|
||||
<ReuseCreateItemTemplate v-if="creatable && creatable.position === 'bottom'" />
|
||||
<ReuseCreateItemTemplate v-if="createItem && createItemPosition === 'bottom'" />
|
||||
</ComboboxViewport>
|
||||
|
||||
<ComboboxArrow v-if="!!arrow" v-bind="arrowProps" :class="ui.arrow({ class: props.ui?.arrow })" />
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { tv, type VariantProps } from 'tailwind-variants'
|
||||
import type { NumberFieldRootProps } from 'radix-vue'
|
||||
import type { NumberFieldRootProps } from 'reka-ui'
|
||||
import type { AppConfig } from '@nuxt/schema'
|
||||
import _appConfig from '#build/app.config'
|
||||
import theme from '#build/ui/input-number'
|
||||
@@ -75,7 +75,7 @@ export interface InputNumberSlots {
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref, computed } from 'vue'
|
||||
import { NumberFieldRoot, NumberFieldInput, NumberFieldDecrement, NumberFieldIncrement, useForwardPropsEmits } from 'radix-vue'
|
||||
import { NumberFieldRoot, NumberFieldInput, NumberFieldDecrement, NumberFieldIncrement, useForwardPropsEmits } from 'reka-ui'
|
||||
import { reactivePick } from '@vueuse/core'
|
||||
import { useFormField } from '../composables/useFormField'
|
||||
import { useLocale } from '../composables/useLocale'
|
||||
|
||||
@@ -31,7 +31,7 @@ extendDevtoolsMeta({ defaultProps: { value: 'K' } })
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Primitive } from 'radix-vue'
|
||||
import { Primitive } from 'reka-ui'
|
||||
import { useKbd } from '../composables/useKbd'
|
||||
|
||||
const props = withDefaults(defineProps<KbdProps>(), {
|
||||
|
||||
@@ -95,7 +95,7 @@ extendDevtoolsMeta({ example: 'LinkExample' })
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { isEqual, diff } from 'ohash'
|
||||
import { useForwardProps } from 'radix-vue'
|
||||
import { useForwardProps } from 'reka-ui'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { useRoute } from '#imports'
|
||||
import ULinkBase from './LinkBase.vue'
|
||||
|
||||
@@ -13,7 +13,7 @@ export interface LinkBaseProps {
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Primitive } from 'radix-vue'
|
||||
import { Primitive } from 'reka-ui'
|
||||
|
||||
const props = withDefaults(defineProps<LinkBaseProps>(), {
|
||||
as: 'button',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { tv } from 'tailwind-variants'
|
||||
import type { DialogRootProps, DialogRootEmits, DialogContentProps } from 'radix-vue'
|
||||
import type { DialogRootProps, DialogRootEmits, DialogContentProps } from 'reka-ui'
|
||||
import type { AppConfig } from '@nuxt/schema'
|
||||
import _appConfig from '#build/app.config'
|
||||
import theme from '#build/ui/modal'
|
||||
@@ -74,7 +74,7 @@ extendDevtoolsMeta({ example: 'ModalExample' })
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, toRef } from 'vue'
|
||||
import { DialogRoot, DialogTrigger, DialogPortal, DialogOverlay, DialogContent, DialogTitle, DialogDescription, DialogClose, useForwardPropsEmits } from 'radix-vue'
|
||||
import { DialogRoot, DialogTrigger, DialogPortal, DialogOverlay, DialogContent, DialogTitle, DialogDescription, DialogClose, useForwardPropsEmits } from 'reka-ui'
|
||||
import { reactivePick } from '@vueuse/core'
|
||||
import { useAppConfig } from '#imports'
|
||||
import { useLocale } from '../composables/useLocale'
|
||||
@@ -90,6 +90,9 @@ const props = withDefaults(defineProps<ModalProps>(), {
|
||||
const emits = defineEmits<ModalEmits>()
|
||||
const slots = defineSlots<ModalSlots>()
|
||||
|
||||
const { t } = useLocale()
|
||||
const appConfig = useAppConfig()
|
||||
|
||||
const rootProps = useForwardPropsEmits(reactivePick(props, 'open', 'defaultOpen', 'modal'), emits)
|
||||
const contentProps = toRef(() => props.content)
|
||||
const contentEvents = computed(() => {
|
||||
@@ -110,9 +113,6 @@ const contentEvents = computed(() => {
|
||||
}
|
||||
})
|
||||
|
||||
const appConfig = useAppConfig()
|
||||
const { t } = useLocale()
|
||||
|
||||
const ui = computed(() => modal({
|
||||
transition: props.transition,
|
||||
fullscreen: props.fullscreen
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { tv, type VariantProps } from 'tailwind-variants'
|
||||
import type { NavigationMenuRootProps, NavigationMenuRootEmits, NavigationMenuContentProps, CollapsibleRootProps } from 'radix-vue'
|
||||
import type { NavigationMenuRootProps, NavigationMenuRootEmits, NavigationMenuContentProps, CollapsibleRootProps } from 'reka-ui'
|
||||
import type { AppConfig } from '@nuxt/schema'
|
||||
import _appConfig from '#build/app.config'
|
||||
import theme from '#build/ui/navigation-menu'
|
||||
@@ -31,7 +31,7 @@ export interface NavigationMenuItem extends Omit<LinkProps, 'raw' | 'custom'>, P
|
||||
|
||||
type NavigationMenuVariants = VariantProps<typeof navigationMenu>
|
||||
|
||||
export interface NavigationMenuProps<T> extends Pick<NavigationMenuRootProps, 'defaultValue' | 'delayDuration' | 'disableClickTrigger' | 'disableHoverTrigger' | 'modelValue' | 'skipDelayDuration'> {
|
||||
export interface NavigationMenuProps<T> extends Pick<NavigationMenuRootProps, 'modelValue' | 'defaultValue' | 'delayDuration' | 'disableClickTrigger' | 'disableHoverTrigger' | 'skipDelayDuration' | 'disablePointerLeaveClose' | 'unmountOnHide'> {
|
||||
/**
|
||||
* The element or component this component should render as.
|
||||
* @defaultValue 'div'
|
||||
@@ -122,8 +122,8 @@ extendDevtoolsMeta({
|
||||
</script>
|
||||
|
||||
<script setup lang="ts" generic="T extends MaybeArrayOfArrayItem<I>, I extends MaybeArrayOfArray<NavigationMenuItem>">
|
||||
import { computed, reactive, toRef } from 'vue'
|
||||
import { NavigationMenuRoot, NavigationMenuList, NavigationMenuItem, NavigationMenuTrigger, NavigationMenuContent, NavigationMenuLink, NavigationMenuIndicator, NavigationMenuViewport, useForwardPropsEmits } from 'radix-vue'
|
||||
import { computed, toRef } from 'vue'
|
||||
import { NavigationMenuRoot, NavigationMenuList, NavigationMenuItem, NavigationMenuTrigger, NavigationMenuContent, NavigationMenuLink, NavigationMenuIndicator, NavigationMenuViewport, useForwardPropsEmits } from 'reka-ui'
|
||||
import { createReusableTemplate } from '@vueuse/core'
|
||||
import { get } from '../utils'
|
||||
import { pickLinkProps } from '../utils/link'
|
||||
@@ -137,19 +137,24 @@ import UCollapsible from './Collapsible.vue'
|
||||
const props = withDefaults(defineProps<NavigationMenuProps<I>>(), {
|
||||
orientation: 'horizontal',
|
||||
delayDuration: 0,
|
||||
labelKey: 'label'
|
||||
labelKey: 'label',
|
||||
unmountOnHide: true
|
||||
})
|
||||
const emits = defineEmits<NavigationMenuEmits>()
|
||||
const slots = defineSlots<NavigationMenuSlots<T>>()
|
||||
|
||||
const rootProps = useForwardPropsEmits(reactive({
|
||||
const rootProps = useForwardPropsEmits(computed(() => ({
|
||||
as: props.as,
|
||||
modelValue: props.modelValue,
|
||||
defaultValue: props.defaultValue,
|
||||
delayDuration: props.delayDuration,
|
||||
skipDelayDuration: props.skipDelayDuration,
|
||||
orientation: props.orientation
|
||||
}), emits)
|
||||
orientation: props.orientation,
|
||||
disableClickTrigger: props.disableClickTrigger,
|
||||
disableHoverTrigger: props.disableHoverTrigger,
|
||||
disablePointerLeaveClose: props.disablePointerLeaveClose,
|
||||
unmountOnHide: props.unmountOnHide
|
||||
})), emits)
|
||||
|
||||
const contentProps = toRef(() => props.content)
|
||||
|
||||
@@ -199,7 +204,7 @@ const lists = computed(() => props.items?.length ? (Array.isArray(props.items[0]
|
||||
</slot>
|
||||
</DefineItemTemplate>
|
||||
|
||||
<NavigationMenuRoot v-bind="rootProps" :data-orientation="orientation" :class="ui.root({ class: [props.class, props.ui?.root] })">
|
||||
<NavigationMenuRoot v-bind="rootProps" :class="ui.root({ class: [props.class, props.ui?.root] })">
|
||||
<template v-for="(list, listIndex) in lists" :key="`list-${listIndex}`">
|
||||
<NavigationMenuList :class="ui.list({ class: props.ui?.list })">
|
||||
<component
|
||||
@@ -209,6 +214,7 @@ const lists = computed(() => props.items?.length ? (Array.isArray(props.items[0]
|
||||
as="li"
|
||||
:value="item.value || String(index)"
|
||||
:default-open="item.defaultOpen"
|
||||
:unmount-on-hide="(item.children?.length && orientation === 'vertical') ? unmountOnHide : undefined"
|
||||
:open="item.open"
|
||||
:class="ui.item({ class: props.ui?.item })"
|
||||
>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { tv } from 'tailwind-variants'
|
||||
import type { PaginationRootProps, PaginationRootEmits } from 'radix-vue'
|
||||
import type { PaginationRootProps, PaginationRootEmits } from 'reka-ui'
|
||||
import type { AppConfig } from '@nuxt/schema'
|
||||
import type { RouteLocationRaw } from '#vue-router'
|
||||
import _appConfig from '#build/app.config'
|
||||
@@ -12,7 +12,7 @@ const appConfig = _appConfig as AppConfig & { ui: { pagination: Partial<typeof t
|
||||
|
||||
const pagination = tv({ extend: tv(theme), ...(appConfig.ui?.pagination || {}) })
|
||||
|
||||
export interface PaginationProps extends Pick<PaginationRootProps, 'defaultPage' | 'disabled' | 'itemsPerPage' | 'page' | 'showEdges' | 'siblingCount' | 'total'> {
|
||||
export interface PaginationProps extends Partial<Pick<PaginationRootProps, 'defaultPage' | 'disabled' | 'itemsPerPage' | 'page' | 'showEdges' | 'siblingCount' | 'total'>> {
|
||||
/**
|
||||
* The element or component this component should render as.
|
||||
* @defaultValue 'div'
|
||||
@@ -104,7 +104,7 @@ extendDevtoolsMeta({ defaultProps: { total: 50 } })
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { PaginationRoot, PaginationList, PaginationListItem, PaginationFirst, PaginationPrev, PaginationEllipsis, PaginationNext, PaginationLast, useForwardPropsEmits } from 'radix-vue'
|
||||
import { PaginationRoot, PaginationList, PaginationListItem, PaginationFirst, PaginationPrev, PaginationEllipsis, PaginationNext, PaginationLast, useForwardPropsEmits } from 'reka-ui'
|
||||
import { reactivePick } from '@vueuse/core'
|
||||
import { useAppConfig } from '#imports'
|
||||
import { useLocale } from '../composables/useLocale'
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import _appConfig from '#build/app.config'
|
||||
import theme from '#build/ui/pin-input'
|
||||
import type { AppConfig } from '@nuxt/schema'
|
||||
import type { PinInputRootEmits, PinInputRootProps } from 'radix-vue'
|
||||
import type { PinInputRootEmits, PinInputRootProps } from 'reka-ui'
|
||||
import { tv, type VariantProps } from 'tailwind-variants'
|
||||
import type { PartialString } from '../types/utils'
|
||||
|
||||
@@ -35,7 +35,7 @@ export type PinInputEmits = PinInputRootEmits & {
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { PinInputInput, PinInputRoot, useForwardPropsEmits } from 'radix-vue'
|
||||
import { PinInputInput, PinInputRoot, useForwardPropsEmits } from 'reka-ui'
|
||||
import { reactivePick } from '@vueuse/core'
|
||||
import { useFormField } from '../composables/useFormField'
|
||||
import { looseToNumber } from '../utils'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { tv } from 'tailwind-variants'
|
||||
import type { PopoverRootProps, HoverCardRootProps, PopoverRootEmits, PopoverContentProps, PopoverArrowProps } from 'radix-vue'
|
||||
import type { PopoverRootProps, HoverCardRootProps, PopoverRootEmits, PopoverContentProps, PopoverArrowProps } from 'reka-ui'
|
||||
import type { AppConfig } from '@nuxt/schema'
|
||||
import _appConfig from '#build/app.config'
|
||||
import theme from '#build/ui/popover'
|
||||
@@ -53,8 +53,8 @@ extendDevtoolsMeta({ example: 'PopoverExample' })
|
||||
<script setup lang="ts">
|
||||
import { computed, toRef } from 'vue'
|
||||
import { defu } from 'defu'
|
||||
import { useForwardPropsEmits } from 'radix-vue'
|
||||
import { Popover, HoverCard } from 'radix-vue/namespaced'
|
||||
import { useForwardPropsEmits } from 'reka-ui'
|
||||
import { Popover, HoverCard } from 'reka-ui/namespaced'
|
||||
import { reactivePick } from '@vueuse/core'
|
||||
|
||||
const props = withDefaults(defineProps<PopoverProps>(), {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<!-- eslint-disable vue/block-tag-newline -->
|
||||
<script lang="ts">
|
||||
import { tv, type VariantProps } from 'tailwind-variants'
|
||||
import type { ProgressRootProps, ProgressRootEmits } from 'radix-vue'
|
||||
import type { ProgressRootProps, ProgressRootEmits } from 'reka-ui'
|
||||
import type { AppConfig } from '@nuxt/schema'
|
||||
import _appConfig from '#build/app.config'
|
||||
import theme from '#build/ui/progress'
|
||||
@@ -48,7 +48,7 @@ export type ProgressSlots = {
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { ProgressIndicator, ProgressRoot, useForwardPropsEmits } from 'radix-vue'
|
||||
import { Primitive, ProgressRoot, ProgressIndicator, useForwardPropsEmits } from 'reka-ui'
|
||||
import { reactivePick } from '@vueuse/core'
|
||||
import { useLocale } from '../composables/useLocale'
|
||||
|
||||
@@ -62,7 +62,7 @@ defineSlots<ProgressSlots>()
|
||||
|
||||
const { dir } = useLocale()
|
||||
|
||||
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'getValueLabel', 'modelValue'), emits)
|
||||
const rootProps = useForwardPropsEmits(reactivePick(props, 'getValueLabel', 'modelValue'), emits)
|
||||
|
||||
const isIndeterminate = computed(() => rootProps.value.modelValue === null)
|
||||
const hasSteps = computed(() => Array.isArray(props.max))
|
||||
@@ -159,7 +159,7 @@ const ui = computed(() => progress({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="ui.root({ class: [props.class, props.ui?.root] })">
|
||||
<Primitive :as="as" :class="ui.root({ class: [props.class, props.ui?.root] })">
|
||||
<div v-if="!isIndeterminate && (status || $slots.status)" :class="ui.status({ class: props.ui?.status })" :style="statusStyle">
|
||||
<slot name="status" :percent="percent">
|
||||
{{ percent }}%
|
||||
@@ -177,5 +177,5 @@ const ui = computed(() => progress({
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Primitive>
|
||||
</template>
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { tv, type VariantProps } from 'tailwind-variants'
|
||||
import type { RadioGroupRootProps, RadioGroupRootEmits } from 'radix-vue'
|
||||
import type { RadioGroupRootProps, RadioGroupRootEmits, AcceptableValue } from 'reka-ui'
|
||||
import type { AppConfig } from '@nuxt/schema'
|
||||
import _appConfig from '#build/app.config'
|
||||
import theme from '#build/ui/radio-group'
|
||||
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
||||
import type { AcceptableValue } from '../types/utils'
|
||||
|
||||
const appConfig = _appConfig as AppConfig & { ui: { radioGroup: Partial<typeof theme> } }
|
||||
|
||||
@@ -58,7 +57,7 @@ export type RadioGroupEmits = RadioGroupRootEmits & {
|
||||
change: [payload: Event]
|
||||
}
|
||||
|
||||
type SlotProps<T> = (props: { item: T, modelValue?: string }) => any
|
||||
type SlotProps<T> = (props: { item: T, modelValue?: AcceptableValue }) => any
|
||||
|
||||
export interface RadioGroupSlots<T> {
|
||||
legend(props?: {}): any
|
||||
@@ -71,7 +70,7 @@ extendDevtoolsMeta({ defaultProps: { items: ['Option 1', 'Option 2', 'Option 3']
|
||||
|
||||
<script setup lang="ts" generic="T extends RadioGroupItem | AcceptableValue">
|
||||
import { computed, useId } from 'vue'
|
||||
import { RadioGroupRoot, RadioGroupItem, RadioGroupIndicator, Label, useForwardPropsEmits } from 'radix-vue'
|
||||
import { RadioGroupRoot, RadioGroupItem, RadioGroupIndicator, Label, useForwardPropsEmits } from 'reka-ui'
|
||||
import { reactivePick } from '@vueuse/core'
|
||||
import { useFormField } from '../composables/useFormField'
|
||||
import { get } from '../utils'
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { tv, type VariantProps } from 'tailwind-variants'
|
||||
import type { SelectRootProps, SelectRootEmits, SelectContentProps, SelectArrowProps } from 'radix-vue'
|
||||
import type { SelectRootProps, SelectRootEmits, SelectContentProps, SelectArrowProps, AcceptableValue } from 'reka-ui'
|
||||
import type { AppConfig } from '@nuxt/schema'
|
||||
import _appConfig from '#build/app.config'
|
||||
import theme from '#build/ui/select'
|
||||
import type { UseComponentIconsProps } from '../composables/useComponentIcons'
|
||||
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
||||
import type { AvatarProps, ChipProps, InputProps } from '../types'
|
||||
import type { AcceptableValue, PartialString, MaybeArrayOfArray, MaybeArrayOfArrayItem, SelectModelValue, SelectModelValueEmits, SelectItemKey } from '../types/utils'
|
||||
import type { PartialString, MaybeArrayOfArray, MaybeArrayOfArrayItem, SelectModelValue, SelectModelValueEmits, SelectItemKey } from '../types/utils'
|
||||
|
||||
const appConfig = _appConfig as AppConfig & { ui: { select: Partial<typeof theme> } }
|
||||
|
||||
@@ -29,7 +29,7 @@ export interface SelectItem {
|
||||
|
||||
type SelectVariants = VariantProps<typeof select>
|
||||
|
||||
export interface SelectProps<T extends MaybeArrayOfArrayItem<I>, I extends MaybeArrayOfArray<SelectItem | AcceptableValue> = MaybeArrayOfArray<SelectItem | AcceptableValue>, V extends SelectItemKey<T> | undefined = undefined> extends Omit<SelectRootProps, 'dir' | 'modelValue'>, UseComponentIconsProps {
|
||||
export interface SelectProps<T extends MaybeArrayOfArrayItem<I>, I extends MaybeArrayOfArray<SelectItem | AcceptableValue | boolean> = MaybeArrayOfArray<SelectItem | AcceptableValue | boolean>, V extends SelectItemKey<T> | undefined = undefined, M extends boolean = false> extends Omit<SelectRootProps<T>, 'dir' | 'multiple' | 'modelValue' | 'defaultValue' | 'by'>, UseComponentIconsProps {
|
||||
id?: string
|
||||
/** The placeholder text when the select is empty. */
|
||||
placeholder?: string
|
||||
@@ -72,25 +72,30 @@ export interface SelectProps<T extends MaybeArrayOfArrayItem<I>, I extends Maybe
|
||||
*/
|
||||
labelKey?: V
|
||||
items?: I
|
||||
/** The value of the Select when initially rendered. Use when you do not need to control the state of the Select. */
|
||||
defaultValue?: SelectModelValue<T, V, M, T extends { value: infer U } ? U : never>
|
||||
/** The controlled value of the Select. Can be bind as `v-model`. */
|
||||
modelValue?: SelectModelValue<T, V, M, T extends { value: infer U } ? U : never>
|
||||
/** Whether multiple options can be selected or not. */
|
||||
multiple?: M & boolean
|
||||
/** Highlight the ring color like a focus state. */
|
||||
highlight?: boolean
|
||||
class?: any
|
||||
ui?: PartialString<typeof select.slots>
|
||||
/** The controlled value of the Select. Can be bind as `v-model`. */
|
||||
modelValue?: SelectModelValue<T, V, false, T extends { value: infer U } ? U : never>
|
||||
}
|
||||
|
||||
export type SelectEmits<T, V> = Omit<SelectRootEmits, 'update:modelValue'> & {
|
||||
export type SelectEmits<T, V, M extends boolean> = Omit<SelectRootEmits<T>, 'update:modelValue'> & {
|
||||
change: [payload: Event]
|
||||
blur: [payload: FocusEvent]
|
||||
focus: [payload: FocusEvent]
|
||||
} & SelectModelValueEmits<T, V, false, T extends { value: infer U } ? U : never>
|
||||
} & SelectModelValueEmits<T, V, M, T extends { value: infer U } ? U : never>
|
||||
|
||||
type SlotProps<T> = (props: { item: T, index: number }) => any
|
||||
|
||||
export interface SelectSlots<T> {
|
||||
'leading'(props: { modelValue: string, open: boolean, ui: any }): any
|
||||
'trailing'(props: { modelValue: string, open: boolean, ui: any }): any
|
||||
export interface SelectSlots<T, M extends boolean> {
|
||||
'leading'(props: { modelValue?: M extends true ? AcceptableValue[] : AcceptableValue, open: boolean, ui: any }): any
|
||||
'default'(props: { modelValue?: M extends true ? AcceptableValue[] : AcceptableValue, open: boolean }): any
|
||||
'trailing'(props: { modelValue?: M extends true ? AcceptableValue[] : AcceptableValue, open: boolean, ui: any }): any
|
||||
'item': SlotProps<T>
|
||||
'item-leading': SlotProps<T>
|
||||
'item-label': SlotProps<T>
|
||||
@@ -100,30 +105,30 @@ export interface SelectSlots<T> {
|
||||
extendDevtoolsMeta({ defaultProps: { items: ['Option 1', 'Option 2', 'Option 3'] } })
|
||||
</script>
|
||||
|
||||
<script setup lang="ts" generic="T extends MaybeArrayOfArrayItem<I>, I extends MaybeArrayOfArray<SelectItem | AcceptableValue> = MaybeArrayOfArray<SelectItem | AcceptableValue>, V extends SelectItemKey<T> | undefined = undefined">
|
||||
<script setup lang="ts" generic="T extends MaybeArrayOfArrayItem<I>, I extends MaybeArrayOfArray<SelectItem | AcceptableValue | boolean> = MaybeArrayOfArray<SelectItem | AcceptableValue | boolean>, V extends SelectItemKey<T> | undefined = undefined, M extends boolean = false">
|
||||
import { computed, toRef } from 'vue'
|
||||
import { SelectRoot, SelectArrow, SelectTrigger, SelectValue, SelectPortal, SelectContent, SelectViewport, SelectLabel, SelectGroup, SelectItem, SelectItemIndicator, SelectItemText, SelectSeparator, useForwardPropsEmits } from 'radix-vue'
|
||||
import { SelectRoot, SelectArrow, SelectTrigger, SelectPortal, SelectContent, SelectViewport, SelectLabel, SelectGroup, SelectItem, SelectItemIndicator, SelectItemText, SelectSeparator, useForwardPropsEmits } from 'reka-ui'
|
||||
import { defu } from 'defu'
|
||||
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 { get } from '../utils'
|
||||
import { get, compare } from '../utils'
|
||||
import UIcon from './Icon.vue'
|
||||
import UAvatar from './Avatar.vue'
|
||||
import UChip from './Chip.vue'
|
||||
|
||||
const props = withDefaults(defineProps<SelectProps<T, I, V>>(), {
|
||||
const props = withDefaults(defineProps<SelectProps<T, I, V, M>>(), {
|
||||
valueKey: 'value' as never,
|
||||
labelKey: 'label' as never,
|
||||
portal: true
|
||||
})
|
||||
const emits = defineEmits<SelectEmits<T, V>>()
|
||||
const slots = defineSlots<SelectSlots<T>>()
|
||||
const emits = defineEmits<SelectEmits<T, V, M>>()
|
||||
const slots = defineSlots<SelectSlots<T, M>>()
|
||||
|
||||
const appConfig = useAppConfig()
|
||||
const rootProps = useForwardPropsEmits(reactivePick(props, 'modelValue', 'defaultValue', 'open', 'defaultOpen', 'disabled', 'autocomplete', 'required'), emits)
|
||||
const rootProps = useForwardPropsEmits(reactivePick(props, 'modelValue', 'defaultValue', 'open', 'defaultOpen', 'disabled', 'autocomplete', 'required', 'multiple'), emits)
|
||||
const contentProps = toRef(() => defu(props.content, { side: 'bottom', sideOffset: 8, collisionPadding: 8, position: 'popper' }) as SelectContentProps)
|
||||
const arrowProps = toRef(() => props.arrow as SelectArrowProps)
|
||||
|
||||
@@ -145,6 +150,17 @@ const ui = computed(() => select({
|
||||
}))
|
||||
|
||||
const groups = computed(() => props.items?.length ? (Array.isArray(props.items[0]) ? props.items : [props.items]) as SelectItem[][] : [])
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
const items = computed(() => groups.value.flatMap(group => group) as T[])
|
||||
|
||||
function displayValue(value?: AcceptableValue | AcceptableValue[]): string | undefined {
|
||||
if (props.multiple && Array.isArray(value)) {
|
||||
return value.map(v => displayValue(v)).filter(Boolean).join(', ')
|
||||
}
|
||||
|
||||
const item = items.value.find(item => compare(typeof item === 'object' ? get(item, props.valueKey as string) : item, value))
|
||||
return item && (typeof item === 'object' ? get(item, props.labelKey as string) : item)
|
||||
}
|
||||
|
||||
function onUpdate(value: any) {
|
||||
// @ts-expect-error - 'target' does not exist in type 'EventInit'
|
||||
@@ -166,14 +182,13 @@ function onUpdateOpen(value: boolean) {
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- eslint-disable vue/no-template-shadow -->
|
||||
<template>
|
||||
<SelectRoot
|
||||
:id="id"
|
||||
v-slot="{ modelValue, open }"
|
||||
v-bind="rootProps"
|
||||
:name="name"
|
||||
:default-value="(defaultValue as string)"
|
||||
:model-value="(modelValue as string)"
|
||||
:autocomplete="autocomplete"
|
||||
:disabled="disabled"
|
||||
@update:model-value="onUpdate"
|
||||
@@ -181,16 +196,25 @@ function onUpdateOpen(value: boolean) {
|
||||
>
|
||||
<SelectTrigger :class="ui.base({ class: [props.class, props.ui?.base] })">
|
||||
<span v-if="isLeading || !!avatar || !!slots.leading" :class="ui.leading({ class: props.ui?.leading })">
|
||||
<slot name="leading" :model-value="modelValue" :open="open" :ui="ui">
|
||||
<slot name="leading" :model-value="(modelValue as M extends true ? AcceptableValue[] : AcceptableValue)" :open="open" :ui="ui">
|
||||
<UIcon v-if="isLeading && leadingIconName" :name="leadingIconName" :class="ui.leadingIcon({ class: props.ui?.leadingIcon })" />
|
||||
<UAvatar v-else-if="!!avatar" :size="((props.ui?.itemLeadingAvatarSize || ui.itemLeadingAvatarSize()) as AvatarProps['size'])" v-bind="avatar" :class="ui.itemLeadingAvatar({ class: props.ui?.itemLeadingAvatar })" />
|
||||
</slot>
|
||||
</span>
|
||||
|
||||
<SelectValue :placeholder="placeholder ?? ' '" :class="ui.value({ class: props.ui?.value })" />
|
||||
<slot :model-value="(modelValue as M extends true ? AcceptableValue[] : AcceptableValue)" :open="open">
|
||||
<template v-for="displayedModelValue in [displayValue(modelValue)]" :key="displayedModelValue">
|
||||
<span v-if="displayedModelValue" :class="ui.value({ class: props.ui?.value })">
|
||||
{{ displayedModelValue }}
|
||||
</span>
|
||||
<span v-else :class="ui.placeholder({ class: props.ui?.placeholder })">
|
||||
{{ placeholder ?? ' ' }}
|
||||
</span>
|
||||
</template>
|
||||
</slot>
|
||||
|
||||
<span v-if="isTrailing || !!slots.trailing" :class="ui.trailing({ class: props.ui?.trailing })">
|
||||
<slot name="trailing" :model-value="modelValue" :open="open" :ui="ui">
|
||||
<slot name="trailing" :model-value="(modelValue as M extends true ? AcceptableValue[] : AcceptableValue)" :open="open" :ui="ui">
|
||||
<UIcon v-if="trailingIconName" :name="trailingIconName" :class="ui.trailingIcon({ class: props.ui?.trailingIcon })" />
|
||||
</slot>
|
||||
</span>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { tv, type VariantProps } from 'tailwind-variants'
|
||||
import type { ComboboxRootProps, ComboboxRootEmits, ComboboxContentProps, ComboboxArrowProps } from 'radix-vue'
|
||||
import type { ComboboxRootProps, ComboboxRootEmits, ComboboxContentProps, ComboboxArrowProps, AcceptableValue } from 'reka-ui'
|
||||
import type { AppConfig } from '@nuxt/schema'
|
||||
import _appConfig from '#build/app.config'
|
||||
import theme from '#build/ui/select-menu'
|
||||
import type { UseComponentIconsProps } from '../composables/useComponentIcons'
|
||||
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
|
||||
import type { AvatarProps, ChipProps, InputProps } from '../types'
|
||||
import type { AcceptableValue, ArrayOrWrapped, PartialString, MaybeArrayOfArray, MaybeArrayOfArrayItem, SelectModelValue, SelectModelValueEmits, SelectItemKey } from '../types/utils'
|
||||
import type { PartialString, MaybeArrayOfArray, MaybeArrayOfArrayItem, SelectModelValue, SelectModelValueEmits, SelectItemKey } from '../types/utils'
|
||||
|
||||
const appConfig = _appConfig as AppConfig & { ui: { selectMenu: Partial<typeof theme> } }
|
||||
|
||||
@@ -29,7 +29,7 @@ export interface SelectMenuItem {
|
||||
|
||||
type SelectMenuVariants = VariantProps<typeof selectMenu>
|
||||
|
||||
export interface SelectMenuProps<T extends MaybeArrayOfArrayItem<I>, I extends MaybeArrayOfArray<SelectMenuItem | AcceptableValue> = MaybeArrayOfArray<SelectMenuItem | AcceptableValue>, V extends SelectItemKey<T> | undefined = undefined, M extends boolean = false> extends Pick<ComboboxRootProps<T>, 'defaultValue' | 'selectedValue' | 'open' | 'defaultOpen' | 'searchTerm' | 'disabled' | 'name' | 'resetSearchTermOnBlur'>, UseComponentIconsProps {
|
||||
export interface SelectMenuProps<T extends MaybeArrayOfArrayItem<I>, I extends MaybeArrayOfArray<SelectMenuItem | AcceptableValue | boolean> = MaybeArrayOfArray<SelectMenuItem | AcceptableValue | boolean>, V extends SelectItemKey<T> | undefined = undefined, M extends boolean = false> extends Pick<ComboboxRootProps<T>, 'open' | 'defaultOpen' | 'disabled' | 'name' | 'resetSearchTermOnBlur' | 'highlightOnHover'>, UseComponentIconsProps {
|
||||
id?: string
|
||||
/** The placeholder text when the select is empty. */
|
||||
placeholder?: string
|
||||
@@ -69,13 +69,6 @@ export interface SelectMenuProps<T extends MaybeArrayOfArrayItem<I>, I extends M
|
||||
* @defaultValue true
|
||||
*/
|
||||
portal?: boolean
|
||||
/**
|
||||
* Whether to filter items or not, can be an array of fields to filter. Defaults to `[labelKey]`.
|
||||
* When `false`, items will not be filtered which is useful for custom filtering (useAsyncData, useFetch, etc.).
|
||||
* `['label']`{lang="ts-type"}
|
||||
* @defaultValue true
|
||||
*/
|
||||
filter?: boolean | string[]
|
||||
/**
|
||||
* When `items` is an array of objects, select the field to use as the value instead of the object itself.
|
||||
* @defaultValue undefined
|
||||
@@ -87,92 +80,100 @@ export interface SelectMenuProps<T extends MaybeArrayOfArrayItem<I>, I extends M
|
||||
*/
|
||||
labelKey?: V
|
||||
items?: I
|
||||
/** The value of the SelectMenu when initially rendered. Use when you do not need to control the state of the SelectMenu. */
|
||||
defaultValue?: SelectModelValue<T, V, M>
|
||||
/** The controlled value of the SelectMenu. Can be binded-with with `v-model`. */
|
||||
modelValue?: SelectModelValue<T, V, M>
|
||||
/** Whether multiple options can be selected or not. */
|
||||
multiple?: M & boolean
|
||||
/** Highlight the ring color like a focus state. */
|
||||
highlight?: boolean
|
||||
/**
|
||||
* Determines if custom user input that does not exist in options can be added.
|
||||
* @defaultValue false
|
||||
*/
|
||||
createItem?: boolean | 'always' | { placement?: 'top' | 'bottom', when?: 'empty' | 'always' }
|
||||
createItem?: boolean | 'always' | { position?: 'top' | 'bottom', when?: 'empty' | 'always' }
|
||||
/**
|
||||
* Fields to filter items by.
|
||||
* @defaultValue [labelKey]
|
||||
*/
|
||||
filterFields?: string[]
|
||||
/**
|
||||
* When `true`, disable the default filters, useful for custom filtering (useAsyncData, useFetch, etc.).
|
||||
* @defaultValue false
|
||||
*/
|
||||
ignoreFilter?: boolean
|
||||
class?: any
|
||||
ui?: PartialString<typeof selectMenu.slots>
|
||||
/** The controlled value of the Combobox. Can be binded-with with `v-model`. */
|
||||
modelValue?: SelectModelValue<T, V, M>
|
||||
/** Whether multiple options can be selected or not. */
|
||||
multiple?: M & boolean
|
||||
}
|
||||
|
||||
export type SelectMenuEmits<T, V, M extends boolean> = Omit<ComboboxRootEmits<T>, 'update:modelValue'> & {
|
||||
change: [payload: Event]
|
||||
blur: [payload: FocusEvent]
|
||||
focus: [payload: FocusEvent]
|
||||
create: [payload: Event, item: T]
|
||||
create: [item: string]
|
||||
} & SelectModelValueEmits<T, V, M>
|
||||
|
||||
type SlotProps<T> = (props: { item: T, index: number }) => any
|
||||
|
||||
export interface SelectMenuSlots<T> {
|
||||
'leading'(props: { modelValue: T, open: boolean, ui: any }): any
|
||||
'default'(props: { modelValue: T, open: boolean }): any
|
||||
'trailing'(props: { modelValue: T, open: boolean, ui: any }): any
|
||||
export interface SelectMenuSlots<T, M extends boolean> {
|
||||
'leading'(props: { modelValue?: M extends true ? T[] : T, open: boolean, ui: any }): any
|
||||
'default'(props: { modelValue?: M extends true ? T[] : T, open: boolean }): any
|
||||
'trailing'(props: { modelValue?: M extends true ? T[] : T, open: boolean, ui: any }): any
|
||||
'empty'(props: { searchTerm?: string }): any
|
||||
'item': SlotProps<T>
|
||||
'item-leading': SlotProps<T>
|
||||
'item-label': SlotProps<T>
|
||||
'item-trailing': SlotProps<T>
|
||||
'create-item-label'(props: { item: T }): any
|
||||
'create-item-label'(props: { item: string }): any
|
||||
}
|
||||
|
||||
extendDevtoolsMeta({ defaultProps: { items: ['Option 1', 'Option 2', 'Option 3'] } })
|
||||
</script>
|
||||
|
||||
<script setup lang="ts" generic="T extends MaybeArrayOfArrayItem<I>, I extends MaybeArrayOfArray<SelectMenuItem | AcceptableValue> = MaybeArrayOfArray<SelectMenuItem | AcceptableValue>, V extends SelectItemKey<T> | undefined = undefined, M extends boolean = false">
|
||||
<script setup lang="ts" generic="T extends MaybeArrayOfArrayItem<I>, I extends MaybeArrayOfArray<SelectMenuItem | AcceptableValue | boolean> = MaybeArrayOfArray<SelectMenuItem | AcceptableValue | boolean>, V extends SelectItemKey<T> | undefined = undefined, M extends boolean = false">
|
||||
import { computed, toRef, toRaw } from 'vue'
|
||||
import { ComboboxRoot, ComboboxArrow, ComboboxAnchor, ComboboxInput, ComboboxTrigger, ComboboxPortal, ComboboxContent, ComboboxViewport, ComboboxEmpty, ComboboxGroup, ComboboxLabel, ComboboxSeparator, ComboboxItem, ComboboxItemIndicator, useForwardPropsEmits } from 'radix-vue'
|
||||
import { ComboboxRoot, ComboboxArrow, ComboboxAnchor, ComboboxInput, ComboboxTrigger, ComboboxPortal, ComboboxContent, ComboboxViewport, ComboboxEmpty, ComboboxGroup, ComboboxLabel, ComboboxSeparator, ComboboxItem, ComboboxItemIndicator, useForwardPropsEmits, useFilter } from 'reka-ui'
|
||||
import { defu } from 'defu'
|
||||
import { isEqual } from 'ohash'
|
||||
import { reactivePick, createReusableTemplate } from '@vueuse/core'
|
||||
import { useAppConfig } from '#imports'
|
||||
import { useButtonGroup } from '../composables/useButtonGroup'
|
||||
import { useComponentIcons } from '../composables/useComponentIcons'
|
||||
import { useFormField } from '../composables/useFormField'
|
||||
import { useLocale } from '../composables/useLocale'
|
||||
import { get, escapeRegExp } from '../utils'
|
||||
import { get, compare } from '../utils'
|
||||
import UIcon from './Icon.vue'
|
||||
import UAvatar from './Avatar.vue'
|
||||
import UChip from './Chip.vue'
|
||||
import UInput from './Input.vue'
|
||||
|
||||
const props = withDefaults(defineProps<SelectMenuProps<T, I, V, M>>(), {
|
||||
search: true,
|
||||
portal: true,
|
||||
autofocusDelay: 0,
|
||||
searchInput: true,
|
||||
filter: true,
|
||||
labelKey: 'label' as never
|
||||
})
|
||||
|
||||
const emits = defineEmits<SelectMenuEmits<T, V, M>>()
|
||||
const slots = defineSlots<SelectMenuSlots<T>>()
|
||||
const slots = defineSlots<SelectMenuSlots<T, M>>()
|
||||
|
||||
const searchTerm = defineModel<string>('searchTerm', { default: '' })
|
||||
|
||||
const appConfig = useAppConfig()
|
||||
|
||||
const { t } = useLocale()
|
||||
const rootProps = useForwardPropsEmits(reactivePick(props, 'modelValue', 'defaultValue', 'selectedValue', 'open', 'defaultOpen', 'multiple', 'resetSearchTermOnBlur'), emits)
|
||||
const appConfig = useAppConfig()
|
||||
const { contains } = useFilter({ sensitivity: 'base' })
|
||||
|
||||
const rootProps = useForwardPropsEmits(reactivePick(props, 'modelValue', 'defaultValue', 'open', 'defaultOpen', 'multiple', 'resetSearchTermOnBlur', 'highlightOnHover'), emits)
|
||||
const contentProps = toRef(() => defu(props.content, { side: 'bottom', sideOffset: 8, collisionPadding: 8, position: 'popper' }) as ComboboxContentProps)
|
||||
const arrowProps = toRef(() => props.arrow as ComboboxArrowProps)
|
||||
const searchInputProps = toRef(() => defu(props.searchInput, { placeholder: 'Search...', variant: 'none' }) as InputProps)
|
||||
|
||||
const [DefineCreateItemTemplate, ReuseCreateItemTemplate] = createReusableTemplate()
|
||||
|
||||
const { emitFormBlur, emitFormInput, emitFormChange, size: formGroupSize, color, id, name, highlight, disabled } = useFormField<InputProps>(props)
|
||||
const { orientation, size: buttonGroupSize } = useButtonGroup<InputProps>(props)
|
||||
const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponentIcons(toRef(() => defu(props, { trailingIcon: appConfig.ui.icons.chevronDown })))
|
||||
|
||||
const selectSize = computed(() => buttonGroupSize.value || formGroupSize.value)
|
||||
|
||||
const [DefineCreateItemTemplate, ReuseCreateItemTemplate] = createReusableTemplate()
|
||||
|
||||
const ui = computed(() => selectMenu({
|
||||
color: color.value,
|
||||
variant: props.variant,
|
||||
@@ -193,69 +194,49 @@ function displayValue(value: T | T[]): string {
|
||||
return value && (typeof value === 'object' ? get(value, props.labelKey as string) : value)
|
||||
}
|
||||
|
||||
const item = items.value.find(item => isEqual(get(item as Record<string, any>, props.valueKey as string), value)) ?? (props.createItem && value)
|
||||
|
||||
const item = items.value.find(item => compare(typeof item === 'object' ? get(item, props.valueKey as string) : item, value))
|
||||
return item && (typeof item === 'object' ? get(item, props.labelKey as string) : item)
|
||||
}
|
||||
|
||||
function filterFunction(
|
||||
inputItems: ArrayOrWrapped<T> = items.value as ArrayOrWrapped<T>,
|
||||
filterSearchTerm: string = searchTerm.value,
|
||||
comparator = (item: any, term: string) => String(item).search(new RegExp(term, 'i')) !== -1
|
||||
): ArrayOrWrapped<T> {
|
||||
if (props.filter === false) {
|
||||
return inputItems
|
||||
}
|
||||
|
||||
const fields = Array.isArray(props.filter) ? props.filter : [props.labelKey]
|
||||
const escapedSearchTerm = escapeRegExp(filterSearchTerm)
|
||||
|
||||
return inputItems.filter((item: T) => {
|
||||
if (typeof item !== 'object') {
|
||||
return comparator(item, escapedSearchTerm)
|
||||
}
|
||||
|
||||
return fields.some((field) => {
|
||||
const child = get(item, field as string)
|
||||
|
||||
return child !== null && child !== undefined && comparator(child, escapedSearchTerm)
|
||||
})
|
||||
}) as ArrayOrWrapped<T>
|
||||
}
|
||||
|
||||
const groups = computed(() => props.items?.length ? (Array.isArray(props.items[0]) ? props.items : [props.items]) as SelectMenuItem[][] : [])
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
const items = computed(() => groups.value.flatMap(group => group) as T[])
|
||||
|
||||
const creatable = computed(() => {
|
||||
if (!props.createItem) {
|
||||
const filteredGroups = computed(() => {
|
||||
if (props.ignoreFilter || !searchTerm.value) {
|
||||
return groups.value
|
||||
}
|
||||
|
||||
const fields = Array.isArray(props.filterFields) ? props.filterFields : [props.labelKey] as string[]
|
||||
|
||||
return groups.value.map(items => items.filter((item) => {
|
||||
if (typeof item !== 'object') {
|
||||
return contains(item, searchTerm.value)
|
||||
}
|
||||
|
||||
if (item.type && ['label', 'separator'].includes(item.type)) {
|
||||
return true
|
||||
}
|
||||
|
||||
return fields.some(field => contains(get(item, field), searchTerm.value))
|
||||
})).filter(group => group.filter(item => !item.type || !['label', 'separator'].includes(item.type)).length > 0)
|
||||
})
|
||||
const filteredItems = computed(() => filteredGroups.value.flatMap(group => group) as T[])
|
||||
|
||||
const createItem = computed(() => {
|
||||
if (!props.createItem || !searchTerm.value) {
|
||||
return false
|
||||
}
|
||||
|
||||
const isModelValueCustom = props.modelValue && filterFunction((props.multiple && Array.isArray(props.modelValue) ? props.modelValue : [props.modelValue]) as ArrayOrWrapped<T>, searchTerm.value, (item, term) => String(item) === term).length === 1
|
||||
|
||||
if (isModelValueCustom) {
|
||||
return false
|
||||
}
|
||||
|
||||
const filteredItems = filterFunction()
|
||||
const newItem = searchTerm.value && {
|
||||
item: props.valueKey ? { [props.valueKey]: searchTerm.value, [props.labelKey ?? 'label']: searchTerm.value } : searchTerm.value,
|
||||
position: ((typeof props.createItem === 'object' && props.createItem.placement) || 'bottom') as 'top' | 'bottom'
|
||||
}
|
||||
const newItem = props.valueKey ? { [props.valueKey]: searchTerm.value } as T : searchTerm.value
|
||||
|
||||
if ((typeof props.createItem === 'object' && props.createItem.when === 'always') || props.createItem === 'always') {
|
||||
return (filteredItems.length === 1 && filterFunction(filteredItems, searchTerm.value, (item, term) => String(item) === term).length === 1) ? false : newItem
|
||||
return !filteredItems.value.find(item => compare(item, newItem, props.valueKey))
|
||||
}
|
||||
|
||||
return filteredItems.length > 0 ? false : newItem
|
||||
return !filteredItems.value.length
|
||||
})
|
||||
|
||||
const rootItems = computed(() => [
|
||||
...(creatable.value && creatable.value.position === 'top' ? [creatable.value.item] : []),
|
||||
...filterFunction(),
|
||||
...(creatable.value && creatable.value.position === 'bottom' ? [creatable.value.item] : [])
|
||||
] as ArrayOrWrapped<T>)
|
||||
const createItemPosition = computed(() => typeof props.createItem === 'object' ? props.createItem.position : 'bottom')
|
||||
|
||||
function onUpdate(value: any) {
|
||||
if (toRaw(props.modelValue) === value) {
|
||||
@@ -280,17 +261,18 @@ function onUpdateOpen(value: boolean) {
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- eslint-disable vue/no-template-shadow -->
|
||||
<template>
|
||||
<DefineCreateItemTemplate>
|
||||
<ComboboxGroup v-if="creatable" :class="ui.group({ class: props.ui?.group })">
|
||||
<ComboboxGroup :class="ui.group({ class: props.ui?.group })">
|
||||
<ComboboxItem
|
||||
:class="ui.item({ class: props.ui?.item })"
|
||||
:value="valueKey && typeof creatable.item === 'object' ? get(creatable.item, props.valueKey as string) : creatable.item"
|
||||
@select="e => emits('create', e, (creatable as any).item as T)"
|
||||
:value="searchTerm"
|
||||
@select.prevent="emits('create', searchTerm)"
|
||||
>
|
||||
<span :class="ui.itemLabel({ class: props.ui?.itemLabel })">
|
||||
<slot name="create-item-label" :item="(creatable.item as T)">
|
||||
{{ t('selectMenu.create', { label: typeof creatable.item === 'object' ? get(creatable.item, props.labelKey as string) : creatable.item }) }}
|
||||
<slot name="create-item-label" :item="searchTerm">
|
||||
{{ t('selectMenu.create', { label: searchTerm }) }}
|
||||
</slot>
|
||||
</span>
|
||||
</ComboboxItem>
|
||||
@@ -301,26 +283,24 @@ function onUpdateOpen(value: boolean) {
|
||||
:id="id"
|
||||
v-slot="{ modelValue, open }"
|
||||
v-bind="rootProps"
|
||||
v-model:search-term="searchTerm"
|
||||
ignore-filter
|
||||
as-child
|
||||
:name="name"
|
||||
:disabled="disabled"
|
||||
:display-value="() => searchTerm"
|
||||
:filter-function="() => rootItems"
|
||||
@update:model-value="onUpdate"
|
||||
@update:open="onUpdateOpen"
|
||||
>
|
||||
<ComboboxAnchor as-child>
|
||||
<ComboboxTrigger :class="ui.base({ class: [props.class, props.ui?.base] })" tabindex="0">
|
||||
<span v-if="isLeading || !!avatar || !!slots.leading" :class="ui.leading({ class: props.ui?.leading })">
|
||||
<slot name="leading" :model-value="(modelValue as T)" :open="open" :ui="ui">
|
||||
<slot name="leading" :model-value="(modelValue as M extends true ? T[] : T)" :open="open" :ui="ui">
|
||||
<UIcon v-if="isLeading && leadingIconName" :name="leadingIconName" :class="ui.leadingIcon({ class: props.ui?.leadingIcon })" />
|
||||
<UAvatar v-else-if="!!avatar" :size="((props.ui?.itemLeadingAvatarSize || ui.itemLeadingAvatarSize()) as AvatarProps['size'])" v-bind="avatar" :class="ui.itemLeadingAvatar({ class: props.ui?.itemLeadingAvatar })" />
|
||||
</slot>
|
||||
</span>
|
||||
|
||||
<slot :model-value="(modelValue as T)" :open="open">
|
||||
<template v-for="displayedModelValue in [displayValue(modelValue)]" :key="displayedModelValue">
|
||||
<slot :model-value="(modelValue as M extends true ? T[] : T)" :open="open">
|
||||
<template v-for="displayedModelValue in [displayValue(modelValue as M extends true ? T[] : T)]" :key="displayedModelValue">
|
||||
<span v-if="displayedModelValue" :class="ui.value({ class: props.ui?.value })">
|
||||
{{ displayedModelValue }}
|
||||
</span>
|
||||
@@ -331,7 +311,7 @@ function onUpdateOpen(value: boolean) {
|
||||
</slot>
|
||||
|
||||
<span v-if="isTrailing || !!slots.trailing" :class="ui.trailing({ class: props.ui?.trailing })">
|
||||
<slot name="trailing" :model-value="(modelValue as T)" :open="open" :ui="ui">
|
||||
<slot name="trailing" :model-value="(modelValue as M extends true ? T[] : T)" :open="open" :ui="ui">
|
||||
<UIcon v-if="trailingIconName" :name="trailingIconName" :class="ui.trailingIcon({ class: props.ui?.trailingIcon })" />
|
||||
</slot>
|
||||
</span>
|
||||
@@ -340,7 +320,7 @@ function onUpdateOpen(value: boolean) {
|
||||
|
||||
<ComboboxPortal :disabled="!portal">
|
||||
<ComboboxContent :class="ui.content({ class: props.ui?.content })" v-bind="contentProps">
|
||||
<ComboboxInput v-if="!!searchInput" as-child>
|
||||
<ComboboxInput v-if="!!searchInput" v-model="searchTerm" :display-value="() => searchTerm" as-child>
|
||||
<UInput autofocus autocomplete="off" v-bind="searchInputProps" :class="ui.input({ class: props.ui?.input })" />
|
||||
</ComboboxInput>
|
||||
|
||||
@@ -351,9 +331,9 @@ function onUpdateOpen(value: boolean) {
|
||||
</ComboboxEmpty>
|
||||
|
||||
<ComboboxViewport :class="ui.viewport({ class: props.ui?.viewport })">
|
||||
<ReuseCreateItemTemplate v-if="creatable && creatable.position === 'top'" />
|
||||
<ReuseCreateItemTemplate v-if="createItem && createItemPosition === 'top'" />
|
||||
|
||||
<ComboboxGroup v-for="(group, groupIndex) in groups" :key="`group-${groupIndex}`" :class="ui.group({ class: props.ui?.group })">
|
||||
<ComboboxGroup v-for="(group, groupIndex) in filteredGroups" :key="`group-${groupIndex}`" :class="ui.group({ class: props.ui?.group })">
|
||||
<template v-for="(item, index) in group" :key="`group-${groupIndex}-${index}`">
|
||||
<ComboboxLabel v-if="item?.type === 'label'" :class="ui.label({ class: props.ui?.label })">
|
||||
{{ get(item, props.labelKey as string) }}
|
||||
@@ -400,7 +380,7 @@ function onUpdateOpen(value: boolean) {
|
||||
</template>
|
||||
</ComboboxGroup>
|
||||
|
||||
<ReuseCreateItemTemplate v-if="creatable && creatable.position === 'bottom'" />
|
||||
<ReuseCreateItemTemplate v-if="createItem && createItemPosition === 'bottom'" />
|
||||
</ComboboxViewport>
|
||||
|
||||
<ComboboxArrow v-if="!!arrow" v-bind="arrowProps" :class="ui.arrow({ class: props.ui?.arrow })" />
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { tv, type VariantProps } from 'tailwind-variants'
|
||||
import type { SeparatorProps as _SeparatorProps } from 'radix-vue'
|
||||
import type { SeparatorProps as _SeparatorProps } from 'reka-ui'
|
||||
import type { AppConfig } from '@nuxt/schema'
|
||||
import _appConfig from '#build/app.config'
|
||||
import theme from '#build/ui/separator'
|
||||
@@ -43,7 +43,7 @@ export interface SeparatorSlots {
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { Separator, useForwardProps } from 'radix-vue'
|
||||
import { Separator, useForwardProps } from 'reka-ui'
|
||||
import { reactivePick } from '@vueuse/core'
|
||||
import UIcon from './Icon.vue'
|
||||
import UAvatar from './Avatar.vue'
|
||||
|
||||
@@ -22,7 +22,7 @@ extendDevtoolsMeta({ example: 'SkeletonExample' })
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Primitive } from 'radix-vue'
|
||||
import { Primitive } from 'reka-ui'
|
||||
|
||||
const props = defineProps<SkeletonProps>()
|
||||
</script>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { tv, type VariantProps } from 'tailwind-variants'
|
||||
import type { DialogRootProps, DialogRootEmits, DialogContentProps } from 'radix-vue'
|
||||
import type { DialogRootProps, DialogRootEmits, DialogContentProps } from 'reka-ui'
|
||||
import type { AppConfig } from '@nuxt/schema'
|
||||
import _appConfig from '#build/app.config'
|
||||
import theme from '#build/ui/slideover'
|
||||
@@ -72,7 +72,7 @@ extendDevtoolsMeta({ example: 'SlideoverExample' })
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, toRef } from 'vue'
|
||||
import { DialogRoot, DialogTrigger, DialogPortal, DialogOverlay, DialogContent, DialogTitle, DialogDescription, DialogClose, useForwardPropsEmits } from 'radix-vue'
|
||||
import { DialogRoot, DialogTrigger, DialogPortal, DialogOverlay, DialogContent, DialogTitle, DialogDescription, DialogClose, useForwardPropsEmits } from 'reka-ui'
|
||||
import { reactivePick } from '@vueuse/core'
|
||||
import { useAppConfig } from '#imports'
|
||||
import { useLocale } from '../composables/useLocale'
|
||||
@@ -89,6 +89,9 @@ const props = withDefaults(defineProps<SlideoverProps>(), {
|
||||
const emits = defineEmits<SlideoverEmits>()
|
||||
const slots = defineSlots<SlideoverSlots>()
|
||||
|
||||
const { t } = useLocale()
|
||||
const appConfig = useAppConfig()
|
||||
|
||||
const rootProps = useForwardPropsEmits(reactivePick(props, 'open', 'defaultOpen', 'modal'), emits)
|
||||
const contentProps = toRef(() => props.content)
|
||||
const contentEvents = computed(() => {
|
||||
@@ -109,9 +112,6 @@ const contentEvents = computed(() => {
|
||||
}
|
||||
})
|
||||
|
||||
const appConfig = useAppConfig()
|
||||
const { t } = useLocale()
|
||||
|
||||
const ui = computed(() => slideover({
|
||||
transition: props.transition,
|
||||
side: props.side
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { tv, type VariantProps } from 'tailwind-variants'
|
||||
import type { SliderRootProps } from 'radix-vue'
|
||||
import type { SliderRootProps } from 'reka-ui'
|
||||
import type { AppConfig } from '@nuxt/schema'
|
||||
import _appConfig from '#build/app.config'
|
||||
import theme from '#build/ui/slider'
|
||||
@@ -38,7 +38,7 @@ export interface SliderEmits {
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { SliderRoot, SliderRange, SliderTrack, SliderThumb, useForwardPropsEmits } from 'radix-vue'
|
||||
import { SliderRoot, SliderRange, SliderTrack, SliderThumb, useForwardPropsEmits } from 'reka-ui'
|
||||
import { reactivePick } from '@vueuse/core'
|
||||
import { useFormField } from '../composables/useFormField'
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { tv, type VariantProps } from 'tailwind-variants'
|
||||
import type { SwitchRootProps } from 'radix-vue'
|
||||
import type { SwitchRootProps } from 'reka-ui'
|
||||
import type { AppConfig } from '@nuxt/schema'
|
||||
import _appConfig from '#build/app.config'
|
||||
import theme from '#build/ui/switch'
|
||||
@@ -13,7 +13,7 @@ const switchTv = tv({ extend: tv(theme), ...(appConfig.ui?.switch || {}) })
|
||||
|
||||
type SwitchVariants = VariantProps<typeof switchTv>
|
||||
|
||||
export interface SwitchProps extends Pick<SwitchRootProps, 'disabled' | 'id' | 'name' | 'required' | 'value'> {
|
||||
export interface SwitchProps extends Pick<SwitchRootProps, 'disabled' | 'id' | 'name' | 'required' | 'value' | 'defaultValue'> {
|
||||
/**
|
||||
* The element or component this component should render as.
|
||||
* @defaultValue 'div'
|
||||
@@ -34,15 +34,12 @@ export interface SwitchProps extends Pick<SwitchRootProps, 'disabled' | 'id' | '
|
||||
uncheckedIcon?: string
|
||||
label?: string
|
||||
description?: string
|
||||
/** The state of the switch when it is initially rendered. Use when you do not need to control its state. */
|
||||
defaultValue?: boolean
|
||||
class?: any
|
||||
ui?: PartialString<typeof switchTv.slots>
|
||||
}
|
||||
|
||||
export interface SwitchEmits {
|
||||
(e: 'update:modelValue', payload: boolean): void
|
||||
(e: 'change', payload: Event): void
|
||||
export type SwitchEmits = {
|
||||
change: [payload: Event]
|
||||
}
|
||||
|
||||
export interface SwitchSlots {
|
||||
@@ -55,7 +52,7 @@ extendDevtoolsMeta({ defaultProps: { label: 'Switch me!' } })
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, useId } from 'vue'
|
||||
import { SwitchRoot, SwitchThumb, useForwardProps, Label } from 'radix-vue'
|
||||
import { Primitive, SwitchRoot, SwitchThumb, useForwardProps, Label } from 'reka-ui'
|
||||
import { reactivePick } from '@vueuse/core'
|
||||
import { useAppConfig } from '#imports'
|
||||
import { useFormField } from '../composables/useFormField'
|
||||
@@ -65,10 +62,10 @@ const props = defineProps<SwitchProps>()
|
||||
const slots = defineSlots<SwitchSlots>()
|
||||
const emits = defineEmits<SwitchEmits>()
|
||||
|
||||
const modelValue = defineModel<boolean | undefined>({ default: undefined })
|
||||
const modelValue = defineModel<boolean>({ default: undefined })
|
||||
|
||||
const appConfig = useAppConfig()
|
||||
const rootProps = useForwardProps(reactivePick(props, 'as', 'required', 'value'))
|
||||
const rootProps = useForwardProps(reactivePick(props, 'required', 'value', 'defaultValue'))
|
||||
|
||||
const { id: _id, emitFormChange, emitFormInput, size, color, name, disabled } = useFormField<SwitchProps>(props)
|
||||
const id = _id.value ?? useId()
|
||||
@@ -91,17 +88,16 @@ function onUpdate(value: any) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="ui.root({ class: [props.class, props.ui?.root] })">
|
||||
<Primitive :as="as" :class="ui.root({ class: [props.class, props.ui?.root] })">
|
||||
<div :class="ui.container({ class: props.ui?.container })">
|
||||
<SwitchRoot
|
||||
:id="id"
|
||||
v-model:checked="modelValue"
|
||||
:default-checked="defaultValue"
|
||||
v-bind="rootProps"
|
||||
v-model="modelValue"
|
||||
:name="name"
|
||||
:disabled="disabled || loading"
|
||||
:class="ui.base({ class: props.ui?.base })"
|
||||
@update:checked="onUpdate"
|
||||
@update:model-value="onUpdate"
|
||||
>
|
||||
<SwitchThumb :class="ui.thumb({ class: props.ui?.thumb })">
|
||||
<UIcon v-if="loading" :name="loadingIcon || appConfig.ui.icons.loading" :class="ui.icon({ class: props.ui?.icon, checked: true, unchecked: true })" />
|
||||
@@ -124,5 +120,5 @@ function onUpdate(value: any) {
|
||||
</slot>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Primitive>
|
||||
</template>
|
||||
|
||||
@@ -50,6 +50,11 @@ export interface TableData {
|
||||
}
|
||||
|
||||
export interface TableProps<T> {
|
||||
/**
|
||||
* The element or component this component should render as.
|
||||
* @defaultValue 'div'
|
||||
*/
|
||||
as?: any
|
||||
data?: T[]
|
||||
columns?: TableColumn<T>[]
|
||||
caption?: string
|
||||
@@ -114,14 +119,8 @@ export type TableSlots<T> = {
|
||||
|
||||
<script setup lang="ts" generic="T extends TableData">
|
||||
import { computed } from 'vue'
|
||||
import {
|
||||
FlexRender,
|
||||
getCoreRowModel,
|
||||
getFilteredRowModel,
|
||||
getSortedRowModel,
|
||||
getExpandedRowModel,
|
||||
useVueTable
|
||||
} from '@tanstack/vue-table'
|
||||
import { Primitive } from 'reka-ui'
|
||||
import { FlexRender, getCoreRowModel, getFilteredRowModel, getSortedRowModel, getExpandedRowModel, useVueTable } from '@tanstack/vue-table'
|
||||
import { upperFirst } from 'scule'
|
||||
import { useLocale } from '../composables/useLocale'
|
||||
|
||||
@@ -129,6 +128,7 @@ const props = defineProps<TableProps<T>>()
|
||||
defineSlots<TableSlots<T>>()
|
||||
|
||||
const { t } = useLocale()
|
||||
|
||||
const data = computed(() => props.data ?? [])
|
||||
const columns = computed<TableColumn<T>[]>(() => props.columns ?? Object.keys(data.value[0] ?? {}).map((accessorKey: string) => ({ accessorKey, header: upperFirst(accessorKey) })))
|
||||
|
||||
@@ -203,7 +203,7 @@ defineExpose({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="ui.root({ class: [props.class, props.ui?.root] })">
|
||||
<Primitive :as="as" :class="ui.root({ class: [props.class, props.ui?.root] })">
|
||||
<table :class="ui.base({ class: [props.ui?.base] })">
|
||||
<caption v-if="caption" :class="ui.caption({ class: [props.ui?.caption] })">
|
||||
<slot name="caption">
|
||||
@@ -258,5 +258,5 @@ defineExpose({
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</Primitive>
|
||||
</template>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { tv, type VariantProps } from 'tailwind-variants'
|
||||
import type { TabsRootProps, TabsRootEmits, TabsContentProps } from 'radix-vue'
|
||||
import type { TabsRootProps, TabsRootEmits } from 'reka-ui'
|
||||
import type { AppConfig } from '@nuxt/schema'
|
||||
import _appConfig from '#build/app.config'
|
||||
import theme from '#build/ui/tabs'
|
||||
@@ -25,7 +25,7 @@ export interface TabsItem {
|
||||
|
||||
type TabsVariants = VariantProps<typeof tabs>
|
||||
|
||||
export interface TabsProps<T> extends Pick<TabsRootProps<string | number>, 'defaultValue' | 'modelValue' | 'activationMode'> {
|
||||
export interface TabsProps<T> extends Pick<TabsRootProps<string | number>, 'defaultValue' | 'modelValue' | 'activationMode' | 'unmountOnHide'> {
|
||||
/**
|
||||
* The element or component this component should render as.
|
||||
* @defaultValue 'div'
|
||||
@@ -44,7 +44,7 @@ export interface TabsProps<T> extends Pick<TabsRootProps<string | number>, 'defa
|
||||
* The content of the tabs, can be disabled to prevent rendering the content.
|
||||
* @defaultValue true
|
||||
*/
|
||||
content?: boolean | Omit<TabsContentProps, 'as' | 'asChild' | 'value'>
|
||||
content?: boolean
|
||||
/**
|
||||
* The key used to get the label from the item.
|
||||
* @defaultValue 'label'
|
||||
@@ -85,9 +85,8 @@ extendDevtoolsMeta({
|
||||
</script>
|
||||
|
||||
<script setup lang="ts" generic="T extends TabsItem">
|
||||
import { computed, toRef } from 'vue'
|
||||
import { defu } from 'defu'
|
||||
import { TabsRoot, TabsList, TabsIndicator, TabsTrigger, TabsContent, useForwardPropsEmits } from 'radix-vue'
|
||||
import { computed } from 'vue'
|
||||
import { TabsRoot, TabsList, TabsIndicator, TabsTrigger, TabsContent, useForwardPropsEmits } from 'reka-ui'
|
||||
import { reactivePick } from '@vueuse/core'
|
||||
import { get } from '../utils'
|
||||
import UIcon from './Icon.vue'
|
||||
@@ -102,8 +101,7 @@ const props = withDefaults(defineProps<TabsProps<T>>(), {
|
||||
const emits = defineEmits<TabsEmits>()
|
||||
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)
|
||||
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'modelValue', 'defaultValue', 'orientation', 'activationMode', 'unmountOnHide'), emits)
|
||||
|
||||
const ui = computed(() => tabs({
|
||||
color: props.color,
|
||||
@@ -133,7 +131,7 @@ const ui = computed(() => tabs({
|
||||
</TabsList>
|
||||
|
||||
<template v-if="!!content">
|
||||
<TabsContent v-for="(item, index) of items" :key="index" v-bind="contentProps" :value="item.value || String(index)" :class="ui.content({ class: props.ui?.content })">
|
||||
<TabsContent v-for="(item, index) of items" :key="index" :value="item.value || String(index)" :class="ui.content({ class: props.ui?.content })">
|
||||
<slot :name="item.slot || 'content'" :item="item" :index="index">
|
||||
{{ item.content }}
|
||||
</slot>
|
||||
|
||||
@@ -11,6 +11,11 @@ const textarea = tv({ extend: tv(theme), ...(appConfig.ui?.textarea || {}) })
|
||||
type TextareaVariants = VariantProps<typeof textarea>
|
||||
|
||||
export interface TextareaProps {
|
||||
/**
|
||||
* The element or component this component should render as.
|
||||
* @defaultValue 'div'
|
||||
*/
|
||||
as?: any
|
||||
id?: string
|
||||
name?: string
|
||||
/** The placeholder text when the textarea is empty. */
|
||||
@@ -44,6 +49,7 @@ export interface TextareaSlots {
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, nextTick, watch } from 'vue'
|
||||
import { Primitive } from 'reka-ui'
|
||||
import { useFormField } from '../composables/useFormField'
|
||||
import { looseToNumber } from '../utils'
|
||||
|
||||
@@ -167,7 +173,7 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="ui.root({ class: [props.class, props.ui?.root] })">
|
||||
<Primitive :as="as" :class="ui.root({ class: [props.class, props.ui?.root] })">
|
||||
<textarea
|
||||
:id="id"
|
||||
ref="textareaRef"
|
||||
@@ -185,5 +191,5 @@ onMounted(() => {
|
||||
/>
|
||||
|
||||
<slot />
|
||||
</div>
|
||||
</Primitive>
|
||||
</template>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { tv, type VariantProps } from 'tailwind-variants'
|
||||
import type { ToastRootProps, ToastRootEmits } from 'radix-vue'
|
||||
import type { ToastRootProps, ToastRootEmits } from 'reka-ui'
|
||||
import type { AppConfig } from '@nuxt/schema'
|
||||
import _appConfig from '#build/app.config'
|
||||
import theme from '#build/ui/toast'
|
||||
@@ -61,7 +61,7 @@ extendDevtoolsMeta<ToastProps>({ ignore: true })
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { ToastRoot, ToastTitle, ToastDescription, ToastAction, ToastClose, useForwardPropsEmits } from 'radix-vue'
|
||||
import { ToastRoot, ToastTitle, ToastDescription, ToastAction, ToastClose, useForwardPropsEmits } from 'reka-ui'
|
||||
import { reactivePick } from '@vueuse/core'
|
||||
import { useAppConfig } from '#imports'
|
||||
import { useLocale } from '../composables/useLocale'
|
||||
@@ -75,8 +75,9 @@ const props = withDefaults(defineProps<ToastProps>(), {
|
||||
const emits = defineEmits<ToastEmits>()
|
||||
const slots = defineSlots<ToastSlots>()
|
||||
|
||||
const appConfig = useAppConfig()
|
||||
const { t } = useLocale()
|
||||
const appConfig = useAppConfig()
|
||||
|
||||
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'defaultOpen', 'open', 'duration', 'type'), emits)
|
||||
|
||||
const multiline = computed(() => !!props.title && !!props.description)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { tv, type VariantProps } from 'tailwind-variants'
|
||||
import type { ToastProviderProps } from 'radix-vue'
|
||||
import type { ToastProviderProps } from 'reka-ui'
|
||||
import type { AppConfig } from '@nuxt/schema'
|
||||
import _appConfig from '#build/app.config'
|
||||
import theme from '#build/ui/toaster'
|
||||
@@ -41,7 +41,7 @@ extendDevtoolsMeta({ example: 'ToasterExample' })
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { ToastProvider, ToastViewport, ToastPortal, useForwardProps } from 'radix-vue'
|
||||
import { ToastProvider, ToastViewport, ToastPortal, useForwardProps } from 'reka-ui'
|
||||
import { reactivePick } from '@vueuse/core'
|
||||
import { useToast } from '../composables/useToast'
|
||||
import { omit } from '../utils'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { tv } from 'tailwind-variants'
|
||||
import type { TooltipRootProps, TooltipRootEmits, TooltipContentProps, TooltipArrowProps } from 'radix-vue'
|
||||
import type { TooltipRootProps, TooltipRootEmits, TooltipContentProps, TooltipArrowProps } from 'reka-ui'
|
||||
import type { AppConfig } from '@nuxt/schema'
|
||||
import _appConfig from '#build/app.config'
|
||||
import theme from '#build/ui/tooltip'
|
||||
@@ -48,7 +48,7 @@ extendDevtoolsMeta({ example: 'TooltipExample', defaultProps: { text: 'Hello wor
|
||||
<script setup lang="ts">
|
||||
import { computed, toRef } from 'vue'
|
||||
import { defu } from 'defu'
|
||||
import { TooltipRoot, TooltipTrigger, TooltipPortal, TooltipContent, TooltipArrow, useForwardPropsEmits } from 'radix-vue'
|
||||
import { TooltipRoot, TooltipTrigger, TooltipPortal, TooltipContent, TooltipArrow, useForwardPropsEmits } from 'reka-ui'
|
||||
import { reactivePick } from '@vueuse/core'
|
||||
import UKbd from './Kbd.vue'
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@keyframes accordion-up {
|
||||
from {
|
||||
height: var(--radix-accordion-content-height);
|
||||
height: var(--reka-accordion-content-height);
|
||||
}
|
||||
|
||||
to {
|
||||
@@ -14,13 +14,13 @@
|
||||
}
|
||||
|
||||
to {
|
||||
height: var(--radix-accordion-content-height);
|
||||
height: var(--reka-accordion-content-height);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes collapsible-up {
|
||||
from {
|
||||
height: var(--radix-collapsible-content-height);
|
||||
height: var(--reka-collapsible-content-height);
|
||||
}
|
||||
|
||||
to {
|
||||
@@ -34,7 +34,7 @@
|
||||
}
|
||||
|
||||
to {
|
||||
height: var(--radix-collapsible-content-height);
|
||||
height: var(--reka-collapsible-content-height);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,10 +17,6 @@ export type GetObjectField<MaybeObject, Key extends string> = MaybeObject extend
|
||||
? MaybeObject[Key]
|
||||
: never
|
||||
|
||||
export type AcceptableValue = string | number | boolean | Record<string, any>
|
||||
|
||||
export type ArrayOrWrapped<T> = T extends any[] ? T : Array<T>
|
||||
|
||||
export type PartialString<T> = {
|
||||
[K in keyof T]?: string
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { isEqual } from 'ohash'
|
||||
|
||||
export function pick<Data extends object, Keys extends keyof Data>(data: Data, keys: Keys[]): Pick<Data, Keys> {
|
||||
const result = {} as Pick<Data, Keys>
|
||||
|
||||
@@ -60,6 +62,22 @@ export function looseToNumber(val: any): any {
|
||||
return Number.isNaN(n) ? val : n
|
||||
}
|
||||
|
||||
export function escapeRegExp(string: string) {
|
||||
return string.replace(/[.*+?^${}()|[\]\\]/g, match => `\\${match}`)
|
||||
export function compare<T>(value?: T, currentValue?: T, comparator?: string | ((a: T, b: T) => boolean)) {
|
||||
if (value === undefined || currentValue === undefined) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (typeof value === 'string') {
|
||||
return value === currentValue
|
||||
}
|
||||
|
||||
if (typeof comparator === 'function') {
|
||||
return comparator(value, currentValue)
|
||||
}
|
||||
|
||||
if (typeof comparator === 'string') {
|
||||
return get(value!, comparator) === get(currentValue!, comparator)
|
||||
}
|
||||
|
||||
return isEqual(value, currentValue)
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ export interface LinkSlots {
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { isEqual, diff } from 'ohash'
|
||||
import { useForwardProps } from 'radix-vue'
|
||||
import { useForwardProps } from 'reka-ui'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { hasProtocol } from 'ufo'
|
||||
import { useRoute } from '#imports'
|
||||
|
||||
@@ -3,7 +3,7 @@ import { buttonGroupVariant } from './button-group'
|
||||
|
||||
export default (options: Required<ModuleOptions>) => ({
|
||||
slots: {
|
||||
base: ['rounded-[calc(var(--ui-radius)*1.5)] font-medium inline-flex items-center focus:outline-none disabled:cursor-not-allowed aria-disabled:cursor-not-allowed disabled:opacity-75 aria-disabled:opacity-75', options.theme.transitions && 'transition-colors'],
|
||||
base: ['rounded-[calc(var(--ui-radius)*1.5)] font-medium inline-flex items-center focus:outline-hidden disabled:cursor-not-allowed aria-disabled:cursor-not-allowed disabled:opacity-75 aria-disabled:opacity-75', options.theme.transitions && 'transition-colors'],
|
||||
label: 'truncate',
|
||||
leadingIcon: 'shrink-0',
|
||||
leadingAvatar: 'shrink-0',
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { ModuleOptions } from '../module'
|
||||
export default (options: Required<ModuleOptions>) => ({
|
||||
slots: {
|
||||
root: 'relative flex items-start',
|
||||
base: 'shrink-0 flex items-center justify-center rounded-[var(--ui-radius)] text-[var(--ui-bg)] ring ring-inset ring-[var(--ui-border-accented)] focus:outline-none focus-visible:outline-2 focus-visible:outline-offset-2',
|
||||
base: 'shrink-0 flex items-center justify-center rounded-[var(--ui-radius)] text-[var(--ui-bg)] ring ring-inset ring-[var(--ui-border-accented)] focus-visible:outline-2 focus-visible:outline-offset-2',
|
||||
container: 'flex items-center',
|
||||
wrapper: 'ms-2',
|
||||
icon: 'shrink-0 size-full',
|
||||
|
||||
@@ -5,8 +5,8 @@ export default (options: Required<ModuleOptions>) => ({
|
||||
root: 'flex flex-col min-h-0 min-w-0 divide-y divide-[var(--ui-border)]',
|
||||
input: '[&>input]:h-12',
|
||||
close: '',
|
||||
content: 'relative overflow-hidden',
|
||||
viewport: 'divide-y divide-[var(--ui-border)] scroll-py-1',
|
||||
content: 'relative overflow-hidden flex flex-col',
|
||||
viewport: 'relative divide-y divide-[var(--ui-border)] scroll-py-1 overflow-y-auto flex-1 focus:outline-none',
|
||||
group: 'p-1 isolate',
|
||||
empty: 'py-6 text-center text-sm text-[var(--ui-text-muted)]',
|
||||
label: 'px-2 py-1.5 text-xs font-semibold text-[var(--ui-text-highlighted)]',
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { ModuleOptions } from '../module'
|
||||
|
||||
export default (options: Required<ModuleOptions>) => ({
|
||||
slots: {
|
||||
content: 'min-w-32 bg-[var(--ui-bg)] shadow-lg rounded-[calc(var(--ui-radius)*1.5)] ring ring-[var(--ui-border)] divide-y divide-[var(--ui-border)] overflow-y-auto scroll-py-1 data-[state=open]:animate-[scale-in_100ms_ease-out]',
|
||||
content: 'min-w-32 bg-[var(--ui-bg)] shadow-lg rounded-[calc(var(--ui-radius)*1.5)] ring ring-[var(--ui-border)] divide-y divide-[var(--ui-border)] overflow-y-auto scroll-py-1 data-[state=open]:animate-[scale-in_100ms_ease-out] data-[state=closed]:animate-[scale-out_100ms_ease-in]',
|
||||
group: 'p-1 isolate',
|
||||
label: 'w-full flex items-center font-semibold text-[var(--ui-text-highlighted)]',
|
||||
separator: '-mx-1 my-1 h-px bg-[var(--ui-border)]',
|
||||
|
||||
@@ -8,7 +8,7 @@ export default (options: Required<ModuleOptions>) => {
|
||||
base: () => ['rounded-[calc(var(--ui-radius)*1.5)]', options.theme.transitions && 'transition-colors'],
|
||||
trailing: 'group absolute inset-y-0 end-0 flex items-center disabled:cursor-not-allowed disabled:opacity-75',
|
||||
arrow: 'fill-[var(--ui-border)]',
|
||||
content: 'max-h-60 w-[var(--radix-popper-anchor-width)] bg-[var(--ui-bg)] shadow-lg rounded-[calc(var(--ui-radius)*1.5)] ring ring-[var(--ui-border)] overflow-hidden data-[state=open]:animate-[scale-in_100ms_ease-out] data-[state=closed]:animate-[scale-out_100ms_ease-in]',
|
||||
content: 'max-h-60 w-[var(--reka-popper-anchor-width)] bg-[var(--ui-bg)] shadow-lg rounded-[calc(var(--ui-radius)*1.5)] ring ring-[var(--ui-border)] overflow-hidden data-[state=open]:animate-[scale-in_100ms_ease-out] data-[state=closed]:animate-[scale-out_100ms_ease-in]',
|
||||
viewport: 'divide-y divide-[var(--ui-border)] scroll-py-1',
|
||||
group: 'p-1 isolate',
|
||||
empty: 'py-2 text-center text-sm text-[var(--ui-text-muted)]',
|
||||
@@ -33,7 +33,6 @@ export default (options: Required<ModuleOptions>) => {
|
||||
multiple: {
|
||||
true: {
|
||||
root: 'flex-wrap',
|
||||
base: '',
|
||||
tagsInput: 'border-0 bg-transparent placeholder:text-[var(--ui-text-dimmed)] focus:outline-none disabled:cursor-not-allowed disabled:opacity-75'
|
||||
},
|
||||
false: {
|
||||
@@ -102,12 +101,12 @@ export default (options: Required<ModuleOptions>) => {
|
||||
color,
|
||||
multiple: true,
|
||||
variant: ['outline', 'subtle'],
|
||||
class: `has-[:focus-visible]:ring-2 has-[:focus-visible]:ring-[var(--${color})]`
|
||||
class: `has-focus-visible:ring-2 has-focus-visible:ring-[var(--ui-${color})]`
|
||||
})), {
|
||||
color: 'neutral',
|
||||
multiple: true,
|
||||
variant: ['outline', 'subtle'],
|
||||
class: 'has-[:focus-visible]:ring-2 has-[:focus-visible]:ring-[var(--ui-border-inverted)]'
|
||||
class: 'has-focus-visible:ring-2 has-focus-visible:ring-[var(--ui-border-inverted)]'
|
||||
}]
|
||||
}, input(options))
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ export default (options: Required<ModuleOptions>) => ({
|
||||
childLinkDescription: 'text-sm text-[var(--ui-text-muted)]',
|
||||
separator: 'px-2 h-px bg-[var(--ui-border)]',
|
||||
viewportWrapper: 'absolute top-full left-0 flex w-full justify-center',
|
||||
viewport: 'relative overflow-hidden bg-[var(--ui-bg)] shadow-lg rounded-[calc(var(--ui-radius)*1.5)] ring ring-[var(--ui-border)] h-[var(--radix-navigation-menu-viewport-height)] w-full transition-[width,height] origin-[top_center] data-[state=open]:animate-[scale-in_100ms_ease-out] data-[state=closed]:animate-[scale-out_100ms_ease-in]',
|
||||
viewport: 'relative overflow-hidden bg-[var(--ui-bg)] shadow-lg rounded-[calc(var(--ui-radius)*1.5)] ring ring-[var(--ui-border)] h-[var(--reka-navigation-menu-viewport-height)] w-full transition-[width,height] origin-[top_center] data-[state=open]:animate-[scale-in_100ms_ease-out] data-[state=closed]:animate-[scale-out_100ms_ease-in]',
|
||||
content: 'absolute top-0 left-0 w-full data-[motion=from-start]:animate-[enter-from-left_200ms_ease] data-[motion=from-end]:animate-[enter-from-right_200ms_ease] data-[motion=to-start]:animate-[exit-to-left_200ms_ease] data-[motion=to-end]:animate-[exit-to-right_200ms_ease]',
|
||||
indicator: 'data-[state=visible]:animate-[fade-in_100ms_ease-out] data-[state=hidden]:animate-[fade-out_100ms_ease-in] bottom-0 z-[1] flex h-2.5 items-end justify-center overflow-hidden transition-transform duration-200 ease-out',
|
||||
arrow: 'relative top-[50%] size-2.5 rotate-45 border border-[var(--ui-border)] bg-[var(--ui-bg)] z-[1] rounded-[calc(var(--ui-radius)/2)]'
|
||||
|
||||
@@ -6,7 +6,7 @@ export default (options: Required<ModuleOptions>) => ({
|
||||
fieldset: 'flex',
|
||||
legend: 'mb-1 block font-medium text-[var(--ui-text)]',
|
||||
item: 'flex items-start',
|
||||
base: 'rounded-full ring ring-inset ring-[var(--ui-border-accented)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-offset-[var(--ui-bg)]',
|
||||
base: 'rounded-full ring ring-inset ring-[var(--ui-border-accented)] focus-visible:outline-2 focus-visible:outline-offset-2',
|
||||
indicator: 'flex items-center justify-center size-full rounded-full after:bg-[var(--ui-bg)] after:rounded-full',
|
||||
container: 'flex items-center',
|
||||
wrapper: 'ms-2',
|
||||
|
||||
@@ -5,8 +5,6 @@ import select from './select'
|
||||
export default (options: Required<ModuleOptions>) => {
|
||||
return defu({
|
||||
slots: {
|
||||
value: 'truncate',
|
||||
placeholder: 'truncate text-[var(--ui-text-dimmed)]',
|
||||
input: 'border-b border-[var(--ui-border)]'
|
||||
}
|
||||
}, select(options))
|
||||
|
||||
@@ -8,9 +8,10 @@ export default (options: Required<ModuleOptions>) => {
|
||||
slots: {
|
||||
root: () => undefined,
|
||||
base: () => ['relative group rounded-[calc(var(--ui-radius)*1.5)] inline-flex items-center focus:outline-none disabled:cursor-not-allowed disabled:opacity-75', options.theme.transitions && 'transition-colors'],
|
||||
value: 'truncate group-data-placeholder:text-[var(--ui-text-dimmed)]',
|
||||
value: 'truncate pointer-events-none',
|
||||
placeholder: 'truncate text-[var(--ui-text-dimmed)]',
|
||||
arrow: 'fill-[var(--ui-border)]',
|
||||
content: 'max-h-60 w-[var(--radix-popper-anchor-width)] bg-[var(--ui-bg)] shadow-lg rounded-[calc(var(--ui-radius)*1.5)] ring ring-[var(--ui-border)] overflow-hidden data-[state=open]:animate-[scale-in_100ms_ease-out] data-[state=closed]:animate-[scale-out_100ms_ease-in]',
|
||||
content: 'max-h-60 w-[var(--reka-popper-anchor-width)] bg-[var(--ui-bg)] shadow-lg rounded-[calc(var(--ui-radius)*1.5)] ring ring-[var(--ui-border)] overflow-hidden data-[state=open]:animate-[scale-in_100ms_ease-out] data-[state=closed]:animate-[scale-out_100ms_ease-in]',
|
||||
viewport: 'divide-y divide-[var(--ui-border)] scroll-py-1',
|
||||
group: 'p-1 isolate',
|
||||
empty: 'py-2 text-center text-sm text-[var(--ui-text-muted)]',
|
||||
|
||||
@@ -5,13 +5,13 @@ export default (options: Required<ModuleOptions>) => ({
|
||||
root: 'relative flex items-center select-none touch-none',
|
||||
track: 'relative bg-[var(--ui-bg-accented)] overflow-hidden rounded-full grow',
|
||||
range: 'absolute rounded-full',
|
||||
thumb: 'rounded-full bg-[var(--ui-bg)] ring-2 focus:outline-none focus-visible:outline-2 focus-visible:outline-offset-2'
|
||||
thumb: 'rounded-full bg-[var(--ui-bg)] ring-2 focus-visible:outline-2 focus-visible:outline-offset-2'
|
||||
},
|
||||
variants: {
|
||||
color: {
|
||||
...Object.fromEntries((options.theme.colors || []).map((color: string) => [color, {
|
||||
range: `bg-[var(--ui-${color})]`,
|
||||
thumb: `ring-[var(--ui-${color})] focus-visible:outline-[var(--ui-${color})]`
|
||||
thumb: `ring-[var(--ui-${color})] focus-visible:outline-[var(--ui-${color})]/50`
|
||||
}])),
|
||||
neutral: {
|
||||
range: 'bg-[var(--ui-bg-inverted)]',
|
||||
|
||||
@@ -3,9 +3,9 @@ import type { ModuleOptions } from '../module'
|
||||
export default (options: Required<ModuleOptions>) => ({
|
||||
slots: {
|
||||
root: 'relative flex items-start',
|
||||
base: ['inline-flex items-center shrink-0 rounded-full border-2 border-transparent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--ui-bg)] data-[state=unchecked]:bg-[var(--ui-bg-accented)]', options.theme.transitions && 'transition-colors duration-200'],
|
||||
base: ['inline-flex items-center shrink-0 rounded-full border-2 border-transparent focus-visible:outline-2 focus-visible:outline-offset-2 data-[state=unchecked]:bg-[var(--ui-bg-accented)]', options.theme.transitions && 'transition-colors duration-200'],
|
||||
container: 'flex items-center',
|
||||
thumb: 'group pointer-events-none block rounded-full bg-[var(--ui-bg)] shadow-lg ring-0 transition-transform duration-200 data-[state=unchecked]:translate-x-0 data-[state=unchecked]:rtl:-translate-x-0 flex items-center justify-center',
|
||||
thumb: 'group pointer-events-none rounded-full bg-[var(--ui-bg)] shadow-lg ring-0 transition-transform duration-200 data-[state=unchecked]:translate-x-0 data-[state=unchecked]:rtl:-translate-x-0 flex items-center justify-center',
|
||||
icon: ['absolute shrink-0 group-data-[state=unchecked]:text-[var(--ui-text-dimmed)] opacity-0 size-10/12', options.theme.transitions && 'transition-[color,opacity] duration-200'],
|
||||
wrapper: 'ms-2',
|
||||
label: 'block font-medium text-[var(--ui-text)]',
|
||||
@@ -14,11 +14,11 @@ export default (options: Required<ModuleOptions>) => ({
|
||||
variants: {
|
||||
color: {
|
||||
...Object.fromEntries((options.theme.colors || []).map((color: string) => [color, {
|
||||
base: `data-[state=checked]:bg-[var(--ui-${color})] focus-visible:ring-[var(--ui-${color})]`,
|
||||
base: `data-[state=checked]:bg-[var(--ui-${color})] focus-visible:outline-[var(--ui-${color})]`,
|
||||
icon: `group-data-[state=checked]:text-[var(--ui-${color})]`
|
||||
}])),
|
||||
neutral: {
|
||||
base: 'data-[state=checked]:bg-[var(--ui-bg-inverted)] focus-visible:ring-[var(--ui-border-inverted)]',
|
||||
base: 'data-[state=checked]:bg-[var(--ui-bg-inverted)] focus-visible:outline-[var(--ui-border-inverted)]',
|
||||
icon: 'group-data-[state=checked]:text-[var(--ui-text-highlighted)]'
|
||||
}
|
||||
},
|
||||
|
||||
@@ -5,7 +5,7 @@ export default (options: Required<ModuleOptions>) => ({
|
||||
root: 'flex items-center gap-2',
|
||||
list: 'relative flex p-1 group',
|
||||
indicator: 'absolute transition-[translate,width] duration-200',
|
||||
trigger: ['relative inline-flex items-center shrink-0 data-[state=inactive]:text-[var(--ui-text-muted)] hover:data-[state=inactive]:text-[var(--ui-text)] font-medium rounded-[calc(var(--ui-radius)*1.5)] disabled:cursor-not-allowed disabled:opacity-75 focus:outline-none', options.theme.transitions && 'transition-colors'],
|
||||
trigger: ['relative inline-flex items-center shrink-0 data-[state=inactive]:text-[var(--ui-text-muted)] hover:data-[state=inactive]:text-[var(--ui-text)] font-medium rounded-[calc(var(--ui-radius)*1.5)] disabled:cursor-not-allowed disabled:opacity-75 focus:outline-hidden', options.theme.transitions && 'transition-colors'],
|
||||
content: 'focus:outline-none w-full',
|
||||
leadingIcon: 'shrink-0',
|
||||
leadingAvatar: 'shrink-0',
|
||||
@@ -32,12 +32,12 @@ export default (options: Required<ModuleOptions>) => ({
|
||||
horizontal: {
|
||||
root: 'flex-col',
|
||||
list: 'w-full',
|
||||
indicator: 'left-0 w-[var(--radix-tabs-indicator-size)] translate-x-[var(--radix-tabs-indicator-position)]',
|
||||
indicator: 'left-0 w-[var(--reka-tabs-indicator-size)] translate-x-[var(--reka-tabs-indicator-position)]',
|
||||
trigger: 'justify-center'
|
||||
},
|
||||
vertical: {
|
||||
list: 'flex-col',
|
||||
indicator: 'top-0 h-[var(--radix-tabs-indicator-size)] translate-y-[var(--radix-tabs-indicator-position)]'
|
||||
indicator: 'top-0 h-[var(--reka-tabs-indicator-size)] translate-y-[var(--reka-tabs-indicator-position)]'
|
||||
}
|
||||
},
|
||||
size: {
|
||||
|
||||
@@ -45,10 +45,10 @@ export default {
|
||||
}
|
||||
}, {
|
||||
swipeDirection: ['left', 'right'],
|
||||
class: 'data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=cancel]:translate-x-0'
|
||||
class: 'data-[swipe=move]:translate-x-[var(--reka-toast-swipe-move-x)] data-[swipe=end]:translate-x-[var(--reka-toast-swipe-end-x)] data-[swipe=cancel]:translate-x-0'
|
||||
}, {
|
||||
swipeDirection: ['up', 'down'],
|
||||
class: 'data-[swipe=move]:translate-y-[var(--radix-toast-swipe-move-y)] data-[swipe=end]:translate-y-[var(--radix-toast-swipe-end-y)] data-[swipe=cancel]:translate-y-0'
|
||||
class: 'data-[swipe=move]:translate-y-[var(--reka-toast-swipe-move-y)] data-[swipe=end]:translate-y-[var(--reka-toast-swipe-end-y)] data-[swipe=cancel]:translate-y-0'
|
||||
}],
|
||||
defaultVariants: {
|
||||
position: 'bottom-right'
|
||||
|
||||
Reference in New Issue
Block a user