mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-28 19:00:35 +01:00
fix: dynamic slots autocomplete (#77)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
This commit is contained in:
@@ -22,7 +22,7 @@ const items = [{
|
|||||||
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed neque elit, tristique placerat feugiat ac, facilisis vitae arcu. Proin eget egestas augue. Praesent ut sem nec arcu pellentesque aliquet. Duis dapibus diam vel metus tempus vulputate.'
|
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed neque elit, tristique placerat feugiat ac, facilisis vitae arcu. Proin eget egestas augue. Praesent ut sem nec arcu pellentesque aliquet. Duis dapibus diam vel metus tempus vulputate.'
|
||||||
}, {
|
}, {
|
||||||
label: 'Utilities',
|
label: 'Utilities',
|
||||||
slot: 'toto',
|
slot: 'custom' as const,
|
||||||
icon: 'i-heroicons-wrench-screwdriver',
|
icon: 'i-heroicons-wrench-screwdriver',
|
||||||
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed neque elit, tristique placerat feugiat ac, facilisis vitae arcu. Proin eget egestas augue. Praesent ut sem nec arcu pellentesque aliquet. Duis dapibus diam vel metus tempus vulputate.'
|
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed neque elit, tristique placerat feugiat ac, facilisis vitae arcu. Proin eget egestas augue. Praesent ut sem nec arcu pellentesque aliquet. Duis dapibus diam vel metus tempus vulputate.'
|
||||||
}]
|
}]
|
||||||
@@ -30,6 +30,10 @@ const items = [{
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<UCard :ui="{ body: 'p-0 sm:p-0' }">
|
<UCard :ui="{ body: 'p-0 sm:p-0' }">
|
||||||
<UAccordion :items="items" class="w-96" :ui="{ trigger: 'px-3.5', content: 'px-3.5' }" />
|
<UAccordion :items="items" class="w-96" :ui="{ trigger: 'px-3.5', content: 'px-3.5' }">
|
||||||
|
<template #custom="{ item }">
|
||||||
|
<span class="text-gray-500 dark:text-gray-400">Custom: {{ item.content }}</span>
|
||||||
|
</template>
|
||||||
|
</UAccordion>
|
||||||
</UCard>
|
</UCard>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ const items = [{
|
|||||||
label: 'Home',
|
label: 'Home',
|
||||||
to: '/'
|
to: '/'
|
||||||
}, {
|
}, {
|
||||||
slot: 'dropdown',
|
slot: 'dropdown' as const,
|
||||||
icon: 'i-heroicons-ellipsis-horizontal',
|
icon: 'i-heroicons-ellipsis-horizontal',
|
||||||
children: [{
|
children: [{
|
||||||
label: 'Documentation'
|
label: 'Documentation'
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
const appConfig = useAppConfig()
|
||||||
|
|
||||||
const items = [
|
const items = [
|
||||||
[{
|
[{
|
||||||
label: 'My account',
|
label: 'My account',
|
||||||
@@ -10,6 +12,7 @@ const items = [
|
|||||||
[{
|
[{
|
||||||
label: 'Profile',
|
label: 'Profile',
|
||||||
icon: 'i-heroicons-user',
|
icon: 'i-heroicons-user',
|
||||||
|
slot: 'custom' as const,
|
||||||
select(e: Event) {
|
select(e: Event) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
console.log('Profile clicked')
|
console.log('Profile clicked')
|
||||||
@@ -116,6 +119,14 @@ defineShortcuts(extractShortcuts(items))
|
|||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<UDropdownMenu :items="items" arrow :content="{ side: 'bottom' }" class="min-w-48">
|
<UDropdownMenu :items="items" arrow :content="{ side: 'bottom' }" class="min-w-48">
|
||||||
<UButton label="Open" color="white" />
|
<UButton label="Open" color="white" />
|
||||||
|
|
||||||
|
<template #custom="{ item }">
|
||||||
|
<UIcon :name="item.icon" class="shrink-0 size-5" />
|
||||||
|
|
||||||
|
<span class="truncate">{{ item.label }}</span>
|
||||||
|
|
||||||
|
<UIcon :name="appConfig.ui.icons.check" class="shrink-0 size-5 text-primary-500 dark:text-primary-400 ms-auto" />
|
||||||
|
</template>
|
||||||
</UDropdownMenu>
|
</UDropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ const items = [
|
|||||||
label: 'Examples',
|
label: 'Examples',
|
||||||
icon: 'i-heroicons-light-bulb',
|
icon: 'i-heroicons-light-bulb',
|
||||||
to: 'https://ui.nuxt.com',
|
to: 'https://ui.nuxt.com',
|
||||||
target: '_blank'
|
target: '_blank',
|
||||||
|
slot: 'custom' as const
|
||||||
}, {
|
}, {
|
||||||
label: 'Help',
|
label: 'Help',
|
||||||
icon: 'i-heroicons-question-mark-circle',
|
icon: 'i-heroicons-question-mark-circle',
|
||||||
@@ -37,7 +38,13 @@ const items = [
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col gap-12 w-full max-w-4xl">
|
<div class="flex flex-col gap-12 w-full max-w-4xl">
|
||||||
<UNavigationMenu :items="items" class="border-b border-gray-200 dark:border-gray-800" />
|
<UNavigationMenu :items="items" class="border-b border-gray-200 dark:border-gray-800">
|
||||||
|
<template #custom="{ item }">
|
||||||
|
<UIcon :name="item.icon" class="size-5" />
|
||||||
|
|
||||||
|
<span class="truncate text-primary-500 dark:text-primary-400">{{ item.label }}</span>
|
||||||
|
</template>
|
||||||
|
</UNavigationMenu>
|
||||||
|
|
||||||
<UNavigationMenu :items="items" orientation="vertical" class="w-48" />
|
<UNavigationMenu :items="items" orientation="vertical" class="w-48" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -12,13 +12,19 @@ const items = [{
|
|||||||
}, {
|
}, {
|
||||||
label: 'Tab3',
|
label: 'Tab3',
|
||||||
icon: 'i-heroicons-bell',
|
icon: 'i-heroicons-bell',
|
||||||
content: 'Finally, this is the content for Tab3'
|
content: 'Finally, this is the content for Tab3',
|
||||||
|
slot: 'custom' as const
|
||||||
}]
|
}]
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
<UTabs :items="items" class="w-96" />
|
<UTabs :items="items" class="w-96">
|
||||||
|
<template #custom="{ item }">
|
||||||
|
<span class="text-gray-500 dark:text-gray-400">Custom: {{ item.content }}</span>
|
||||||
|
</template>
|
||||||
|
</UTabs>
|
||||||
|
|
||||||
<UTabs :items="items" orientation="vertical" />
|
<UTabs :items="items" orientation="vertical" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import type { AccordionRootProps, AccordionRootEmits, AccordionContentProps } fr
|
|||||||
import type { AppConfig } from '@nuxt/schema'
|
import type { AppConfig } from '@nuxt/schema'
|
||||||
import _appConfig from '#build/app.config'
|
import _appConfig from '#build/app.config'
|
||||||
import theme from '#build/ui/accordion'
|
import theme from '#build/ui/accordion'
|
||||||
|
import type { DynamicSlots } from '#ui/types/utils'
|
||||||
|
|
||||||
const appConfig = _appConfig as AppConfig & { ui: { accordion: Partial<typeof theme> } }
|
const appConfig = _appConfig as AppConfig & { ui: { accordion: Partial<typeof theme> } }
|
||||||
|
|
||||||
@@ -31,14 +32,13 @@ export interface AccordionEmits extends AccordionRootEmits {}
|
|||||||
|
|
||||||
type SlotProps<T> = (props: { item: T, index: number }) => any
|
type SlotProps<T> = (props: { item: T, index: number }) => any
|
||||||
|
|
||||||
export type AccordionSlots<T> = {
|
export type AccordionSlots<T extends { slot?: string }> = {
|
||||||
default: SlotProps<T>
|
default: SlotProps<T>
|
||||||
leading: SlotProps<T>
|
leading: SlotProps<T>
|
||||||
label: SlotProps<T>
|
label: SlotProps<T>
|
||||||
trailing: SlotProps<T>
|
trailing: SlotProps<T>
|
||||||
content: SlotProps<T>
|
content: SlotProps<T>
|
||||||
[key: string]: SlotProps<T>
|
} & DynamicSlots<T, SlotProps<T>>
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script setup lang="ts" generic="T extends AccordionItem">
|
<script setup lang="ts" generic="T extends AccordionItem">
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import type { AppConfig } from '@nuxt/schema'
|
|||||||
import _appConfig from '#build/app.config'
|
import _appConfig from '#build/app.config'
|
||||||
import theme from '#build/ui/breadcrumb'
|
import theme from '#build/ui/breadcrumb'
|
||||||
import type { AvatarProps, LinkProps } from '#ui/types'
|
import type { AvatarProps, LinkProps } from '#ui/types'
|
||||||
|
import type { DynamicSlots } from '#ui/types/utils'
|
||||||
|
|
||||||
const appConfig = _appConfig as AppConfig & { ui: { breadcrumb: Partial<typeof theme> } }
|
const appConfig = _appConfig as AppConfig & { ui: { breadcrumb: Partial<typeof theme> } }
|
||||||
|
|
||||||
@@ -26,14 +27,13 @@ export interface BreadcrumbProps<T> extends Omit<PrimitiveProps, 'asChild'> {
|
|||||||
|
|
||||||
type SlotProps<T> = (props: { item: T, index: number, active?: boolean }) => any
|
type SlotProps<T> = (props: { item: T, index: number, active?: boolean }) => any
|
||||||
|
|
||||||
export interface BreadcrumbSlots<T> {
|
export type BreadcrumbSlots<T extends { slot?: string }> = {
|
||||||
leading: SlotProps<T>
|
leading: SlotProps<T>
|
||||||
label: SlotProps<T>
|
label: SlotProps<T>
|
||||||
trailing: SlotProps<T>
|
trailing: SlotProps<T>
|
||||||
item: SlotProps<T>
|
item: SlotProps<T>
|
||||||
[key: string]: SlotProps<T>
|
|
||||||
separator(): any
|
separator(): any
|
||||||
}
|
} & DynamicSlots<T, SlotProps<T>>
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script setup lang="ts" generic="T extends BreadcrumbItem">
|
<script setup lang="ts" generic="T extends BreadcrumbItem">
|
||||||
@@ -56,8 +56,8 @@ const ui = computed(() => tv({ extend: breadcrumb, slots: props.ui })())
|
|||||||
<ol :class="ui.list()">
|
<ol :class="ui.list()">
|
||||||
<template v-for="(item, index) in items" :key="index">
|
<template v-for="(item, index) in items" :key="index">
|
||||||
<li :class="ui.item()">
|
<li :class="ui.item()">
|
||||||
<slot :name="item.slot || 'item'" :item="item" :index="index">
|
<ULink as="span" v-bind="omit(item, ['label', 'icon', 'avatar', 'slot'])" :aria-current="index === items!.length - 1 ? 'page' : undefined" :class="ui.link({ active: index === items!.length - 1, disabled: !!item.disabled, to: !!item.to })" raw>
|
||||||
<ULink as="span" v-bind="omit(item, ['label', 'icon', 'avatar'])" :aria-current="index === items!.length - 1 ? 'page' : undefined" :class="ui.link({ active: index === items!.length - 1, disabled: !!item.disabled, to: !!item.to })" raw>
|
<slot :name="item.slot || 'item'" :item="item" :index="index">
|
||||||
<slot name="leading" :item="item" :active="index === items!.length - 1" :index="index">
|
<slot name="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 })" />
|
<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 })" />
|
<UIcon v-else-if="item.icon" :name="item.icon" :class="ui.linkLeadingIcon({ active: index === items!.length - 1 })" />
|
||||||
@@ -70,8 +70,8 @@ const ui = computed(() => tv({ extend: breadcrumb, slots: props.ui })())
|
|||||||
</span>
|
</span>
|
||||||
|
|
||||||
<slot name="trailing" :item="item" :active="index === items!.length - 1" :index="index" />
|
<slot name="trailing" :item="item" :active="index === items!.length - 1" :index="index" />
|
||||||
</ULink>
|
</slot>
|
||||||
</slot>
|
</ULink>
|
||||||
</li>
|
</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()">
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import type { AppConfig } from '@nuxt/schema'
|
|||||||
import _appConfig from '#build/app.config'
|
import _appConfig from '#build/app.config'
|
||||||
import theme from '#build/ui/dropdown-menu'
|
import theme from '#build/ui/dropdown-menu'
|
||||||
import type { AvatarProps, KbdProps, LinkProps } from '#ui/types'
|
import type { AvatarProps, KbdProps, LinkProps } from '#ui/types'
|
||||||
|
import type { DynamicSlots } from '#ui/types/utils'
|
||||||
|
|
||||||
const appConfig = _appConfig as AppConfig & { ui: { dropdownMenu: Partial<typeof theme> } }
|
const appConfig = _appConfig as AppConfig & { ui: { dropdownMenu: Partial<typeof theme> } }
|
||||||
|
|
||||||
@@ -43,14 +44,13 @@ export interface DropdownMenuEmits extends DropdownMenuRootEmits {}
|
|||||||
|
|
||||||
type SlotProps<T> = (props: { item: T, active?: boolean, index: number }) => any
|
type SlotProps<T> = (props: { item: T, active?: boolean, index: number }) => any
|
||||||
|
|
||||||
export interface DropdownMenuSlots<T> {
|
export type DropdownMenuSlots<T extends { slot?: string }> = {
|
||||||
default(): any
|
default(): any
|
||||||
leading: SlotProps<T>
|
leading: SlotProps<T>
|
||||||
label: SlotProps<T>
|
label: SlotProps<T>
|
||||||
trailing: SlotProps<T>
|
trailing: SlotProps<T>
|
||||||
item: SlotProps<T>
|
item: SlotProps<T>
|
||||||
[key: string]: SlotProps<T>
|
} & DynamicSlots<T, SlotProps<T>>
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script setup lang="ts" generic="T extends DropdownMenuItem">
|
<script setup lang="ts" generic="T extends DropdownMenuItem">
|
||||||
@@ -71,7 +71,7 @@ const slots = defineSlots<DropdownMenuSlots<T>>()
|
|||||||
const rootProps = useForwardPropsEmits(reactivePick(props, 'defaultOpen', 'open', 'modal'), emits)
|
const rootProps = useForwardPropsEmits(reactivePick(props, 'defaultOpen', 'open', 'modal'), emits)
|
||||||
const contentProps = toRef(() => defu(props.content, { side: 'bottom', sideOffset: 8 }) as DropdownMenuContentProps)
|
const contentProps = toRef(() => defu(props.content, { side: 'bottom', sideOffset: 8 }) as DropdownMenuContentProps)
|
||||||
const arrowProps = toRef(() => props.arrow as DropdownMenuArrowProps)
|
const arrowProps = toRef(() => props.arrow as DropdownMenuArrowProps)
|
||||||
const proxySlots = omit(slots, ['default'])
|
const proxySlots = omit(slots, ['default']) as Record<string, DropdownMenuSlots<T>[string]>
|
||||||
|
|
||||||
const ui = computed(() => tv({ extend: dropdownMenu, slots: props.ui })())
|
const ui = computed(() => tv({ extend: dropdownMenu, slots: props.ui })())
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ interface DropdownMenuContentProps<T> extends Omit<RadixDropdownMenuContentProps
|
|||||||
|
|
||||||
interface DropdownMenuContentEmits extends RadixDropdownMenuContentEmits {}
|
interface DropdownMenuContentEmits extends RadixDropdownMenuContentEmits {}
|
||||||
|
|
||||||
interface DropdownMenuContentSlots<T> extends DropdownMenuSlots<T> {}
|
type DropdownMenuContentSlots<T extends { slot?: string }> = DropdownMenuSlots<T>
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script setup lang="ts" generic="T extends DropdownMenuItem">
|
<script setup lang="ts" generic="T extends DropdownMenuItem">
|
||||||
@@ -34,7 +34,7 @@ const slots = defineSlots<DropdownMenuContentSlots<T>>()
|
|||||||
|
|
||||||
const appConfig = useAppConfig()
|
const appConfig = useAppConfig()
|
||||||
const contentProps = useForwardPropsEmits(reactiveOmit(props, 'sub', 'items', 'portal', 'class', 'ui'), emits)
|
const contentProps = useForwardPropsEmits(reactiveOmit(props, 'sub', 'items', 'portal', 'class', 'ui'), emits)
|
||||||
const proxySlots = omit(slots, ['default'])
|
const proxySlots = omit(slots, ['default']) as Record<string, DropdownMenuContentSlots<T>[string]>
|
||||||
|
|
||||||
const [DefineItemTemplate, ReuseItemTemplate] = createReusableTemplate()
|
const [DefineItemTemplate, ReuseItemTemplate] = createReusableTemplate()
|
||||||
|
|
||||||
@@ -43,25 +43,27 @@ const groups = computed(() => props.items?.length ? (Array.isArray(props.items[0
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<DefineItemTemplate v-slot="{ item, active, index }">
|
<DefineItemTemplate v-slot="{ item, active, index }">
|
||||||
<slot name="leading" :item="item" :active="active" :index="index">
|
<slot :name="item.slot || 'item'" :item="item" :index="index">
|
||||||
<UAvatar v-if="item.avatar" size="2xs" v-bind="item.avatar" :class="ui.linkLeadingAvatar({ active })" />
|
<slot name="leading" :item="item" :active="active" :index="index">
|
||||||
<UIcon v-else-if="item.icon" :name="item.icon" :class="ui.linkLeadingIcon({ active })" />
|
<UAvatar v-if="item.avatar" size="2xs" v-bind="item.avatar" :class="ui.linkLeadingAvatar({ active })" />
|
||||||
|
<UIcon v-else-if="item.icon" :name="item.icon" :class="ui.linkLeadingIcon({ active })" />
|
||||||
|
</slot>
|
||||||
|
|
||||||
|
<span v-if="item.label || $slots.label" :class="ui.linkLabel()">
|
||||||
|
<slot name="label" :item="item" :active="active" :index="index">
|
||||||
|
{{ item.label }}
|
||||||
|
</slot>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span v-if="$slots.trailing || item.children?.length || item.kbds?.length" :class="ui.linkTrailing()">
|
||||||
|
<slot name="trailing" :item="item" :active="active" :index="index">
|
||||||
|
<UIcon v-if="item.children?.length" :name="appConfig.ui.icons.chevronRight" :class="ui.linkTrailingIcon()" />
|
||||||
|
<span v-else-if="item.kbds?.length" :class="ui.linkTrailingKbds()">
|
||||||
|
<UKbd v-for="(kbd, kbdIndex) in item.kbds" :key="kbdIndex" size="md" v-bind="typeof kbd === 'string' ? { value: kbd } : kbd" />
|
||||||
|
</span>
|
||||||
|
</slot>
|
||||||
|
</span>
|
||||||
</slot>
|
</slot>
|
||||||
|
|
||||||
<span v-if="item.label || $slots.label" :class="ui.linkLabel()">
|
|
||||||
<slot name="label" :item="item" :active="active" :index="index">
|
|
||||||
{{ item.label }}
|
|
||||||
</slot>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span v-if="$slots.trailing || item.children?.length || item.kbds?.length" :class="ui.linkTrailing()">
|
|
||||||
<slot name="trailing" :item="item" :active="active" :index="index">
|
|
||||||
<UIcon v-if="item.children?.length" :name="appConfig.ui.icons.chevronRight" :class="ui.linkTrailingIcon()" />
|
|
||||||
<span v-else-if="item.kbds?.length" :class="ui.linkTrailingKbds()">
|
|
||||||
<UKbd v-for="(kbd, kbdIndex) in item.kbds" :key="kbdIndex" size="md" v-bind="typeof kbd === 'string' ? { value: kbd } : kbd" />
|
|
||||||
</span>
|
|
||||||
</slot>
|
|
||||||
</span>
|
|
||||||
</DefineItemTemplate>
|
</DefineItemTemplate>
|
||||||
|
|
||||||
<DropdownMenu.Portal :disabled="!portal">
|
<DropdownMenu.Portal :disabled="!portal">
|
||||||
@@ -69,9 +71,7 @@ const groups = computed(() => props.items?.length ? (Array.isArray(props.items[0
|
|||||||
<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()">
|
||||||
<template v-for="(item, index) in group" :key="`group-${groupIndex}-${index}`">
|
<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()">
|
||||||
<slot :name="item.slot || 'item'" :item="item" :index="index">
|
<ReuseItemTemplate :item="item" :index="index" />
|
||||||
<ReuseItemTemplate :item="item" :index="index" />
|
|
||||||
</slot>
|
|
||||||
</DropdownMenu.Label>
|
</DropdownMenu.Label>
|
||||||
<DropdownMenu.Sub v-else-if="item?.children?.length">
|
<DropdownMenu.Sub v-else-if="item?.children?.length">
|
||||||
<DropdownMenu.SubTrigger
|
<DropdownMenu.SubTrigger
|
||||||
@@ -83,9 +83,7 @@ const groups = computed(() => props.items?.length ? (Array.isArray(props.items[0
|
|||||||
:text-value="item.label"
|
:text-value="item.label"
|
||||||
:class="ui.link()"
|
:class="ui.link()"
|
||||||
>
|
>
|
||||||
<slot :name="item.slot || 'item'" :item="item" :index="index">
|
<ReuseItemTemplate :item="item" :index="index" />
|
||||||
<ReuseItemTemplate :item="item" :index="index" />
|
|
||||||
</slot>
|
|
||||||
</DropdownMenu.SubTrigger>
|
</DropdownMenu.SubTrigger>
|
||||||
|
|
||||||
<UDropdownMenuContent
|
<UDropdownMenuContent
|
||||||
@@ -106,13 +104,11 @@ const groups = computed(() => props.items?.length ? (Array.isArray(props.items[0
|
|||||||
</UDropdownMenuContent>
|
</UDropdownMenuContent>
|
||||||
</DropdownMenu.Sub>
|
</DropdownMenu.Sub>
|
||||||
<DropdownMenu.Item v-else as-child :disabled="item.disabled" :text-value="item.label" @select="item.select">
|
<DropdownMenu.Item v-else as-child :disabled="item.disabled" :text-value="item.label" @select="item.select">
|
||||||
<slot :name="item.slot || 'item'" :item="item" :index="index">
|
<ULink v-slot="{ active, ...slotProps }" v-bind="omit((item as DropdownMenuItem), ['label', 'icon', 'avatar', 'kbds', 'slot', 'open', 'defaultOpen', 'select', 'children', 'type'])" custom>
|
||||||
<ULink v-slot="{ active, ...slotProps }" v-bind="omit((item as DropdownMenuItem), ['label', 'icon', 'avatar', 'kbds', 'slot', 'open', 'defaultOpen', 'select', 'children', 'type'])" custom>
|
<ULinkBase v-bind="slotProps" :class="ui.link({ active })">
|
||||||
<ULinkBase v-bind="slotProps" :class="ui.link({ active })">
|
<ReuseItemTemplate :item="item" :active="active" :index="index" />
|
||||||
<ReuseItemTemplate :item="item" :active="active" :index="index" />
|
</ULinkBase>
|
||||||
</ULinkBase>
|
</ULink>
|
||||||
</ULink>
|
|
||||||
</slot>
|
|
||||||
</DropdownMenu.Item>
|
</DropdownMenu.Item>
|
||||||
</template>
|
</template>
|
||||||
</DropdownMenu.Group>
|
</DropdownMenu.Group>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import type { AppConfig } from '@nuxt/schema'
|
|||||||
import _appConfig from '#build/app.config'
|
import _appConfig from '#build/app.config'
|
||||||
import theme from '#build/ui/navigation-menu'
|
import theme from '#build/ui/navigation-menu'
|
||||||
import type { AvatarProps, BadgeProps, LinkProps, SeparatorProps } from '#ui/types'
|
import type { AvatarProps, BadgeProps, LinkProps, SeparatorProps } from '#ui/types'
|
||||||
|
import type { DynamicSlots } from '#ui/types/utils'
|
||||||
|
|
||||||
const appConfig = _appConfig as AppConfig & { ui: { navigationMenu: Partial<typeof theme> } }
|
const appConfig = _appConfig as AppConfig & { ui: { navigationMenu: Partial<typeof theme> } }
|
||||||
|
|
||||||
@@ -31,13 +32,12 @@ export interface NavigationMenuEmits extends NavigationMenuRootEmits {}
|
|||||||
|
|
||||||
type SlotProps<T> = (props: { item: T, index: number, active?: boolean }) => any
|
type SlotProps<T> = (props: { item: T, index: number, active?: boolean }) => any
|
||||||
|
|
||||||
export interface NavigationMenuSlots<T> {
|
export type NavigationMenuSlots<T extends { slot?: string }> = {
|
||||||
leading: SlotProps<T>
|
leading: SlotProps<T>
|
||||||
label: SlotProps<T>
|
label: SlotProps<T>
|
||||||
trailing: SlotProps<T>
|
trailing: SlotProps<T>
|
||||||
item: SlotProps<T>
|
item: SlotProps<T>
|
||||||
[key: string]: SlotProps<T>
|
} & DynamicSlots<T, SlotProps<T>>
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script setup lang="ts" generic="T extends NavigationMenuItem">
|
<script setup lang="ts" generic="T extends NavigationMenuItem">
|
||||||
@@ -63,10 +63,10 @@ const lists = computed(() => props.items?.length ? (Array.isArray(props.items[0]
|
|||||||
<template v-for="(list, listIndex) in lists" :key="`list-${listIndex}`">
|
<template v-for="(list, listIndex) in lists" :key="`list-${listIndex}`">
|
||||||
<NavigationMenuList :class="ui.list()">
|
<NavigationMenuList :class="ui.list()">
|
||||||
<NavigationMenuItem v-for="(item, index) in list" :key="`list-${listIndex}-${index}`" :value="item.value || String(index)" :class="ui.item()">
|
<NavigationMenuItem v-for="(item, index) in list" :key="`list-${listIndex}-${index}`" :value="item.value || String(index)" :class="ui.item()">
|
||||||
<slot :name="item.slot || 'item'" :item="item" :index="index">
|
<ULink v-slot="{ active, ...slotProps }" v-bind="omit(item, ['label', 'icon', 'avatar', 'badge', 'slot', 'select'])" custom>
|
||||||
<ULink v-slot="{ active, ...slotProps }" v-bind="omit(item, ['label', 'icon', 'avatar', 'badge', 'select'])" custom>
|
<NavigationMenuLink as-child :active="active" @select="item.select">
|
||||||
<NavigationMenuLink as-child :active="active" @select="item.select">
|
<ULinkBase v-bind="slotProps" :class="ui.link({ active, disabled: !!item.disabled })">
|
||||||
<ULinkBase v-bind="slotProps" :class="ui.link({ active, disabled: !!item.disabled })">
|
<slot :name="item.slot || 'item'" :item="item" :index="index">
|
||||||
<slot name="leading" :item="item" :active="active" :index="index">
|
<slot name="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 })" />
|
<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 })" />
|
<UIcon v-else-if="item.icon" :name="item.icon" :class="ui.linkLeadingIcon({ active, disabled: !!item.disabled })" />
|
||||||
@@ -89,10 +89,10 @@ const lists = computed(() => props.items?.length ? (Array.isArray(props.items[0]
|
|||||||
/>
|
/>
|
||||||
</slot>
|
</slot>
|
||||||
</span>
|
</span>
|
||||||
</ULinkBase>
|
</slot>
|
||||||
</NavigationMenuLink>
|
</ULinkBase>
|
||||||
</ULink>
|
</NavigationMenuLink>
|
||||||
</slot>
|
</ULink>
|
||||||
</NavigationMenuItem>
|
</NavigationMenuItem>
|
||||||
</NavigationMenuList>
|
</NavigationMenuList>
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import type { AppConfig } from '@nuxt/schema'
|
|||||||
import _appConfig from '#build/app.config'
|
import _appConfig from '#build/app.config'
|
||||||
import theme from '#build/ui/tabs'
|
import theme from '#build/ui/tabs'
|
||||||
import type { AvatarProps } from '#ui/types'
|
import type { AvatarProps } from '#ui/types'
|
||||||
|
import type { DynamicSlots } from '#ui/types/utils'
|
||||||
|
|
||||||
const appConfig = _appConfig as AppConfig & { ui: { tabs: Partial<typeof theme> } }
|
const appConfig = _appConfig as AppConfig & { ui: { tabs: Partial<typeof theme> } }
|
||||||
|
|
||||||
@@ -31,14 +32,13 @@ export interface TabsEmits extends TabsRootEmits {}
|
|||||||
|
|
||||||
type SlotProps<T> = (props: { item: T, index: number }) => any
|
type SlotProps<T> = (props: { item: T, index: number }) => any
|
||||||
|
|
||||||
export type TabsSlots<T> = {
|
export type TabsSlots<T extends { slot?: string }> = {
|
||||||
default: SlotProps<T>
|
default: SlotProps<T>
|
||||||
leading: SlotProps<T>
|
leading: SlotProps<T>
|
||||||
label: SlotProps<T>
|
label: SlotProps<T>
|
||||||
trailing: SlotProps<T>
|
trailing: SlotProps<T>
|
||||||
content: SlotProps<T>
|
content: SlotProps<T>
|
||||||
[key: string]: SlotProps<T>
|
} & DynamicSlots<T, SlotProps<T>>
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script setup lang="ts" generic="T extends TabsItem">
|
<script setup lang="ts" generic="T extends TabsItem">
|
||||||
|
|||||||
3
src/runtime/types/utils.d.ts
vendored
3
src/runtime/types/utils.d.ts
vendored
@@ -1,3 +1,6 @@
|
|||||||
export type DeepPartial<T> = Partial<{
|
export type DeepPartial<T> = Partial<{
|
||||||
[P in keyof T]: DeepPartial<T[P]> | { [key: string]: string | object }
|
[P in keyof T]: DeepPartial<T[P]> | { [key: string]: string | object }
|
||||||
}>
|
}>
|
||||||
|
|
||||||
|
export type DynamicSlots<T extends { slot?: string }, SlotProps, Slot = T['slot']> =
|
||||||
|
Record<string, SlotProps> & (Slot extends string ? Record<Slot, SlotProps> : Record<string, never>)
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ describe('Tabs', () => {
|
|||||||
}, {
|
}, {
|
||||||
label: 'Tab3',
|
label: 'Tab3',
|
||||||
icon: 'i-heroicons-bell',
|
icon: 'i-heroicons-bell',
|
||||||
content: 'Finally, this is the content for Tab3'
|
content: 'Finally, this is the content for Tab3',
|
||||||
|
slot: 'custom'
|
||||||
}]
|
}]
|
||||||
|
|
||||||
const props = { items }
|
const props = { items }
|
||||||
|
|||||||
Reference in New Issue
Block a user