fix(components): ui prop override with class (#136)

This commit is contained in:
Benjamin Canac
2024-07-03 14:35:58 +02:00
committed by GitHub
parent 5a22c62b2a
commit 235556d3e0
50 changed files with 383 additions and 375 deletions

View File

@@ -1,6 +1,6 @@
const useContentExamplesCodeState = () => useState('content-examples-code', () => ({}))
export async function fetchContentExampleCode (name?: string) {
export async function fetchContentExampleCode(name?: string) {
if (!name) return
const state = useContentExamplesCodeState()
@@ -8,7 +8,9 @@ export async function fetchContentExampleCode (name?: string) {
await state.value[name]
return state.value[name]
}
if (state.value[name]) { return state.value[name] }
if (state.value[name]) {
return state.value[name]
}
// add to nitro prerender
if (import.meta.server) {

View File

@@ -1,31 +0,0 @@
import { createShikiHighlighter } from '@nuxtjs/mdc/runtime/highlighter/shiki'
import MaterialTheme from 'shiki/themes/material-theme.mjs'
import MaterialThemeLighter from 'shiki/themes/material-theme-lighter.mjs'
import MaterialThemePalenight from 'shiki/themes/material-theme-palenight.mjs'
import HtmlLang from 'shiki/langs/html.mjs'
import MdcLang from 'shiki/langs/mdc.mjs'
import VueLang from 'shiki/langs/vue.mjs'
import YamlLang from 'shiki/langs/yaml.mjs'
import PostcssLang from 'shiki/langs/postcss.mjs'
let highlighter
export const useShikiHighlighter = () => {
if (!highlighter) {
highlighter = createShikiHighlighter({
bundledThemes: {
'material-theme': MaterialTheme,
'material-theme-lighter': MaterialThemeLighter,
'material-theme-palenight': MaterialThemePalenight
},
bundledLangs: {
html: HtmlLang,
mdc: MdcLang,
vue: VueLang,
yml: YamlLang,
postcss: PostcssLang
}
})
}
return highlighter
}

View File

@@ -2,10 +2,10 @@ import type { Options } from 'prettier'
import PrettierWorker from '@/workers/prettier.js?worker&inline'
export interface SimplePrettier {
format: (source: string, options?: Options) => Promise<string>;
format: (source: string, options?: Options) => Promise<string>
}
function createPrettierWorkerApi (worker: Worker): SimplePrettier {
function createPrettierWorkerApi(worker: Worker): SimplePrettier {
let counter = 0
const handlers = {}
@@ -17,6 +17,7 @@ function createPrettierWorkerApi (worker: Worker): SimplePrettier {
}
const [resolve, reject] = handlers[uid]
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete handlers[uid]
if (error) {
@@ -26,7 +27,7 @@ function createPrettierWorkerApi (worker: Worker): SimplePrettier {
}
})
function postMessage<T> (message) {
function postMessage<T>(message) {
const uid = ++counter
return new Promise<T>((resolve, reject) => {
handlers[uid] = [resolve, reject]
@@ -35,19 +36,19 @@ function createPrettierWorkerApi (worker: Worker): SimplePrettier {
}
return {
format (source: string, options?: Options) {
format(source: string, options?: Options) {
return postMessage({ type: 'format', source, options })
}
}
}
export default defineNuxtPlugin({
async setup () {
async setup() {
let prettier: SimplePrettier
if (import.meta.server) {
const prettierModule = await import('prettier')
prettier = {
format (source, options = {
format(source, options = {
parser: 'markdown'
}) {
return prettierModule.format(source, options)

View File

@@ -10,14 +10,14 @@ self.onmessage = async function (event) {
})
}
function handleMessage (message) {
function handleMessage(message) {
switch (message.type) {
case 'format':
return handleFormatMessage(message)
}
}
async function handleFormatMessage (message) {
async function handleFormatMessage(message) {
const { options, source } = message
const formatted = await prettier.format(source, {
parser: 'markdown',

View File

@@ -1,5 +1,5 @@
import { existsSync, readFileSync } from 'fs'
import fsp from 'fs/promises'
import { existsSync, readFileSync } from 'node:fs'
import fsp from 'node:fs/promises'
import { dirname, join } from 'pathe'
import { defineNuxtModule, addTemplate, addServerHandler, createResolver } from '@nuxt/kit'
@@ -7,20 +7,20 @@ export default defineNuxtModule({
meta: {
name: 'content-examples-code'
},
async setup (_options, nuxt) {
async setup(_options, nuxt) {
const resolver = createResolver(import.meta.url)
let _configResolved: any
let components: Record<string, any>
const outputPath = join(nuxt.options.buildDir, 'content-examples-code')
async function stubOutput () {
async function stubOutput() {
if (existsSync(outputPath + '.mjs')) {
return
}
await updateOutput('export default {}')
}
async function fetchComponent (component: string | any) {
async function fetchComponent(component: string | any) {
if (typeof component === 'string') {
if (components[component]) {
component = components[component]
@@ -51,7 +51,7 @@ export default defineNuxtModule({
const getVirtualModuleContent = () =>
`export default ${getStringifiedComponents()}`
async function updateOutput (content?: string) {
async function updateOutput(content?: string) {
const path = outputPath + '.mjs'
if (!existsSync(dirname(path))) {
await fsp.mkdir(dirname(path), { recursive: true })
@@ -62,13 +62,13 @@ export default defineNuxtModule({
await fsp.writeFile(path, content || getVirtualModuleContent(), 'utf-8')
}
async function fetchComponents () {
async function fetchComponents() {
await Promise.all(Object.keys(components).map(fetchComponent))
}
nuxt.hook('components:extend', async (_components) => {
components = _components
.filter((v) => v.shortPath.includes('components/content/examples/'))
.filter(v => v.shortPath.includes('components/content/examples/'))
.reduce((acc, component) => {
acc[component.pascalName] = component
return acc
@@ -87,17 +87,17 @@ export default defineNuxtModule({
vite.config.plugins.push({
name: 'content-examples-code',
enforce: 'post',
async buildStart () {
async buildStart() {
if (_configResolved?.build.ssr) {
return
}
await fetchComponents()
await updateOutput()
},
configResolved (config) {
configResolved(config) {
_configResolved = config
},
async handleHotUpdate ({ file }) {
async handleHotUpdate({ file }) {
if (
Object.entries(components).some(
([, comp]: any) => comp.filePath === file

View File

@@ -1,6 +1,6 @@
import { defineEventHandler, createError, appendHeader } from 'h3'
import { pascalCase } from 'scule'
// @ts-expect-error
// @ts-expect-error - no types available
import components from '#content-examples-code/nitro'
export default defineEventHandler((event) => {

View File

@@ -1,6 +1,6 @@
import { Octokit } from '@octokit/rest'
function isUserABot (user) {
function isUserABot(user) {
return user?.login?.endsWith('-bot') || user?.login?.endsWith('[bot]')
}

View File

@@ -65,7 +65,9 @@ const appConfig = useAppConfig()
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'collapsible', 'defaultValue', 'disabled', 'modelValue', 'type'), emits)
const contentProps = toRef(() => props.content)
const ui = computed(() => tv({ extend: accordion, slots: props.ui })({ disabled: props.disabled }))
const ui = computed(() => accordion({
disabled: props.disabled
}))
</script>
<template>
@@ -76,25 +78,25 @@ const ui = computed(() => tv({ extend: accordion, slots: props.ui })({ disabled:
:key="index"
:value="item.value || String(index)"
:disabled="item.disabled"
:class="ui.item()"
:class="ui.item({ class: props.ui?.item })"
>
<AccordionHeader :class="ui.header()">
<AccordionTrigger :class="ui.trigger({ disabled: item.disabled })">
<AccordionHeader :class="ui.header({ class: props.ui?.header })">
<AccordionTrigger :class="ui.trigger({ class: props.ui?.trigger, disabled: item.disabled })">
<slot name="leading" :item="item" :index="index" :open="open">
<UIcon v-if="item.icon" :name="item.icon" :class="ui.leadingIcon()" />
<UIcon v-if="item.icon" :name="item.icon" :class="ui.leadingIcon({ class: props.ui?.leadingIcon })" />
</slot>
<span v-if="item.label || !!slots.default" :class="ui.label()">
<span v-if="item.label || !!slots.default" :class="ui.label({ class: props.ui?.label })">
<slot :item="item" :index="index" :open="open">{{ item.label }}</slot>
</span>
<slot name="trailing" :item="item" :index="index" :open="open">
<UIcon :name="item.trailingIcon || trailingIcon || appConfig.ui.icons.chevronDown" :class="ui.trailingIcon()" />
<UIcon :name="item.trailingIcon || trailingIcon || appConfig.ui.icons.chevronDown" :class="ui.trailingIcon({ class: props.ui?.trailingIcon })" />
</slot>
</AccordionTrigger>
</AccordionHeader>
<AccordionContent v-if="item.content || !!slots.content || (item.slot && !!slots[item.slot])" v-bind="contentProps" :class="ui.content()">
<AccordionContent v-if="item.content || !!slots.content || (item.slot && !!slots[item.slot])" v-bind="contentProps" :class="ui.content({ class: props.ui?.content })">
<slot :name="item.slot || 'content'" :item="item" :index="index" :open="open">
{{ item.content }}
</slot>

View File

@@ -54,7 +54,7 @@ export interface AlertSlots {
title(props?: {}): any
description(props?: {}): any
actions(props?: {}): any
close(props: { class: string }): any
close(props: { ui: any }): any
}
</script>
@@ -72,7 +72,7 @@ const appConfig = useAppConfig()
const multiline = computed(() => !!props.title && !!props.description)
const ui = computed(() => tv({ extend: alert, slots: props.ui })({
const ui = computed(() => alert({
color: props.color,
variant: props.variant
}))
@@ -81,39 +81,39 @@ const ui = computed(() => tv({ extend: alert, slots: props.ui })({
<template>
<Primitive :as="as" :class="ui.root({ class: props.class, multiline })">
<slot name="leading">
<UAvatar v-if="avatar" size="2xl" v-bind="avatar" :class="ui.avatar()" />
<UIcon v-else-if="icon" :name="icon" :class="ui.icon()" />
<UAvatar v-if="avatar" size="2xl" v-bind="avatar" :class="ui.avatar({ class: props.ui?.avatar })" />
<UIcon v-else-if="icon" :name="icon" :class="ui.icon({ class: props.ui?.icon })" />
</slot>
<div :class="ui.wrapper()">
<div v-if="title || !!slots.title" :class="ui.title()">
<div :class="ui.wrapper({ class: props.ui?.wrapper })">
<div v-if="title || !!slots.title" :class="ui.title({ class: props.ui?.title })">
<slot name="title">
{{ title }}
</slot>
</div>
<template v-if="description || !!slots.description">
<div :class="ui.description()">
<div :class="ui.description({ class: props.ui?.description })">
<slot name="description">
{{ description }}
</slot>
</div>
</template>
<div v-if="multiline && actions?.length" :class="ui.actions({ multiline: true })">
<div v-if="multiline && actions?.length" :class="ui.actions({ class: props.ui?.actions, multiline: true })">
<slot name="actions">
<UButton v-for="(action, index) in actions" :key="index" size="xs" v-bind="action" />
</slot>
</div>
</div>
<div v-if="(!multiline && actions?.length) || close" :class="ui.actions({ multiline: false })">
<div v-if="(!multiline && actions?.length) || close" :class="ui.actions({ class: props.ui?.actions, multiline: false })">
<template v-if="!multiline">
<slot name="actions">
<UButton v-for="(action, index) in actions" :key="index" size="xs" v-bind="action" />
</slot>
</template>
<slot name="close" :class="ui.close()">
<slot name="close" :ui="ui">
<UButton
v-if="close"
:icon="closeIcon || appConfig.ui.icons.close"
@@ -122,7 +122,7 @@ const ui = computed(() => tv({ extend: alert, slots: props.ui })({
variant="link"
aria-label="Close"
v-bind="typeof close === 'object' ? close : undefined"
:class="ui.close()"
:class="ui.close({ class: props.ui?.close })"
@click="emits('close')"
/>
</slot>

View File

@@ -38,16 +38,19 @@ const fallback = computed(() => props.text || (props.alt || '').split(' ').map(w
const { size } = useAvatarGroup(props)
const ui = computed(() => tv({ extend: avatar, slots: props.ui })({ size: size.value }))
// eslint-disable-next-line vue/no-dupe-keys
const ui = computed(() => avatar({
size: size.value
}))
</script>
<template>
<AvatarRoot :class="ui.root({ class: props.class })">
<AvatarImage v-if="src" :as="as" :src="src" :alt="alt" :class="ui.image()" />
<AvatarImage v-if="src" :as="as" :src="src" :alt="alt" :class="ui.image({ class: props.ui?.image })" />
<AvatarFallback as-child v-bind="fallbackProps">
<UIcon v-if="icon" :name="icon" :class="ui.icon()" />
<span v-else :class="ui.fallback()">{{ fallback }}</span>
<UIcon v-if="icon" :name="icon" :class="ui.icon({ class: props.ui?.icon })" />
<span v-else :class="ui.fallback({ class: props.ui?.fallback })">{{ fallback }}</span>
</AvatarFallback>
</AvatarRoot>
</template>

View File

@@ -39,7 +39,7 @@ import { avatarGroupInjectionKey } from '#imports'
const props = defineProps<AvatarGroupProps>()
const slots = defineSlots<AvatarGroupSlots>()
const ui = computed(() => tv({ extend: avatarGroup, slots: props.ui })({
const ui = computed(() => avatarGroup({
size: props.size
}))
@@ -74,7 +74,7 @@ provide(avatarGroupInjectionKey, computed(() => ({
<template>
<Primitive :as="as" :class="ui.root({ class: props.class })">
<UAvatar v-if="hiddenCount > 0" :text="`+${hiddenCount}`" :class="ui.base()" />
<component :is="avatar" v-for="(avatar, count) in visibleAvatars" :key="count" :class="ui.base()" />
<UAvatar v-if="hiddenCount > 0" :text="`+${hiddenCount}`" :class="ui.base({ class: props.ui?.base })" />
<component :is="avatar" v-for="(avatar, count) in visibleAvatars" :key="count" :class="ui.base({ class: props.ui?.base })" />
</Primitive>
</template>

View File

@@ -45,7 +45,6 @@ export type BreadcrumbSlots<T extends { slot?: string }> = {
</script>
<script setup lang="ts" generic="T extends BreadcrumbItem">
import { computed } from 'vue'
import { Primitive } from 'radix-vue'
import { useAppConfig } from '#imports'
import { ULink, UIcon, UAvatar } from '#components'
@@ -56,23 +55,24 @@ const slots = defineSlots<BreadcrumbSlots<T>>()
const appConfig = useAppConfig()
const ui = computed(() => tv({ extend: breadcrumb, slots: props.ui })())
// eslint-disable-next-line vue/no-dupe-keys
const ui = breadcrumb()
</script>
<template>
<Primitive :as="as" aria-label="breadcrumb" :class="ui.root({ class: props.class })">
<ol :class="ui.list()">
<ol :class="ui.list({ class: props.ui?.list })">
<template v-for="(item, index) in items" :key="index">
<li :class="ui.item()">
<li :class="ui.item({ class: props.ui?.item })">
<ULink v-slot="{ active, ...slotProps }" v-bind="pickLinkProps(item)" custom>
<ULinkBase v-bind="slotProps" as="span" :aria-current="active && (index === items!.length - 1) ? 'page' : undefined" :class="ui.link({ active: index === items!.length - 1, disabled: !!item.disabled, to: !!item.to })">
<ULinkBase v-bind="slotProps" as="span" :aria-current="active && (index === items!.length - 1) ? 'page' : undefined" :class="ui.link({ class: props.ui?.link, active: index === items!.length - 1, disabled: !!item.disabled, to: !!item.to })">
<slot :name="item.slot || 'item'" :item="item" :index="index">
<slot :name="item.slot ? `${item.slot}-leading`: 'item-leading'" :item="item" :active="index === items!.length - 1" :index="index">
<UAvatar v-if="item.avatar" size="2xs" v-bind="item.avatar" :class="ui.linkLeadingAvatar({ active: index === items!.length - 1 })" />
<UIcon v-else-if="item.icon" :name="item.icon" :class="ui.linkLeadingIcon({ active: index === items!.length - 1 })" />
<UAvatar v-if="item.avatar" size="2xs" v-bind="item.avatar" :class="ui.linkLeadingAvatar({ class: props.ui?.linkLeadingAvatar, active: index === items!.length - 1 })" />
<UIcon v-else-if="item.icon" :name="item.icon" :class="ui.linkLeadingIcon({ class: props.ui?.linkLeadingIcon, active: index === items!.length - 1 })" />
</slot>
<span v-if="item.label || !!slots[item.slot ? `${item.slot}-label`: 'item-label']" :class="ui.linkLabel()">
<span v-if="item.label || !!slots[item.slot ? `${item.slot}-label`: 'item-label']" :class="ui.linkLabel({ class: props.ui?.linkLabel })">
<slot :name="item.slot ? `${item.slot}-label`: 'item-label'" :item="item" :active="index === items!.length - 1" :index="index">
{{ item.label }}
</slot>
@@ -84,9 +84,9 @@ const ui = computed(() => tv({ extend: breadcrumb, slots: props.ui })())
</ULink>
</li>
<li v-if="index < items!.length - 1" role="presentation" :class="ui.separator()">
<li v-if="index < items!.length - 1" role="presentation" :class="ui.separator({ class: props.ui?.separator })">
<slot name="separator">
<UIcon :name="separatorIcon || appConfig.ui.icons.chevronRight" :class="ui.separatorIcon()" />
<UIcon :name="separatorIcon || appConfig.ui.icons.chevronRight" :class="ui.separatorIcon({ class: props.ui?.separatorIcon })" />
</slot>
</li>
</template>

View File

@@ -48,7 +48,7 @@ const linkProps = useForwardProps(pickLinkProps(props))
const { orientation, size: buttonSize } = useButtonGroup<ButtonProps>(props)
const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponentIcons(props)
const ui = computed(() => tv({ extend: button, slots: props.ui })({
const ui = computed(() => button({
color: props.color,
variant: props.variant,
size: buttonSize.value,
@@ -64,17 +64,17 @@ const ui = computed(() => tv({ extend: button, slots: props.ui })({
<template>
<ULink :type="type" :disabled="disabled || loading" :class="ui.base({ class: props.class })" v-bind="linkProps" raw>
<slot name="leading">
<UIcon v-if="isLeading && leadingIconName" :name="leadingIconName" :class="ui.leadingIcon()" />
<UIcon v-if="isLeading && leadingIconName" :name="leadingIconName" :class="ui.leadingIcon({ class: props.ui?.leadingIcon })" />
</slot>
<span v-if="label || !!slots.default" :class="ui.label()">
<span v-if="label || !!slots.default" :class="ui.label({ class: props.ui?.label })">
<slot>
{{ label }}
</slot>
</span>
<slot name="trailing">
<UIcon v-if="isTrailing && trailingIconName" :name="trailingIconName" :class="ui.trailingIcon()" />
<UIcon v-if="isTrailing && trailingIconName" :name="trailingIconName" :class="ui.trailingIcon({ class: props.ui?.trailingIcon })" />
</slot>
</ULink>
</template>

View File

@@ -26,26 +26,26 @@ export interface CardSlots {
</script>
<script setup lang="ts">
import { computed } from 'vue'
import { Primitive } from 'radix-vue'
const props = defineProps<CardProps>()
const slots = defineSlots<CardSlots>()
const ui = computed(() => tv({ extend: card, slots: props.ui })())
// eslint-disable-next-line vue/no-dupe-keys
const ui = card()
</script>
<template>
<Primitive :as="as" :class="ui.root({ class: props.class })">
<div v-if="!!slots.header" :class="ui.header()">
<div v-if="!!slots.header" :class="ui.header({ class: props.ui?.header })">
<slot name="header" />
</div>
<div v-if="!!slots.default" :class="ui.body()">
<div v-if="!!slots.default" :class="ui.body({ class: props.ui?.body })">
<slot />
</div>
<div v-if="!!slots.footer" :class="ui.footer()">
<div v-if="!!slots.footer" :class="ui.footer({ class: props.ui?.footer })">
<slot name="footer" />
</div>
</Primitive>

View File

@@ -74,7 +74,7 @@ const checked = computed({
}
})
const ui = computed(() => tv({ extend: checkbox, slots: props.ui })({
const ui = computed(() => checkbox({
size: size.value,
color: color.value,
required: props.required,
@@ -93,7 +93,7 @@ function onUpdate(value: any) {
<template>
<div :class="ui.root({ class: props.class })">
<div :class="ui.container()">
<div :class="ui.container({ class: props.ui?.container })">
<CheckboxRoot
:id="id"
v-model:checked="checked"
@@ -101,23 +101,23 @@ function onUpdate(value: any) {
v-bind="rootProps"
:name="name"
:disabled="disabled"
:class="ui.base()"
:class="ui.base({ class: props.ui?.base })"
@update:checked="onUpdate"
>
<CheckboxIndicator as-child>
<UIcon v-if="indeterminate" :name="indeterminateIcon || appConfig.ui.icons.minus" :class="ui.icon()" />
<UIcon v-else :name="icon || appConfig.ui.icons.check" :class="ui.icon()" />
<UIcon v-if="indeterminate" :name="indeterminateIcon || appConfig.ui.icons.minus" :class="ui.icon({ class: props.ui?.icon })" />
<UIcon v-else :name="icon || appConfig.ui.icons.check" :class="ui.icon({ class: props.ui?.icon })" />
</CheckboxIndicator>
</CheckboxRoot>
</div>
<div v-if="(label || !!slots.label) || (description || !!slots.description)" :class="ui.wrapper()">
<Label v-if="label || !!slots.label" :for="id" :class="ui.label()">
<div v-if="(label || !!slots.label) || (description || !!slots.description)" :class="ui.wrapper({ class: props.ui?.wrapper })">
<Label v-if="label || !!slots.label" :for="id" :class="ui.label({ class: props.ui?.label })">
<slot name="label" :label="label">
{{ label }}
</slot>
</Label>
<p v-if="description || !!slots.description" :class="ui.description()">
<p v-if="description || !!slots.description" :class="ui.description({ class: props.ui?.description })">
<slot name="description" :description="description">
{{ description }}
</slot>

View File

@@ -51,7 +51,7 @@ const show = defineModel<boolean>('show', { default: true })
const { size } = useAvatarGroup(props)
const ui = computed(() => tv({ extend: chip, slots: props.ui })({
const ui = computed(() => chip({
color: props.color,
size: size.value,
position: props.position,
@@ -64,7 +64,7 @@ const ui = computed(() => tv({ extend: chip, slots: props.ui })({
<Primitive :as="as" :class="ui.root({ class: props.class })">
<slot />
<span v-if="show" :class="ui.base()">
<span v-if="show" :class="ui.base({ class: props.ui?.base })">
<slot name="content">
{{ text }}
</slot>

View File

@@ -28,7 +28,6 @@ export interface CollapsibleSlots {
</script>
<script setup lang="ts">
import { computed } from 'vue'
import { CollapsibleRoot, CollapsibleTrigger, CollapsibleContent, useForwardPropsEmits } from 'radix-vue'
import { reactivePick } from '@vueuse/core'
@@ -38,7 +37,8 @@ const slots = defineSlots<CollapsibleSlots>()
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'defaultOpen', 'open', 'disabled'), emits)
const ui = computed(() => tv({ extend: collapsible, slots: props.ui })())
// eslint-disable-next-line vue/no-dupe-keys
const ui = collapsible()
</script>
<template>
@@ -47,7 +47,7 @@ const ui = computed(() => tv({ extend: collapsible, slots: props.ui })())
<slot :open="open" />
</CollapsibleTrigger>
<CollapsibleContent :class="ui.content()">
<CollapsibleContent :class="ui.content({ class: props.ui?.content })">
<slot name="content" />
</CollapsibleContent>
</CollapsibleRoot>

View File

@@ -84,7 +84,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?: {}): any
'close'(props: { ui: any }): any
'item': SlotProps<T>
'item-leading': SlotProps<T>
'item-label': SlotProps<T>
@@ -116,7 +116,8 @@ const appConfig = useAppConfig()
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'disabled', 'multiple', 'modelValue', 'defaultValue'), emits)
const inputProps = useForwardProps(reactivePick(props, 'loading', 'loadingIcon', 'placeholder'))
const ui = computed(() => tv({ extend: commandPalette, slots: props.ui })())
// eslint-disable-next-line vue/no-dupe-keys
const ui = commandPalette()
const fuse = computed(() => defu({}, props.fuse, {
fuseOptions: {
@@ -187,10 +188,10 @@ const groups = computed(() => {
size="lg"
v-bind="inputProps"
:icon="icon || appConfig.ui.icons.search"
:class="ui.input()"
:class="ui.input({ class: props.ui?.input })"
>
<template v-if="close || !!slots.close" #trailing>
<slot name="close" :class="ui.close()">
<slot name="close" :ui="ui">
<UButton
v-if="close"
:icon="closeIcon || appConfig.ui.icons.close"
@@ -199,7 +200,7 @@ const groups = computed(() => {
variant="ghost"
aria-label="Close"
v-bind="typeof close === 'object' ? close : undefined"
:class="ui.close()"
:class="ui.close({ class: props.ui?.close })"
@click="emits('update:open', false)"
/>
</slot>
@@ -208,16 +209,16 @@ const groups = computed(() => {
</ComboboxInput>
<ComboboxPortal disabled>
<ComboboxContent :class="ui.content()" :dismissable="false">
<ComboboxEmpty :class="ui.empty()">
<ComboboxContent :class="ui.content({ class: props.ui?.content })" :dismissable="false">
<ComboboxEmpty :class="ui.empty({ class: props.ui?.empty })">
<slot name="empty" :search-term="searchTerm">
{{ searchTerm ? `No results for ${searchTerm}` : 'No results' }}
</slot>
</ComboboxEmpty>
<ComboboxViewport :class="ui.viewport()">
<ComboboxGroup v-for="(group, groupIndex) in groups" :key="`group-${groupIndex}`" :class="ui.group()">
<ComboboxLabel v-if="group.label" :class="ui.label()">
<ComboboxViewport :class="ui.viewport({ class: props.ui?.viewport })">
<ComboboxGroup v-for="(group, groupIndex) in groups" :key="`group-${groupIndex}`" :class="ui.group({ class: props.ui?.group })">
<ComboboxLabel v-if="group.label" :class="ui.label({ class: props.ui?.label })">
{{ group.label }}
</ComboboxLabel>
@@ -226,43 +227,43 @@ const groups = computed(() => {
:key="`group-${groupIndex}-${index}`"
:value="omit(item, ['matches' as any, 'group' as any, 'select', 'labelHtml', 'suffixHtml'])"
:disabled="item.disabled"
:class="ui.item()"
:class="ui.item({ class: props.ui?.item })"
@select="item.select"
>
<slot :name="item.slot || group.slot || 'item'" :item="item" :index="index">
<slot :name="item.slot ? `${item.slot}-leading` : group.slot ? `${group.slot}-leading` : `item-leading`" :item="item" :index="index">
<UAvatar v-if="item.avatar" size="2xs" v-bind="item.avatar" :class="ui.itemLeadingAvatar()" />
<UIcon v-else-if="item.icon" :name="item.icon" :class="ui.itemLeadingIcon()" />
<UAvatar v-if="item.avatar" size="2xs" v-bind="item.avatar" :class="ui.itemLeadingAvatar({ class: props.ui?.itemLeadingAvatar })" />
<UIcon v-else-if="item.icon" :name="item.icon" :class="ui.itemLeadingIcon({ class: props.ui?.itemLeadingIcon })" />
<UChip
v-else-if="item.chip"
size="md"
inset
standalone
v-bind="item.chip"
:class="ui.itemLeadingChip()"
:class="ui.itemLeadingChip({ class: props.ui?.itemLeadingChip })"
/>
</slot>
<span v-if="item.label || !!slots[item.slot ? `${item.slot}-label` : group.slot ? `${group.slot}-label` : `item-label`]" :class="ui.itemLabel()">
<span v-if="item.label || !!slots[item.slot ? `${item.slot}-label` : group.slot ? `${group.slot}-label` : `item-label`]" :class="ui.itemLabel({ class: props.ui?.itemLabel })">
<slot :name="item.slot ? `${item.slot}-label` : group.slot ? `${group.slot}-label` : `item-label`" :item="item" :index="index">
<span v-if="item.prefix" :class="ui.itemLabelPrefix()">{{ item.prefix }}</span>
<span v-if="item.prefix" :class="ui.itemLabelPrefix({ class: props.ui?.itemLabelPrefix })">{{ item.prefix }}</span>
<span :class="ui.itemLabelBase()" v-html="item.labelHtml || item.label" />
<span :class="ui.itemLabelBase({ class: props.ui?.itemLabelBase })" v-html="item.labelHtml || item.label" />
<span :class="ui.itemLabelSuffix()" v-html="item.suffixHtml || item.suffix" />
<span :class="ui.itemLabelSuffix({ class: props.ui?.itemLabelSuffix })" v-html="item.suffixHtml || item.suffix" />
</slot>
</span>
<span :class="ui.itemTrailing()">
<span :class="ui.itemTrailing({ class: props.ui?.itemTrailing })">
<slot :name="item.slot ? `${item.slot}-trailing` : group.slot ? `${group.slot}-trailing` : `item-trailing`" :item="item" :index="index">
<span v-if="item.kbds?.length" :class="ui.itemTrailingKbds()">
<span v-if="item.kbds?.length" :class="ui.itemTrailingKbds({ class: props.ui?.itemTrailingKbds })">
<UKbd v-for="(kbd, kbdIndex) in item.kbds" :key="kbdIndex" size="md" v-bind="typeof kbd === 'string' ? { value: kbd } : kbd" />
</span>
<UIcon v-else-if="group.highlightedIcon" :name="group.highlightedIcon" :class="ui.itemTrailingHighlightedIcon()" />
<UIcon v-else-if="group.highlightedIcon" :name="group.highlightedIcon" :class="ui.itemTrailingHighlightedIcon({ class: props.ui?.itemTrailingHighlightedIcon })" />
</slot>
<ComboboxItemIndicator as-child>
<UIcon :name="selectedIcon || appConfig.ui.icons.check" :class="ui.itemTrailingIcon()" />
<UIcon :name="selectedIcon || appConfig.ui.icons.check" :class="ui.itemTrailingIcon({ class: props.ui?.itemTrailingIcon })" />
</ComboboxItemIndicator>
</span>
</slot>

View File

@@ -76,7 +76,7 @@ const rootProps = useForwardPropsEmits(reactivePick(props, 'modal'), emits)
const contentProps = toRef(() => props.content as ContextMenuContentProps)
const proxySlots = omit(slots, ['default']) as Record<string, ContextMenuSlots<T>[string]>
const ui = computed(() => tv({ extend: contextMenu, slots: props.ui })({
const ui = computed(() => contextMenu({
size: props.size
}))
</script>
@@ -87,7 +87,14 @@ const ui = computed(() => tv({ extend: contextMenu, slots: props.ui })({
<slot />
</ContextMenuTrigger>
<UContextMenuContent :class="ui.content({ class: props.class })" :ui="ui" v-bind="contentProps" :items="items" :portal="portal">
<UContextMenuContent
:class="ui.content({ class: props.class })"
:ui="ui"
:ui-override="props.ui"
v-bind="contentProps"
:items="items"
:portal="portal"
>
<template v-for="(_, name) in proxySlots" #[name]="slotData: any">
<slot :name="name" v-bind="slotData" />
</template>

View File

@@ -12,6 +12,7 @@ interface ContextMenuContentProps<T> extends Omit<RadixContextMenuContentProps,
sub?: boolean
class?: any
ui: typeof contextMenu
uiOverride?: any
}
interface ContextMenuContentEmits extends RadixContextMenuContentEmits {}
@@ -44,22 +45,22 @@ const groups = computed(() => props.items?.length ? (Array.isArray(props.items[0
<DefineItemTemplate v-slot="{ item, active, index }">
<slot :name="item.slot || 'item'" :item="(item as T)" :index="index">
<slot :name="item.slot ? `${item.slot}-leading`: 'item-leading'" :item="(item as T)" :active="active" :index="index">
<UAvatar v-if="item.avatar" :size="(ui.itemLeadingAvatarSize() as AvatarProps['size'])" v-bind="item.avatar" :class="ui.itemLeadingAvatar({ active })" />
<UIcon v-else-if="item.icon" :name="item.icon" :class="ui.itemLeadingIcon({ active })" />
<UAvatar v-if="item.avatar" :size="(ui.itemLeadingAvatarSize() as AvatarProps['size'])" v-bind="item.avatar" :class="ui.itemLeadingAvatar({ class: uiOverride?.itemLeadingAvatar, active })" />
<UIcon v-else-if="item.icon" :name="item.icon" :class="ui.itemLeadingIcon({ class: uiOverride?.itemLeadingIcon, active })" />
</slot>
<span v-if="item.label || !!slots[item.slot ? `${item.slot}-label`: 'item-label']" :class="ui.itemLabel({ active })">
<span v-if="item.label || !!slots[item.slot ? `${item.slot}-label`: 'item-label']" :class="ui.itemLabel({ class: uiOverride?.itemLabel, active })">
<slot :name="item.slot ? `${item.slot}-label`: 'item-label'" :item="(item as T)" :active="active" :index="index">
{{ item.label }}
</slot>
<UIcon v-if="item.target === '_blank'" :name="appConfig.ui.icons.external" :class="ui.itemLabelExternalIcon({ active })" />
<UIcon v-if="item.target === '_blank'" :name="appConfig.ui.icons.external" :class="ui.itemLabelExternalIcon({ class: uiOverride?.itemLabelExternalIcon, active })" />
</span>
<span v-if="item.children?.length || item.kbds?.length || !!slots[item.slot ? `${item.slot}-trailing`: 'item-trailing']" :class="ui.itemTrailing()">
<span v-if="item.children?.length || item.kbds?.length || !!slots[item.slot ? `${item.slot}-trailing`: 'item-trailing']" :class="ui.itemTrailing({ class: uiOverride?.itemTrailing })">
<slot :name="item.slot ? `${item.slot}-trailing`: 'item-trailing'" :item="(item as T)" :active="active" :index="index">
<UIcon v-if="item.children?.length" :name="appConfig.ui.icons.chevronRight" :class="ui.itemTrailingIcon({ active })" />
<span v-else-if="item.kbds?.length" :class="ui.itemTrailingKbds()">
<UIcon v-if="item.children?.length" :name="appConfig.ui.icons.chevronRight" :class="ui.itemTrailingIcon({ class: uiOverride?.itemTrailingIcon, active })" />
<span v-else-if="item.kbds?.length" :class="ui.itemTrailingKbds({ class: uiOverride?.itemTrailingKbds })">
<UKbd v-for="(kbd, kbdIndex) in item.kbds" :key="kbdIndex" :size="(ui.itemTrailingKbdsSize() as KbdProps['size'])" v-bind="typeof kbd === 'string' ? { value: kbd } : kbd" />
</span>
</slot>
@@ -69,12 +70,12 @@ const groups = computed(() => props.items?.length ? (Array.isArray(props.items[0
<ContextMenu.Portal :disabled="!portal">
<component :is="sub ? ContextMenu.SubContent : ContextMenu.Content" :class="props.class" v-bind="contentProps">
<ContextMenu.Group v-for="(group, groupIndex) in groups" :key="`group-${groupIndex}`" :class="ui.group()">
<ContextMenu.Group v-for="(group, groupIndex) in groups" :key="`group-${groupIndex}`" :class="ui.group({ class: uiOverride?.group })">
<template v-for="(item, index) in group" :key="`group-${groupIndex}-${index}`">
<ContextMenu.Label v-if="item.type === 'label'" :class="ui.label()">
<ContextMenu.Label v-if="item.type === 'label'" :class="ui.label({ class: uiOverride?.label })">
<ReuseItemTemplate :item="item" :index="index" />
</ContextMenu.Label>
<ContextMenu.Separator v-else-if="item.type === 'separator'" :class="ui.separator()" />
<ContextMenu.Separator v-else-if="item.type === 'separator'" :class="ui.separator({ class: uiOverride?.separator })" />
<ContextMenu.Sub v-else-if="item?.children?.length">
<ContextMenu.SubTrigger
as="button"
@@ -83,7 +84,7 @@ const groups = computed(() => props.items?.length ? (Array.isArray(props.items[0
:open="item.open"
:default-open="item.defaultOpen"
:text-value="item.label"
:class="ui.item()"
:class="ui.item({ class: uiOverride?.item })"
>
<ReuseItemTemplate :item="item" :index="index" />
</ContextMenu.SubTrigger>
@@ -92,6 +93,7 @@ const groups = computed(() => props.items?.length ? (Array.isArray(props.items[0
sub
:class="props.class"
:ui="ui"
:ui-override="uiOverride"
:portal="portal"
:items="item.children"
:align-offset="-4"
@@ -104,7 +106,7 @@ const groups = computed(() => props.items?.length ? (Array.isArray(props.items[0
</ContextMenu.Sub>
<ContextMenu.Item v-else as-child :disabled="item.disabled" :text-value="item.label" @select="item.select">
<ULink v-slot="{ active, ...slotProps }" v-bind="pickLinkProps(item as Omit<ContextMenuItem, 'type'>)" custom>
<ULinkBase v-bind="slotProps" :class="ui.item({ active })">
<ULinkBase v-bind="slotProps" :class="ui.item({ class: uiOverride?.item, active })">
<ReuseItemTemplate :item="item" :active="active" :index="index" />
</ULinkBase>
</ULink>

View File

@@ -45,7 +45,7 @@ export interface DrawerSlots {
</script>
<script setup lang="ts">
import { computed, toRef } from 'vue'
import { toRef } from 'vue'
import { useForwardPropsEmits } from 'radix-vue'
import { DrawerRoot, DrawerTrigger, DrawerPortal, DrawerOverlay, DrawerContent, DrawerTitle, DrawerDescription } from 'vaul-vue'
import { reactivePick } from '@vueuse/core'
@@ -60,7 +60,8 @@ const slots = defineSlots<DrawerSlots>()
const rootProps = useForwardPropsEmits(reactivePick(props, 'activeSnapPoint', 'closeThreshold', 'defaultOpen', 'dismissible', 'fadeFromIndex', 'fixed', 'modal', 'nested', 'open', 'scrollLockTimeout', 'shouldScaleBackground', 'snapPoints'), emits)
const contentProps = toRef(() => props.content)
const ui = computed(() => tv({ extend: drawer, slots: props.ui })())
// eslint-disable-next-line vue/no-dupe-keys
const ui = drawer()
</script>
<template>
@@ -70,24 +71,24 @@ const ui = computed(() => tv({ extend: drawer, slots: props.ui })())
</DrawerTrigger>
<DrawerPortal :disabled="!portal">
<DrawerOverlay v-if="overlay" :class="ui.overlay()" />
<DrawerOverlay v-if="overlay" :class="ui.overlay({ class: props.ui?.overlay })" />
<DrawerContent :class="ui.content({ class: props.class })" v-bind="contentProps">
<slot name="handle">
<div :class="ui.handle()" />
<div :class="ui.handle({ class: props.ui?.handle })" />
</slot>
<slot name="content">
<div :class="ui.container()">
<div v-if="!!slots.header || (title || !!slots.title) || (description || !!slots.description)" :class="ui.header()">
<div :class="ui.container({ class: props.ui?.container })">
<div v-if="!!slots.header || (title || !!slots.title) || (description || !!slots.description)" :class="ui.header({ class: props.ui?.header })">
<slot name="header">
<DrawerTitle v-if="title || !!slots.title" :class="ui.title()">
<DrawerTitle v-if="title || !!slots.title" :class="ui.title({ class: props.ui?.title })">
<slot name="title">
{{ title }}
</slot>
</DrawerTitle>
<DrawerDescription v-if="description || !!slots.description" :class="ui.description()">
<DrawerDescription v-if="description || !!slots.description" :class="ui.description({ class: props.ui?.description })">
<slot name="description">
{{ description }}
</slot>
@@ -95,11 +96,11 @@ const ui = computed(() => tv({ extend: drawer, slots: props.ui })())
</slot>
</div>
<div v-if="!!slots.body" :class="ui.body()">
<div v-if="!!slots.body" :class="ui.body({ class: props.ui?.body })">
<slot name="body" />
</div>
<div v-if="!!slots.footer" :class="ui.footer()">
<div v-if="!!slots.footer" :class="ui.footer({ class: props.ui?.footer })">
<slot name="footer" />
</div>
</div>

View File

@@ -86,7 +86,7 @@ const contentProps = toRef(() => defu(props.content, { side: 'bottom', sideOffse
const arrowProps = toRef(() => props.arrow as DropdownMenuArrowProps)
const proxySlots = omit(slots, ['default']) as Record<string, DropdownMenuSlots<T>[string]>
const ui = computed(() => tv({ extend: dropdownMenu, slots: props.ui })({
const ui = computed(() => dropdownMenu({
size: props.size
}))
</script>
@@ -97,12 +97,19 @@ const ui = computed(() => tv({ extend: dropdownMenu, slots: props.ui })({
<slot :open="open" />
</DropdownMenuTrigger>
<UDropdownMenuContent :class="ui.content({ class: props.class })" :ui="ui" v-bind="contentProps" :items="items" :portal="portal">
<UDropdownMenuContent
:class="ui.content({ class: props.class })"
:ui="ui"
:ui-override="props.ui"
v-bind="contentProps"
:items="items"
:portal="portal"
>
<template v-for="(_, name) in proxySlots" #[name]="slotData: any">
<slot :name="name" v-bind="slotData" />
</template>
<DropdownMenuArrow v-if="!!arrow" v-bind="arrowProps" :class="ui.arrow()" />
<DropdownMenuArrow v-if="!!arrow" v-bind="arrowProps" :class="ui.arrow({ class: props.ui?.arrow })" />
</UDropdownMenuContent>
</DropdownMenuRoot>
</template>

View File

@@ -12,6 +12,7 @@ interface DropdownMenuContentProps<T> extends Omit<RadixDropdownMenuContentProps
sub?: boolean
class?: any
ui: typeof dropdownMenu
uiOverride?: any
}
interface DropdownMenuContentEmits extends RadixDropdownMenuContentEmits {}
@@ -48,22 +49,22 @@ const groups = computed(() => props.items?.length ? (Array.isArray(props.items[0
<DefineItemTemplate v-slot="{ item, active, index }">
<slot :name="item.slot || 'item'" :item="(item as T)" :index="index">
<slot :name="item.slot ? `${item.slot}-leading`: 'item-leading'" :item="(item as T)" :active="active" :index="index">
<UAvatar v-if="item.avatar" :size="(ui.itemLeadingAvatarSize() as AvatarProps['size'])" v-bind="item.avatar" :class="ui.itemLeadingAvatar({ active })" />
<UIcon v-else-if="item.icon" :name="item.icon" :class="ui.itemLeadingIcon({ active })" />
<UAvatar v-if="item.avatar" :size="(ui.itemLeadingAvatarSize() as AvatarProps['size'])" v-bind="item.avatar" :class="ui.itemLeadingAvatar({ class: uiOverride?.itemLeadingAvatar, active })" />
<UIcon v-else-if="item.icon" :name="item.icon" :class="ui.itemLeadingIcon({ class: uiOverride?.itemLeadingIcon, active })" />
</slot>
<span v-if="item.label || !!slots[item.slot ? `${item.slot}-label`: 'item-label']" :class="ui.itemLabel({ active })">
<span v-if="item.label || !!slots[item.slot ? `${item.slot}-label`: 'item-label']" :class="ui.itemLabel({ class: uiOverride?.itemLabel, active })">
<slot :name="item.slot ? `${item.slot}-label`: 'item-label'" :item="(item as T)" :active="active" :index="index">
{{ item.label }}
</slot>
<UIcon v-if="item.target === '_blank'" :name="appConfig.ui.icons.external" :class="ui.itemLabelExternalIcon({ active })" />
<UIcon v-if="item.target === '_blank'" :name="appConfig.ui.icons.external" :class="ui.itemLabelExternalIcon({ class: uiOverride?.itemLabelExternalIcon, active })" />
</span>
<span v-if="item.children?.length || item.kbds?.length || !!slots[item.slot ? `${item.slot}-trailing`: 'item-trailing']" :class="ui.itemTrailing()">
<span v-if="item.children?.length || item.kbds?.length || !!slots[item.slot ? `${item.slot}-trailing`: 'item-trailing']" :class="ui.itemTrailing({ class: uiOverride?.itemTrailing })">
<slot :name="item.slot ? `${item.slot}-trailing`: 'item-trailing'" :item="(item as T)" :active="active" :index="index">
<UIcon v-if="item.children?.length" :name="appConfig.ui.icons.chevronRight" :class="ui.itemTrailingIcon({ active })" />
<span v-else-if="item.kbds?.length" :class="ui.itemTrailingKbds()">
<UIcon v-if="item.children?.length" :name="appConfig.ui.icons.chevronRight" :class="ui.itemTrailingIcon({ class: uiOverride?.itemTrailingIcon, active })" />
<span v-else-if="item.kbds?.length" :class="ui.itemTrailingKbds({ class: uiOverride?.itemTrailingKbds })">
<UKbd v-for="(kbd, kbdIndex) in item.kbds" :key="kbdIndex" :size="(ui.itemTrailingKbdsSize() as KbdProps['size'])" v-bind="typeof kbd === 'string' ? { value: kbd } : kbd" />
</span>
</slot>
@@ -73,12 +74,12 @@ const groups = computed(() => props.items?.length ? (Array.isArray(props.items[0
<DropdownMenu.Portal :disabled="!portal">
<component :is="sub ? DropdownMenu.SubContent : DropdownMenu.Content" :class="props.class" v-bind="contentProps">
<DropdownMenu.Group v-for="(group, groupIndex) in groups" :key="`group-${groupIndex}`" :class="ui.group()">
<DropdownMenu.Group v-for="(group, groupIndex) in groups" :key="`group-${groupIndex}`" :class="ui.group({ class: uiOverride?.group })">
<template v-for="(item, index) in group" :key="`group-${groupIndex}-${index}`">
<DropdownMenu.Label v-if="item.type === 'label'" :class="ui.label()">
<DropdownMenu.Label v-if="item.type === 'label'" :class="ui.label({ class: uiOverride?.label })">
<ReuseItemTemplate :item="item" :index="index" />
</DropdownMenu.Label>
<DropdownMenu.Separator v-else-if="item.type === 'separator'" :class="ui.separator()" />
<DropdownMenu.Separator v-else-if="item.type === 'separator'" :class="ui.separator({ class: uiOverride?.separator })" />
<DropdownMenu.Sub v-else-if="item?.children?.length">
<DropdownMenu.SubTrigger
as="button"
@@ -87,7 +88,7 @@ const groups = computed(() => props.items?.length ? (Array.isArray(props.items[0
:open="item.open"
:default-open="item.defaultOpen"
:text-value="item.label"
:class="ui.item()"
:class="ui.item({ class: uiOverride?.item })"
>
<ReuseItemTemplate :item="item" :index="index" />
</DropdownMenu.SubTrigger>
@@ -96,6 +97,7 @@ const groups = computed(() => props.items?.length ? (Array.isArray(props.items[0
sub
:class="props.class"
:ui="ui"
:ui-override="uiOverride"
:portal="portal"
:items="item.children"
side="right"
@@ -111,7 +113,7 @@ const groups = computed(() => props.items?.length ? (Array.isArray(props.items[0
</DropdownMenu.Sub>
<DropdownMenu.Item v-else as-child :disabled="item.disabled" :text-value="item.label" @select="item.select">
<ULink v-slot="{ active, ...slotProps }" v-bind="pickLinkProps(item as Omit<DropdownMenuItem, 'type'>)" custom>
<ULinkBase v-bind="slotProps" :class="ui.item({ active })">
<ULinkBase v-bind="slotProps" :class="ui.item({ class: uiOverride?.item, active })">
<ReuseItemTemplate :item="item" :active="active" :index="index" />
</ULinkBase>
</ULink>

View File

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

View File

@@ -65,7 +65,7 @@ const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponen
const inputSize = computed(() => buttonGroupSize.value || formGroupSize.value)
const ui = computed(() => tv({ extend: input, slots: props.ui })({
const ui = computed(() => input({
type: props.type as InputVariants['type'],
color: color.value,
variant: props.variant,
@@ -141,7 +141,7 @@ onMounted(() => {
:value="modelValue"
:name="name"
:placeholder="placeholder"
:class="ui.base()"
:class="ui.base({ class: props.ui?.base })"
:disabled="disabled"
:required="required"
v-bind="$attrs"
@@ -152,15 +152,15 @@ onMounted(() => {
<slot />
<span v-if="isLeading || !!slots.leading" :class="ui.leading()">
<span v-if="isLeading || !!slots.leading" :class="ui.leading({ class: props.ui?.leading })">
<slot name="leading">
<UIcon v-if="leadingIconName" :name="leadingIconName" :class="ui.leadingIcon()" />
<UIcon v-if="leadingIconName" :name="leadingIconName" :class="ui.leadingIcon({ class: props.ui?.leadingIcon })" />
</slot>
</span>
<span v-if="isTrailing || !!slots.trailing" :class="ui.trailing()">
<span v-if="isTrailing || !!slots.trailing" :class="ui.trailing({ class: props.ui?.trailing })">
<slot name="trailing">
<UIcon v-if="trailingIconName" :name="trailingIconName" :class="ui.trailingIcon()" />
<UIcon v-if="trailingIconName" :name="trailingIconName" :class="ui.trailingIcon({ class: props.ui?.trailingIcon })" />
</slot>
</span>
</div>

View File

@@ -137,7 +137,7 @@ const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponen
const inputSize = computed(() => buttonGroupSize.value || formGroupSize.value)
const ui = computed(() => tv({ extend: inputMenu, slots: props.ui })({
const ui = computed(() => inputMenu({
color: color.value,
variant: props.variant,
size: inputSize?.value,
@@ -233,7 +233,7 @@ function onUpdateOpen(value: boolean) {
@update:open="onUpdateOpen"
@keydown.enter="$event.preventDefault()"
>
<ComboboxAnchor :as-child="!multiple" :class="ui.base()">
<ComboboxAnchor :as-child="!multiple" :class="ui.base({ class: props.ui?.base })">
<TagsInputRoot
v-if="multiple"
v-slot="{ modelValue: tags }: { modelValue: AcceptableValue[] }"
@@ -243,16 +243,16 @@ function onUpdateOpen(value: boolean) {
as-child
@blur="onBlur"
>
<TagsInputItem v-for="(item, index) in tags" :key="index" :value="(item as string)" :class="ui.tagsItem()">
<TagsInputItemText :class="ui.tagsItemText()">
<TagsInputItem v-for="(item, index) in tags" :key="index" :value="(item as string)" :class="ui.tagsItem({ class: props.ui?.tagsItem })">
<TagsInputItemText :class="ui.tagsItemText({ class: props.ui?.tagsItemText })">
<slot name="tags-item-text" :item="(item as T)" :index="index">
{{ typeof item === 'object' ? item.label : item }}
</slot>
</TagsInputItemText>
<TagsInputItemDelete :class="ui.tagsItemDelete()" :disabled="disabled">
<TagsInputItemDelete :class="ui.tagsItemDelete({ class: props.ui?.tagsItemDelete })" :disabled="disabled">
<slot name="tags-item-delete" :item="(item as T)" :index="index">
<UIcon :name="deleteIcon || appConfig.ui.icons.close" :class="ui.tagsItemDeleteIcon()" />
<UIcon :name="deleteIcon || appConfig.ui.icons.close" :class="ui.tagsItemDeleteIcon({ class: props.ui?.tagsItemDeleteIcon })" />
</slot>
</TagsInputItemDelete>
</TagsInputItem>
@@ -263,7 +263,7 @@ function onUpdateOpen(value: boolean) {
v-bind="$attrs"
:placeholder="placeholder"
:required="required"
:class="ui.tagsInput()"
:class="ui.tagsInput({ class: props.ui?.tagsInput })"
@keydown.enter.prevent
/>
</ComboboxInput>
@@ -276,66 +276,66 @@ function onUpdateOpen(value: boolean) {
:type="type"
:placeholder="placeholder"
:required="required"
:class="ui.base()"
:class="ui.base({ class: props.ui?.base })"
@blur="onBlur"
/>
<span v-if="isLeading || !!slots.leading" :class="ui.leading()">
<span v-if="isLeading || !!slots.leading" :class="ui.leading({ class: props.ui?.leading })">
<slot name="leading" :model-value="(modelValue as T)" :open="open" :ui="ui">
<UIcon v-if="leadingIconName" :name="leadingIconName" :class="ui.leadingIcon()" />
<UIcon v-if="leadingIconName" :name="leadingIconName" :class="ui.leadingIcon({ class: props.ui?.leadingIcon })" />
</slot>
</span>
<ComboboxTrigger v-if="isTrailing || !!slots.trailing" :class="ui.trailing()">
<ComboboxTrigger v-if="isTrailing || !!slots.trailing" :class="ui.trailing({ class: props.ui?.trailing })">
<slot name="trailing" :model-value="(modelValue as T)" :open="open" :ui="ui">
<UIcon v-if="trailingIconName" :name="trailingIconName" :class="ui.trailingIcon()" />
<UIcon v-if="trailingIconName" :name="trailingIconName" :class="ui.trailingIcon({ class: props.ui?.trailingIcon })" />
</slot>
</ComboboxTrigger>
</ComboboxAnchor>
<ComboboxPortal :disabled="!portal">
<ComboboxContent :class="ui.content()" v-bind="contentProps">
<ComboboxEmpty :class="ui.empty()">
<ComboboxContent :class="ui.content({ class: props.ui?.content })" v-bind="contentProps">
<ComboboxEmpty :class="ui.empty({ class: props.ui?.empty })">
<slot name="empty" :search-term="searchTerm">
{{ searchTerm ? `No results for ${searchTerm}` : 'No results' }}
</slot>
</ComboboxEmpty>
<ComboboxViewport :class="ui.viewport()">
<ComboboxGroup v-for="(group, groupIndex) in groups" :key="`group-${groupIndex}`" :class="ui.group()">
<ComboboxViewport :class="ui.viewport({ class: props.ui?.viewport })">
<ComboboxGroup v-for="(group, groupIndex) in groups" :key="`group-${groupIndex}`" :class="ui.group({ class: props.ui?.group })">
<template v-for="(item, index) in group" :key="`group-${groupIndex}-${index}`">
<ComboboxLabel v-if="item?.type === 'label'" :class="ui.label()">
<ComboboxLabel v-if="item?.type === 'label'" :class="ui.label({ class: props.ui?.label })">
{{ item.label }}
</ComboboxLabel>
<ComboboxSeparator v-else-if="item?.type === 'separator'" :class="ui.separator()" />
<ComboboxSeparator v-else-if="item?.type === 'separator'" :class="ui.separator({ class: props.ui?.separator })" />
<ComboboxItem v-else :class="ui.item()" :disabled="item.disabled" :value="item">
<ComboboxItem v-else :class="ui.item({ class: props.ui?.item })" :disabled="item.disabled" :value="item">
<slot name="item" :item="(item as T)" :index="index">
<slot name="item-leading" :item="(item as T)" :index="index">
<UAvatar v-if="item.avatar" size="2xs" v-bind="item.avatar" :class="ui.itemLeadingAvatar()" />
<UIcon v-else-if="item.icon" :name="item.icon" :class="ui.itemLeadingIcon()" />
<UAvatar v-if="item.avatar" size="2xs" v-bind="item.avatar" :class="ui.itemLeadingAvatar({ class: props.ui?.itemLeadingAvatar })" />
<UIcon v-else-if="item.icon" :name="item.icon" :class="ui.itemLeadingIcon({ class: props.ui?.itemLeadingIcon })" />
<UChip
v-else-if="item.chip"
size="md"
inset
standalone
v-bind="item.chip"
:class="ui.itemLeadingChip()"
:class="ui.itemLeadingChip({ class: props.ui?.itemLeadingChip })"
/>
</slot>
<span :class="ui.itemLabel()">
<span :class="ui.itemLabel({ class: props.ui?.itemLabel })">
<slot name="item-label" :item="(item as T)" :index="index">
{{ displayValue(item as T) }}
</slot>
</span>
<span :class="ui.itemTrailing()">
<span :class="ui.itemTrailing({ class: props.ui?.itemTrailing })">
<slot name="item-trailing" :item="(item as T)" :index="index" />
<ComboboxItemIndicator as-child>
<UIcon :name="selectedIcon || appConfig.ui.icons.check" :class="ui.itemTrailingIcon()" />
<UIcon :name="selectedIcon || appConfig.ui.icons.check" :class="ui.itemTrailingIcon({ class: props.ui?.itemTrailingIcon })" />
</ComboboxItemIndicator>
</span>
</slot>

View File

@@ -48,7 +48,7 @@ export interface ModalSlots {
header(props?: {}): any
title(props?: {}): any
description(props?: {}): any
close(props: { class: string }): any
close(props: { ui: any }): any
body(props?: {}): any
footer(props?: {}): any
}
@@ -85,7 +85,7 @@ const contentEvents = computed(() => {
const appConfig = useAppConfig()
const ui = computed(() => tv({ extend: modal, slots: props.ui })({
const ui = computed(() => modal({
transition: props.transition,
fullscreen: props.fullscreen
}))
@@ -98,26 +98,26 @@ const ui = computed(() => tv({ extend: modal, slots: props.ui })({
</DialogTrigger>
<DialogPortal :disabled="!portal">
<DialogOverlay v-if="overlay" :class="ui.overlay()" />
<DialogOverlay v-if="overlay" :class="ui.overlay({ class: props.ui?.overlay })" />
<DialogContent :class="ui.content({ class: props.class })" v-bind="contentProps" v-on="contentEvents">
<slot name="content">
<div v-if="!!slots.header || (title || !!slots.title) || (description || !!slots.description) || (close || !!slots.close)" :class="ui.header()">
<div v-if="!!slots.header || (title || !!slots.title) || (description || !!slots.description) || (close || !!slots.close)" :class="ui.header({ class: props.ui?.header })">
<slot name="header">
<DialogTitle v-if="title || !!slots.title" :class="ui.title()">
<DialogTitle v-if="title || !!slots.title" :class="ui.title({ class: props.ui?.title })">
<slot name="title">
{{ title }}
</slot>
</DialogTitle>
<DialogDescription v-if="description || !!slots.description" :class="ui.description()">
<DialogDescription v-if="description || !!slots.description" :class="ui.description({ class: props.ui?.description })">
<slot name="description">
{{ description }}
</slot>
</DialogDescription>
<DialogClose as-child>
<slot name="close" :class="ui.close()">
<slot name="close" :ui="ui">
<UButton
v-if="close"
:icon="closeIcon || appConfig.ui.icons.close"
@@ -126,18 +126,18 @@ const ui = computed(() => tv({ extend: modal, slots: props.ui })({
variant="ghost"
aria-label="Close"
v-bind="typeof close === 'object' ? close : undefined"
:class="ui.close()"
:class="ui.close({ class: props.ui?.close })"
/>
</slot>
</DialogClose>
</slot>
</div>
<div v-if="!!slots.body" :class="ui.body()">
<div v-if="!!slots.body" :class="ui.body({ class: props.ui?.body })">
<slot name="body" />
</div>
<div v-if="!!slots.footer" :class="ui.footer()">
<div v-if="!!slots.footer" :class="ui.footer({ class: props.ui?.footer })">
<slot name="footer" />
</div>
</slot>

View File

@@ -93,7 +93,7 @@ const slots = defineSlots<NavigationMenuSlots<T>>()
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'modelValue', 'defaultValue', 'delayDuration', 'skipDelayDuration', 'orientation'), emits)
const contentProps = toRef(() => props.content)
const ui = computed(() => tv({ extend: navigationMenu, slots: props.ui })({
const ui = computed(() => navigationMenu({
orientation: props.orientation,
color: props.color,
variant: props.variant,
@@ -107,8 +107,8 @@ const lists = computed(() => props.items?.length ? (Array.isArray(props.items[0]
<template>
<NavigationMenuRoot v-bind="rootProps" :class="ui.root({ class: props.class })">
<template v-for="(list, listIndex) in lists" :key="`list-${listIndex}`">
<NavigationMenuList :class="ui.list()">
<NavigationMenuItem v-for="(item, index) in list" :key="`list-${listIndex}-${index}`" :value="item.value || String(index)" :class="ui.item()">
<NavigationMenuList :class="ui.list({ class: props.ui?.list })">
<NavigationMenuItem v-for="(item, index) in list" :key="`list-${listIndex}-${index}`" :value="item.value || String(index)" :class="ui.item({ class: props.ui?.item })">
<ULink v-slot="{ active, ...slotProps }" v-bind="pickLinkProps(item)" custom>
<component
:is="item.children?.length ? NavigationMenuTrigger : NavigationMenuLink"
@@ -117,52 +117,52 @@ const lists = computed(() => props.items?.length ? (Array.isArray(props.items[0]
:active="active"
@select="item.select"
>
<ULinkBase v-bind="slotProps" :class="ui.link({ active, disabled: !!item.disabled })">
<ULinkBase v-bind="slotProps" :class="ui.link({ class: props.ui?.link, active, disabled: !!item.disabled })">
<slot :name="item.slot || 'item'" :item="item" :index="index">
<slot :name="item.slot ? `${item.slot}-leading`: 'item-leading'" :item="item" :active="active" :index="index">
<UAvatar v-if="item.avatar" size="2xs" v-bind="item.avatar" :class="ui.linkLeadingAvatar({ active, disabled: !!item.disabled })" />
<UIcon v-else-if="item.icon" :name="item.icon" :class="ui.linkLeadingIcon({ active, disabled: !!item.disabled })" />
<UAvatar v-if="item.avatar" size="2xs" v-bind="item.avatar" :class="ui.linkLeadingAvatar({ class: props.ui?.linkLeadingAvatar, active, disabled: !!item.disabled })" />
<UIcon v-else-if="item.icon" :name="item.icon" :class="ui.linkLeadingIcon({ class: props.ui?.linkLeadingIcon, active, disabled: !!item.disabled })" />
</slot>
<span v-if="item.label || !!slots[item.slot ? `${item.slot}-label`: 'item-label']" :class="ui.linkLabel()">
<span v-if="item.label || !!slots[item.slot ? `${item.slot}-label`: 'item-label']" :class="ui.linkLabel({ class: props.ui?.linkLabel })">
<slot :name="item.slot ? `${item.slot}-label`: 'item-label'" :item="item" :active="active" :index="index">
{{ item.label }}
</slot>
<UIcon v-if="item.target === '_blank'" :name="appConfig.ui.icons.external" :class="ui.linkLabelExternalIcon({ active })" />
<UIcon v-if="item.target === '_blank'" :name="appConfig.ui.icons.external" :class="ui.linkLabelExternalIcon({ class: props.ui?.linkLabelExternalIcon, active })" />
</span>
<span v-if="item.badge || (item.children?.length && orientation === 'horizontal') || !!slots[item.slot ? `${item.slot}-trailing`: 'item-trailing']" :class="ui.linkTrailing()">
<span v-if="item.badge || (item.children?.length && orientation === 'horizontal') || !!slots[item.slot ? `${item.slot}-trailing`: 'item-trailing']" :class="ui.linkTrailing({ class: props.ui?.linkTrailing })">
<slot :name="item.slot ? `${item.slot}-trailing`: 'item-trailing'" :item="item" :active="active" :index="index">
<UBadge
v-if="item.badge"
color="white"
size="sm"
v-bind="(typeof item.badge === 'string' || typeof item.badge === 'number') ? { label: item.badge } : item.badge"
:class="ui.linkTrailingBadge()"
:class="ui.linkTrailingBadge({ class: props.ui?.linkTrailingBadge })"
/>
<UIcon v-if="item.children?.length && orientation === 'horizontal'" :name="item.trailingIcon || trailingIcon || appConfig.ui.icons.chevronDown" :class="ui.linkTrailingIcon({ active })" />
<UIcon v-if="item.children?.length && orientation === 'horizontal'" :name="item.trailingIcon || trailingIcon || appConfig.ui.icons.chevronDown" :class="ui.linkTrailingIcon({ class: props.ui?.linkTrailingIcon, active })" />
</slot>
</span>
</slot>
</ULinkBase>
</component>
<NavigationMenuContent v-if="item.children?.length && orientation === 'horizontal'" v-bind="contentProps" :class="ui.content()">
<ul :class="ui.childList()">
<li v-for="(childItem, childIndex) in item.children" :key="childIndex" :class="ui.childItem()">
<NavigationMenuContent v-if="item.children?.length && orientation === 'horizontal'" v-bind="contentProps" :class="ui.content({ class: props.ui?.content })">
<ul :class="ui.childList({ class: props.ui?.childList })">
<li v-for="(childItem, childIndex) in item.children" :key="childIndex" :class="ui.childItem({ class: props.ui?.childItem })">
<ULink v-slot="{ active: childActive, ...childSlotProps }" v-bind="pickLinkProps(childItem)" custom>
<NavigationMenuLink as-child :active="childActive" @select="childItem.select">
<ULinkBase v-bind="childSlotProps" :class="ui.childLink({ active: childActive })">
<UIcon v-if="childItem.icon" :name="childItem.icon" :class="ui.childLinkIcon({ active: childActive })" />
<ULinkBase v-bind="childSlotProps" :class="ui.childLink({ class: props.ui?.childLink, active: childActive })">
<UIcon v-if="childItem.icon" :name="childItem.icon" :class="ui.childLinkIcon({ class: props.ui?.childLinkIcon, active: childActive })" />
<div :class="ui.childLinkWrapper()">
<p :class="ui.childLinkLabel({ active: childActive })">
<div :class="ui.childLinkWrapper({ class: props.ui?.childLinkWrapper })">
<p :class="ui.childLinkLabel({ class: props.ui?.childLinkLabel, active: childActive })">
{{ childItem.label }}
<UIcon v-if="childItem.target === '_blank'" :name="appConfig.ui.icons.external" :class="ui.childLinkLabelExternalIcon({ active: childActive })" />
<UIcon v-if="childItem.target === '_blank'" :name="appConfig.ui.icons.external" :class="ui.childLinkLabelExternalIcon({ class: props.ui?.childLinkLabelExternalIcon, active: childActive })" />
</p>
<p v-if="childItem.description" :class="ui.childLinkDescription({ active: childActive })">
<p v-if="childItem.description" :class="ui.childLinkDescription({ class: props.ui?.childLinkDescription, active: childActive })">
{{ childItem.description }}
</p>
</div>
@@ -176,15 +176,15 @@ const lists = computed(() => props.items?.length ? (Array.isArray(props.items[0]
</NavigationMenuItem>
</NavigationMenuList>
<div v-if="orientation === 'vertical' && listIndex < lists.length - 1" :class="ui.separator()" />
<div v-if="orientation === 'vertical' && listIndex < lists.length - 1" :class="ui.separator({ class: props.ui?.separator })" />
</template>
<div v-if="orientation === 'horizontal'" :class="ui.viewportWrapper()">
<NavigationMenuIndicator v-if="arrow" :class="ui.indicator()">
<div :class="ui.arrow()" />
<div v-if="orientation === 'horizontal'" :class="ui.viewportWrapper({ class: props.ui?.viewportWrapper })">
<NavigationMenuIndicator v-if="arrow" :class="ui.indicator({ class: props.ui?.indicator })">
<div :class="ui.arrow({ class: props.ui?.arrow })" />
</NavigationMenuIndicator>
<NavigationMenuViewport :class="ui.viewport()" />
<NavigationMenuViewport :class="ui.viewport({ class: props.ui?.viewport })" />
</div>
</NavigationMenuRoot>
</template>

View File

@@ -100,7 +100,6 @@ export interface PaginationSlots {
</script>
<script setup lang="ts">
import { computed } from 'vue'
import { PaginationRoot, PaginationList, PaginationListItem, PaginationFirst, PaginationPrev, PaginationEllipsis, PaginationNext, PaginationLast, useForwardPropsEmits } from 'radix-vue'
import { reactivePick } from '@vueuse/core'
import { useAppConfig } from '#imports'
@@ -119,12 +118,13 @@ const appConfig = useAppConfig()
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'defaultPage', 'disabled', 'itemsPerPage', 'page', 'showEdges', 'siblingCount', 'total'), emits)
const ui = computed(() => tv({ extend: pagination, slots: props.ui })())
// eslint-disable-next-line vue/no-dupe-keys
const ui = pagination()
</script>
<template>
<PaginationRoot v-slot="{ page, pageCount }" v-bind="rootProps" :class="ui.root({ class: props.class })">
<PaginationList v-slot="{ items }" :class="ui.list()">
<PaginationList v-slot="{ items }" :class="ui.list({ class: props.ui?.list })">
<PaginationFirst v-if="showControls || !!slots.first" as-child>
<slot name="first">
<UButton :color="color" :variant="variant" :size="size" :icon="firstIcon || appConfig.ui.icons.chevronDoubleLeft" :to="to?.(1)" />
@@ -153,7 +153,7 @@ const ui = computed(() => tv({ extend: pagination, slots: props.ui })())
<PaginationEllipsis v-else :key="item.type" :index="index" as-child>
<slot name="ellipsis">
<UButton :color="color" :variant="variant" :size="size" :icon="ellipsisIcon || appConfig.ui.icons.ellipsis" :class="ui.ellipsis()" />
<UButton :color="color" :variant="variant" :size="size" :icon="ellipsisIcon || appConfig.ui.icons.ellipsis" :class="ui.ellipsis({ class: props.ui?.ellipsis })" />
</slot>
</PaginationEllipsis>
</template>

View File

@@ -63,7 +63,10 @@ const rootProps = useForwardPropsEmits(pick, emits)
const contentProps = toRef(() => defu(props.content, { side: 'bottom', sideOffset: 8 }) as PopoverContentProps)
const arrowProps = toRef(() => props.arrow as PopoverArrowProps)
const ui = computed(() => tv({ extend: popover, slots: props.ui })({ side: contentProps.value.side }))
// eslint-disable-next-line vue/no-dupe-keys
const ui = computed(() => popover({
side: contentProps.value.side
}))
const Component = computed(() => props.mode === 'hover' ? HoverCard : Popover)
</script>
@@ -78,7 +81,7 @@ const Component = computed(() => props.mode === 'hover' ? HoverCard : Popover)
<Component.Content v-bind="contentProps" :class="ui.content({ class: props.class })">
<slot name="content" />
<Component.Arrow v-if="!!arrow" v-bind="arrowProps" :class="ui.arrow()" />
<Component.Arrow v-if="!!arrow" v-bind="arrowProps" :class="ui.arrow({ class: props.ui?.arrow })" />
</Component.Content>
</Component.Portal>
</Component.Root>

View File

@@ -129,7 +129,7 @@ function stepVariant(index: number | string) {
return 'other'
}
const ui = computed(() => tv({ extend: progress, slots: props.ui })({
const ui = computed(() => progress({
animation: props.animation,
size: props.size,
color: props.color,
@@ -140,18 +140,18 @@ const ui = computed(() => tv({ extend: progress, slots: props.ui })({
<template>
<div :class="ui.root({ class: props.class })">
<div v-if="!isIndeterminate && (status || $slots.status)" :class="ui.status()" :style="statusStyle">
<div v-if="!isIndeterminate && (status || $slots.status)" :class="ui.status({ class: props.ui?.status })" :style="statusStyle">
<slot name="status" :percent="percent">
{{ percent }}%
</slot>
</div>
<ProgressRoot v-bind="rootProps" :max="realMax" :class="ui.base()" style="transform: translateZ(0)">
<ProgressIndicator :class="ui.indicator()" :style="indicatorStyle" />
<ProgressRoot v-bind="rootProps" :max="realMax" :class="ui.base({ class: props.ui?.base })" style="transform: translateZ(0)">
<ProgressIndicator :class="ui.indicator({ class: props.ui?.indicator })" :style="indicatorStyle" />
</ProgressRoot>
<div v-if="hasSteps" :class="ui.steps()">
<div v-for="(step, index) in max" :key="index" :class="ui.step({ step: stepVariant(index) })">
<div v-if="hasSteps" :class="ui.steps({ class: props.ui?.steps })">
<div v-for="(step, index) in max" :key="index" :class="ui.step({ class: props.ui?.step, step: stepVariant(index) })">
<slot :name="`step-${index}`" :step="step">
{{ step }}
</slot>

View File

@@ -50,7 +50,7 @@ export interface RadioGroupSlots<T> {
</script>
<script setup lang="ts" generic="T extends RadioGroupItem | AcceptableValue">
import { ref, computed } from 'vue'
import { computed } from 'vue'
import { RadioGroupRoot, RadioGroupItem, RadioGroupIndicator, Label, useForwardPropsEmits } from 'radix-vue'
import { reactivePick } from '@vueuse/core'
import { useId, useFormField } from '#imports'
@@ -66,7 +66,7 @@ const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'modelValue', '
const { emitFormChange, emitFormInput, color, name, size, id: _id, disabled } = useFormField<RadioGroupProps<T>>(props)
const id = _id.value ?? useId()
const ui = computed(() => tv({ extend: radioGroup, slots: props.ui })({
const ui = computed(() => radioGroup({
size: size.value,
color: color.value,
disabled: disabled.value,
@@ -113,29 +113,29 @@ function onUpdate(value: any) {
:class="ui.root({ class: props.class })"
@update:model-value="onUpdate"
>
<fieldset :class="ui.fieldset()">
<legend v-if="legend || !!slots.legend" :class="ui.legend()">
<fieldset :class="ui.fieldset({ class: props.ui?.fieldset })">
<legend v-if="legend || !!slots.legend" :class="ui.legend({ class: props.ui?.legend })">
<slot name="legend">
{{ legend }}
</slot>
</legend>
<div v-for="item in normalizedItems" :key="item.value" :class="ui.item()">
<div :class="ui.container()">
<div v-for="item in normalizedItems" :key="item.value" :class="ui.item({ class: props.ui?.item })">
<div :class="ui.container({ class: props.ui?.container })">
<RadioGroupItem
:id="item.id"
:value="item.value"
:disabled="disabled"
:class="ui.base()"
:class="ui.base({ class: props.ui?.base })"
>
<RadioGroupIndicator :class="ui.indicator()" />
<RadioGroupIndicator :class="ui.indicator({ class: props.ui?.indicator })" />
</RadioGroupItem>
</div>
<div :class="ui.wrapper()">
<Label :class="ui.label()" :for="item.id">
<div :class="ui.wrapper({ class: props.ui?.wrapper })">
<Label :class="ui.label({ class: props.ui?.label })" :for="item.id">
<slot name="label" :item="item" :model-value="modelValue">{{ item.label }}</slot>
</Label>
<p v-if="item.description || !!slots.description" :class="ui.description()">
<p v-if="item.description || !!slots.description" :class="ui.description({ class: props.ui?.description })">
<slot name="description" :item="item" :model-value="modelValue">
{{ item.description }}
</slot>

View File

@@ -105,7 +105,7 @@ const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponen
const selectSize = computed(() => buttonGroupSize.value || formGroupSize.value)
const ui = computed(() => tv({ extend: select, slots: props.ui })({
const ui = computed(() => select({
color: color.value,
variant: props.variant,
size: selectSize?.value,
@@ -148,56 +148,56 @@ function onUpdateOpen(value: boolean) {
@update:open="onUpdateOpen"
>
<SelectTrigger :class="ui.base({ class: props.class })">
<span v-if="isLeading || !!slots.leading" :class="ui.leading()">
<span v-if="isLeading || !!slots.leading" :class="ui.leading({ class: props.ui?.leading })">
<slot name="leading" :model-value="modelValue" :open="open" :ui="ui">
<UIcon v-if="leadingIconName" :name="leadingIconName" :class="ui.leadingIcon()" />
<UIcon v-if="leadingIconName" :name="leadingIconName" :class="ui.leadingIcon({ class: props.ui?.leadingIcon })" />
</slot>
</span>
<SelectValue :placeholder="placeholder ?? '&nbsp;'" :class="ui.value()" />
<SelectValue :placeholder="placeholder ?? '&nbsp;'" :class="ui.value({ class: props.ui?.value })" />
<span v-if="isTrailing || !!slots.trailing" :class="ui.trailing()">
<span v-if="isTrailing || !!slots.trailing" :class="ui.trailing({ class: props.ui?.trailing })">
<slot name="trailing" :model-value="modelValue" :open="open" :ui="ui">
<UIcon v-if="trailingIconName" :name="trailingIconName" :class="ui.trailingIcon()" />
<UIcon v-if="trailingIconName" :name="trailingIconName" :class="ui.trailingIcon({ class: props.ui?.trailingIcon })" />
</slot>
</span>
</SelectTrigger>
<SelectPortal :disabled="!portal">
<SelectContent :class="ui.content()" v-bind="contentProps">
<SelectViewport :class="ui.viewport()">
<SelectGroup v-for="(group, groupIndex) in groups" :key="`group-${groupIndex}`" :class="ui.group()">
<SelectContent :class="ui.content({ class: props.ui?.content })" v-bind="contentProps">
<SelectViewport :class="ui.viewport({ class: props.ui?.viewport })">
<SelectGroup v-for="(group, groupIndex) in groups" :key="`group-${groupIndex}`" :class="ui.group({ class: props.ui?.group })">
<template v-for="(item, index) in group" :key="`group-${groupIndex}-${index}`">
<SelectLabel v-if="item?.type === 'label'" :class="ui.label()">
<SelectLabel v-if="item?.type === 'label'" :class="ui.label({ class: props.ui?.label })">
{{ item.label }}
</SelectLabel>
<SelectSeparator v-else-if="item?.type === 'separator'" :class="ui.separator()" />
<SelectItem v-else :class="ui.item()" :disabled="item.disabled" :value="typeof item === 'object' ? item.value : item">
<SelectSeparator v-else-if="item?.type === 'separator'" :class="ui.separator({ class: props.ui?.separator })" />
<SelectItem v-else :class="ui.item({ class: props.ui?.item })" :disabled="item.disabled" :value="typeof item === 'object' ? item.value : item">
<slot name="item" :item="(item as T)" :index="index">
<slot name="item-leading" :item="(item as T)" :index="index">
<UAvatar v-if="item.avatar" :size="(ui.itemLeadingAvatarSize() as AvatarProps['size'])" v-bind="item.avatar" :class="ui.itemLeadingAvatar()" />
<UIcon v-else-if="item.icon" :name="item.icon" :class="ui.itemLeadingIcon()" />
<UAvatar v-if="item.avatar" :size="(ui.itemLeadingAvatarSize() as AvatarProps['size'])" v-bind="item.avatar" :class="ui.itemLeadingAvatar({ class: props.ui?.itemLeadingAvatar })" />
<UIcon v-else-if="item.icon" :name="item.icon" :class="ui.itemLeadingIcon({ class: props.ui?.itemLeadingIcon })" />
<UChip
v-else-if="item.chip"
:size="(ui.itemLeadingChipSize() as ChipProps['size'])"
inset
standalone
v-bind="item.chip"
:class="ui.itemLeadingChip()"
:class="ui.itemLeadingChip({ class: props.ui?.itemLeadingChip })"
/>
</slot>
<SelectItemText :class="ui.itemLabel()">
<SelectItemText :class="ui.itemLabel({ class: props.ui?.itemLabel })">
<slot name="item-label" :item="(item as T)" :index="index">
{{ typeof item === 'object' ? item.label : item }}
</slot>
</SelectItemText>
<span :class="ui.itemTrailing()">
<span :class="ui.itemTrailing({ class: props.ui?.itemTrailing })">
<slot name="item-trailing" :item="(item as T)" :index="index" />
<SelectItemIndicator as-child>
<UIcon :name="selectedIcon || appConfig.ui.icons.check" :class="ui.itemTrailingIcon()" />
<UIcon :name="selectedIcon || appConfig.ui.icons.check" :class="ui.itemTrailingIcon({ class: props.ui?.itemTrailingIcon })" />
</SelectItemIndicator>
</span>
</slot>

View File

@@ -121,7 +121,7 @@ const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponen
const selectSize = computed(() => buttonGroupSize.value || formGroupSize.value)
const ui = computed(() => tv({ extend: selectMenu, slots: props.ui })({
const ui = computed(() => selectMenu({
color: color.value,
variant: props.variant,
size: selectSize?.value,
@@ -201,74 +201,74 @@ function onUpdateOpen(value: boolean) {
>
<ComboboxAnchor as-child>
<ComboboxTrigger :class="ui.base({ class: props.class })" tabindex="0">
<span v-if="isLeading || !!slots.leading" :class="ui.leading()">
<span v-if="isLeading || !!slots.leading" :class="ui.leading({ class: props.ui?.leading })">
<slot name="leading" :model-value="(modelValue as T)" :open="open" :ui="ui">
<UIcon v-if="leadingIconName" :name="leadingIconName" :class="ui.leadingIcon()" />
<UIcon v-if="leadingIconName" :name="leadingIconName" :class="ui.leadingIcon({ class: props.ui?.leadingIcon })" />
</slot>
</span>
<slot :model-value="(modelValue as T)" :open="open">
<span v-if="multiple ? !!modelValue?.length : !!modelValue" :class="ui.value()">
<span v-if="multiple ? !!modelValue?.length : !!modelValue" :class="ui.value({ class: props.ui?.value })">
{{ displayValue(modelValue as T, multiple) }}
</span>
<span v-else :class="ui.placeholder()">
<span v-else :class="ui.placeholder({ class: props.ui?.placeholder })">
{{ placeholder ?? '&nbsp;' }}
</span>
</slot>
<span v-if="isTrailing || !!slots.trailing" :class="ui.trailing()">
<span v-if="isTrailing || !!slots.trailing" :class="ui.trailing({ class: props.ui?.trailing })">
<slot name="trailing" :model-value="(modelValue as T)" :open="open" :ui="ui">
<UIcon v-if="trailingIconName" :name="trailingIconName" :class="ui.trailingIcon()" />
<UIcon v-if="trailingIconName" :name="trailingIconName" :class="ui.trailingIcon({ class: props.ui?.trailingIcon })" />
</slot>
</span>
</ComboboxTrigger>
</ComboboxAnchor>
<ComboboxPortal :disabled="!portal">
<ComboboxContent :class="ui.content()" v-bind="contentProps">
<ComboboxInput :placeholder="searchPlaceholder" :class="ui.input()" autofocus autocomplete="off" />
<ComboboxContent :class="ui.content({ class: props.ui?.content })" v-bind="contentProps">
<ComboboxInput :placeholder="searchPlaceholder" :class="ui.input({ class: props.ui?.input })" autofocus autocomplete="off" />
<ComboboxEmpty :class="ui.empty()">
<ComboboxEmpty :class="ui.empty({ class: props.ui?.empty })">
<slot name="empty" :search-term="searchTerm">
{{ searchTerm ? `No results for ${searchTerm}` : 'No results' }}
</slot>
</ComboboxEmpty>
<ComboboxViewport :class="ui.viewport()">
<ComboboxGroup v-for="(group, groupIndex) in groups" :key="`group-${groupIndex}`" :class="ui.group()">
<ComboboxViewport :class="ui.viewport({ class: props.ui?.viewport })">
<ComboboxGroup v-for="(group, groupIndex) in groups" :key="`group-${groupIndex}`" :class="ui.group({ class: props.ui?.group })">
<template v-for="(item, index) in group" :key="`group-${groupIndex}-${index}`">
<ComboboxLabel v-if="item?.type === 'label'" :class="ui.label()">
<ComboboxLabel v-if="item?.type === 'label'" :class="ui.label({ class: props.ui?.label })">
{{ item.label }}
</ComboboxLabel>
<ComboboxSeparator v-else-if="item?.type === 'separator'" :class="ui.separator()" />
<ComboboxSeparator v-else-if="item?.type === 'separator'" :class="ui.separator({ class: props.ui?.separator })" />
<ComboboxItem v-else :class="ui.item()" :disabled="item.disabled" :value="item">
<ComboboxItem v-else :class="ui.item({ class: props.ui?.item })" :disabled="item.disabled" :value="item">
<slot name="item" :item="(item as T)" :index="index">
<slot name="item-leading" :item="(item as T)" :index="index">
<UAvatar v-if="item.avatar" :size="(ui.itemLeadingAvatarSize() as AvatarProps['size'])" v-bind="item.avatar" :class="ui.itemLeadingAvatar()" />
<UIcon v-else-if="item.icon" :name="item.icon" :class="ui.itemLeadingIcon()" />
<UAvatar v-if="item.avatar" :size="(ui.itemLeadingAvatarSize() as AvatarProps['size'])" v-bind="item.avatar" :class="ui.itemLeadingAvatar({ class: props.ui?.itemLeadingAvatar })" />
<UIcon v-else-if="item.icon" :name="item.icon" :class="ui.itemLeadingIcon({ class: props.ui?.itemLeadingIcon })" />
<UChip
v-else-if="item.chip"
:size="(ui.itemLeadingChipSize() as ChipProps['size'])"
inset
standalone
v-bind="item.chip"
:class="ui.itemLeadingChip()"
:class="ui.itemLeadingChip({ class: props.ui?.itemLeadingChip })"
/>
</slot>
<span :class="ui.itemLabel()">
<span :class="ui.itemLabel({ class: props.ui?.itemLabel })">
<slot name="item-label" :item="(item as T)" :index="index">
{{ displayValue(item as T) }}
</slot>
</span>
<span :class="ui.itemTrailing()">
<span :class="ui.itemTrailing({ class: props.ui?.itemTrailing })">
<slot name="item-trailing" :item="(item as T)" :index="index" />
<ComboboxItemIndicator as-child>
<UIcon :name="selectedIcon || appConfig.ui.icons.check" :class="ui.itemTrailingIcon()" />
<UIcon :name="selectedIcon || appConfig.ui.icons.check" :class="ui.itemTrailingIcon({ class: props.ui?.itemTrailingIcon })" />
</ComboboxItemIndicator>
</span>
</slot>

View File

@@ -54,7 +54,7 @@ const slots = defineSlots<SeparatorSlots>()
const rootProps = useForwardProps(reactivePick(props, 'as', 'decorative', 'orientation'))
const ui = computed(() => tv({ extend: separator, slots: props.ui })({
const ui = computed(() => separator({
color: props.color,
orientation: props.orientation,
size: props.size,
@@ -64,18 +64,18 @@ const ui = computed(() => tv({ extend: separator, slots: props.ui })({
<template>
<Separator v-bind="rootProps" :class="ui.root({ class: props.class })">
<div :class="ui.border()" />
<div :class="ui.border({ class: props.ui?.border })" />
<template v-if="label || icon || avatar || !!slots.default">
<div :class="ui.container()">
<div :class="ui.container({ class: props.ui?.container })">
<slot>
<span v-if="label" :class="ui.label()">{{ label }}</span>
<UIcon v-else-if="icon" :name="icon" :class="ui.icon()" />
<UAvatar v-else-if="avatar" size="2xs" v-bind="avatar" :class="ui.avatar()" />
<span v-if="label" :class="ui.label({ class: props.ui?.label })">{{ label }}</span>
<UIcon v-else-if="icon" :name="icon" :class="ui.icon({ class: props.ui?.icon })" />
<UAvatar v-else-if="avatar" size="2xs" v-bind="avatar" :class="ui.avatar({ class: props.ui?.avatar })" />
</slot>
</div>
<div :class="ui.border()" />
<div :class="ui.border({ class: props.ui?.border })" />
</template>
</Separator>
</template>

View File

@@ -58,7 +58,7 @@ export interface SlideoverSlots {
header(props?: {}): any
title(props?: {}): any
description(props?: {}): any
close(props: { class: string }): any
close(props: { ui: any }): any
body(props?: {}): any
footer(props?: {}): any
}
@@ -96,7 +96,7 @@ const contentEvents = computed(() => {
const appConfig = useAppConfig()
const ui = computed(() => tv({ extend: slideover, slots: props.ui })({
const ui = computed(() => slideover({
transition: props.transition,
side: props.side
}))
@@ -109,26 +109,26 @@ const ui = computed(() => tv({ extend: slideover, slots: props.ui })({
</DialogTrigger>
<DialogPortal :disabled="!portal">
<DialogOverlay v-if="overlay" :class="ui.overlay()" />
<DialogOverlay v-if="overlay" :class="ui.overlay({ class: props.ui?.overlay })" />
<DialogContent :data-side="side" :class="ui.content({ class: props.class })" v-bind="contentProps" v-on="contentEvents">
<slot name="content">
<div v-if="!!slots.header || (title || !!slots.title) || (description || !!slots.description) || (close || !!slots.close)" :class="ui.header()">
<div v-if="!!slots.header || (title || !!slots.title) || (description || !!slots.description) || (close || !!slots.close)" :class="ui.header({ class: props.ui?.header })">
<slot name="header">
<DialogTitle v-if="title || !!slots.title" :class="ui.title()">
<DialogTitle v-if="title || !!slots.title" :class="ui.title({ class: props.ui?.title })">
<slot name="title">
{{ title }}
</slot>
</DialogTitle>
<DialogDescription v-if="description || !!slots.description" :class="ui.description()">
<DialogDescription v-if="description || !!slots.description" :class="ui.description({ class: props.ui?.description })">
<slot name="description">
{{ description }}
</slot>
</DialogDescription>
<DialogClose as-child>
<slot name="close" :class="ui.close()">
<slot name="close" :ui="ui">
<UButton
v-if="close"
:icon="closeIcon || appConfig.ui.icons.close"
@@ -137,18 +137,18 @@ const ui = computed(() => tv({ extend: slideover, slots: props.ui })({
variant="ghost"
aria-label="Close"
v-bind="typeof close === 'object' ? close : undefined"
:class="ui.close()"
:class="ui.close({ class: props.ui?.close })"
/>
</slot>
</DialogClose>
</slot>
</div>
<div :class="ui.body()">
<div :class="ui.body({ class: props.ui?.body })">
<slot name="body" />
</div>
<div v-if="!!slots.footer" :class="ui.footer()">
<div v-if="!!slots.footer" :class="ui.footer({ class: props.ui?.footer })">
<slot name="footer" />
</div>
</slot>

View File

@@ -76,7 +76,7 @@ const sliderValue = computed({
const thumbsCount = computed(() => sliderValue.value?.length ?? 1)
const ui = computed(() => tv({ extend: slider, slots: props.ui })({
const ui = computed(() => slider({
disabled: disabled.value,
size: size.value,
color: color.value,
@@ -103,10 +103,10 @@ function onChange(value: any) {
@update:model-value="emitFormInput()"
@value-commit="onChange"
>
<SliderTrack :class="ui.track()">
<SliderRange :class="ui.range()" />
<SliderTrack :class="ui.track({ class: props.ui?.track })">
<SliderRange :class="ui.range({ class: props.ui?.range })" />
</SliderTrack>
<SliderThumb v-for="count in thumbsCount" :key="count" :class="ui.thumb()" />
<SliderThumb v-for="count in thumbsCount" :key="count" :class="ui.thumb({ class: props.ui?.thumb })" />
</SliderRoot>
</template>

View File

@@ -68,7 +68,7 @@ const rootProps = useForwardProps(reactivePick(props, 'as', 'required', 'value')
const { id: _id, emitFormChange, emitFormInput, size, color, name, disabled } = useFormField<SwitchProps>(props)
const id = _id.value ?? useId()
const ui = computed(() => tv({ extend: switchTv, slots: props.ui })({
const ui = computed(() => switchTv({
size: size.value,
color: color.value,
required: props.required,
@@ -87,7 +87,7 @@ function onUpdate(value: any) {
<template>
<div :class="ui.root({ class: props.class })">
<div :class="ui.container()">
<div :class="ui.container({ class: props.ui?.container })">
<SwitchRoot
:id="id"
v-model:checked="modelValue"
@@ -95,25 +95,25 @@ function onUpdate(value: any) {
v-bind="rootProps"
:name="name"
:disabled="disabled || loading"
:class="ui.base()"
:class="ui.base({ class: props.ui?.base })"
@update:checked="onUpdate"
>
<SwitchThumb :class="ui.thumb()">
<UIcon v-if="loading" :name="loadingIcon || appConfig.ui.icons.loading" :class="ui.icon({ checked: true, unchecked: true })" />
<SwitchThumb :class="ui.thumb({ class: props.ui?.thumb })">
<UIcon v-if="loading" :name="loadingIcon || appConfig.ui.icons.loading" :class="ui.icon({ class: props.ui?.icon, checked: true, unchecked: true })" />
<template v-else>
<UIcon v-if="checkedIcon" :name="checkedIcon" :class="ui.icon({ checked: true })" />
<UIcon v-if="uncheckedIcon" :name="uncheckedIcon" :class="ui.icon({ unchecked: true })" />
<UIcon v-if="checkedIcon" :name="checkedIcon" :class="ui.icon({ class: props.ui?.icon, checked: true })" />
<UIcon v-if="uncheckedIcon" :name="uncheckedIcon" :class="ui.icon({ class: props.ui?.icon, unchecked: true })" />
</template>
</SwitchThumb>
</SwitchRoot>
</div>
<div v-if="(label || !!slots.label) || (description || !!slots.description)" :class="ui.wrapper()">
<Label v-if="label || !!slots.label" :for="id" :class="ui.label()">
<div v-if="(label || !!slots.label) || (description || !!slots.description)" :class="ui.wrapper({ class: props.ui?.wrapper })">
<Label v-if="label || !!slots.label" :for="id" :class="ui.label({ class: props.ui?.label })">
<slot name="label" :label="label">
{{ label }}
</slot>
</Label>
<p v-if="description || !!slots.description" :class="ui.description()">
<p v-if="description || !!slots.description" :class="ui.description({ class: props.ui?.description })">
<slot name="description" :description="description">
{{ description }}
</slot>

View File

@@ -74,7 +74,7 @@ const slots = defineSlots<TabsSlots<T>>()
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'defaultValue', 'orientation', 'activationMode', 'modelValue'), emits)
const contentProps = toRef(() => defu(props.content || {}, { forceMount: true }) as TabsContentProps)
const ui = computed(() => tv({ extend: tabs, slots: props.ui })({
const ui = computed(() => tabs({
color: props.color,
variant: props.variant,
size: props.size,
@@ -84,16 +84,16 @@ const ui = computed(() => tv({ extend: tabs, slots: props.ui })({
<template>
<TabsRoot v-bind="rootProps" :class="ui.root({ class: props.class })">
<TabsList :class="ui.list()">
<TabsIndicator :class="ui.indicator()" />
<TabsList :class="ui.list({ class: props.ui?.list })">
<TabsIndicator :class="ui.indicator({ class: props.ui?.indicator })" />
<TabsTrigger v-for="(item, index) of items" :key="index" :value="item.value || String(index)" :disabled="item.disabled" :class="ui.trigger()">
<TabsTrigger v-for="(item, index) of items" :key="index" :value="item.value || String(index)" :disabled="item.disabled" :class="ui.trigger({ class: props.ui?.trigger })">
<slot name="leading" :item="item" :index="index">
<UAvatar v-if="item.avatar" :size="(ui.leadingAvatarSize() as any)" v-bind="item.avatar" :class="ui.leadingAvatar()" />
<UIcon v-else-if="item.icon" :name="item.icon" :class="ui.leadingIcon()" />
<UAvatar v-if="item.avatar" :size="(ui.leadingAvatarSize() as any)" v-bind="item.avatar" :class="ui.leadingAvatar({ class: props.ui?.leadingAvatar })" />
<UIcon v-else-if="item.icon" :name="item.icon" :class="ui.leadingIcon({ class: props.ui?.leadingIcon })" />
</slot>
<span v-if="item.label || !!slots.default" :class="ui.label()">
<span v-if="item.label || !!slots.default" :class="ui.label({ class: props.ui?.label })">
<slot :item="item" :index="index">{{ item.label }}</slot>
</span>
@@ -102,7 +102,7 @@ const ui = computed(() => tv({ extend: tabs, slots: props.ui })({
</TabsList>
<template v-if="!!content">
<TabsContent v-for="(item, index) of items" :key="index" v-bind="contentProps" :value="item.value || String(index)" :class="ui.content()">
<TabsContent v-for="(item, index) of items" :key="index" v-bind="contentProps" :value="item.value || String(index)" :class="ui.content({ class: props.ui?.content })">
<slot :name="item.slot || 'content'" :item="item" :index="index">
{{ item.content }}
</slot>

View File

@@ -59,7 +59,7 @@ const [modelValue, modelModifiers] = defineModel<string | number>()
const { emitFormBlur, emitFormInput, emitFormChange, size, color, id, name, disabled } = useFormField<TextareaProps>(props)
const ui = computed(() => tv({ extend: textarea, slots: props.ui })({
const ui = computed(() => textarea({
color: color.value,
variant: props.variant,
size: size?.value
@@ -164,7 +164,7 @@ onMounted(() => {
:name="name"
:rows="rows"
:placeholder="placeholder"
:class="ui.base()"
:class="ui.base({ class: props.ui?.base })"
:disabled="disabled"
:required="required"
v-bind="$attrs"

View File

@@ -51,7 +51,7 @@ export interface ToastSlots {
title(props?: {}): any
description(props?: {}): any
actions(props?: {}): any
close(props: { class: string }): any
close(props: { ui: any }): any
}
</script>
@@ -76,7 +76,9 @@ const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'defaultOpen',
const multiline = computed(() => !!props.title && !!props.description)
const duration = computed(() => props.duration || toaster?.value.duration)
const ui = computed(() => tv({ extend: toast, slots: props.ui })({ color: props.color }))
const ui = computed(() => toast({
color: props.color
}))
const el = ref()
const height = ref(0)
@@ -105,25 +107,25 @@ defineExpose({
:style="{ '--height': height }"
>
<slot name="leading">
<UAvatar v-if="avatar" size="2xl" v-bind="avatar" :class="ui.avatar()" />
<UIcon v-else-if="icon" :name="icon" :class="ui.icon()" />
<UAvatar v-if="avatar" size="2xl" v-bind="avatar" :class="ui.avatar({ class: props.ui?.avatar })" />
<UIcon v-else-if="icon" :name="icon" :class="ui.icon({ class: props.ui?.icon })" />
</slot>
<div :class="ui.wrapper()">
<ToastTitle v-if="title || !!slots.title" :class="ui.title()">
<div :class="ui.wrapper({ class: props.ui?.wrapper })">
<ToastTitle v-if="title || !!slots.title" :class="ui.title({ class: props.ui?.title })">
<slot name="title">
{{ title }}
</slot>
</ToastTitle>
<template v-if="description || !!slots.description">
<ToastDescription :class="ui.description()">
<ToastDescription :class="ui.description({ class: props.ui?.description })">
<slot name="description">
{{ description }}
</slot>
</ToastDescription>
</template>
<div v-if="multiline && actions?.length" :class="ui.actions({ multiline: true })">
<div v-if="multiline && actions?.length" :class="ui.actions({ class: props.ui?.actions, multiline: true })">
<slot name="actions">
<ToastAction v-for="(action, index) in actions" :key="index" :alt-text="action.label || 'Action'" as-child @click.stop>
<UButton size="xs" :color="color" v-bind="action" />
@@ -132,7 +134,7 @@ defineExpose({
</div>
</div>
<div v-if="(!multiline && actions?.length) || close !== null" :class="ui.actions({ multiline: false })">
<div v-if="(!multiline && actions?.length) || close !== null" :class="ui.actions({ class: props.ui?.actions, multiline: false })">
<template v-if="!multiline">
<slot name="actions">
<ToastAction v-for="(action, index) in actions" :key="index" :alt-text="action.label || 'Action'" as-child @click.stop>
@@ -142,7 +144,7 @@ defineExpose({
</template>
<ToastClose as-child>
<slot name="close" :class="ui.close()">
<slot name="close" :ui="ui">
<UButton
v-if="close"
:icon="closeIcon || appConfig.ui.icons.close"
@@ -151,13 +153,13 @@ defineExpose({
variant="link"
aria-label="Close"
v-bind="typeof close === 'object' ? close : undefined"
:class="ui.close()"
:class="ui.close({ class: props.ui?.close })"
@click.stop
/>
</slot>
</ToastClose>
</div>
<div v-if="remaining > 0 && duration" :class="ui.progress()" :style="{ width: `${remaining / duration * 100}%` }" />
<div v-if="remaining > 0 && duration" :class="ui.progress({ class: props.ui?.progress })" :style="{ width: `${remaining / duration * 100}%` }" />
</ToastRoot>
</template>

View File

@@ -66,7 +66,7 @@ const swipeDirection = computed(() => {
return 'right'
})
const ui = computed(() => tv({ extend: toaster, slots: props.ui })({
const ui = computed(() => toaster({
position: props.position,
swipeDirection: swipeDirection.value
}))

View File

@@ -59,7 +59,10 @@ const rootProps = useForwardPropsEmits(reactivePick(props, 'defaultOpen', 'open'
const contentProps = toRef(() => defu(props.content, { side: 'bottom', sideOffset: 8 }) as TooltipContentProps)
const arrowProps = toRef(() => props.arrow as TooltipArrowProps)
const ui = computed(() => tv({ extend: tooltip, slots: props.ui })({ side: contentProps.value.side }))
// eslint-disable-next-line vue/no-dupe-keys
const ui = computed(() => tooltip({
side: contentProps.value.side
}))
</script>
<template>
@@ -71,14 +74,14 @@ const ui = computed(() => tv({ extend: tooltip, slots: props.ui })({ side: conte
<TooltipPortal :disabled="!portal">
<TooltipContent v-bind="contentProps" :class="ui.content({ class: props.class })">
<slot name="content">
<span v-if="text" :class="ui.text()">{{ text }}</span>
<span v-if="text" :class="ui.text({ class: props.ui?.text })">{{ text }}</span>
<span v-if="kbds?.length" :class="ui.kbds()">
<span v-if="kbds?.length" :class="ui.kbds({ class: props.ui?.kbds })">
<UKbd v-for="(kbd, index) in kbds" :key="index" size="sm" v-bind="typeof kbd === 'string' ? { value: kbd } : kbd" />
</span>
</slot>
<TooltipArrow v-if="!!arrow" v-bind="arrowProps" :class="ui.arrow()" />
<TooltipArrow v-if="!!arrow" v-bind="arrowProps" :class="ui.arrow({ class: props.ui?.arrow })" />
</TooltipContent>
</TooltipPortal>
</TooltipRoot>

View File

@@ -65,6 +65,9 @@ export default (options: Required<ModuleOptions>) => ({
},
other: {
step: 'opacity-0'
},
last: {
step: ''
}
},
orientation: {

View File

@@ -120,11 +120,11 @@ exports[`Breadcrumb > renders with separatorIcon correctly 1`] = `
exports[`Breadcrumb > renders with ui correctly 1`] = `
"<div aria-label="breadcrumb" class="relative min-w-0">
<ol class="flex items-center gap-1.5">
<li class="flex min-w-0"><a href="/" class="group relative flex items-center gap-1.5 text-sm min-w-0 text-gray-500 dark:text-gray-400 font-medium hover:text-gray-700 dark:hover:text-gray-200 transition-colors"><span class="inline-flex items-center justify-center select-none overflow-hidden rounded-full align-middle bg-gray-100 dark:bg-gray-800 size-5 text-[10px] shrink-0"><img role="img" src="https://avatars.githubusercontent.com/u/739984?v=4" class="h-full w-full rounded-[inherit] object-cover" style="display: none;"><span class="font-medium leading-none text-gray-500 dark:text-gray-400 truncate"></span></span><span class="truncate">Home</span></a></li>
<li class="flex min-w-0"><a href="/" class="group relative flex items-center gap-1.5 text-sm min-w-0 text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 transition-colors font-bold"><span class="inline-flex items-center justify-center select-none overflow-hidden rounded-full align-middle bg-gray-100 dark:bg-gray-800 size-5 text-[10px] shrink-0"><img role="img" src="https://avatars.githubusercontent.com/u/739984?v=4" class="h-full w-full rounded-[inherit] object-cover" style="display: none;"><span class="font-medium leading-none text-gray-500 dark:text-gray-400 truncate"></span></span><span class="truncate">Home</span></a></li>
<li role="presentation" class="flex"><span class="iconify i-heroicons:chevron-right-20-solid shrink-0 size-5 text-gray-500 dark:text-gray-400" aria-hidden="true"></span></li>
<li class="flex min-w-0"><span class="group relative flex items-center gap-1.5 text-sm min-w-0 text-gray-500 dark:text-gray-400 font-medium cursor-not-allowed opacity-75"><span class="iconify i-heroicons:square-3-stack-3d shrink-0 size-5" aria-hidden="true"></span><span class="truncate">Navigation</span></span></li>
<li class="flex min-w-0"><span class="group relative flex items-center gap-1.5 text-sm min-w-0 text-gray-500 dark:text-gray-400 cursor-not-allowed opacity-75 font-bold"><span class="iconify i-heroicons:square-3-stack-3d shrink-0 size-5" aria-hidden="true"></span><span class="truncate">Navigation</span></span></li>
<li role="presentation" class="flex"><span class="iconify i-heroicons:chevron-right-20-solid shrink-0 size-5 text-gray-500 dark:text-gray-400" aria-hidden="true"></span></li>
<li class="flex min-w-0"><a href="/breadcrumb" class="group relative flex items-center gap-1.5 text-sm min-w-0 text-primary-500 dark:text-primary-400 font-semibold"><span class="iconify i-heroicons:link shrink-0 size-5" aria-hidden="true"></span><span class="truncate">Breadcrumb</span></a></li>
<li class="flex min-w-0"><a href="/breadcrumb" class="group relative flex items-center gap-1.5 text-sm min-w-0 text-primary-500 dark:text-primary-400 font-bold"><span class="iconify i-heroicons:link shrink-0 size-5" aria-hidden="true"></span><span class="truncate">Breadcrumb</span></a></li>
<!--v-if-->
</ol>
</div>"

View File

@@ -50,7 +50,7 @@ exports[`Chip > renders with size xs correctly 1`] = `"<div class="relative inli
exports[`Chip > renders with text correctly 1`] = `"<div class="relative inline-flex items-center justify-center shrink-0"><span class="rounded-full ring ring-white dark:ring-gray-900 flex items-center justify-center text-white dark:text-gray-900 font-medium whitespace-nowrap bg-primary-500 dark:bg-primary-400 h-[8px] min-w-[8px] text-[8px] top-0 right-0 absolute -translate-y-1/2 translate-x-1/2 transform">Text</span></div>"`;
exports[`Chip > renders with ui correctly 1`] = `"<div class="relative inline-flex items-center justify-center shrink-0"><span class="rounded-full ring ring-white dark:ring-gray-900 flex items-center justify-center font-medium whitespace-nowrap text-gray-500 dark:text-gray-400 bg-primary-500 dark:bg-primary-400 h-[8px] min-w-[8px] text-[8px] top-0 right-0 absolute -translate-y-1/2 translate-x-1/2 transform"></span></div>"`;
exports[`Chip > renders with ui correctly 1`] = `"<div class="relative inline-flex items-center justify-center shrink-0"><span class="rounded-full ring ring-white dark:ring-gray-900 flex items-center justify-center font-medium whitespace-nowrap bg-primary-500 dark:bg-primary-400 h-[8px] min-w-[8px] text-[8px] top-0 right-0 absolute -translate-y-1/2 translate-x-1/2 transform text-gray-500 dark:text-gray-400"></span></div>"`;
exports[`Chip > renders without show correctly 1`] = `
"<div class="relative inline-flex items-center justify-center shrink-0">

View File

@@ -855,7 +855,7 @@ exports[`DropdownMenu > renders with ui correctly 1`] = `
<div data-radix-popper-content-wrapper="" style="position: fixed; left: 0px; top: 0px; transform: translate(0, -200%); min-width: max-content;">
<div role="menu" aria-orientation="vertical" data-radix-menu-content="" data-state="open" dir="ltr" tabindex="-1" data-orientation="vertical" style="outline-color: none; outline-style: none; outline-width: initial; --radix-dropdown-menu-content-transform-origin: var(--radix-popper-transform-origin); --radix-dropdown-menu-content-available-width: var(--radix-popper-available-width); --radix-dropdown-menu-content-available-height: var(--radix-popper-available-height); --radix-dropdown-menu-trigger-width: var(--radix-popper-anchor-width); --radix-dropdown-menu-trigger-height: var(--radix-popper-anchor-height); animation: none;" data-dismissable-layer="" id="radix-vue-dropdown-menu-content-28" aria-labelledby="" class="min-w-32 bg-white dark:bg-gray-900 shadow-lg rounded-md ring ring-gray-200 dark:ring-gray-800 divide-y divide-gray-200 dark:divide-gray-800 overflow-y-auto scroll-py-1 data-[state=open]:animate-[scale-in_100ms_ease-out] data-[state=closed]:animate-[scale-out_100ms_ease-in]" data-side="bottom" data-align="center">
<div role="menu" aria-orientation="vertical" data-radix-menu-content="" data-state="open" dir="ltr" tabindex="-1" data-orientation="vertical" style="outline-color: none; outline-style: none; outline-width: initial; --radix-dropdown-menu-content-transform-origin: var(--radix-popper-transform-origin); --radix-dropdown-menu-content-available-width: var(--radix-popper-available-width); --radix-dropdown-menu-content-available-height: var(--radix-popper-available-height); --radix-dropdown-menu-trigger-width: var(--radix-popper-anchor-width); --radix-dropdown-menu-trigger-height: var(--radix-popper-anchor-height); animation: none;" data-dismissable-layer="" id="radix-vue-dropdown-menu-content-28" aria-labelledby="" class="min-w-32 bg-white dark:bg-gray-900 shadow-lg rounded-md ring ring-gray-200 dark:ring-gray-800 divide-y divide-gray-200 dark:divide-gray-800 overflow-y-auto scroll-py-1 data-[state=open]:animate-[scale-in_100ms_ease-out] data-[state=closed]:animate-[scale-out_100ms_ease-in]" uioverride="[object Object]" data-side="bottom" data-align="center">
<div role="group" class="p-1 isolate">
<div class="w-full flex items-center font-semibold text-gray-900 dark:text-white p-1.5 text-sm gap-1.5"><span class="inline-flex items-center justify-center select-none overflow-hidden rounded-full align-middle bg-gray-100 dark:bg-gray-800 size-5 text-[10px] shrink-0"><img role="img" src="https://avatars.githubusercontent.com/u/739984?v=4" class="h-full w-full rounded-[inherit] object-cover" style="display: none;"><span class="font-medium leading-none text-gray-500 dark:text-gray-400 truncate"></span></span><span class="truncate">My account<!--v-if--></span>
<!--v-if-->

View File

@@ -181,7 +181,7 @@ exports[`Input > renders with type correctly 1`] = `
`;
exports[`Input > renders with ui correctly 1`] = `
"<div class="relative inline-flex items-center"><input type="text" class="w-full border-0 placeholder-gray-400 dark:placeholder-gray-500 focus:outline-none disabled:cursor-not-allowed disabled:opacity-75 rounded-full px-2.5 py-1.5 text-sm gap-1.5 shadow-sm bg-white dark:bg-gray-900 text-gray-900 dark:text-white ring ring-inset ring-gray-300 dark:ring-gray-700 focus-visible:ring-2 focus-visible:ring-primary-500 dark:focus-visible:ring-primary-400">
"<div class="relative inline-flex items-center"><input type="text" class="w-full border-0 placeholder-gray-400 dark:placeholder-gray-500 focus:outline-none disabled:cursor-not-allowed disabled:opacity-75 px-2.5 py-1.5 text-sm gap-1.5 shadow-sm bg-white dark:bg-gray-900 text-gray-900 dark:text-white ring ring-inset ring-gray-300 dark:ring-gray-700 focus-visible:ring-2 focus-visible:ring-primary-500 dark:focus-visible:ring-primary-400 rounded-full">
<!--v-if-->
<!--v-if-->
</div>"

View File

@@ -262,7 +262,7 @@ exports[`Progress > renders with status slot correctly 1`] = `
exports[`Progress > renders with ui correctly 1`] = `
"<div class="gap-2 w-full flex flex-col">
<!--v-if-->
<div aria-valuemax="100" aria-valuemin="0" aria-valuetext="0%" aria-label="0%" role="progressbar" data-state="indeterminate" data-max="100" class="relative overflow-hidden rounded-full bg-white dark:bg-gray-900 w-full h-2" style="transform: translateZ(0);">
<div aria-valuemax="100" aria-valuemin="0" aria-valuetext="0%" aria-label="0%" role="progressbar" data-state="indeterminate" data-max="100" class="relative overflow-hidden rounded-full w-full h-2 bg-white dark:bg-gray-900" style="transform: translateZ(0);">
<div data-state="indeterminate" data-max="100" class="rounded-full size-full transition-transform duration-200 ease-out bg-primary-500 dark:bg-primary-400 data-[state=indeterminate]:animate-[carousel_2s_ease-in-out_infinite]"></div>
</div>
<!--v-if-->

View File

@@ -111,7 +111,7 @@ exports[`Slider > renders with size xs correctly 1`] = `
`;
exports[`Slider > renders with ui correctly 1`] = `
"<span data-slider-impl="" dir="ltr" data-orientation="horizontal" style="--radix-slider-thumb-transform: translateX(-50%);" class="relative flex items-center select-none touch-none w-full" aria-disabled="false"><span data-orientation="horizontal" class="relative overflow-hidden rounded-full grow bg-gray-100 dark:bg-gray-800 h-[8px]"><span data-orientation="horizontal" style="left: 0%; right: 100%;" class="absolute rounded-full bg-primary-500 dark:bg-primary-400 h-full"></span></span>
"<span data-slider-impl="" dir="ltr" data-orientation="horizontal" style="--radix-slider-thumb-transform: translateX(-50%);" class="relative flex items-center select-none touch-none w-full" aria-disabled="false"><span data-orientation="horizontal" class="relative overflow-hidden rounded-full grow h-[8px] bg-gray-100 dark:bg-gray-800"><span data-orientation="horizontal" style="left: 0%; right: 100%;" class="absolute rounded-full bg-primary-500 dark:bg-primary-400 h-full"></span></span>
<div class="rounded-full bg-white dark:bg-gray-900 ring-2 focus:outline-none focus-visible:outline-2 focus-visible:outline-offset-2 ring-primary-500 dark:ring-primary-400 focus-visible:outline-primary-500/50 dark:focus-visible:outline-primary-400/50 size-4" role="slider" data-radix-vue-collection-item="" tabindex="0" data-orientation="horizontal" aria-valuemin="0" aria-valuemax="100" aria-orientation="horizontal" style="transform: var(--radix-slider-thumb-transform); position: absolute; left: calc(0% + 0px);" aria-valuenow="0"></div></span>
<!---->"
`;