fix(components): refactor types after @nuxt/module-builder upgrade (#3855)

This commit is contained in:
Benjamin Canac
2025-04-12 17:53:03 +02:00
committed by GitHub
parent 333b7e4c9b
commit 39c861a64b
57 changed files with 635 additions and 731 deletions

View File

@@ -2,14 +2,10 @@
<script lang="ts">
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'
import { tv } from '../utils/tv'
import type { DynamicSlots } from '../types/utils'
import type { DynamicSlots, ComponentConfig } from '../types/utils'
const appConfigAccordion = _appConfig as AppConfig & { ui: { accordion: Partial<typeof theme> } }
const accordion = tv({ extend: tv(theme), ...(appConfigAccordion.ui?.accordion || {}) })
type Accordion = ComponentConfig<typeof theme, AppConfig, 'accordion'>
export interface AccordionItem {
label?: string
@@ -48,7 +44,7 @@ export interface AccordionProps<T extends AccordionItem = AccordionItem> extends
*/
labelKey?: string
class?: any
ui?: Partial<typeof accordion.slots>
ui?: Accordion['slots']
}
export interface AccordionEmits extends AccordionRootEmits {}
@@ -71,6 +67,7 @@ import { AccordionRoot, AccordionItem, AccordionHeader, AccordionTrigger, Accord
import { reactivePick } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { get } from '../utils'
import { tv } from '../utils/tv'
import UIcon from './Icon.vue'
const props = withDefaults(defineProps<AccordionProps<T>>(), {
@@ -82,10 +79,11 @@ const props = withDefaults(defineProps<AccordionProps<T>>(), {
const emits = defineEmits<AccordionEmits>()
const slots = defineSlots<AccordionSlots<T>>()
const appConfig = useAppConfig()
const appConfig = useAppConfig() as Accordion['AppConfig']
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'collapsible', 'defaultValue', 'disabled', 'modelValue', 'type', 'unmountOnHide'), emits)
const ui = computed(() => accordion({
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.accordion || {}) })({
disabled: props.disabled
}))
</script>

View File

@@ -1,16 +1,10 @@
<script lang="ts">
import type { VariantProps } from 'tailwind-variants'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/alert'
import { tv } from '../utils/tv'
import type { AvatarProps, ButtonProps } from '../types'
import type { ComponentConfig } from '../types/utils'
const appConfigAlert = _appConfig as AppConfig & { ui: { alert: Partial<typeof theme> } }
const alert = tv({ extend: tv(theme), ...(appConfigAlert.ui?.alert || {}) })
type AlertVariants = VariantProps<typeof alert>
type Alert = ComponentConfig<typeof theme, AppConfig, 'alert'>
export interface AlertProps {
/**
@@ -28,16 +22,16 @@ export interface AlertProps {
/**
* @defaultValue 'primary'
*/
color?: AlertVariants['color']
color?: Alert['variants']['color']
/**
* @defaultValue 'solid'
*/
variant?: AlertVariants['variant']
variant?: Alert['variants']['variant']
/**
* The orientation between the content and the actions.
* @defaultValue 'vertical'
*/
orientation?: AlertVariants['orientation']
orientation?: Alert['variants']['orientation']
/**
* Display a list of actions:
* - under the title and description when orientation is `vertical`
@@ -59,7 +53,7 @@ export interface AlertProps {
*/
closeIcon?: string
class?: any
ui?: Partial<typeof alert.slots>
ui?: Alert['slots']
}
export interface AlertEmits {
@@ -71,7 +65,7 @@ export interface AlertSlots {
title(props?: {}): any
description(props?: {}): any
actions(props?: {}): any
close(props: { ui: ReturnType<typeof alert> }): any
close(props: { ui: { [K in keyof Required<Alert['slots']>]: (props?: Record<string, any>) => string } }): any
}
</script>
@@ -80,6 +74,7 @@ import { computed } from 'vue'
import { Primitive } from 'reka-ui'
import { useAppConfig } from '#imports'
import { useLocale } from '../composables/useLocale'
import { tv } from '../utils/tv'
import UIcon from './Icon.vue'
import UAvatar from './Avatar.vue'
import UButton from './Button.vue'
@@ -91,9 +86,9 @@ const emits = defineEmits<AlertEmits>()
const slots = defineSlots<AlertSlots>()
const { t } = useLocale()
const appConfig = useAppConfig()
const appConfig = useAppConfig() as Alert['AppConfig']
const ui = computed(() => alert({
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.alert || {}) })({
color: props.color,
variant: props.variant,
orientation: props.orientation,

View File

@@ -1,15 +1,9 @@
<script lang="ts">
import type { VariantProps } from 'tailwind-variants'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/avatar'
import { tv } from '../utils/tv'
import type { ComponentConfig } from '../types/utils'
const appConfigAvatar = _appConfig as AppConfig & { ui: { avatar: Partial<typeof theme> } }
const avatar = tv({ extend: tv(theme), ...(appConfigAvatar.ui?.avatar || {}) })
type AvatarVariants = VariantProps<typeof avatar>
type Avatar = ComponentConfig<typeof theme, AppConfig, 'avatar'>
export interface AvatarProps {
/**
@@ -27,10 +21,10 @@ export interface AvatarProps {
/**
* @defaultValue 'md'
*/
size?: AvatarVariants['size']
size?: Avatar['variants']['size']
class?: any
style?: any
ui?: Partial<typeof avatar.slots>
ui?: Avatar['slots']
}
export interface AvatarSlots {
@@ -41,8 +35,10 @@ export interface AvatarSlots {
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
import { Primitive, Slot } from 'reka-ui'
import { useAppConfig } from '#imports'
import ImageComponent from '#build/ui-image-component'
import { useAvatarGroup } from '../composables/useAvatarGroup'
import { tv } from '../utils/tv'
import UIcon from './Icon.vue'
defineOptions({ inheritAttrs: false })
@@ -51,10 +47,11 @@ const props = withDefaults(defineProps<AvatarProps>(), { as: 'span' })
const fallback = computed(() => props.text || (props.alt || '').split(' ').map(word => word.charAt(0)).join('').substring(0, 2))
const appConfig = useAppConfig() as Avatar['AppConfig']
const { size } = useAvatarGroup(props)
// eslint-disable-next-line vue/no-dupe-keys
const ui = computed(() => avatar({
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.avatar || {}) })({
size: size.value
}))

View File

@@ -1,15 +1,9 @@
<script lang="ts">
import type { VariantProps } from 'tailwind-variants'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/avatar-group'
import { tv } from '../utils/tv'
import type { ComponentConfig } from '../types/utils'
const appConfigAvatarGroup = _appConfig as AppConfig & { ui: { avatarGroup: Partial<typeof theme> } }
const avatarGroup = tv({ extend: tv(theme), ...(appConfigAvatarGroup.ui?.avatarGroup || {}) })
type AvatarGroupVariants = VariantProps<typeof avatarGroup>
type AvatarGroup = ComponentConfig<typeof theme, AppConfig, 'avatarGroup'>
export interface AvatarGroupProps {
/**
@@ -20,13 +14,13 @@ export interface AvatarGroupProps {
/**
* @defaultValue 'md'
*/
size?: AvatarGroupVariants['size']
size?: AvatarGroup['variants']['size']
/**
* The maximum number of avatars to display.
*/
max?: number | string
class?: any
ui?: Partial<typeof avatarGroup.slots>
ui?: AvatarGroup['slots']
}
export interface AvatarGroupSlots {
@@ -37,13 +31,17 @@ export interface AvatarGroupSlots {
<script setup lang="ts">
import { computed, provide } from 'vue'
import { Primitive } from 'reka-ui'
import { useAppConfig } from '#imports'
import { avatarGroupInjectionKey } from '../composables/useAvatarGroup'
import { tv } from '../utils/tv'
import UAvatar from './Avatar.vue'
const props = defineProps<AvatarGroupProps>()
const slots = defineSlots<AvatarGroupSlots>()
const ui = computed(() => avatarGroup({
const appConfig = useAppConfig() as AvatarGroup['AppConfig']
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.avatarGroup || {}) })({
size: props.size
}))

View File

@@ -1,17 +1,11 @@
<script lang="ts">
import type { VariantProps } from 'tailwind-variants'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/badge'
import type { UseComponentIconsProps } from '../composables/useComponentIcons'
import { tv } from '../utils/tv'
import type { AvatarProps } from '../types'
import type { ComponentConfig } from '../types/utils'
const appConfigBadge = _appConfig as AppConfig & { ui: { badge: Partial<typeof theme> } }
const badge = tv({ extend: tv(theme), ...(appConfigBadge.ui?.badge || {}) })
type BadgeVariants = VariantProps<typeof badge>
type Badge = ComponentConfig<typeof theme, AppConfig, 'badge'>
export interface BadgeProps extends Omit<UseComponentIconsProps, 'loading' | 'loadingIcon'> {
/**
@@ -23,17 +17,17 @@ export interface BadgeProps extends Omit<UseComponentIconsProps, 'loading' | 'lo
/**
* @defaultValue 'primary'
*/
color?: BadgeVariants['color']
color?: Badge['variants']['color']
/**
* @defaultValue 'solid'
*/
variant?: BadgeVariants['variant']
variant?: Badge['variants']['variant']
/**
* @defaultValue 'md'
*/
size?: BadgeVariants['size']
size?: Badge['variants']['size']
class?: any
ui?: Partial<typeof badge.slots>
ui?: Badge['slots']
}
export interface BadgeSlots {
@@ -46,8 +40,10 @@ export interface BadgeSlots {
<script setup lang="ts">
import { computed } from 'vue'
import { Primitive } from 'reka-ui'
import { useAppConfig } from '#imports'
import { useButtonGroup } from '../composables/useButtonGroup'
import { useComponentIcons } from '../composables/useComponentIcons'
import { tv } from '../utils/tv'
import UIcon from './Icon.vue'
import UAvatar from './Avatar.vue'
@@ -56,10 +52,11 @@ const props = withDefaults(defineProps<BadgeProps>(), {
})
defineSlots<BadgeSlots>()
const appConfig = useAppConfig() as Badge['AppConfig']
const { orientation, size: buttonGroupSize } = useButtonGroup<BadgeProps>(props)
const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponentIcons(props)
const ui = computed(() => badge({
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.badge || {}) })({
color: props.color,
variant: props.variant,
size: buttonGroupSize.value || props.size,

View File

@@ -1,15 +1,11 @@
<!-- eslint-disable vue/block-tag-newline -->
<script lang="ts">
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/breadcrumb'
import { tv } from '../utils/tv'
import type { AvatarProps, LinkProps } from '../types'
import type { DynamicSlots, PartialString } from '../types/utils'
import type { DynamicSlots, ComponentConfig } from '../types/utils'
const appConfigBreadcrumb = _appConfig as AppConfig & { ui: { breadcrumb: Partial<typeof theme> } }
const breadcrumb = tv({ extend: tv(theme), ...(appConfigBreadcrumb.ui?.breadcrumb || {}) })
type Breadcrumb = ComponentConfig<typeof theme, AppConfig, 'breadcrumb'>
export interface BreadcrumbItem extends Omit<LinkProps, 'raw' | 'custom'> {
label?: string
@@ -41,7 +37,7 @@ export interface BreadcrumbProps<T extends BreadcrumbItem = BreadcrumbItem> {
*/
labelKey?: string
class?: any
ui?: PartialString<typeof breadcrumb.slots>
ui?: Breadcrumb['slots']
}
type SlotProps<T extends BreadcrumbItem> = (props: { item: T, index: number, active?: boolean }) => any
@@ -62,6 +58,7 @@ import { Primitive } from 'reka-ui'
import { useAppConfig } from '#imports'
import { useLocale } from '../composables/useLocale'
import { get } from '../utils'
import { tv } from '../utils/tv'
import { pickLinkProps } from '../utils/link'
import UIcon from './Icon.vue'
import UAvatar from './Avatar.vue'
@@ -73,13 +70,14 @@ const props = withDefaults(defineProps<BreadcrumbProps<T>>(), {
labelKey: 'label'
})
const slots = defineSlots<BreadcrumbSlots<T>>()
const { dir } = useLocale()
const appConfig = useAppConfig()
const appConfig = useAppConfig() as Breadcrumb['AppConfig']
const separatorIcon = computed(() => props.separatorIcon || (dir.value === 'rtl' ? appConfig.ui.icons.chevronLeft : appConfig.ui.icons.chevronRight))
// eslint-disable-next-line vue/no-dupe-keys
const ui = breadcrumb()
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.breadcrumb || {}) })())
</script>
<template>

View File

@@ -1,36 +1,29 @@
<script lang="ts">
import type { VariantProps } from 'tailwind-variants'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/button'
import type { LinkProps } from './Link.vue'
import type { UseComponentIconsProps } from '../composables/useComponentIcons'
import { tv } from '../utils/tv'
import type { AvatarProps } from '../types'
import type { PartialString } from '../types/utils'
import type { ComponentConfig } from '../types/utils'
const appConfigButton = _appConfig as AppConfig & { ui: { button: Partial<typeof theme> } }
const button = tv({ extend: tv(theme), ...(appConfigButton.ui?.button || {}) })
type ButtonVariants = VariantProps<typeof button>
type Button = ComponentConfig<typeof theme, AppConfig, 'button'>
export interface ButtonProps extends UseComponentIconsProps, Omit<LinkProps, 'raw' | 'custom'> {
label?: string
/**
* @defaultValue 'primary'
*/
color?: ButtonVariants['color']
activeColor?: ButtonVariants['color']
color?: Button['variants']['color']
activeColor?: Button['variants']['color']
/**
* @defaultValue 'solid'
*/
variant?: ButtonVariants['variant']
activeVariant?: ButtonVariants['variant']
variant?: Button['variants']['variant']
activeVariant?: Button['variants']['variant']
/**
* @defaultValue 'md'
*/
size?: ButtonVariants['size']
size?: Button['variants']['size']
/** Render the button with equal padding on all sides. */
square?: boolean
/** Render the button full width. */
@@ -39,7 +32,7 @@ export interface ButtonProps extends UseComponentIconsProps, Omit<LinkProps, 'ra
loadingAuto?: boolean
onClick?: ((event: MouseEvent) => void | Promise<void>) | Array<((event: MouseEvent) => void | Promise<void>)>
class?: any
ui?: PartialString<typeof button.slots>
ui?: Button['slots']
}
export interface ButtonSlots {
@@ -51,11 +44,14 @@ export interface ButtonSlots {
<script setup lang="ts">
import { type Ref, computed, ref, inject } from 'vue'
import { defu } from 'defu'
import { useForwardProps } from 'reka-ui'
import { useAppConfig } from '#imports'
import { useComponentIcons } from '../composables/useComponentIcons'
import { useButtonGroup } from '../composables/useButtonGroup'
import { formLoadingInjectionKey } from '../composables/useFormField'
import { omit } from '../utils'
import { tv } from '../utils/tv'
import { pickLinkProps } from '../utils/link'
import UIcon from './Icon.vue'
import UAvatar from './Avatar.vue'
@@ -69,10 +65,11 @@ const props = withDefaults(defineProps<ButtonProps>(), {
})
const slots = defineSlots<ButtonSlots>()
const linkProps = useForwardProps(pickLinkProps(props))
const appConfig = useAppConfig() as Button['AppConfig']
const { orientation, size: buttonSize } = useButtonGroup<ButtonProps>(props)
const linkProps = useForwardProps(pickLinkProps(props))
const loadingAutoState = ref(false)
const formLoading = inject<Ref<boolean> | undefined>(formLoadingInjectionKey, undefined)
@@ -95,17 +92,19 @@ const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponen
)
const ui = computed(() => tv({
extend: button,
variants: {
active: {
true: {
base: props.activeClass
},
false: {
base: props.inactiveClass
extend: tv(theme),
...defu({
variants: {
active: {
true: {
base: props.activeClass
},
false: {
base: props.inactiveClass
}
}
}
}
}, appConfig.ui?.button || {})
})({
color: props.color,
variant: props.variant,

View File

@@ -1,15 +1,9 @@
<script lang="ts">
import type { VariantProps } from 'tailwind-variants'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/button-group'
import { tv } from '../utils/tv'
import type { ComponentConfig } from '../types/utils'
const appConfigButtonGroup = _appConfig as AppConfig & { ui: { buttonGroup: Partial<typeof theme> } }
const buttonGroup = tv({ extend: tv(theme), ...(appConfigButtonGroup.ui?.buttonGroup) })
type ButtonGroupVariants = VariantProps<typeof buttonGroup>
type ButtonGroup = ComponentConfig<typeof theme, AppConfig, 'buttonGroup'>
export interface ButtonGroupProps {
/**
@@ -20,13 +14,14 @@ export interface ButtonGroupProps {
/**
* @defaultValue 'md'
*/
size?: ButtonGroupVariants['size']
size?: ButtonGroup['variants']['size']
/**
* The orientation the buttons are laid out.
* @defaultValue 'horizontal'
*/
orientation?: ButtonGroupVariants['orientation']
orientation?: ButtonGroup['variants']['orientation']
class?: any
ui?: ButtonGroup['slots']
}
export interface ButtonGroupSlots {
@@ -37,13 +32,20 @@ export interface ButtonGroupSlots {
<script setup lang="ts">
import { provide, computed } from 'vue'
import { Primitive } from 'reka-ui'
import { useAppConfig } from '#imports'
import { buttonGroupInjectionKey } from '../composables/useButtonGroup'
import { tv } from '../utils/tv'
const props = withDefaults(defineProps<ButtonGroupProps>(), {
orientation: 'horizontal'
})
defineSlots<ButtonGroupSlots>()
const appConfig = useAppConfig() as ButtonGroup['AppConfig']
// eslint-disable-next-line vue/no-dupe-keys
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.buttonGroup || {}) }))
provide(buttonGroupInjectionKey, computed(() => ({
orientation: props.orientation,
size: props.size
@@ -51,7 +53,7 @@ provide(buttonGroupInjectionKey, computed(() => ({
</script>
<template>
<Primitive :as="as" :class="buttonGroup({ orientation, class: props.class })">
<Primitive :as="as" :class="ui({ orientation, class: props.class })">
<slot />
</Primitive>
</template>

View File

@@ -1,19 +1,12 @@
<script lang="ts">
import type { VariantProps } from 'tailwind-variants'
import type { CalendarRootProps, CalendarRootEmits, RangeCalendarRootProps, RangeCalendarRootEmits, DateRange, CalendarCellTriggerProps } from 'reka-ui'
import type { DateValue } from '@internationalized/date'
import type { AppConfig } from '@nuxt/schema'
import type { ButtonProps } from '../types'
import _appConfig from '#build/app.config'
import theme from '#build/ui/calendar'
import { tv } from '../utils/tv'
import type { PartialString } from '../types/utils'
import type { ButtonProps } from '../types'
import type { ComponentConfig } from '../types/utils'
const appConfigCalendar = _appConfig as AppConfig & { ui: { calendar: Partial<typeof theme> } }
const calendar = tv({ extend: tv(theme), ...(appConfigCalendar.ui?.calendar || {}) })
type CalendarVariants = VariantProps<typeof calendar>
type Calendar = ComponentConfig<typeof theme, AppConfig, 'calendar'>
type CalendarDefaultValue<R extends boolean = false, M extends boolean = false> = R extends true
? DateRange
@@ -82,11 +75,11 @@ export interface CalendarProps<R extends boolean = false, M extends boolean = fa
/**
* @defaultValue 'primary'
*/
color?: CalendarVariants['color']
color?: Calendar['variants']['color']
/**
* @defaultValue 'md'
*/
size?: CalendarVariants['size']
size?: Calendar['variants']['size']
/** Whether or not a range of dates can be selected */
range?: R & boolean
/** Whether or not multiple dates can be selected */
@@ -98,7 +91,7 @@ export interface CalendarProps<R extends boolean = false, M extends boolean = fa
defaultValue?: CalendarDefaultValue<R, M>
modelValue?: CalendarModelValue<R, M>
class?: any
ui?: PartialString<typeof calendar.slots>
ui?: Calendar['slots']
}
export interface CalendarEmits<R extends boolean, M extends boolean> extends Omit<CalendarRootEmits & RangeCalendarRootEmits, 'update:modelValue'> {
@@ -119,6 +112,7 @@ import { Calendar as SingleCalendar, RangeCalendar } from 'reka-ui/namespaced'
import { reactiveOmit } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { useLocale } from '../composables/useLocale'
import { tv } from '../utils/tv'
import UButton from './Button.vue'
const props = withDefaults(defineProps<CalendarProps<R, M>>(), {
@@ -129,8 +123,8 @@ const props = withDefaults(defineProps<CalendarProps<R, M>>(), {
const emits = defineEmits<CalendarEmits<R, M>>()
defineSlots<CalendarSlots>()
const appConfig = useAppConfig()
const { code: locale, dir, t } = useLocale()
const appConfig = useAppConfig() as Calendar['AppConfig']
const rootProps = useForwardPropsEmits(reactiveOmit(props, 'range', 'modelValue', 'defaultValue', 'color', 'size', 'monthControls', 'yearControls', 'class', 'ui'), emits)
@@ -139,7 +133,7 @@ const nextMonthIcon = computed(() => props.nextMonthIcon || (dir.value === 'rtl'
const prevYearIcon = computed(() => props.prevYearIcon || (dir.value === 'rtl' ? appConfig.ui.icons.chevronDoubleRight : appConfig.ui.icons.chevronDoubleLeft))
const prevMonthIcon = computed(() => props.prevMonthIcon || (dir.value === 'rtl' ? appConfig.ui.icons.chevronRight : appConfig.ui.icons.chevronLeft))
const ui = computed(() => calendar({
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.calendar || {}) })({
color: props.color,
size: props.size
}))

View File

@@ -1,15 +1,9 @@
<script lang="ts">
import type { VariantProps } from 'tailwind-variants'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/card'
import { tv } from '../utils/tv'
import type { ComponentConfig } from '../types/utils'
const appConfigCard = _appConfig as AppConfig & { ui: { card: Partial<typeof theme> } }
const card = tv({ extend: tv(theme), ...(appConfigCard.ui?.card || {}) })
type CardVariants = VariantProps<typeof card>
type Card = ComponentConfig<typeof theme, AppConfig, 'card'>
export interface CardProps {
/**
@@ -20,9 +14,9 @@ export interface CardProps {
/**
* @defaultValue 'outline'
*/
variant?: CardVariants['variant']
variant?: Card['variants']['variant']
class?: any
ui?: Partial<typeof card.slots>
ui?: Card['slots']
}
export interface CardSlots {
@@ -35,11 +29,17 @@ export interface CardSlots {
<script setup lang="ts">
import { computed } from 'vue'
import { Primitive } from 'reka-ui'
import { useAppConfig } from '#imports'
import { tv } from '../utils/tv'
const props = defineProps<CardProps>()
const slots = defineSlots<CardSlots>()
const ui = computed(() => card({ variant: props.variant }))
const appConfig = useAppConfig() as Card['AppConfig']
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.card || {}) })({
variant: props.variant
}))
</script>
<template>

View File

@@ -1,6 +1,5 @@
<!-- eslint-disable vue/block-tag-newline -->
<script lang="ts">
import 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'
@@ -10,17 +9,11 @@ import type { AutoHeightOptionsType } from 'embla-carousel-auto-height'
import type { ClassNamesOptionsType } from 'embla-carousel-class-names'
import type { FadeOptionsType } from 'embla-carousel-fade'
import type { WheelGesturesPluginOptions } from 'embla-carousel-wheel-gestures'
import _appConfig from '#build/app.config'
import theme from '#build/ui/carousel'
import { tv } from '../utils/tv'
import type { ButtonProps } from '../types'
import type { PartialString } from '../types/utils'
import type { ComponentConfig } from '../types/utils'
const appConfigCarousel = _appConfig as AppConfig & { ui: { carousel: Partial<typeof theme> } }
const carousel = tv({ extend: tv(theme), ...(appConfigCarousel.ui?.carousel || {}) })
type CarouselVariants = VariantProps<typeof carousel>
type Carousel = ComponentConfig<typeof theme, AppConfig, 'carousel'>
export type CarouselItem = AcceptableValue
@@ -66,7 +59,7 @@ export interface CarouselProps<T extends CarouselItem = CarouselItem> extends Om
* The orientation of the carousel.
* @defaultValue 'horizontal'
*/
orientation?: CarouselVariants['orientation']
orientation?: Carousel['variants']['orientation']
items?: T[]
/**
* Enable Autoplay plugin
@@ -99,7 +92,7 @@ export interface CarouselProps<T extends CarouselItem = CarouselItem> extends Om
*/
wheelGestures?: boolean | WheelGesturesPluginOptions
class?: any
ui?: PartialString<typeof carousel.slots>
ui?: Carousel['slots']
}
export type CarouselSlots<T extends CarouselItem = CarouselItem> = {
@@ -115,6 +108,7 @@ import { Primitive, useForwardProps } from 'reka-ui'
import { reactivePick, computedAsync } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { useLocale } from '../composables/useLocale'
import { tv } from '../utils/tv'
import UButton from './Button.vue'
const props = withDefaults(defineProps<CarouselProps<T>>(), {
@@ -148,14 +142,15 @@ const props = withDefaults(defineProps<CarouselProps<T>>(), {
})
defineSlots<CarouselSlots<T>>()
const appConfig = useAppConfig()
const { dir, t } = useLocale()
const appConfig = useAppConfig() as Carousel['AppConfig']
const rootProps = useForwardProps(reactivePick(props, 'active', 'align', 'breakpoints', 'containScroll', 'dragFree', 'dragThreshold', 'duration', 'inViewThreshold', 'loop', 'skipSnaps', 'slidesToScroll', 'startIndex', 'watchDrag', 'watchResize', 'watchSlides', 'watchFocus'))
const prevIcon = computed(() => props.prevIcon || (dir.value === 'rtl' ? appConfig.ui.icons.arrowRight : appConfig.ui.icons.arrowLeft))
const nextIcon = computed(() => props.nextIcon || (dir.value === 'rtl' ? appConfig.ui.icons.arrowLeft : appConfig.ui.icons.arrowRight))
const ui = computed(() => carousel({
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.carousel || {}) })({
orientation: props.orientation
}))

View File

@@ -1,16 +1,10 @@
<script lang="ts">
import type { VariantProps } from 'tailwind-variants'
import type { CheckboxRootProps } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/checkbox'
import { tv } from '../utils/tv'
import type { ComponentConfig } from '../types/utils'
const appConfigCheckbox = _appConfig as AppConfig & { ui: { checkbox: Partial<typeof theme> } }
const checkbox = tv({ extend: tv(theme), ...(appConfigCheckbox.ui?.checkbox || {}) })
type CheckboxVariants = VariantProps<typeof checkbox>
type Checkbox = ComponentConfig<typeof theme, AppConfig, 'checkbox'>
export interface CheckboxProps extends Pick<CheckboxRootProps, 'disabled' | 'required' | 'name' | 'value' | 'id' | 'defaultValue'> {
/**
@@ -23,11 +17,11 @@ export interface CheckboxProps extends Pick<CheckboxRootProps, 'disabled' | 'req
/**
* @defaultValue 'primary'
*/
color?: CheckboxVariants['color']
color?: Checkbox['variants']['color']
/**
* @defaultValue 'md'
*/
size?: CheckboxVariants['size']
size?: Checkbox['variants']['size']
/**
* The icon displayed when checked.
* @defaultValue appConfig.ui.icons.check
@@ -41,7 +35,7 @@ export interface CheckboxProps extends Pick<CheckboxRootProps, 'disabled' | 'req
*/
indeterminateIcon?: string
class?: any
ui?: Partial<typeof checkbox.slots>
ui?: Checkbox['slots']
}
export type CheckboxEmits = {
@@ -60,6 +54,7 @@ import { Primitive, CheckboxRoot, CheckboxIndicator, Label, useForwardProps } fr
import { reactivePick } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { useFormField } from '../composables/useFormField'
import { tv } from '../utils/tv'
import UIcon from './Icon.vue'
defineOptions({ inheritAttrs: false })
@@ -70,13 +65,14 @@ const emits = defineEmits<CheckboxEmits>()
const modelValue = defineModel<boolean | 'indeterminate'>({ default: undefined })
const appConfig = useAppConfig() as Checkbox['AppConfig']
const rootProps = useForwardProps(reactivePick(props, 'required', 'value', 'defaultValue'))
const appConfig = useAppConfig()
const { id: _id, emitFormChange, emitFormInput, size, color, name, disabled, ariaAttrs } = useFormField<CheckboxProps>(props)
const id = _id.value ?? useId()
const ui = computed(() => checkbox({
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.checkbox || {}) })({
size: size.value,
color: color.value,
required: props.required,

View File

@@ -1,15 +1,9 @@
<script lang="ts">
import type { VariantProps } from 'tailwind-variants'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/chip'
import { tv } from '../utils/tv'
import type { ComponentConfig } from '../types/utils'
const appConfigChip = _appConfig as AppConfig & { ui: { chip: Partial<typeof theme> } }
const chip = tv({ extend: tv(theme), ...(appConfigChip.ui?.chip || {}) })
type ChipVariants = VariantProps<typeof chip>
type Chip = ComponentConfig<typeof theme, AppConfig, 'chip'>
export interface ChipProps {
/**
@@ -22,22 +16,22 @@ export interface ChipProps {
/**
* @defaultValue 'primary'
*/
color?: ChipVariants['color']
color?: Chip['variants']['color']
/**
* @defaultValue 'md'
*/
size?: ChipVariants['size']
size?: Chip['variants']['size']
/**
* The position of the chip.
* @defaultValue 'top-right'
*/
position?: ChipVariants['position']
position?: Chip['variants']['position']
/** When `true`, keep the chip inside the component for rounded elements. */
inset?: boolean
/** When `true`, render the chip relatively to the parent. */
standalone?: boolean
class?: any
ui?: Partial<typeof chip.slots>
ui?: Chip['slots']
}
export interface ChipEmits {
@@ -53,7 +47,9 @@ export interface ChipSlots {
<script setup lang="ts">
import { computed } from 'vue'
import { Primitive, Slot } from 'reka-ui'
import { useAppConfig } from '#imports'
import { useAvatarGroup } from '../composables/useAvatarGroup'
import { tv } from '../utils/tv'
defineOptions({ inheritAttrs: false })
@@ -66,8 +62,9 @@ defineSlots<ChipSlots>()
const show = defineModel<boolean>('show', { default: true })
const { size } = useAvatarGroup(props)
const appConfig = useAppConfig() as Chip['AppConfig']
const ui = computed(() => chip({
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.chip || {}) })({
color: props.color,
size: size.value,
position: props.position,

View File

@@ -1,13 +1,10 @@
<script lang="ts">
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'
import { tv } from '../utils/tv'
import type { ComponentConfig } from '../types/utils'
const appConfigCollapsible = _appConfig as AppConfig & { ui: { collapsible: Partial<typeof theme> } }
const collapsible = tv({ extend: tv(theme), ...(appConfigCollapsible.ui?.collapsible || {}) })
type Collapsible = ComponentConfig<typeof theme, AppConfig, 'collapsible'>
export interface CollapsibleProps extends Pick<CollapsibleRootProps, 'defaultOpen' | 'open' | 'disabled' | 'unmountOnHide'> {
/**
@@ -16,7 +13,7 @@ export interface CollapsibleProps extends Pick<CollapsibleRootProps, 'defaultOpe
*/
as?: any
class?: any
ui?: Partial<typeof collapsible.slots>
ui?: Collapsible['slots']
}
export interface CollapsibleEmits extends CollapsibleRootEmits {}
@@ -28,8 +25,11 @@ export interface CollapsibleSlots {
</script>
<script setup lang="ts">
import { computed } from 'vue'
import { CollapsibleRoot, CollapsibleTrigger, CollapsibleContent, useForwardPropsEmits } from 'reka-ui'
import { reactivePick } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { tv } from '../utils/tv'
const props = withDefaults(defineProps<CollapsibleProps>(), {
unmountOnHide: true
@@ -37,10 +37,12 @@ const props = withDefaults(defineProps<CollapsibleProps>(), {
const emits = defineEmits<CollapsibleEmits>()
const slots = defineSlots<CollapsibleSlots>()
const appConfig = useAppConfig() as Collapsible['AppConfig']
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'defaultOpen', 'open', 'disabled', 'unmountOnHide'), emits)
// eslint-disable-next-line vue/no-dupe-keys
const ui = collapsible()
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.collapsible || {}) })())
</script>
<template>

View File

@@ -1,18 +1,12 @@
<!-- eslint-disable vue/block-tag-newline -->
<script lang="ts">
import type { VariantProps } from 'tailwind-variants'
import type { MaybeRefOrGetter } from '@vueuse/shared'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/color-picker'
import { tv } from '../utils/tv'
import type { HSLObject } from 'colortranslator'
import type { ComponentConfig } from '../types/utils'
const appConfigColorPicker = _appConfig as AppConfig & { ui: { colorPicker: Partial<typeof theme> } }
const colorPicker = tv({ extend: tv(theme), ...(appConfigColorPicker.ui?.colorPicker || {}) })
type ColorPickerVariants = VariantProps<typeof colorPicker>
type ColorPicker = ComponentConfig<typeof theme, AppConfig, 'colorPicker'>
type HSVColor = {
h: number
@@ -67,9 +61,9 @@ export type ColorPickerProps = {
/**
* @defaultValue 'md'
*/
size?: ColorPickerVariants['size']
size?: ColorPicker['variants']['size']
class?: any
ui?: Partial<typeof colorPicker.slots>
ui?: ColorPicker['slots']
}
</script>
@@ -80,6 +74,8 @@ import { Primitive } from 'reka-ui'
import { useEventListener, useElementBounding, watchThrottled, watchPausable } from '@vueuse/core'
import { isClient } from '@vueuse/shared'
import { ColorTranslator } from 'colortranslator'
import { useAppConfig } from '#imports'
import { tv } from '../utils/tv'
const props = withDefaults(defineProps<ColorPickerProps>(), {
format: 'hex',
@@ -88,6 +84,12 @@ const props = withDefaults(defineProps<ColorPickerProps>(), {
})
const modelValue = defineModel<string>(undefined)
const appConfig = useAppConfig() as ColorPicker['AppConfig']
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.colorPicker || {}) })({
size: props.size
}))
const pickedColor = computed<HSVColor>({
get() {
try {
@@ -258,10 +260,6 @@ const trackThumbStyle = computed(() => ({
backgroundColor: trackThumbColor.value,
top: `${trackThumbPosition.value.y}%`
}))
const ui = computed(() => colorPicker({
size: props.size
}))
</script>
<template>

View File

@@ -4,16 +4,12 @@ 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'
import _appConfig from '#build/app.config'
import theme from '#build/ui/command-palette'
import type { UseComponentIconsProps } from '../composables/useComponentIcons'
import { tv } from '../utils/tv'
import type { AvatarProps, ButtonProps, ChipProps, KbdProps, InputProps, LinkProps } from '../types'
import type { PartialString } from '../types/utils'
import type { ComponentConfig } from '../types/utils'
const appConfigCommandPalette = _appConfig as AppConfig & { ui: { commandPalette: Partial<typeof theme> } }
const commandPalette = tv({ extend: tv(theme), ...(appConfigCommandPalette.ui?.commandPalette || {}) })
type CommandPalette = ComponentConfig<typeof theme, AppConfig, 'commandPalette'>
export interface CommandPaletteItem extends Omit<LinkProps, 'type' | 'raw' | 'custom'> {
prefix?: string
@@ -115,7 +111,7 @@ export interface CommandPaletteProps<G, T> extends Pick<ListboxRootProps, 'multi
*/
labelKey?: string
class?: any
ui?: PartialString<typeof commandPalette.slots>
ui?: CommandPalette['slots']
}
export type CommandPaletteEmits<T> = ListboxRootEmits<T> & {
@@ -126,7 +122,7 @@ type SlotProps<T> = (props: { item: T, index: number }) => any
export type CommandPaletteSlots<G extends { slot?: string }, T extends { slot?: string }> = {
'empty'(props: { searchTerm?: string }): any
'close'(props: { ui: ReturnType<typeof commandPalette> }): any
'close'(props: { ui: { [K in keyof Required<CommandPalette['slots']>]: (props?: Record<string, any>) => string } }): any
'item': SlotProps<T>
'item-leading': SlotProps<T>
'item-label': SlotProps<T>
@@ -144,6 +140,7 @@ import { useFuse } from '@vueuse/integrations/useFuse'
import { useAppConfig } from '#imports'
import { useLocale } from '../composables/useLocale'
import { omit, get } from '../utils'
import { tv } from '../utils/tv'
import { highlight } from '../utils/fuse'
import { pickLinkProps } from '../utils/link'
import UIcon from './Icon.vue'
@@ -166,13 +163,13 @@ const slots = defineSlots<CommandPaletteSlots<G, T>>()
const searchTerm = defineModel<string>('searchTerm', { default: '' })
const { t } = useLocale()
const appConfig = useAppConfig()
const appConfig = useAppConfig() as CommandPalette['AppConfig']
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'disabled', 'multiple', 'modelValue', 'defaultValue', 'highlightOnHover'), emits)
const inputProps = useForwardProps(reactivePick(props, 'loading', 'loadingIcon'))
// eslint-disable-next-line vue/no-dupe-keys
const ui = commandPalette()
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.commandPalette || {}) })())
const fuse = computed(() => defu({}, props.fuse, {
fuseOptions: {

View File

@@ -1,12 +1,9 @@
<script lang="ts">
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/container'
import { tv } from '../utils/tv'
import type { ComponentConfig } from '../types/utils'
const appConfigContainer = _appConfig as AppConfig & { ui: { container: Partial<typeof theme> } }
const container = tv({ extend: tv(theme), ...(appConfigContainer.ui?.container || {}) })
type Container = ComponentConfig<typeof theme, AppConfig, 'container'>
export interface ContainerProps {
/**
@@ -23,14 +20,21 @@ export interface ContainerSlots {
</script>
<script setup lang="ts">
import { computed } from 'vue'
import { Primitive } from 'reka-ui'
import { useAppConfig } from '#imports'
import { tv } from '../utils/tv'
const props = defineProps<ContainerProps>()
defineSlots<ContainerSlots>()
const appConfig = useAppConfig() as Container['AppConfig']
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.container || {}) }))
</script>
<template>
<Primitive :as="as" :class="container({ class: props.class })">
<Primitive :as="as" :class="ui({ class: props.class })">
<slot />
</Primitive>
</template>

View File

@@ -1,26 +1,12 @@
<!-- eslint-disable vue/block-tag-newline -->
<script lang="ts">
import type { VariantProps } from 'tailwind-variants'
import type { ContextMenuRootProps, ContextMenuRootEmits, ContextMenuContentProps, ContextMenuContentEmits } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/context-menu'
import { tv } from '../utils/tv'
import type { AvatarProps, KbdProps, LinkProps } from '../types'
import type {
ArrayOrNested,
DynamicSlots,
MergeTypes,
NestedItem,
PartialString,
EmitsToProps
} from '../types/utils'
import type { ArrayOrNested, DynamicSlots, MergeTypes, NestedItem, EmitsToProps, ComponentConfig } from '../types/utils'
const appConfigContextMenu = _appConfig as AppConfig & { ui: { contextMenu: Partial<typeof theme> } }
const contextMenu = tv({ extend: tv(theme), ...(appConfigContextMenu.ui?.contextMenu || {}) })
type ContextMenuVariants = VariantProps<typeof contextMenu>
type ContextMenu = ComponentConfig<typeof theme, AppConfig, 'contextMenu'>
export interface ContextMenuItem extends Omit<LinkProps, 'type' | 'raw' | 'custom'> {
label?: string
@@ -28,7 +14,7 @@ export interface ContextMenuItem extends Omit<LinkProps, 'type' | 'raw' | 'custo
* @IconifyIcon
*/
icon?: string
color?: ContextMenuVariants['color']
color?: ContextMenu['variants']['color']
avatar?: AvatarProps
content?: Omit<ContextMenuContentProps, 'as' | 'asChild' | 'forceMount'> & Partial<EmitsToProps<ContextMenuContentEmits>>
kbds?: KbdProps['value'][] | KbdProps[]
@@ -53,7 +39,7 @@ export interface ContextMenuProps<T extends ArrayOrNested<ContextMenuItem> = Arr
/**
* @defaultValue 'md'
*/
size?: ContextMenuVariants['size']
size?: ContextMenu['variants']['size']
items?: T
/**
* The icon displayed when an item is checked.
@@ -88,7 +74,7 @@ export interface ContextMenuProps<T extends ArrayOrNested<ContextMenuItem> = Arr
labelKey?: keyof NestedItem<T>
disabled?: boolean
class?: any
ui?: PartialString<typeof contextMenu.slots>
ui?: ContextMenu['slots']
}
export interface ContextMenuEmits extends ContextMenuRootEmits {}
@@ -112,7 +98,9 @@ export type ContextMenuSlots<
import { computed, toRef } from 'vue'
import { ContextMenuRoot, ContextMenuTrigger, useForwardPropsEmits } from 'reka-ui'
import { reactivePick } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { omit } from '../utils'
import { tv } from '../utils/tv'
import UContextMenuContent from './ContextMenuContent.vue'
const props = withDefaults(defineProps<ContextMenuProps<T>>(), {
@@ -124,12 +112,13 @@ const props = withDefaults(defineProps<ContextMenuProps<T>>(), {
const emits = defineEmits<ContextMenuEmits>()
const slots = defineSlots<ContextMenuSlots<T>>()
const rootProps = useForwardPropsEmits(reactivePick(props, 'modal'), emits)
const appConfig = useAppConfig() as ContextMenu['AppConfig']
const rootProps = useForwardPropsEmits(reactivePick(props, 'modal'), emits)
const contentProps = toRef(() => props.content)
const proxySlots = omit(slots, ['default'])
const ui = computed(() => contextMenu({
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.contextMenu || {}) })({
size: props.size
}))
</script>

View File

@@ -1,11 +1,11 @@
<script lang="ts">
import type { ContextMenuContentProps as RekaContextMenuContentProps, ContextMenuContentEmits as RekaContextMenuContentEmits } from 'reka-ui'
import theme from '#build/ui/context-menu'
import { tv } from '../utils/tv'
import type { AppConfig } from '@nuxt/schema'
import type theme from '#build/ui/context-menu'
import type { AvatarProps, ContextMenuItem, ContextMenuSlots, KbdProps } from '../types'
import type { ArrayOrNested, NestedItem } from '../types/utils'
import type { ArrayOrNested, NestedItem, ComponentConfig } from '../types/utils'
const _contextMenu = tv(theme)()
type ContextMenu = ComponentConfig<typeof theme, AppConfig, 'contextMenu'>
interface ContextMenuContentProps<T extends ArrayOrNested<ContextMenuItem>> extends Omit<RekaContextMenuContentProps, 'as' | 'asChild' | 'forceMount'> {
items?: T
@@ -25,8 +25,8 @@ interface ContextMenuContentProps<T extends ArrayOrNested<ContextMenuItem>> exte
*/
externalIcon?: boolean | string
class?: any
ui: typeof _contextMenu
uiOverride?: any
ui: { [K in keyof Required<ContextMenu['slots']>]: (props?: Record<string, any>) => string }
uiOverride?: ContextMenu['slots']
}
interface ContextMenuContentEmits extends RekaContextMenuContentEmits {}
@@ -53,8 +53,9 @@ const props = defineProps<ContextMenuContentProps<T>>()
const emits = defineEmits<ContextMenuContentEmits>()
const slots = defineSlots<ContextMenuSlots<T>>()
const appConfig = useAppConfig()
const { dir } = useLocale()
const appConfig = useAppConfig()
const contentProps = useForwardPropsEmits(reactiveOmit(props, 'sub', 'items', 'portal', 'labelKey', 'checkedIcon', 'loadingIcon', 'externalIcon', 'class', 'ui', 'uiOverride'), emits)
const proxySlots = omit(slots, ['default'])

View File

@@ -2,14 +2,10 @@
import type { DrawerRootProps, DrawerRootEmits } from 'vaul-vue'
import type { DialogContentProps, DialogContentEmits } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/drawer'
import { tv } from '../utils/tv'
import type { EmitsToProps } from '../types/utils'
import type { EmitsToProps, ComponentConfig } from '../types/utils'
const appConfigDrawer = _appConfig as AppConfig & { ui: { drawer: Partial<typeof theme> } }
const drawer = tv({ extend: tv(theme), ...(appConfigDrawer.ui?.drawer || {}) })
type Drawer = ComponentConfig<typeof theme, AppConfig, 'drawer'>
export interface DrawerProps extends Pick<DrawerRootProps, 'activeSnapPoint' | 'closeThreshold' | 'shouldScaleBackground' | 'setBackgroundColorOnScale' | 'scrollLockTimeout' | 'fixed' | 'dismissible' | 'modal' | 'open' | 'defaultOpen' | 'nested' | 'direction' | 'noBodyStyles' | 'handleOnly' | 'preventScrollRestoration' | 'snapPoints'> {
/**
@@ -42,7 +38,7 @@ export interface DrawerProps extends Pick<DrawerRootProps, 'activeSnapPoint' | '
*/
portal?: boolean
class?: any
ui?: Partial<typeof drawer.slots>
ui?: Drawer['slots']
}
export interface DrawerEmits extends DrawerRootEmits {}
@@ -63,6 +59,8 @@ import { computed, toRef } from 'vue'
import { useForwardPropsEmits } from 'reka-ui'
import { DrawerRoot, DrawerTrigger, DrawerPortal, DrawerOverlay, DrawerContent, DrawerTitle, DrawerDescription, DrawerHandle } from 'vaul-vue'
import { reactivePick } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { tv } from '../utils/tv'
const props = withDefaults(defineProps<DrawerProps>(), {
direction: 'bottom',
@@ -75,13 +73,15 @@ const props = withDefaults(defineProps<DrawerProps>(), {
const emits = defineEmits<DrawerEmits>()
const slots = defineSlots<DrawerSlots>()
const appConfig = useAppConfig() as Drawer['AppConfig']
const rootProps = useForwardPropsEmits(reactivePick(props, 'activeSnapPoint', 'closeThreshold', 'shouldScaleBackground', 'setBackgroundColorOnScale', 'scrollLockTimeout', 'fixed', 'dismissible', 'modal', 'open', 'defaultOpen', 'nested', 'direction', 'noBodyStyles', 'handleOnly', 'preventScrollRestoration', 'snapPoints'), emits)
const contentProps = toRef(() => props.content)
const contentEvents = {
closeAutoFocus: (e: Event) => e.preventDefault()
}
const ui = computed(() => drawer({
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.drawer || {}) })({
direction: props.direction,
inset: props.inset
}))

View File

@@ -1,26 +1,12 @@
<!-- eslint-disable vue/block-tag-newline -->
<script lang="ts">
import type { VariantProps } from 'tailwind-variants'
import type { DropdownMenuRootProps, DropdownMenuRootEmits, DropdownMenuContentProps, DropdownMenuContentEmits, DropdownMenuArrowProps } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/dropdown-menu'
import { tv } from '../utils/tv'
import type { AvatarProps, KbdProps, LinkProps } from '../types'
import type {
ArrayOrNested,
DynamicSlots,
MergeTypes,
NestedItem,
PartialString,
EmitsToProps
} from '../types/utils'
import type { ArrayOrNested, DynamicSlots, MergeTypes, NestedItem, EmitsToProps, ComponentConfig } from '../types/utils'
const appConfigDropdownMenu = _appConfig as AppConfig & { ui: { dropdownMenu: Partial<typeof theme> } }
const dropdownMenu = tv({ extend: tv(theme), ...(appConfigDropdownMenu.ui?.dropdownMenu || {}) })
type DropdownMenuVariants = VariantProps<typeof dropdownMenu>
type DropdownMenu = ComponentConfig<typeof theme, AppConfig, 'dropdownMenu'>
export interface DropdownMenuItem extends Omit<LinkProps, 'type' | 'raw' | 'custom'> {
label?: string
@@ -28,7 +14,7 @@ export interface DropdownMenuItem extends Omit<LinkProps, 'type' | 'raw' | 'cust
* @IconifyIcon
*/
icon?: string
color?: DropdownMenuVariants['color']
color?: DropdownMenu['variants']['color']
avatar?: AvatarProps
content?: Omit<DropdownMenuContentProps, 'as' | 'asChild' | 'forceMount'> & Partial<EmitsToProps<DropdownMenuContentEmits>>
kbds?: KbdProps['value'][] | KbdProps[]
@@ -53,7 +39,7 @@ export interface DropdownMenuProps<T extends ArrayOrNested<DropdownMenuItem> = A
/**
* @defaultValue 'md'
*/
size?: DropdownMenuVariants['size']
size?: DropdownMenu['variants']['size']
items?: T
/**
* The icon displayed when an item is checked.
@@ -96,7 +82,7 @@ export interface DropdownMenuProps<T extends ArrayOrNested<DropdownMenuItem> = A
labelKey?: keyof NestedItem<T>
disabled?: boolean
class?: any
ui?: PartialString<typeof dropdownMenu.slots>
ui?: DropdownMenu['slots']
}
export interface DropdownMenuEmits extends DropdownMenuRootEmits {}
@@ -121,7 +107,9 @@ import { computed, toRef } from 'vue'
import { defu } from 'defu'
import { DropdownMenuRoot, DropdownMenuTrigger, DropdownMenuArrow, useForwardPropsEmits } from 'reka-ui'
import { reactivePick } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { omit } from '../utils'
import { tv } from '../utils/tv'
import UDropdownMenuContent from './DropdownMenuContent.vue'
const props = withDefaults(defineProps<DropdownMenuProps<T>>(), {
@@ -133,12 +121,14 @@ const props = withDefaults(defineProps<DropdownMenuProps<T>>(), {
const emits = defineEmits<DropdownMenuEmits>()
const slots = defineSlots<DropdownMenuSlots<T>>()
const appConfig = useAppConfig() as DropdownMenu['AppConfig']
const rootProps = useForwardPropsEmits(reactivePick(props, 'defaultOpen', 'open', 'modal'), emits)
const contentProps = toRef(() => defu(props.content, { side: 'bottom', sideOffset: 8, collisionPadding: 8 }) as DropdownMenuContentProps)
const arrowProps = toRef(() => props.arrow as DropdownMenuArrowProps)
const proxySlots = omit(slots, ['default'])
const ui = computed(() => dropdownMenu({
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.dropdownMenu || {}) })({
size: props.size
}))
</script>

View File

@@ -1,12 +1,12 @@
<!-- eslint-disable vue/block-tag-newline -->
<script lang="ts">
import type { DropdownMenuContentProps as RekaDropdownMenuContentProps, DropdownMenuContentEmits as RekaDropdownMenuContentEmits } from 'reka-ui'
import theme from '#build/ui/dropdown-menu'
import { tv } from '../utils/tv'
import type { AppConfig } from '@nuxt/schema'
import type theme from '#build/ui/dropdown-menu'
import type { KbdProps, AvatarProps, DropdownMenuItem, DropdownMenuSlots } from '../types'
import type { ArrayOrNested, NestedItem } from '../types/utils'
import type { ArrayOrNested, NestedItem, ComponentConfig } from '../types/utils'
const _dropdownMenu = tv(theme)()
type DropdownMenu = ComponentConfig<typeof theme, AppConfig, 'dropdownMenu'>
interface DropdownMenuContentProps<T extends ArrayOrNested<DropdownMenuItem>> extends Omit<RekaDropdownMenuContentProps, 'as' | 'asChild' | 'forceMount'> {
items?: T
@@ -26,8 +26,8 @@ interface DropdownMenuContentProps<T extends ArrayOrNested<DropdownMenuItem>> ex
*/
externalIcon?: boolean | string
class?: any
ui: typeof _dropdownMenu
uiOverride?: any
ui: { [K in keyof Required<DropdownMenu['slots']>]: (props?: Record<string, any>) => string }
uiOverride?: DropdownMenu['slots']
}
interface DropdownMenuContentEmits extends RekaDropdownMenuContentEmits {}
@@ -59,8 +59,9 @@ const props = defineProps<DropdownMenuContentProps<T>>()
const emits = defineEmits<DropdownMenuContentEmits>()
const slots = defineSlots<DropdownMenuContentSlots<T>>()
const appConfig = useAppConfig()
const { dir } = useLocale()
const appConfig = useAppConfig()
const contentProps = useForwardPropsEmits(reactiveOmit(props, 'sub', 'items', 'portal', 'labelKey', 'checkedIcon', 'loadingIcon', 'externalIcon', 'class', 'ui', 'uiOverride'), emits)
const proxySlots = omit(slots, ['default'])

View File

@@ -1,14 +1,11 @@
<script lang="ts">
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/form'
import { tv } from '../utils/tv'
import type { FormSchema, FormError, FormInputEvents, FormErrorEvent, FormSubmitEvent, FormEvent, Form, FormErrorWithId } from '../types/form'
import type { DeepReadonly } from 'vue'
import type { AppConfig } from '@nuxt/schema'
import theme from '#build/ui/form'
import type { FormSchema, FormError, FormInputEvents, FormErrorEvent, FormSubmitEvent, FormEvent, Form, FormErrorWithId } from '../types/form'
import type { ComponentConfig } from '../types/utils'
const appConfigForm = _appConfig as AppConfig & { ui: { form: Partial<typeof theme> } }
const form = tv({ extend: tv(theme), ...(appConfigForm.ui?.form || {}) })
type FormConfig = ComponentConfig<typeof theme, AppConfig, 'form'>
export interface FormProps<T extends object> {
id?: string | number
@@ -56,7 +53,9 @@ export interface FormSlots {
<script lang="ts" setup generic="T extends object">
import { provide, inject, nextTick, ref, onUnmounted, onMounted, computed, useId, readonly } from 'vue'
import { useEventBus } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { formOptionsInjectionKey, formInputsInjectionKey, formBusInjectionKey, formLoadingInjectionKey } from '../composables/useFormField'
import { tv } from '../utils/tv'
import { validateSchema } from '../utils/form'
import { FormValidationException } from '../types/form'
@@ -71,6 +70,10 @@ const props = withDefaults(defineProps<FormProps<T>>(), {
const emits = defineEmits<FormEmits<T>>()
defineSlots<FormSlots>()
const appConfig = useAppConfig() as FormConfig['AppConfig']
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.form || {}) }))
const formId = props.id ?? useId() as string
const bus = useEventBus<FormEvent<T>>(`form-${formId}`)
@@ -287,7 +290,7 @@ defineExpose<Form<T>>({
<component
:is="parentBus ? 'div' : 'form'"
:id="formId"
:class="form({ class: props.class })"
:class="ui({ class: props.class })"
@submit.prevent="onSubmitWrapper"
>
<slot :errors="errors" />

View File

@@ -1,15 +1,9 @@
<script lang="ts">
import type { VariantProps } from 'tailwind-variants'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/form-field'
import { tv } from '../utils/tv'
import type { ComponentConfig } from '../types/utils'
const appConfigFormField = _appConfig as AppConfig & { ui: { formField: Partial<typeof theme> } }
const formField = tv({ extend: tv(theme), ...(appConfigFormField.ui?.formField || {}) })
type FormFieldVariants = VariantProps<typeof formField>
type FormField = ComponentConfig<typeof theme, AppConfig, 'formField'>
export interface FormFieldProps {
/**
@@ -29,7 +23,7 @@ export interface FormFieldProps {
/**
* @defaultValue 'md'
*/
size?: FormFieldVariants['size']
size?: FormField['variants']['size']
required?: boolean
/** If true, validation on input will be active immediately instead of waiting for a blur event. */
eagerValidation?: boolean
@@ -39,7 +33,7 @@ export interface FormFieldProps {
*/
validateOnInputDelay?: number
class?: any
ui?: Partial<typeof formField.slots>
ui?: FormField['slots']
}
export interface FormFieldSlots {
@@ -55,13 +49,17 @@ export interface FormFieldSlots {
<script setup lang="ts">
import { computed, ref, inject, provide, type Ref, useId } from 'vue'
import { Primitive, Label } from 'reka-ui'
import { useAppConfig } from '#imports'
import { formFieldInjectionKey, inputIdInjectionKey } from '../composables/useFormField'
import { tv } from '../utils/tv'
import type { FormError, FormFieldInjectedOptions } from '../types/form'
const props = defineProps<FormFieldProps>()
const slots = defineSlots<FormFieldSlots>()
const ui = computed(() => formField({
const appConfig = useAppConfig() as FormField['AppConfig']
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.formField || {}) })({
size: props.size,
required: props.required
}))

View File

@@ -1,19 +1,12 @@
<script lang="ts">
import type { InputHTMLAttributes } from 'vue'
import type { VariantProps } from 'tailwind-variants'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/input'
import type { UseComponentIconsProps } from '../composables/useComponentIcons'
import { tv } from '../utils/tv'
import type { AvatarProps } from '../types'
import type { PartialString } from '../types/utils'
import type { ComponentConfig } from '../types/utils'
const appConfigInput = _appConfig as AppConfig & { ui: { input: Partial<typeof theme> } }
const input = tv({ extend: tv(theme), ...(appConfigInput.ui?.input || {}) })
type InputVariants = VariantProps<typeof input>
type Input = ComponentConfig<typeof theme, AppConfig, 'input'>
export interface InputProps extends UseComponentIconsProps {
/**
@@ -29,15 +22,15 @@ export interface InputProps extends UseComponentIconsProps {
/**
* @defaultValue 'primary'
*/
color?: InputVariants['color']
color?: Input['variants']['color']
/**
* @defaultValue 'outline'
*/
variant?: InputVariants['variant']
variant?: Input['variants']['variant']
/**
* @defaultValue 'md'
*/
size?: InputVariants['size']
size?: Input['variants']['size']
required?: boolean
autocomplete?: InputHTMLAttributes['autocomplete']
autofocus?: boolean
@@ -46,7 +39,7 @@ export interface InputProps extends UseComponentIconsProps {
/** Highlight the ring color like a focus state. */
highlight?: boolean
class?: any
ui?: PartialString<typeof input.slots>
ui?: Input['slots']
}
export interface InputEmits {
@@ -65,10 +58,12 @@ export interface InputSlots {
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { Primitive } from 'reka-ui'
import { useAppConfig } from '#imports'
import { useButtonGroup } from '../composables/useButtonGroup'
import { useComponentIcons } from '../composables/useComponentIcons'
import { useFormField } from '../composables/useFormField'
import { looseToNumber } from '../utils'
import { tv } from '../utils/tv'
import UIcon from './Icon.vue'
import UAvatar from './Avatar.vue'
@@ -84,14 +79,15 @@ const slots = defineSlots<InputSlots>()
const [modelValue, modelModifiers] = defineModel<string | number | null>()
const appConfig = useAppConfig() as Input['AppConfig']
const { emitFormBlur, emitFormInput, emitFormChange, size: formGroupSize, color, id, name, highlight, disabled, emitFormFocus, ariaAttrs } = useFormField<InputProps>(props, { deferInputValidation: true })
const { orientation, size: buttonGroupSize } = useButtonGroup<InputProps>(props)
const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponentIcons(props)
const inputSize = computed(() => buttonGroupSize.value || formGroupSize.value)
const ui = computed(() => input({
type: props.type as InputVariants['type'],
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.input || {}) })({
type: props.type as Input['variants']['type'],
color: color.value,
variant: props.variant,
size: inputSize?.value,

View File

@@ -1,27 +1,13 @@
<script lang="ts">
import type { InputHTMLAttributes } from 'vue'
import type { VariantProps } from 'tailwind-variants'
import type { ComboboxRootProps, ComboboxRootEmits, ComboboxContentProps, ComboboxContentEmits, ComboboxArrowProps } 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 { tv } from '../utils/tv'
import type { AvatarProps, ChipProps, InputProps } from '../types'
import type {
AcceptableValue,
ArrayOrNested,
GetItemKeys,
GetModelValue,
GetModelValueEmits,
NestedItem,
PartialString,
EmitsToProps
} from '../types/utils'
import type { AcceptableValue, ArrayOrNested, GetItemKeys, GetModelValue, GetModelValueEmits, NestedItem, EmitsToProps, ComponentConfig } from '../types/utils'
const appConfigInputMenu = _appConfig as AppConfig & { ui: { inputMenu: Partial<typeof theme> } }
const inputMenu = tv({ extend: tv(theme), ...(appConfigInputMenu.ui?.inputMenu || {}) })
type InputMenu = ComponentConfig<typeof theme, AppConfig, 'inputMenu'>
interface _InputMenuItem {
label?: string
@@ -42,8 +28,6 @@ interface _InputMenuItem {
}
export type InputMenuItem = _InputMenuItem | AcceptableValue | boolean
type InputMenuVariants = VariantProps<typeof inputMenu>
export interface InputMenuProps<T extends ArrayOrNested<InputMenuItem> = ArrayOrNested<InputMenuItem>, VK extends GetItemKeys<T> | undefined = undefined, M extends boolean = false> extends Pick<ComboboxRootProps<T>, 'open' | 'defaultOpen' | 'disabled' | 'name' | 'resetSearchTermOnBlur' | 'resetSearchTermOnSelect' | 'highlightOnHover'>, UseComponentIconsProps {
/**
* The element or component this component should render as.
@@ -57,15 +41,15 @@ export interface InputMenuProps<T extends ArrayOrNested<InputMenuItem> = ArrayOr
/**
* @defaultValue 'primary'
*/
color?: InputMenuVariants['color']
color?: InputMenu['variants']['color']
/**
* @defaultValue 'outline'
*/
variant?: InputMenuVariants['variant']
variant?: InputMenu['variants']['variant']
/**
* @defaultValue 'md'
*/
size?: InputMenuVariants['size']
size?: InputMenu['variants']['size']
required?: boolean
autofocus?: boolean
autofocusDelay?: number
@@ -138,7 +122,7 @@ export interface InputMenuProps<T extends ArrayOrNested<InputMenuItem> = ArrayOr
*/
ignoreFilter?: boolean
class?: any
ui?: PartialString<typeof inputMenu.slots>
ui?: InputMenu['slots']
}
export type InputMenuEmits<A extends ArrayOrNested<InputMenuItem>, VK extends GetItemKeys<A> | undefined, M extends boolean> = Pick<ComboboxRootEmits, 'update:open'> & {
@@ -161,8 +145,16 @@ export interface InputMenuSlots<
M extends boolean = false,
T extends NestedItem<A> = NestedItem<A>
> {
'leading'(props: { modelValue?: GetModelValue<A, VK, M>, open: boolean, ui: ReturnType<typeof inputMenu> }): any
'trailing'(props: { modelValue?: GetModelValue<A, VK, M>, open: boolean, ui: ReturnType<typeof inputMenu> }): any
'leading'(props: {
modelValue?: GetModelValue<A, VK, M>
open: boolean
ui: { [K in keyof Required<InputMenu['slots']>]: (props?: Record<string, any>) => string }
}): any
'trailing'(props: {
modelValue?: GetModelValue<A, VK, M>
open: boolean
ui: { [K in keyof Required<InputMenu['slots']>]: (props?: Record<string, any>) => string }
}): any
'empty'(props: { searchTerm?: string }): any
'item': SlotProps<T>
'item-leading': SlotProps<T>
@@ -186,6 +178,7 @@ import { useComponentIcons } from '../composables/useComponentIcons'
import { useFormField } from '../composables/useFormField'
import { useLocale } from '../composables/useLocale'
import { compare, get, isArrayOfArray } from '../utils'
import { tv } from '../utils/tv'
import UIcon from './Icon.vue'
import UAvatar from './Avatar.vue'
import UChip from './Chip.vue'
@@ -206,7 +199,7 @@ const slots = defineSlots<InputMenuSlots<T, VK, M>>()
const searchTerm = defineModel<string>('searchTerm', { default: '' })
const { t } = useLocale()
const appConfig = useAppConfig()
const appConfig = useAppConfig() as InputMenu['AppConfig']
const { contains } = useFilter({ sensitivity: 'base' })
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'modelValue', 'defaultValue', 'open', 'defaultOpen', 'required', 'multiple', 'resetSearchTermOnBlur', 'resetSearchTermOnSelect', 'highlightOnHover', 'ignoreFilter'), emits)
@@ -221,7 +214,7 @@ const inputSize = computed(() => buttonGroupSize.value || formGroupSize.value)
const [DefineCreateItemTemplate, ReuseCreateItemTemplate] = createReusableTemplate()
const ui = computed(() => inputMenu({
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.inputMenu || {}) })({
color: color.value,
variant: props.variant,
size: inputSize?.value,

View File

@@ -1,18 +1,11 @@
<script lang="ts">
import type { VariantProps } from 'tailwind-variants'
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'
import { tv } from '../utils/tv'
import type { ButtonProps } from '../types'
import type { PartialString } from '../types/utils'
import type { ComponentConfig } from '../types/utils'
const appConfigInputNumber = _appConfig as AppConfig & { ui: { inputNumber: Partial<typeof theme> } }
const inputNumber = tv({ extend: tv(theme), ...(appConfigInputNumber.ui?.inputNumber || {}) })
type InputNumberVariants = VariantProps<typeof inputNumber>
type InputNumber = ComponentConfig<typeof theme, AppConfig, 'inputNumber'>
export interface InputNumberProps extends Pick<NumberFieldRootProps, 'modelValue' | 'defaultValue' | 'min' | 'max' | 'step' | 'stepSnapping' | 'disabled' | 'required' | 'id' | 'name' | 'formatOptions' | 'disableWheelChange'> {
/**
@@ -22,9 +15,9 @@ export interface InputNumberProps extends Pick<NumberFieldRootProps, 'modelValue
as?: any
/** The placeholder text when the input is empty. */
placeholder?: string
color?: InputNumberVariants['color']
variant?: InputNumberVariants['variant']
size?: InputNumberVariants['size']
color?: InputNumber['variants']['color']
variant?: InputNumber['variants']['variant']
size?: InputNumber['variants']['size']
/** Highlight the ring color like a focus state. */
highlight?: boolean
/**
@@ -62,7 +55,7 @@ export interface InputNumberProps extends Pick<NumberFieldRootProps, 'modelValue
*/
locale?: string
class?: any
ui?: PartialString<typeof inputNumber.slots>
ui?: InputNumber['slots']
}
export interface InputNumberEmits {
@@ -84,6 +77,7 @@ import { reactivePick } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { useFormField } from '../composables/useFormField'
import { useLocale } from '../composables/useLocale'
import { tv } from '../utils/tv'
import UButton from './Button.vue'
defineOptions({ inheritAttrs: false })
@@ -94,15 +88,16 @@ const props = withDefaults(defineProps<InputNumberProps>(), {
const emits = defineEmits<InputNumberEmits>()
defineSlots<InputNumberSlots>()
const appConfig = useAppConfig() as InputNumber['AppConfig']
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'modelValue', 'defaultValue', 'min', 'max', 'step', 'stepSnapping', 'formatOptions', 'disableWheelChange'), emits)
const appConfig = useAppConfig()
const { emitFormBlur, emitFormFocus, emitFormChange, emitFormInput, id, color, size, name, highlight, disabled, ariaAttrs } = useFormField<InputNumberProps>(props)
const { t, code: codeLocale } = useLocale()
const locale = computed(() => props.locale || codeLocale.value)
const ui = computed(() => inputNumber({
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.inputNumber || {}) })({
color: color.value,
variant: props.variant,
size: size.value,

View File

@@ -1,16 +1,10 @@
<script lang="ts">
import type { VariantProps } from 'tailwind-variants'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/kbd'
import type { KbdKey } from '../composables/useKbd'
import { tv } from '../utils/tv'
import type { ComponentConfig } from '../types/utils'
const appConfigKbd = _appConfig as AppConfig & { ui: { kbd: Partial<typeof theme> } }
const kbd = tv({ extend: tv(theme), ...(appConfigKbd.ui?.kbd || {}) })
type KbdVariants = VariantProps<typeof kbd>
type Kbd = ComponentConfig<typeof theme, AppConfig, 'kbd'>
export interface KbdProps {
/**
@@ -22,11 +16,11 @@ export interface KbdProps {
/**
* @defaultValue 'outline'
*/
variant?: KbdVariants['variant']
variant?: Kbd['variants']['variant']
/**
* @defaultValue 'md'
*/
size?: KbdVariants['size']
size?: Kbd['variants']['size']
class?: any
}
@@ -36,8 +30,11 @@ export interface KbdSlots {
</script>
<script setup lang="ts">
import { computed } from 'vue'
import { Primitive } from 'reka-ui'
import { useAppConfig } from '#imports'
import { useKbd } from '../composables/useKbd'
import { tv } from '../utils/tv'
const props = withDefaults(defineProps<KbdProps>(), {
as: 'kbd'
@@ -45,10 +42,13 @@ const props = withDefaults(defineProps<KbdProps>(), {
defineSlots<KbdSlots>()
const { getKbdKey } = useKbd()
const appConfig = useAppConfig() as Kbd['AppConfig']
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.kbd || {}) }))
</script>
<template>
<Primitive :as="as" :class="kbd({ variant, size, class: props.class })">
<Primitive :as="as" :class="ui({ variant, size, class: props.class })">
<slot>
{{ getKbdKey(value) }}
</slot>

View File

@@ -1,10 +1,11 @@
<script lang="ts">
import type { ButtonHTMLAttributes } from 'vue'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import type { RouterLinkProps, RouteLocationRaw } from 'vue-router'
import theme from '#build/ui/link'
import { tv } from '../utils/tv'
import type { ComponentConfig } from '../types/utils'
type Link = ComponentConfig<typeof theme, AppConfig, 'link'>
interface NuxtLinkProps extends Omit<RouterLinkProps, 'to'> {
/**
@@ -52,10 +53,6 @@ interface NuxtLinkProps extends Omit<RouterLinkProps, 'to'> {
noPrefetch?: boolean
}
const appConfigLink = _appConfig as AppConfig & { ui: { link: Partial<typeof theme> } }
const link = tv({ extend: tv(theme), ...(appConfigLink.ui?.link || {}) })
export interface LinkProps extends NuxtLinkProps {
/**
* The element or component this component should render as when not a link.
@@ -91,10 +88,12 @@ export interface LinkSlots {
<script setup lang="ts">
import { computed } from 'vue'
import { defu } from 'defu'
import { isEqual, diff } from 'ohash/utils'
import { useForwardProps } from 'reka-ui'
import { reactiveOmit } from '@vueuse/core'
import { useRoute } from '#imports'
import { useRoute, useAppConfig } from '#imports'
import { tv } from '../utils/tv'
import ULinkBase from './LinkBase.vue'
defineOptions({ inheritAttrs: false })
@@ -110,16 +109,20 @@ const props = withDefaults(defineProps<LinkProps>(), {
defineSlots<LinkSlots>()
const route = useRoute()
const appConfig = useAppConfig() as Link['AppConfig']
const nuxtLinkProps = useForwardProps(reactiveOmit(props, 'as', 'type', 'disabled', 'active', 'exact', 'exactQuery', 'exactHash', 'activeClass', 'inactiveClass', 'raw', 'class'))
const ui = computed(() => tv({
extend: link,
variants: {
active: {
true: props.activeClass,
false: props.inactiveClass
extend: tv(theme),
...defu({
variants: {
active: {
true: props.activeClass,
false: props.inactiveClass
}
}
}
}, appConfig.ui?.link || {})
}))
function isPartiallyEqual(item1: any, item2: any) {

View File

@@ -1,15 +1,11 @@
<script lang="ts">
import type { DialogRootProps, DialogRootEmits, DialogContentProps, DialogContentEmits } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/modal'
import { tv } from '../utils/tv'
import type { ButtonProps } from '../types'
import type { EmitsToProps } from '../types/utils'
import type { EmitsToProps, ComponentConfig } from '../types/utils'
const appConfigModal = _appConfig as AppConfig & { ui: { modal: Partial<typeof theme> } }
const modal = tv({ extend: tv(theme), ...(appConfigModal.ui?.modal || {}) })
type Modal = ComponentConfig<typeof theme, AppConfig, 'modal'>
export interface ModalProps extends DialogRootProps {
title?: string
@@ -54,7 +50,7 @@ export interface ModalProps extends DialogRootProps {
*/
dismissible?: boolean
class?: any
ui?: Partial<typeof modal.slots>
ui?: Modal['slots']
}
export interface ModalEmits extends DialogRootEmits {
@@ -67,7 +63,7 @@ export interface ModalSlots {
header(props?: {}): any
title(props?: {}): any
description(props?: {}): any
close(props: { ui: ReturnType<typeof modal> }): any
close(props: { ui: { [K in keyof Required<Modal['slots']>]: (props?: Record<string, any>) => string } }): any
body(props?: {}): any
footer(props?: {}): any
}
@@ -79,6 +75,7 @@ import { DialogRoot, DialogTrigger, DialogPortal, DialogOverlay, DialogContent,
import { reactivePick } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { useLocale } from '../composables/useLocale'
import { tv } from '../utils/tv'
import UButton from './Button.vue'
const props = withDefaults(defineProps<ModalProps>(), {
@@ -93,7 +90,7 @@ const emits = defineEmits<ModalEmits>()
const slots = defineSlots<ModalSlots>()
const { t } = useLocale()
const appConfig = useAppConfig()
const appConfig = useAppConfig() as Modal['AppConfig']
const rootProps = useForwardPropsEmits(reactivePick(props, 'open', 'defaultOpen', 'modal'), emits)
const contentProps = toRef(() => props.content)
@@ -114,7 +111,7 @@ const contentEvents = computed(() => {
return events
})
const ui = computed(() => modal({
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.modal || {}) })({
transition: props.transition,
fullscreen: props.fullscreen
}))

View File

@@ -1,24 +1,12 @@
<!-- eslint-disable vue/block-tag-newline -->
<script lang="ts">
import type { VariantProps } from 'tailwind-variants'
import type { NavigationMenuRootProps, NavigationMenuRootEmits, NavigationMenuContentProps, NavigationMenuContentEmits, CollapsibleRootProps } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/navigation-menu'
import { tv } from '../utils/tv'
import type { AvatarProps, BadgeProps, LinkProps } from '../types'
import type {
ArrayOrNested,
DynamicSlots,
MergeTypes,
NestedItem,
PartialString,
EmitsToProps
} from '../types/utils'
import type { ArrayOrNested, DynamicSlots, MergeTypes, NestedItem, EmitsToProps, ComponentConfig } from '../types/utils'
const appConfigNavigationMenu = _appConfig as AppConfig & { ui: { navigationMenu: Partial<typeof theme> } }
const navigationMenu = tv({ extend: tv(theme), ...(appConfigNavigationMenu.ui?.navigationMenu || {}) })
type NavigationMenu = ComponentConfig<typeof theme, AppConfig, 'navigationMenu'>
export interface NavigationMenuChildItem extends Omit<NavigationMenuItem, 'type'> {
/** Description is only used when `orientation` is `horizontal`. */
@@ -55,8 +43,6 @@ export interface NavigationMenuItem extends Omit<LinkProps, 'type' | 'raw' | 'cu
[key: string]: any
}
type NavigationMenuVariants = VariantProps<typeof navigationMenu>
export interface NavigationMenuProps<T extends ArrayOrNested<NavigationMenuItem> = ArrayOrNested<NavigationMenuItem>> extends Pick<NavigationMenuRootProps, 'modelValue' | 'defaultValue' | 'delayDuration' | 'disableClickTrigger' | 'disableHoverTrigger' | 'skipDelayDuration' | 'disablePointerLeaveClose' | 'unmountOnHide'> {
/**
* The element or component this component should render as.
@@ -80,11 +66,11 @@ export interface NavigationMenuProps<T extends ArrayOrNested<NavigationMenuItem>
/**
* @defaultValue 'primary'
*/
color?: NavigationMenuVariants['color']
color?: NavigationMenu['variants']['color']
/**
* @defaultValue 'pill'
*/
variant?: NavigationMenuVariants['variant']
variant?: NavigationMenu['variants']['variant']
/**
* The orientation of the menu.
* @defaultValue 'horizontal'
@@ -101,7 +87,7 @@ export interface NavigationMenuProps<T extends ArrayOrNested<NavigationMenuItem>
/**
* @defaultValue 'primary'
*/
highlightColor?: NavigationMenuVariants['highlightColor']
highlightColor?: NavigationMenu['variants']['highlightColor']
/** The content of the menu. */
content?: Omit<NavigationMenuContentProps, 'as' | 'asChild' | 'forceMount'> & Partial<EmitsToProps<NavigationMenuContentEmits>>
/**
@@ -109,7 +95,7 @@ export interface NavigationMenuProps<T extends ArrayOrNested<NavigationMenuItem>
* Only works when `orientation` is `horizontal`.
* @defaultValue 'horizontal'
*/
contentOrientation?: NavigationMenuVariants['contentOrientation']
contentOrientation?: NavigationMenu['variants']['contentOrientation']
/**
* Display an arrow alongside the menu.
* @defaultValue false
@@ -121,7 +107,7 @@ export interface NavigationMenuProps<T extends ArrayOrNested<NavigationMenuItem>
*/
labelKey?: keyof NestedItem<T>
class?: any
ui?: PartialString<typeof navigationMenu.slots>
ui?: NavigationMenu['slots']
}
export interface NavigationMenuEmits extends NavigationMenuRootEmits {}
@@ -147,6 +133,7 @@ import { NavigationMenuRoot, NavigationMenuList, NavigationMenuItem, NavigationM
import { createReusableTemplate } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { get, isArrayOfArray } from '../utils'
import { tv } from '../utils/tv'
import { pickLinkProps } from '../utils/link'
import ULinkBase from './LinkBase.vue'
import ULink from './Link.vue'
@@ -166,6 +153,8 @@ const props = withDefaults(defineProps<NavigationMenuProps<T>>(), {
const emits = defineEmits<NavigationMenuEmits>()
const slots = defineSlots<NavigationMenuSlots<T>>()
const appConfig = useAppConfig() as NavigationMenu['AppConfig']
const rootProps = useForwardPropsEmits(computed(() => ({
as: props.as,
modelValue: props.modelValue,
@@ -178,11 +167,8 @@ const rootProps = useForwardPropsEmits(computed(() => ({
disablePointerLeaveClose: props.disablePointerLeaveClose,
unmountOnHide: props.unmountOnHide
})), emits)
const contentProps = toRef(() => props.content)
const appConfig = useAppConfig()
const [DefineLinkTemplate, ReuseLinkTemplate] = createReusableTemplate<{ item: NavigationMenuItem, index: number, active?: boolean }>()
const [DefineItemTemplate, ReuseItemTemplate] = createReusableTemplate<{ item: NavigationMenuItem, index: number, level?: number }>({
props: {
@@ -192,7 +178,7 @@ const [DefineItemTemplate, ReuseItemTemplate] = createReusableTemplate<{ item: N
}
})
const ui = computed(() => navigationMenu({
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.navigationMenu || {}) })({
orientation: props.orientation,
contentOrientation: props.contentOrientation,
collapsed: props.collapsed,

View File

@@ -1,14 +1,11 @@
<script lang="ts">
import type { PaginationRootProps, PaginationRootEmits } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/pagination'
import { tv } from '../utils/tv'
import type { ButtonProps } from '../types'
import type { ComponentConfig } from '../types/utils'
const appConfigPagination = _appConfig as AppConfig & { ui: { pagination: Partial<typeof theme> } }
const pagination = tv({ extend: tv(theme), ...(appConfigPagination.ui?.pagination || {}) })
type Pagination = ComponentConfig<typeof theme, AppConfig, 'pagination'>
export interface PaginationProps extends Partial<Pick<PaginationRootProps, 'defaultPage' | 'disabled' | 'itemsPerPage' | 'page' | 'showEdges' | 'siblingCount' | 'total'>> {
/**
@@ -79,7 +76,7 @@ export interface PaginationProps extends Partial<Pick<PaginationRootProps, 'defa
*/
to?: (page: number) => ButtonProps['to']
class?: any
ui?: Partial<typeof pagination.slots>
ui?: Pagination['slots']
}
export interface PaginationEmits extends PaginationRootEmits {}
@@ -110,6 +107,7 @@ import { PaginationRoot, PaginationList, PaginationListItem, PaginationFirst, Pa
import { reactivePick } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { useLocale } from '../composables/useLocale'
import { tv } from '../utils/tv'
import UButton from './Button.vue'
const props = withDefaults(defineProps<PaginationProps>(), {
@@ -127,8 +125,9 @@ const props = withDefaults(defineProps<PaginationProps>(), {
const emits = defineEmits<PaginationEmits>()
const slots = defineSlots<PaginationSlots>()
const appConfig = useAppConfig()
const { dir } = useLocale()
const appConfig = useAppConfig() as Pagination['AppConfig']
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'defaultPage', 'disabled', 'itemsPerPage', 'page', 'showEdges', 'siblingCount', 'total'), emits)
const firstIcon = computed(() => props.firstIcon || (dir.value === 'rtl' ? appConfig.ui.icons.chevronDoubleRight : appConfig.ui.icons.chevronDoubleLeft))
@@ -137,7 +136,7 @@ const nextIcon = computed(() => props.nextIcon || (dir.value === 'rtl' ? appConf
const lastIcon = computed(() => props.lastIcon || (dir.value === 'rtl' ? appConfig.ui.icons.chevronDoubleLeft : appConfig.ui.icons.chevronDoubleRight))
// eslint-disable-next-line vue/no-dupe-keys
const ui = pagination()
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.pagination || {}) })())
</script>
<template>

View File

@@ -1,18 +1,11 @@
<!-- eslint-disable vue/block-tag-newline -->
<script lang="ts">
import type { VariantProps } from 'tailwind-variants'
import type { PinInputRootEmits, PinInputRootProps } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/pin-input'
import { tv } from '../utils/tv'
import type { PartialString } from '../types/utils'
import type { ComponentConfig } from '../types/utils'
const appConfigPinInput = _appConfig as AppConfig & { ui: { pinInput: Partial<typeof theme> } }
const pinInput = tv({ extend: tv(theme), ...(appConfigPinInput.ui?.pinInput || {}) })
type PinInputVariants = VariantProps<typeof pinInput>
type PinInput = ComponentConfig<typeof theme, AppConfig, 'pinInput'>
export interface PinInputProps extends Pick<PinInputRootProps, 'defaultValue' | 'disabled' | 'id' | 'mask' | 'modelValue' | 'name' | 'otp' | 'placeholder' | 'required' | 'type'> {
/**
@@ -23,15 +16,15 @@ export interface PinInputProps extends Pick<PinInputRootProps, 'defaultValue' |
/**
* @defaultValue 'primary'
*/
color?: PinInputVariants['color']
color?: PinInput['variants']['color']
/**
* @defaultValue 'outline'
*/
variant?: PinInputVariants['variant']
variant?: PinInput['variants']['variant']
/**
* @defaultValue 'md'
*/
size?: PinInputVariants['size']
size?: PinInput['variants']['size']
/**
* The number of input fields.
* @defaultValue 5
@@ -41,7 +34,7 @@ export interface PinInputProps extends Pick<PinInputRootProps, 'defaultValue' |
autofocusDelay?: number
highlight?: boolean
class?: any
ui?: PartialString<typeof pinInput.slots>
ui?: PinInput['slots']
}
export type PinInputEmits = PinInputRootEmits & {
@@ -56,8 +49,10 @@ import type { ComponentPublicInstance } from 'vue'
import { ref, computed, onMounted } from 'vue'
import { PinInputInput, PinInputRoot, useForwardPropsEmits } from 'reka-ui'
import { reactivePick } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { useFormField } from '../composables/useFormField'
import { looseToNumber } from '../utils'
import { tv } from '../utils/tv'
const props = withDefaults(defineProps<PinInputProps>(), {
type: 'text',
@@ -66,10 +61,13 @@ const props = withDefaults(defineProps<PinInputProps>(), {
})
const emits = defineEmits<PinInputEmits>()
const appConfig = useAppConfig() as PinInput['AppConfig']
const rootProps = useForwardPropsEmits(reactivePick(props, 'defaultValue', 'disabled', 'id', 'mask', 'modelValue', 'name', 'otp', 'placeholder', 'required', 'type'), emits)
const { emitFormInput, emitFormFocus, emitFormChange, emitFormBlur, size, color, id, name, highlight, disabled, ariaAttrs } = useFormField<PinInputProps>(props)
const ui = computed(() => pinInput({
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.pinInput || {}) })({
color: color.value,
variant: props.variant,
size: size.value,

View File

@@ -1,14 +1,10 @@
<script lang="ts">
import type { PopoverRootProps, HoverCardRootProps, PopoverRootEmits, PopoverContentProps, PopoverContentEmits, PopoverArrowProps } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/popover'
import { tv } from '../utils/tv'
import type { EmitsToProps } from '../types/utils'
import type { EmitsToProps, ComponentConfig } from '../types/utils'
const appConfigPopover = _appConfig as AppConfig & { ui: { popover: Partial<typeof theme> } }
const popover = tv({ extend: tv(theme), ...(appConfigPopover.ui?.popover || {}) })
type Popover = ComponentConfig<typeof theme, AppConfig, 'popover'>
export interface PopoverProps extends PopoverRootProps, Pick<HoverCardRootProps, 'openDelay' | 'closeDelay'> {
/**
@@ -37,7 +33,7 @@ export interface PopoverProps extends PopoverRootProps, Pick<HoverCardRootProps,
*/
dismissible?: boolean
class?: any
ui?: Partial<typeof popover.slots>
ui?: Popover['slots']
}
export interface PopoverEmits extends PopoverRootEmits {}
@@ -54,6 +50,8 @@ import { defu } from 'defu'
import { useForwardPropsEmits } from 'reka-ui'
import { Popover, HoverCard } from 'reka-ui/namespaced'
import { reactivePick } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { tv } from '../utils/tv'
const props = withDefaults(defineProps<PopoverProps>(), {
portal: true,
@@ -65,6 +63,8 @@ const props = withDefaults(defineProps<PopoverProps>(), {
const emits = defineEmits<PopoverEmits>()
const slots = defineSlots<PopoverSlots>()
const appConfig = useAppConfig() as Popover['AppConfig']
const pick = props.mode === 'hover' ? reactivePick(props, 'defaultOpen', 'open', 'openDelay', 'closeDelay') : reactivePick(props, 'defaultOpen', 'open', 'modal')
const rootProps = useForwardPropsEmits(pick, emits)
const contentProps = toRef(() => defu(props.content, { side: 'bottom', sideOffset: 8, collisionPadding: 8 }) as PopoverContentProps)
@@ -82,7 +82,7 @@ const contentEvents = computed(() => {
const arrowProps = toRef(() => props.arrow as PopoverArrowProps)
// eslint-disable-next-line vue/no-dupe-keys
const ui = computed(() => popover({
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.popover || {}) })({
side: contentProps.value.side
}))

View File

@@ -1,17 +1,11 @@
<!-- eslint-disable vue/block-tag-newline -->
<script lang="ts">
import type { VariantProps } from 'tailwind-variants'
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'
import { tv } from '../utils/tv'
import type { ComponentConfig } from '../types/utils'
const appConfigProgress = _appConfig as AppConfig & { ui: { progress: Partial<typeof theme> } }
const progress = tv({ extend: tv(theme), ...(appConfigProgress.ui?.progress || {}) })
type ProgressVariants = VariantProps<typeof progress>
type Progress = ComponentConfig<typeof theme, AppConfig, 'progress'>
export interface ProgressProps extends Pick<ProgressRootProps, 'getValueLabel' | 'modelValue'> {
/**
@@ -28,23 +22,23 @@ export interface ProgressProps extends Pick<ProgressRootProps, 'getValueLabel' |
/**
* @defaultValue 'md'
*/
size?: ProgressVariants['size']
size?: Progress['variants']['size']
/**
* @defaultValue 'primary'
*/
color?: ProgressVariants['color']
color?: Progress['variants']['color']
/**
* The orientation of the progress bar.
* @defaultValue 'horizontal'
*/
orientation?: ProgressVariants['orientation']
orientation?: Progress['variants']['orientation']
/**
* The animation of the progress bar.
* @defaultValue 'carousel'
*/
animation?: ProgressVariants['animation']
animation?: Progress['variants']['animation']
class?: any
ui?: Partial<typeof progress.slots>
ui?: Progress['slots']
}
export interface ProgressEmits extends ProgressRootEmits {}
@@ -61,7 +55,9 @@ export type ProgressSlots = {
import { computed } from 'vue'
import { Primitive, ProgressRoot, ProgressIndicator, useForwardPropsEmits } from 'reka-ui'
import { reactivePick } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { useLocale } from '../composables/useLocale'
import { tv } from '../utils/tv'
const props = withDefaults(defineProps<ProgressProps>(), {
inverted: false,
@@ -72,6 +68,7 @@ const emits = defineEmits<ProgressEmits>()
const slots = defineSlots<ProgressSlots>()
const { dir } = useLocale()
const appConfig = useAppConfig() as Progress['AppConfig']
const rootProps = useForwardPropsEmits(reactivePick(props, 'getValueLabel', 'modelValue'), emits)
@@ -160,7 +157,7 @@ function stepVariant(index: number | string) {
return 'other'
}
const ui = computed(() => progress({
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.progress || {}) })({
animation: props.animation,
size: props.size,
color: props.color,

View File

@@ -1,17 +1,10 @@
<script lang="ts">
import type { VariantProps } from 'tailwind-variants'
import type { RadioGroupRootProps, RadioGroupRootEmits } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/radio-group'
import { tv } from '../utils/tv'
import type { AcceptableValue } from '../types/utils'
import type { AcceptableValue, ComponentConfig } from '../types/utils'
const appConfigRadioGroup = _appConfig as AppConfig & { ui: { radioGroup: Partial<typeof theme> } }
const radioGroup = tv({ extend: tv(theme), ...(appConfigRadioGroup.ui?.radioGroup || {}) })
type RadioGroupVariants = VariantProps<typeof radioGroup>
type RadioGroup = ComponentConfig<typeof theme, AppConfig, 'radioGroup'>
export type RadioGroupValue = AcceptableValue
export type RadioGroupItem = {
@@ -48,15 +41,15 @@ export interface RadioGroupProps<T extends RadioGroupItem = RadioGroupItem> exte
/**
* @defaultValue 'md'
*/
size?: RadioGroupVariants['size']
size?: RadioGroup['variants']['size']
/**
* @defaultValue 'list'
*/
variant?: RadioGroupVariants['variant']
variant?: RadioGroup['variants']['variant']
/**
* @defaultValue 'primary'
*/
color?: RadioGroupVariants['color']
color?: RadioGroup['variants']['color']
/**
* The orientation the radio buttons are laid out.
* @defaultValue 'vertical'
@@ -66,9 +59,9 @@ export interface RadioGroupProps<T extends RadioGroupItem = RadioGroupItem> exte
* Position of the indicator.
* @defaultValue 'start'
*/
indicator?: RadioGroupVariants['indicator']
indicator?: RadioGroup['variants']['indicator']
class?: any
ui?: Partial<typeof radioGroup.slots>
ui?: RadioGroup['slots']
}
export type RadioGroupEmits = RadioGroupRootEmits & {
@@ -88,8 +81,10 @@ export interface RadioGroupSlots<T extends RadioGroupItem = RadioGroupItem> {
import { computed, useId } from 'vue'
import { RadioGroupRoot, RadioGroupItem, RadioGroupIndicator, Label, useForwardPropsEmits } from 'reka-ui'
import { reactivePick } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { useFormField } from '../composables/useFormField'
import { get } from '../utils'
import { tv } from '../utils/tv'
const props = withDefaults(defineProps<RadioGroupProps<T>>(), {
valueKey: 'value',
@@ -100,12 +95,14 @@ const props = withDefaults(defineProps<RadioGroupProps<T>>(), {
const emits = defineEmits<RadioGroupEmits>()
const slots = defineSlots<RadioGroupSlots<T>>()
const appConfig = useAppConfig() as RadioGroup['AppConfig']
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'modelValue', 'defaultValue', 'orientation', 'loop', 'required'), emits)
const { emitFormChange, emitFormInput, color, name, size, id: _id, disabled, ariaAttrs } = useFormField<RadioGroupProps<T>>(props, { bind: false })
const id = _id.value ?? useId()
const ui = computed(() => radioGroup({
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.radioGroup || {}) })({
size: size.value,
color: color.value,
disabled: disabled.value,

View File

@@ -1,27 +1,12 @@
<script lang="ts">
import type { VariantProps } from 'tailwind-variants'
import type { SelectRootProps, SelectRootEmits, SelectContentProps, SelectContentEmits, SelectArrowProps } 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 { tv } from '../utils/tv'
import type { AvatarProps, ChipProps, InputProps } from '../types'
import type {
AcceptableValue,
ArrayOrNested,
GetItemKeys,
GetItemValue,
GetModelValue,
GetModelValueEmits,
NestedItem,
PartialString,
EmitsToProps
} from '../types/utils'
import type { AcceptableValue, ArrayOrNested, GetItemKeys, GetItemValue, GetModelValue, GetModelValueEmits, NestedItem, EmitsToProps, ComponentConfig } from '../types/utils'
const appConfigSelect = _appConfig as AppConfig & { ui: { select: Partial<typeof theme> } }
const select = tv({ extend: tv(theme), ...(appConfigSelect.ui?.select || {}) })
type Select = ComponentConfig<typeof theme, AppConfig, 'select'>
interface SelectItemBase {
label?: string
@@ -43,8 +28,6 @@ interface SelectItemBase {
}
export type SelectItem = SelectItemBase | AcceptableValue | boolean
type SelectVariants = VariantProps<typeof select>
export interface SelectProps<T extends ArrayOrNested<SelectItem> = ArrayOrNested<SelectItem>, VK extends GetItemKeys<T> = 'value', M extends boolean = false> extends Omit<SelectRootProps<T>, 'dir' | 'multiple' | 'modelValue' | 'defaultValue' | 'by'>, UseComponentIconsProps {
id?: string
/** The placeholder text when the select is empty. */
@@ -52,15 +35,15 @@ export interface SelectProps<T extends ArrayOrNested<SelectItem> = ArrayOrNested
/**
* @defaultValue 'primary'
*/
color?: SelectVariants['color']
color?: Select['variants']['color']
/**
* @defaultValue 'outline'
*/
variant?: SelectVariants['variant']
variant?: Select['variants']['variant']
/**
* @defaultValue 'md'
*/
size?: SelectVariants['size']
size?: Select['variants']['size']
/**
* The icon displayed to open the menu.
* @defaultValue appConfig.ui.icons.chevronDown
@@ -108,7 +91,7 @@ export interface SelectProps<T extends ArrayOrNested<SelectItem> = ArrayOrNested
/** Highlight the ring color like a focus state. */
highlight?: boolean
class?: any
ui?: PartialString<typeof select.slots>
ui?: Select['slots']
}
export type SelectEmits<A extends ArrayOrNested<SelectItem>, VK extends GetItemKeys<A> | undefined, M extends boolean> = Omit<SelectRootEmits, 'update:modelValue'> & {
@@ -125,9 +108,20 @@ export interface SelectSlots<
M extends boolean = false,
T extends NestedItem<A> = NestedItem<A>
> {
'leading'(props: { modelValue?: GetModelValue<A, VK, M>, open: boolean, ui: ReturnType<typeof select> }): any
'default'(props: { modelValue?: GetModelValue<A, VK, M>, open: boolean }): any
'trailing'(props: { modelValue?: GetModelValue<A, VK, M>, open: boolean, ui: ReturnType<typeof select> }): any
'leading'(props: {
modelValue?: GetModelValue<A, VK, M>
open: boolean
ui: { [K in keyof Required<Select['slots']>]: (props?: Record<string, any>) => string }
}): any
'default'(props: {
modelValue?: GetModelValue<A, VK, M>
open: boolean
}): any
'trailing'(props: {
modelValue?: GetModelValue<A, VK, M>
open: boolean
ui: { [K in keyof Required<Select['slots']>]: (props?: Record<string, any>) => string }
}): any
'item': SlotProps<T>
'item-leading': SlotProps<T>
'item-label': SlotProps<T>
@@ -145,6 +139,7 @@ import { useButtonGroup } from '../composables/useButtonGroup'
import { useComponentIcons } from '../composables/useComponentIcons'
import { useFormField } from '../composables/useFormField'
import { compare, get, isArrayOfArray } from '../utils'
import { tv } from '../utils/tv'
import UIcon from './Icon.vue'
import UAvatar from './Avatar.vue'
import UChip from './Chip.vue'
@@ -159,7 +154,8 @@ const props = withDefaults(defineProps<SelectProps<T, VK, M>>(), {
const emits = defineEmits<SelectEmits<T, VK, M>>()
const slots = defineSlots<SelectSlots<T, VK, M>>()
const appConfig = useAppConfig()
const appConfig = useAppConfig() as Select['AppConfig']
const rootProps = useForwardPropsEmits(reactivePick(props, 'open', 'defaultOpen', 'disabled', 'autocomplete', 'required', 'multiple'), emits)
const contentProps = toRef(() => defu(props.content, { side: 'bottom', sideOffset: 8, collisionPadding: 8, position: 'popper' }) as SelectContentProps)
const arrowProps = toRef(() => props.arrow as SelectArrowProps)
@@ -170,7 +166,7 @@ const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponen
const selectSize = computed(() => buttonGroupSize.value || formGroupSize.value)
const ui = computed(() => select({
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.select || {}) })({
color: color.value,
variant: props.variant,
size: selectSize?.value,

View File

@@ -1,27 +1,12 @@
<script lang="ts">
import type { VariantProps } from 'tailwind-variants'
import type { ComboboxRootProps, ComboboxRootEmits, ComboboxContentProps, ComboboxContentEmits, ComboboxArrowProps } 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 { tv } from '../utils/tv'
import type { AvatarProps, ChipProps, InputProps } from '../types'
import type {
AcceptableValue,
ArrayOrNested,
GetItemKeys,
GetItemValue,
GetModelValue,
GetModelValueEmits,
NestedItem,
PartialString,
EmitsToProps
} from '../types/utils'
import type { AcceptableValue, ArrayOrNested, GetItemKeys, GetItemValue, GetModelValue, GetModelValueEmits, NestedItem, EmitsToProps, ComponentConfig } from '../types/utils'
const appConfigSelectMenu = _appConfig as AppConfig & { ui: { selectMenu: Partial<typeof theme> } }
const selectMenu = tv({ extend: tv(theme), ...(appConfigSelectMenu.ui?.selectMenu || {}) })
type SelectMenu = ComponentConfig<typeof theme, AppConfig, 'selectMenu'>
interface _SelectMenuItem {
label?: string
@@ -42,8 +27,6 @@ interface _SelectMenuItem {
}
export type SelectMenuItem = _SelectMenuItem | AcceptableValue | boolean
type SelectMenuVariants = VariantProps<typeof selectMenu>
export interface SelectMenuProps<T extends ArrayOrNested<SelectMenuItem> = ArrayOrNested<SelectMenuItem>, VK extends GetItemKeys<T> | undefined = undefined, M extends boolean = false> extends Pick<ComboboxRootProps<T>, 'open' | 'defaultOpen' | 'disabled' | 'name' | 'resetSearchTermOnBlur' | 'resetSearchTermOnSelect' | 'highlightOnHover'>, UseComponentIconsProps {
id?: string
/** The placeholder text when the select is empty. */
@@ -58,15 +41,15 @@ export interface SelectMenuProps<T extends ArrayOrNested<SelectMenuItem> = Array
/**
* @defaultValue 'primary'
*/
color?: SelectMenuVariants['color']
color?: SelectMenu['variants']['color']
/**
* @defaultValue 'outline'
*/
variant?: SelectMenuVariants['variant']
variant?: SelectMenu['variants']['variant']
/**
* @defaultValue 'md'
*/
size?: SelectMenuVariants['size']
size?: SelectMenu['variants']['size']
required?: boolean
/**
* The icon displayed to open the menu.
@@ -131,7 +114,7 @@ export interface SelectMenuProps<T extends ArrayOrNested<SelectMenuItem> = Array
*/
ignoreFilter?: boolean
class?: any
ui?: PartialString<typeof selectMenu.slots>
ui?: SelectMenu['slots']
}
export type SelectMenuEmits<A extends ArrayOrNested<SelectMenuItem>, VK extends GetItemKeys<A> | undefined, M extends boolean> = Pick<ComboboxRootEmits, 'update:open'> & {
@@ -154,9 +137,20 @@ export interface SelectMenuSlots<
M extends boolean = false,
T extends NestedItem<A> = NestedItem<A>
> {
'leading'(props: { modelValue?: GetModelValue<A, VK, M>, open: boolean, ui: ReturnType<typeof selectMenu> }): any
'default'(props: { modelValue?: GetModelValue<A, VK, M>, open: boolean }): any
'trailing'(props: { modelValue?: GetModelValue<A, VK, M>, open: boolean, ui: ReturnType<typeof selectMenu> }): any
'leading'(props: {
modelValue?: GetModelValue<A, VK, M>
open: boolean
ui: { [K in keyof Required<SelectMenu['slots']>]: (props?: Record<string, any>) => string }
}): any
'default'(props: {
modelValue?: GetModelValue<A, VK, M>
open: boolean
}): any
'trailing'(props: {
modelValue?: GetModelValue<A, VK, M>
open: boolean
ui: { [K in keyof Required<SelectMenu['slots']>]: (props?: Record<string, any>) => string }
}): any
'empty'(props: { searchTerm?: string }): any
'item': SlotProps<T>
'item-leading': SlotProps<T>
@@ -177,6 +171,7 @@ import { useComponentIcons } from '../composables/useComponentIcons'
import { useFormField } from '../composables/useFormField'
import { useLocale } from '../composables/useLocale'
import { compare, get, isArrayOfArray } from '../utils'
import { tv } from '../utils/tv'
import UIcon from './Icon.vue'
import UAvatar from './Avatar.vue'
import UChip from './Chip.vue'
@@ -197,7 +192,7 @@ const slots = defineSlots<SelectMenuSlots<T, VK, M>>()
const searchTerm = defineModel<string>('searchTerm', { default: '' })
const { t } = useLocale()
const appConfig = useAppConfig()
const appConfig = useAppConfig() as SelectMenu['AppConfig']
const { contains } = useFilter({ sensitivity: 'base' })
const rootProps = useForwardPropsEmits(reactivePick(props, 'modelValue', 'defaultValue', 'open', 'defaultOpen', 'required', 'multiple', 'resetSearchTermOnBlur', 'resetSearchTermOnSelect', 'highlightOnHover'), emits)
@@ -213,7 +208,7 @@ const selectSize = computed(() => buttonGroupSize.value || formGroupSize.value)
const [DefineCreateItemTemplate, ReuseCreateItemTemplate] = createReusableTemplate()
const ui = computed(() => selectMenu({
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.selectMenu || {}) })({
color: color.value,
variant: props.variant,
size: selectSize?.value,

View File

@@ -1,17 +1,11 @@
<script lang="ts">
import type { VariantProps } from 'tailwind-variants'
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'
import { tv } from '../utils/tv'
import type { AvatarProps } from '../types'
import type { ComponentConfig } from '../types/utils'
const appConfigSeparator = _appConfig as AppConfig & { ui: { separator: Partial<typeof theme> } }
const separator = tv({ extend: tv(theme), ...(appConfigSeparator.ui?.separator || {}) })
type SeparatorVariants = VariantProps<typeof separator>
type Separator = ComponentConfig<typeof theme, AppConfig, 'separator'>
export interface SeparatorProps extends Pick<_SeparatorProps, 'decorative'> {
/**
@@ -31,22 +25,22 @@ export interface SeparatorProps extends Pick<_SeparatorProps, 'decorative'> {
/**
* @defaultValue 'neutral'
*/
color?: SeparatorVariants['color']
color?: Separator['variants']['color']
/**
* @defaultValue 'xs'
*/
size?: SeparatorVariants['size']
size?: Separator['variants']['size']
/**
* @defaultValue 'solid'
*/
type?: SeparatorVariants['type']
type?: Separator['variants']['type']
/**
* The orientation of the separator.
* @defaultValue 'horizontal'
*/
orientation?: _SeparatorProps['orientation']
class?: any
ui?: Partial<typeof separator.slots>
ui?: Separator['slots']
}
export interface SeparatorSlots {
@@ -58,6 +52,8 @@ export interface SeparatorSlots {
import { computed } from 'vue'
import { Separator, useForwardProps } from 'reka-ui'
import { reactivePick } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { tv } from '../utils/tv'
import UIcon from './Icon.vue'
import UAvatar from './Avatar.vue'
@@ -66,9 +62,11 @@ const props = withDefaults(defineProps<SeparatorProps>(), {
})
const slots = defineSlots<SeparatorSlots>()
const appConfig = useAppConfig() as Separator['AppConfig']
const rootProps = useForwardProps(reactivePick(props, 'as', 'decorative', 'orientation'))
const ui = computed(() => separator({
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.separator || {}) })({
color: props.color,
orientation: props.orientation,
size: props.size,

View File

@@ -1,12 +1,9 @@
<script lang="ts">
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/skeleton'
import { tv } from '../utils/tv'
import type { ComponentConfig } from '../types/utils'
const appConfigSkeleton = _appConfig as AppConfig & { ui: { skeleton: Partial<typeof theme> } }
const skeleton = tv({ extend: tv(theme), ...(appConfigSkeleton.ui?.skeleton || {}) })
type Skeleton = ComponentConfig<typeof theme, AppConfig, 'skeleton'>
export interface SkeletonProps {
/**
@@ -19,13 +16,20 @@ export interface SkeletonProps {
</script>
<script setup lang="ts">
import { computed } from 'vue'
import { Primitive } from 'reka-ui'
import { useAppConfig } from '#imports'
import { tv } from '../utils/tv'
const props = defineProps<SkeletonProps>()
const appConfig = useAppConfig() as Skeleton['AppConfig']
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.skeleton || {}) }))
</script>
<template>
<Primitive :as="as" :class="skeleton({ class: props.class })">
<Primitive :as="as" :class="ui({ class: props.class })">
<slot />
</Primitive>
</template>

View File

@@ -1,18 +1,11 @@
<script lang="ts">
import type { VariantProps } from 'tailwind-variants'
import type { DialogRootProps, DialogRootEmits, DialogContentProps, DialogContentEmits } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/slideover'
import { tv } from '../utils/tv'
import type { ButtonProps } from '../types'
import type { EmitsToProps } from '../types/utils'
import type { EmitsToProps, ComponentConfig } from '../types/utils'
const appConfigSlideover = _appConfig as AppConfig & { ui: { slideover: Partial<typeof theme> } }
const slideover = tv({ extend: tv(theme), ...(appConfigSlideover.ui?.slideover || {}) })
type SlideoverVariants = VariantProps<typeof slideover>
type Slideover = ComponentConfig<typeof theme, AppConfig, 'slideover'>
export interface SlideoverProps extends DialogRootProps {
title?: string
@@ -33,7 +26,7 @@ export interface SlideoverProps extends DialogRootProps {
* The side of the slideover.
* @defaultValue 'right'
*/
side?: SlideoverVariants['side']
side?: Slideover['variants']['side']
/**
* Render the slideover in a portal.
* @defaultValue true
@@ -57,7 +50,7 @@ export interface SlideoverProps extends DialogRootProps {
*/
dismissible?: boolean
class?: any
ui?: Partial<typeof slideover.slots>
ui?: Slideover['slots']
}
export interface SlideoverEmits extends DialogRootEmits {
@@ -70,7 +63,7 @@ export interface SlideoverSlots {
header(props?: {}): any
title(props?: {}): any
description(props?: {}): any
close(props: { ui: ReturnType<typeof slideover> }): any
close(props: { ui: { [K in keyof Required<Slideover['slots']>]: (props?: Record<string, any>) => string } }): any
body(props?: {}): any
footer(props?: {}): any
}
@@ -82,6 +75,7 @@ import { DialogRoot, DialogTrigger, DialogPortal, DialogOverlay, DialogContent,
import { reactivePick } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { useLocale } from '../composables/useLocale'
import { tv } from '../utils/tv'
import UButton from './Button.vue'
const props = withDefaults(defineProps<SlideoverProps>(), {
@@ -97,7 +91,7 @@ const emits = defineEmits<SlideoverEmits>()
const slots = defineSlots<SlideoverSlots>()
const { t } = useLocale()
const appConfig = useAppConfig()
const appConfig = useAppConfig() as Slideover['AppConfig']
const rootProps = useForwardPropsEmits(reactivePick(props, 'open', 'defaultOpen', 'modal'), emits)
const contentProps = toRef(() => props.content)
@@ -118,7 +112,7 @@ const contentEvents = computed(() => {
return events
})
const ui = computed(() => slideover({
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.slideover || {}) })({
transition: props.transition,
side: props.side
}))

View File

@@ -1,16 +1,10 @@
<script lang="ts">
import type { VariantProps } from 'tailwind-variants'
import type { SliderRootProps } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/slider'
import { tv } from '../utils/tv'
import type { ComponentConfig } from '../types/utils'
const appConfigSlider = _appConfig as AppConfig & { ui: { slider: Partial<typeof theme> } }
const slider = tv({ extend: tv(theme), ...(appConfigSlider.ui?.slider || {}) })
type SliderVariants = VariantProps<typeof slider>
type Slider = ComponentConfig<typeof theme, AppConfig, 'slider'>
export interface SliderProps extends Pick<SliderRootProps, 'name' | 'disabled' | 'inverted' | 'min' | 'max' | 'step' | 'minStepsBetweenThumbs'> {
/**
@@ -21,11 +15,11 @@ export interface SliderProps extends Pick<SliderRootProps, 'name' | 'disabled' |
/**
* @defaultValue 'md'
*/
size?: SliderVariants['size']
size?: Slider['variants']['size']
/**
* @defaultValue 'primary'
*/
color?: SliderVariants['color']
color?: Slider['variants']['color']
/**
* The orientation of the slider.
* @defaultValue 'horizontal'
@@ -34,7 +28,7 @@ export interface SliderProps extends Pick<SliderRootProps, 'name' | 'disabled' |
/** The value of the slider when initially rendered. Use when you do not need to control the state of the slider. */
defaultValue?: number | number[]
class?: any
ui?: Partial<typeof slider.slots>
ui?: Slider['slots']
}
export interface SliderEmits {
@@ -47,7 +41,9 @@ export interface SliderEmits {
import { computed } from 'vue'
import { SliderRoot, SliderRange, SliderTrack, SliderThumb, useForwardPropsEmits } from 'reka-ui'
import { reactivePick } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { useFormField } from '../composables/useFormField'
import { tv } from '../utils/tv'
const props = withDefaults(defineProps<SliderProps>(), {
min: 0,
@@ -59,6 +55,8 @@ const emits = defineEmits<SliderEmits>()
const modelValue = defineModel<number | number[]>()
const appConfig = useAppConfig() as Slider['AppConfig']
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'orientation', 'min', 'max', 'step', 'minStepsBetweenThumbs', 'inverted'), emits)
const { id, emitFormChange, emitFormInput, size, color, name, disabled, ariaAttrs } = useFormField<SliderProps>(props)
@@ -84,7 +82,7 @@ const sliderValue = computed({
const thumbsCount = computed(() => sliderValue.value?.length ?? 1)
const ui = computed(() => slider({
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.slider || {}) })({
disabled: disabled.value,
size: size.value,
color: color.value,

View File

@@ -1,18 +1,11 @@
<!-- eslint-disable vue/block-tag-newline -->
<script lang="ts">
import type { VariantProps } from 'tailwind-variants'
import type { StepperRootProps, StepperRootEmits } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/stepper'
import { tv } from '../utils/tv'
import type { DynamicSlots } from '../types/utils'
import type { DynamicSlots, ComponentConfig } from '../types/utils'
const appConfigStepper = _appConfig as AppConfig & { ui: { stepper: Partial<typeof theme> } }
const stepper = tv({ extend: tv(theme), ...(appConfigStepper.ui?.stepper || {}) })
type StepperVariants = VariantProps<typeof stepper>
type Stepper = ComponentConfig<typeof theme, AppConfig, 'stepper'>
export interface StepperItem {
slot?: string
@@ -38,23 +31,23 @@ export interface StepperProps<T extends StepperItem = StepperItem> extends Pick<
/**
* @defaultValue 'md'
*/
size?: StepperVariants['size']
size?: Stepper['variants']['size']
/**
* @defaultValue 'primary'
*/
color?: StepperVariants['color']
color?: Stepper['variants']['color']
/**
* The orientation of the stepper.
* @defaultValue 'horizontal'
*/
orientation?: StepperVariants['orientation']
orientation?: Stepper['variants']['orientation']
/**
* The value of the step that should be active when initially rendered. Use when you do not need to control the state of the steps.
*/
defaultValue?: string | number
disabled?: boolean
ui?: Partial<typeof stepper.slots>
class?: any
ui?: Stepper['slots']
}
export type StepperEmits<T extends StepperItem = StepperItem> = Omit<StepperRootEmits, 'update:modelValue'> & {
@@ -77,6 +70,8 @@ export type StepperSlots<T extends StepperItem = StepperItem> = {
import { computed } from 'vue'
import { StepperRoot, StepperItem, StepperTrigger, StepperIndicator, StepperSeparator, StepperTitle, StepperDescription, useForwardProps } from 'reka-ui'
import { reactivePick } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { tv } from '../utils/tv'
import UIcon from './Icon.vue'
const props = withDefaults(defineProps<StepperProps<T>>(), {
@@ -88,9 +83,11 @@ const slots = defineSlots<StepperSlots<T>>()
const modelValue = defineModel<string | number>()
const appConfig = useAppConfig() as Stepper['AppConfig']
const rootProps = useForwardProps(reactivePick(props, 'as', 'orientation', 'linear'))
const ui = computed(() => stepper({
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.stepper || {}) })({
orientation: props.orientation,
size: props.size,
color: props.color

View File

@@ -1,17 +1,10 @@
<script lang="ts">
import type { VariantProps } from 'tailwind-variants'
import type { SwitchRootProps } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/switch'
import { tv } from '../utils/tv'
import type { PartialString } from '../types/utils'
import type { ComponentConfig } from '../types/utils'
const appConfigSwitch = _appConfig as AppConfig & { ui: { switch: Partial<typeof theme> } }
const switchTv = tv({ extend: tv(theme), ...(appConfigSwitch.ui?.switch || {}) })
type SwitchVariants = VariantProps<typeof switchTv>
type Switch = ComponentConfig<typeof theme, AppConfig, 'switch'>
export interface SwitchProps extends Pick<SwitchRootProps, 'disabled' | 'id' | 'name' | 'required' | 'value' | 'defaultValue'> {
/**
@@ -22,11 +15,11 @@ export interface SwitchProps extends Pick<SwitchRootProps, 'disabled' | 'id' | '
/**
* @defaultValue 'primary'
*/
color?: SwitchVariants['color']
color?: Switch['variants']['color']
/**
* @defaultValue 'md'
*/
size?: SwitchVariants['size']
size?: Switch['variants']['size']
/** When `true`, the loading icon will be displayed. */
loading?: boolean
/**
@@ -48,7 +41,7 @@ export interface SwitchProps extends Pick<SwitchRootProps, 'disabled' | 'id' | '
label?: string
description?: string
class?: any
ui?: PartialString<typeof switchTv.slots>
ui?: Switch['slots']
}
export type SwitchEmits = {
@@ -67,6 +60,7 @@ import { Primitive, SwitchRoot, SwitchThumb, useForwardProps, Label } from 'reka
import { reactivePick } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { useFormField } from '../composables/useFormField'
import { tv } from '../utils/tv'
import UIcon from './Icon.vue'
defineOptions({ inheritAttrs: false })
@@ -77,13 +71,14 @@ const emits = defineEmits<SwitchEmits>()
const modelValue = defineModel<boolean>({ default: undefined })
const appConfig = useAppConfig()
const appConfig = useAppConfig() as Switch['AppConfig']
const rootProps = useForwardProps(reactivePick(props, 'required', 'value', 'defaultValue'))
const { id: _id, emitFormChange, emitFormInput, size, color, name, disabled, ariaAttrs } = useFormField<SwitchProps>(props)
const id = _id.value ?? useId()
const ui = computed(() => switchTv({
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.switch || {}) })({
size: size.value,
color: color.value,
required: props.required,

View File

@@ -1,7 +1,6 @@
<!-- eslint-disable vue/block-tag-newline -->
<script lang="ts">
import type { Ref } from 'vue'
import type { VariantProps } from 'tailwind-variants'
import type { AppConfig } from '@nuxt/schema'
import type { RowData } from '@tanstack/table-core'
import type {
@@ -36,9 +35,8 @@ import type {
VisibilityOptions,
VisibilityState
} from '@tanstack/vue-table'
import _appConfig from '#build/app.config'
import theme from '#build/ui/table'
import { tv } from '../utils/tv'
import type { ComponentConfig } from '../types/utils'
declare module '@tanstack/table-core' {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -50,11 +48,7 @@ declare module '@tanstack/table-core' {
}
}
const appConfigTable = _appConfig as AppConfig & { ui: { table: Partial<typeof theme> } }
const table = tv({ extend: tv(theme), ...(appConfigTable.ui?.table || {}) })
type TableVariants = VariantProps<typeof table>
type Table = ComponentConfig<typeof theme, AppConfig, 'table'>
export type TableRow<T> = Row<T>
export type TableData = RowData
@@ -90,11 +84,11 @@ export interface TableProps<T extends TableData> extends TableOptions<T> {
/**
* @defaultValue 'primary'
*/
loadingColor?: TableVariants['loadingColor']
loadingColor?: Table['variants']['loadingColor']
/**
* @defaultValue 'carousel'
*/
loadingAnimation?: TableVariants['loadingAnimation']
loadingAnimation?: Table['variants']['loadingAnimation']
/**
* @link [API Docs](https://tanstack.com/table/v8/docs/api/features/global-filtering#table-options)
* @link [Guide](https://tanstack.com/table/v8/docs/guide/global-filtering)
@@ -157,7 +151,7 @@ export interface TableProps<T extends TableData> extends TableOptions<T> {
facetedOptions?: FacetedOptions<T>
onSelect?: (row: TableRow<T>, e?: Event) => void
class?: any
ui?: Partial<typeof table.slots>
ui?: Table['slots']
}
type DynamicHeaderSlots<T, K = keyof T> = Record<string, (props: HeaderContext<T, unknown>) => any> & Record<`${K extends string ? K : never}-header`, (props: HeaderContext<T, unknown>) => any>
@@ -178,17 +172,20 @@ import { Primitive } from 'reka-ui'
import { upperFirst } from 'scule'
import { FlexRender, getCoreRowModel, getFilteredRowModel, getSortedRowModel, getExpandedRowModel, useVueTable } from '@tanstack/vue-table'
import { reactiveOmit } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { useLocale } from '../composables/useLocale'
import { tv } from '../utils/tv'
const props = defineProps<TableProps<T>>()
const slots = defineSlots<TableSlots<T>>()
const { t } = useLocale()
const appConfig = useAppConfig() as Table['AppConfig']
const data = computed(() => props.data ?? [])
const columns = computed<TableColumn<T>[]>(() => props.columns ?? Object.keys(data.value[0] ?? {}).map((accessorKey: string) => ({ accessorKey, header: upperFirst(accessorKey) })))
const ui = computed(() => table({
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.table || {}) })({
sticky: props.sticky,
loading: props.loading,
loadingColor: props.loadingColor,

View File

@@ -1,17 +1,12 @@
<!-- eslint-disable vue/block-tag-newline -->
<script lang="ts">
import type { VariantProps } from 'tailwind-variants'
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'
import { tv } from '../utils/tv'
import type { AvatarProps } from '../types'
import type { DynamicSlots, PartialString } from '../types/utils'
import type { DynamicSlots, ComponentConfig } from '../types/utils'
const appConfigTabs = _appConfig as AppConfig & { ui: { tabs: Partial<typeof theme> } }
const tabs = tv({ extend: tv(theme), ...(appConfigTabs.ui?.tabs || {}) })
type Tabs = ComponentConfig<typeof theme, AppConfig, 'tabs'>
export interface TabsItem {
label?: string
@@ -28,8 +23,6 @@ export interface TabsItem {
[key: string]: any
}
type TabsVariants = VariantProps<typeof tabs>
export interface TabsProps<T extends TabsItem = TabsItem> extends Pick<TabsRootProps<string | number>, 'defaultValue' | 'modelValue' | 'activationMode' | 'unmountOnHide'> {
/**
* The element or component this component should render as.
@@ -40,15 +33,15 @@ export interface TabsProps<T extends TabsItem = TabsItem> extends Pick<TabsRootP
/**
* @defaultValue 'primary'
*/
color?: TabsVariants['color']
color?: Tabs['variants']['color']
/**
* @defaultValue 'pill'
*/
variant?: TabsVariants['variant']
variant?: Tabs['variants']['variant']
/**
* @defaultValue 'md'
*/
size?: TabsVariants['size']
size?: Tabs['variants']['size']
/**
* The orientation of the tabs.
* @defaultValue 'horizontal'
@@ -65,7 +58,7 @@ export interface TabsProps<T extends TabsItem = TabsItem> extends Pick<TabsRootP
*/
labelKey?: string
class?: any
ui?: PartialString<typeof tabs.slots>
ui?: Tabs['slots']
}
export interface TabsEmits extends TabsRootEmits<string | number> {}
@@ -87,7 +80,9 @@ export type TabsSlots<T extends TabsItem = TabsItem> = {
import { computed } from 'vue'
import { TabsRoot, TabsList, TabsIndicator, TabsTrigger, TabsContent, useForwardPropsEmits } from 'reka-ui'
import { reactivePick } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { get } from '../utils'
import { tv } from '../utils/tv'
import UIcon from './Icon.vue'
import UAvatar from './Avatar.vue'
@@ -101,9 +96,11 @@ const props = withDefaults(defineProps<TabsProps<T>>(), {
const emits = defineEmits<TabsEmits>()
const slots = defineSlots<TabsSlots<T>>()
const appConfig = useAppConfig() as Tabs['AppConfig']
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'modelValue', 'defaultValue', 'orientation', 'activationMode', 'unmountOnHide'), emits)
const ui = computed(() => tabs({
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.tabs || {}) })({
color: props.color,
variant: props.variant,
size: props.size,

View File

@@ -1,18 +1,11 @@
<script lang="ts">
import type { VariantProps } from 'tailwind-variants'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/textarea'
import type { UseComponentIconsProps } from '../composables/useComponentIcons'
import { tv } from '../utils/tv'
import type { AvatarProps } from '../types'
import type { PartialString } from '../types/utils'
import type { ComponentConfig } from '../types/utils'
const appConfigTextarea = _appConfig as AppConfig & { ui: { textarea: Partial<typeof theme> } }
const textarea = tv({ extend: tv(theme), ...(appConfigTextarea.ui?.textarea || {}) })
type TextareaVariants = VariantProps<typeof textarea>
type Textarea = ComponentConfig<typeof theme, AppConfig, 'textarea'>
export interface TextareaProps extends UseComponentIconsProps {
/**
@@ -27,15 +20,15 @@ export interface TextareaProps extends UseComponentIconsProps {
/**
* @defaultValue 'primary'
*/
color?: TextareaVariants['color']
color?: Textarea['variants']['color']
/**
* @defaultValue 'outline'
*/
variant?: TextareaVariants['variant']
variant?: Textarea['variants']['variant']
/**
* @defaultValue 'md'
*/
size?: TextareaVariants['size']
size?: Textarea['variants']['size']
required?: boolean
autofocus?: boolean
autofocusDelay?: number
@@ -47,7 +40,7 @@ export interface TextareaProps extends UseComponentIconsProps {
maxrows?: number
/** Highlight the ring color like a focus state. */
highlight?: boolean
ui?: PartialString<typeof textarea.slots>
ui?: Textarea['slots']
}
export interface TextareaEmits {
@@ -66,9 +59,11 @@ export interface TextareaSlots {
<script setup lang="ts">
import { ref, computed, onMounted, nextTick, watch } from 'vue'
import { Primitive } from 'reka-ui'
import { useAppConfig } from '#imports'
import { useComponentIcons } from '../composables/useComponentIcons'
import { useFormField } from '../composables/useFormField'
import { looseToNumber } from '../utils'
import { tv } from '../utils/tv'
defineOptions({ inheritAttrs: false })
@@ -83,10 +78,11 @@ const emits = defineEmits<TextareaEmits>()
const [modelValue, modelModifiers] = defineModel<string | number | null>()
const appConfig = useAppConfig() as Textarea['AppConfig']
const { emitFormFocus, emitFormBlur, emitFormInput, emitFormChange, size, color, id, name, highlight, disabled, ariaAttrs } = useFormField<TextareaProps>(props, { deferInputValidation: true })
const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponentIcons(props)
const ui = computed(() => textarea({
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.textarea || {}) })({
color: color.value,
variant: props.variant,
size: size?.value,

View File

@@ -1,18 +1,11 @@
<script lang="ts">
import type { VariantProps } from 'tailwind-variants'
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'
import { tv } from '../utils/tv'
import type { AvatarProps, ButtonProps } from '../types'
import type { StringOrVNode } from '../types/utils'
import type { StringOrVNode, ComponentConfig } from '../types/utils'
const appConfigToast = _appConfig as AppConfig & { ui: { toast: Partial<typeof theme> } }
const toast = tv({ extend: tv(theme), ...(appConfigToast.ui?.toast || {}) })
type ToastVariants = VariantProps<typeof toast>
type Toast = ComponentConfig<typeof theme, AppConfig, 'toast'>
export interface ToastProps extends Pick<ToastRootProps, 'defaultOpen' | 'open' | 'type' | 'duration'> {
/**
@@ -30,12 +23,12 @@ export interface ToastProps extends Pick<ToastRootProps, 'defaultOpen' | 'open'
/**
* @defaultValue 'primary'
*/
color?: ToastVariants['color']
color?: Toast['variants']['color']
/**
* The orientation between the content and the actions.
* @defaultValue 'vertical'
*/
orientation?: ToastVariants['orientation']
orientation?: Toast['variants']['orientation']
/**
* Display a list of actions:
* - under the title and description when orientation is `vertical`
@@ -56,7 +49,7 @@ export interface ToastProps extends Pick<ToastRootProps, 'defaultOpen' | 'open'
*/
closeIcon?: string
class?: any
ui?: Partial<typeof toast.slots>
ui?: Toast['slots']
}
export interface ToastEmits extends ToastRootEmits {}
@@ -66,7 +59,7 @@ export interface ToastSlots {
title(props?: {}): any
description(props?: {}): any
actions(props?: {}): any
close(props: { ui: ReturnType<typeof toast> }): any
close(props: { ui: { [K in keyof Required<Toast['slots']>]: (props?: Record<string, any>) => string } }): any
}
</script>
@@ -76,6 +69,7 @@ import { ToastRoot, ToastTitle, ToastDescription, ToastAction, ToastClose, useFo
import { reactivePick } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { useLocale } from '../composables/useLocale'
import { tv } from '../utils/tv'
import UIcon from './Icon.vue'
import UAvatar from './Avatar.vue'
import UButton from './Button.vue'
@@ -88,11 +82,11 @@ const emits = defineEmits<ToastEmits>()
const slots = defineSlots<ToastSlots>()
const { t } = useLocale()
const appConfig = useAppConfig()
const appConfig = useAppConfig() as Toast['AppConfig']
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'defaultOpen', 'open', 'duration', 'type'), emits)
const ui = computed(() => toast({
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.toast || {}) })({
color: props.color,
orientation: props.orientation,
title: !!props.title || !!slots.title

View File

@@ -1,23 +1,17 @@
<script lang="ts">
import type { VariantProps } from 'tailwind-variants'
import type { ToastProviderProps } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/toaster'
import { tv } from '../utils/tv'
import type { ComponentConfig } from '../types/utils'
const appConfigToaster = _appConfig as AppConfig & { ui: { toaster: Partial<typeof theme> } }
const toaster = tv({ extend: tv(theme), ...(appConfigToaster.ui?.toaster || {}) })
type ToasterVariants = VariantProps<typeof toaster>
type Toaster = ComponentConfig<typeof theme, AppConfig, 'toaster'>
export interface ToasterProps extends Omit<ToastProviderProps, 'swipeDirection'> {
/**
* The position on the screen to display the toasts.
* @defaultValue 'bottom-right'
*/
position?: ToasterVariants['position']
position?: Toaster['variants']['position']
/**
* Expand the toasts to show multiple toasts at once.
* @defaultValue true
@@ -29,7 +23,7 @@ export interface ToasterProps extends Omit<ToastProviderProps, 'swipeDirection'>
*/
portal?: boolean
class?: any
ui?: Partial<typeof toaster.slots>
ui?: Toaster['slots']
}
export interface ToasterSlots {
@@ -45,8 +39,10 @@ export default {
import { ref, computed } from 'vue'
import { ToastProvider, ToastViewport, ToastPortal, useForwardProps } from 'reka-ui'
import { reactivePick } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { useToast } from '../composables/useToast'
import { omit } from '../utils'
import { tv } from '../utils/tv'
import UToast from './Toast.vue'
const props = withDefaults(defineProps<ToasterProps>(), {
@@ -56,9 +52,10 @@ const props = withDefaults(defineProps<ToasterProps>(), {
})
defineSlots<ToasterSlots>()
const providerProps = useForwardProps(reactivePick(props, 'duration', 'label', 'swipeThreshold'))
const { toasts, remove } = useToast()
const appConfig = useAppConfig() as Toaster['AppConfig']
const providerProps = useForwardProps(reactivePick(props, 'duration', 'label', 'swipeThreshold'))
const swipeDirection = computed(() => {
switch (props.position) {
@@ -76,7 +73,7 @@ const swipeDirection = computed(() => {
return 'right'
})
const ui = computed(() => toaster({
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.toaster || {}) })({
position: props.position,
swipeDirection: swipeDirection.value
}))

View File

@@ -1,15 +1,11 @@
<script lang="ts">
import type { TooltipRootProps, TooltipRootEmits, TooltipContentProps, TooltipContentEmits, TooltipArrowProps } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/tooltip'
import { tv } from '../utils/tv'
import type { KbdProps } from '../types'
import type { EmitsToProps } from '../types/utils'
import type { EmitsToProps, ComponentConfig } from '../types/utils'
const appConfigTooltip = _appConfig as AppConfig & { ui: { tooltip: Partial<typeof theme> } }
const tooltip = tv({ extend: tv(theme), ...(appConfigTooltip.ui?.tooltip || {}) })
type Tooltip = ComponentConfig<typeof theme, AppConfig, 'tooltip'>
export interface TooltipProps extends TooltipRootProps {
/** The text content of the tooltip. */
@@ -32,7 +28,7 @@ export interface TooltipProps extends TooltipRootProps {
*/
portal?: boolean
class?: any
ui?: Partial<typeof tooltip.slots>
ui?: Tooltip['slots']
}
export interface TooltipEmits extends TooltipRootEmits {}
@@ -48,6 +44,8 @@ import { computed, toRef } from 'vue'
import { defu } from 'defu'
import { TooltipRoot, TooltipTrigger, TooltipPortal, TooltipContent, TooltipArrow, useForwardPropsEmits } from 'reka-ui'
import { reactivePick } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { tv } from '../utils/tv'
import UKbd from './Kbd.vue'
const props = withDefaults(defineProps<TooltipProps>(), {
@@ -56,12 +54,14 @@ const props = withDefaults(defineProps<TooltipProps>(), {
const emits = defineEmits<TooltipEmits>()
const slots = defineSlots<TooltipSlots>()
const appConfig = useAppConfig() as Tooltip['AppConfig']
const rootProps = useForwardPropsEmits(reactivePick(props, 'defaultOpen', 'open', 'delayDuration', 'disableHoverableContent', 'disableClosingTrigger', 'disabled', 'ignoreNonKeyboardFocus'), emits)
const contentProps = toRef(() => defu(props.content, { side: 'bottom', sideOffset: 8, collisionPadding: 8 }) as TooltipContentProps)
const arrowProps = toRef(() => props.arrow as TooltipArrowProps)
// eslint-disable-next-line vue/no-dupe-keys
const ui = computed(() => tooltip({
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.tooltip || {}) })({
side: contentProps.value.side
}))
</script>

View File

@@ -1,25 +1,11 @@
<!-- eslint-disable vue/block-tag-newline -->
<script lang="ts">
import type { VariantProps } from 'tailwind-variants'
import type { TreeRootProps, TreeRootEmits } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/tree'
import { tv } from '../utils/tv'
import type {
DynamicSlots,
GetItemKeys,
GetModelValue,
GetModelValueEmits,
NestedItem,
PartialString
} from '../types/utils'
import type { DynamicSlots, GetItemKeys, GetModelValue, GetModelValueEmits, NestedItem, ComponentConfig } from '../types/utils'
const appConfig = _appConfig as AppConfig & { ui: { tree: Partial<typeof theme> } }
const tree = tv({ extend: tv(theme), ...(appConfig.ui?.tree || {}) })
type TreeVariants = VariantProps<typeof tree>
type Tree = ComponentConfig<typeof theme, AppConfig, 'tree'>
export type TreeItem = {
/**
@@ -50,11 +36,11 @@ export interface TreeProps<T extends TreeItem[] = TreeItem[], VK extends GetItem
/**
* @defaultValue 'primary'
*/
color?: TreeVariants['color']
color?: Tree['variants']['color']
/**
* @defaultValue 'md'
*/
size?: TreeVariants['size']
size?: Tree['variants']['size']
/**
* The key used to get the value from the item.
* @defaultValue 'value'
@@ -91,7 +77,7 @@ export interface TreeProps<T extends TreeItem[] = TreeItem[], VK extends GetItem
/** Whether multiple options can be selected or not. */
multiple?: M & boolean
class?: any
ui?: PartialString<typeof tree.slots>
ui?: Tree['slots']
}
export type TreeEmits<A extends TreeItem[], VK extends GetItemKeys<A> | undefined, M extends boolean> = Omit<TreeRootEmits, 'update:modelValue'> & GetModelValueEmits<A, VK, M>
@@ -114,7 +100,9 @@ export type TreeSlots<
import { computed } from 'vue'
import { TreeRoot, TreeItem, useForwardPropsEmits } from 'reka-ui'
import { reactivePick, createReusableTemplate } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { get } from '../utils'
import { tv } from '../utils/tv'
import UIcon from './Icon.vue'
const props = withDefaults(defineProps<TreeProps<T, VK, M>>(), {
@@ -124,11 +112,13 @@ const props = withDefaults(defineProps<TreeProps<T, VK, M>>(), {
const emits = defineEmits<TreeEmits<T, VK, M>>()
const slots = defineSlots<TreeSlots<T>>()
const appConfig = useAppConfig() as Tree['AppConfig']
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'modelValue', 'defaultValue', 'items', 'multiple', 'expanded', 'disabled', 'propagateSelect'), emits)
const [DefineTreeTemplate, ReuseTreeTemplate] = createReusableTemplate<{ items?: TreeItem[], level: number }, TreeSlots<T>>()
const ui = computed(() => tree({
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.tree || {}) })({
color: props.color,
size: props.size
}))

View File

@@ -33,10 +33,6 @@ export type GetObjectField<MaybeObject, Key extends string> = MaybeObject extend
? MaybeObject[Key]
: never
export type PartialString<T> = {
[K in keyof T]?: string
}
export type AcceptableValue = Exclude<_AcceptableValue, Record<string, any>>
export type ArrayOrNested<T> = T[] | T[][]
export type NestedItem<T> = T extends Array<infer I> ? NestedItem<I> : T
@@ -91,3 +87,49 @@ export type EmitsToProps<T> = {
? (...args: Args) => void
: never
}
/**
* Utility type to flatten intersection types for better IDE hover information.
* @template T The type to flatten.
*/
type Id<T> = {} & { [P in keyof T]: T[P] }
type ComponentVariants<T extends { variants?: Record<string, Record<string, any>> }> = {
[K in keyof T['variants']]: keyof T['variants'][K]
}
type ComponentSlots<T extends { slots?: Record<string, any> }> = Id<{
[K in keyof T['slots']]?: string
}>
type GetComponentAppConfig<A, U extends string, K extends string> =
A extends Record<U, Record<K, any>> ? A[U][K] : {}
type ComponentAppConfig<
T,
A extends Record<string, any>,
K extends string,
U extends string = 'ui' | 'uiPro' | 'uiPro.prose'
> = A & (
U extends 'uiPro.prose'
? { uiPro?: { prose?: { [k in K]?: Partial<T> } } }
: { [key in Exclude<U, 'uiPro.prose'>]?: { [k in K]?: Partial<T> } }
)
/**
* Defines the configuration shape expected for a component.
* @template T The component's theme imported from `#build/ui/*`.
* @template A The base AppConfig type from `@nuxt/schema`.
* @template K The key identifying the component (e.g., 'badge').
* @template U The top-level key in AppConfig ('ui' or 'uiPro').
*/
export type ComponentConfig<
T extends Record<string, any>,
A extends Record<string, any>,
K extends string,
U extends 'ui' | 'uiPro' | 'uiPro.prose' = 'ui'
> = {
AppConfig: ComponentAppConfig<T, A, K, U>
variants: ComponentVariants<T & GetComponentAppConfig<A, U, K>>
slots: ComponentSlots<T>
}

View File

@@ -1,7 +1,7 @@
import { createTV, type defaultConfig } from 'tailwind-variants'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import appConfig from '#build/app.config'
const appConfigTv = _appConfig as AppConfig & { ui: { tv: typeof defaultConfig } }
const appConfigTv = appConfig as AppConfig & { ui: { tv: typeof defaultConfig } }
export const tv = /* @__PURE__ */ createTV(appConfigTv.ui?.tv)

View File

@@ -1,10 +1,11 @@
<script lang="ts">
import type { ButtonHTMLAttributes } from 'vue'
import { tv } from 'tailwind-variants'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import type { RouterLinkProps, RouteLocationRaw } from 'vue-router'
import theme from '#build/ui/link'
import type { ComponentConfig } from '../../types/utils'
type Link = ComponentConfig<typeof theme, AppConfig, 'link'>
interface NuxtLinkProps extends Omit<RouterLinkProps, 'to'> {
/**
@@ -52,10 +53,6 @@ interface NuxtLinkProps extends Omit<RouterLinkProps, 'to'> {
noPrefetch?: boolean
}
const appConfigLink = _appConfig as AppConfig & { ui: { link: Partial<typeof theme> } }
const link = tv({ extend: tv(theme), ...(appConfigLink.ui?.link || {}) })
export interface LinkProps extends NuxtLinkProps {
/**
* The element or component this component should render as when not a link.
@@ -91,12 +88,14 @@ export interface LinkSlots {
<script setup lang="ts">
import { computed, getCurrentInstance } from 'vue'
import { defu } from 'defu'
import { isEqual, diff } from 'ohash/utils'
import { useForwardProps } from 'reka-ui'
import { reactiveOmit } from '@vueuse/core'
import { hasProtocol } from 'ufo'
import { useRoute } from '#imports'
import { useRoute, useAppConfig } from '#imports'
import { RouterLink } from 'vue-router'
import { tv } from '../../utils/tv'
defineOptions({ inheritAttrs: false })
@@ -126,16 +125,20 @@ const route = computed(() => {
}
})
const appConfig = useAppConfig() as Link['AppConfig']
const routerLinkProps = useForwardProps(reactiveOmit(props, 'as', 'type', 'disabled', 'active', 'exact', 'exactQuery', 'exactHash', 'activeClass', 'inactiveClass', 'to', 'raw', 'class'))
const ui = computed(() => tv({
extend: link,
variants: {
active: {
true: props.activeClass,
false: props.inactiveClass
extend: tv(theme),
...defu({
variants: {
active: {
true: props.activeClass,
false: props.inactiveClass
}
}
}
}, appConfig.ui?.link || {})
}))
function isPartiallyEqual(item1: any, item2: any) {