feat(components): add new content-top and content-bottom slots (#3886)

Co-authored-by: Benjamin Canac <canacb1@gmail.com>
This commit is contained in:
Guillaume Chau
2025-04-23 11:02:50 +02:00
committed by GitHub
parent 9ca213bd33
commit 1a46394668
9 changed files with 36 additions and 8 deletions

View File

@@ -59,14 +59,6 @@ const items = [{
<template #custom="{ item }">
<span class="text-muted">Custom: {{ item.content }}</span>
</template>
<template #list-trailing>
<UButton
icon="lucide:refresh-cw"
variant="soft"
class="ml-2"
/>
</template>
</UTabs>
</div>
</div>

View File

@@ -90,6 +90,8 @@ export type ContextMenuSlots<
'item-leading': SlotProps<T>
'item-label': SlotProps<T>
'item-trailing': SlotProps<T>
'content-top': (props?: {}) => any
'content-bottom': (props?: {}) => any
} & DynamicSlots<MergeTypes<T>, 'leading' | 'label' | 'trailing', { active?: boolean, index: number }>
</script>

View File

@@ -107,6 +107,8 @@ const groups = computed<ContextMenuItem[][]>(() =>
<ContextMenu.Portal v-bind="portalProps">
<component :is="sub ? ContextMenu.SubContent : ContextMenu.Content" :class="props.class" v-bind="contentProps">
<slot name="content-top" />
<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({ class: uiOverride?.label })">
@@ -171,6 +173,8 @@ const groups = computed<ContextMenuItem[][]>(() =>
</ContextMenu.Group>
<slot />
<slot name="content-bottom" />
</component>
</ContextMenu.Portal>
</template>

View File

@@ -98,6 +98,8 @@ export type DropdownMenuSlots<
'item-leading': SlotProps<T>
'item-label': SlotProps<T>
'item-trailing': SlotProps<T>
'content-top': (props?: {}) => any
'content-bottom': (props?: {}) => any
} & DynamicSlots<MergeTypes<T>, 'leading' | 'label' | 'trailing', { active?: boolean, index: number }>
</script>

View File

@@ -113,6 +113,8 @@ const groups = computed<DropdownMenuItem[][]>(() =>
<DropdownMenu.Portal v-bind="portalProps">
<component :is="sub ? DropdownMenu.SubContent : DropdownMenu.Content" :class="props.class" v-bind="contentProps">
<slot name="content-top" />
<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({ class: uiOverride?.label })">
@@ -179,6 +181,8 @@ const groups = computed<DropdownMenuItem[][]>(() =>
</DropdownMenu.Group>
<slot />
<slot name="content-bottom" />
</component>
</DropdownMenu.Portal>
</template>

View File

@@ -162,6 +162,8 @@ export interface InputMenuSlots<
'item-trailing': SlotProps<T>
'tags-item-text': SlotProps<T>
'tags-item-delete': SlotProps<T>
'content-top': (props?: {}) => any
'content-bottom': (props?: {}) => any
'create-item-label'(props: { item: string }): any
}
</script>
@@ -478,6 +480,8 @@ defineExpose({
<ComboboxPortal v-bind="portalProps">
<ComboboxContent :class="ui.content({ class: props.ui?.content })" v-bind="contentProps">
<slot name="content-top" />
<ComboboxEmpty :class="ui.empty({ class: props.ui?.empty })">
<slot name="empty" :search-term="searchTerm">
{{ searchTerm ? t('inputMenu.noMatch', { searchTerm }) : t('inputMenu.noData') }}
@@ -537,6 +541,8 @@ defineExpose({
<ReuseCreateItemTemplate v-if="createItem && createItemPosition === 'bottom'" />
</ComboboxViewport>
<slot name="content-bottom" />
<ComboboxArrow v-if="!!arrow" v-bind="arrowProps" :class="ui.arrow({ class: props.ui?.arrow })" />
</ComboboxContent>
</ComboboxPortal>

View File

@@ -123,6 +123,8 @@ export type NavigationMenuSlots<
'item-label': SlotProps<T>
'item-trailing': SlotProps<T>
'item-content': SlotProps<T>
'list-leading': (props?: {}) => any
'list-trailing': (props?: {}) => any
} & DynamicSlots<MergeTypes<T>, 'leading' | 'label' | 'trailing' | 'content', { index: number, active?: boolean }>
</script>
@@ -303,6 +305,8 @@ const lists = computed<NavigationMenuItem[][]>(() =>
</DefineItemTemplate>
<NavigationMenuRoot v-bind="rootProps" :data-collapsed="collapsed" :class="ui.root({ class: [props.class, props.ui?.root] })">
<slot name="list-leading" />
<template v-for="(list, listIndex) in lists" :key="`list-${listIndex}`">
<NavigationMenuList :class="ui.list({ class: props.ui?.list })">
<ReuseItemTemplate v-for="(item, index) in list" :key="`list-${listIndex}-${index}`" :item="item" :index="index" :class="ui.item({ class: props.ui?.item })" />
@@ -311,6 +315,8 @@ const lists = computed<NavigationMenuItem[][]>(() =>
<div v-if="orientation === 'vertical' && listIndex < lists.length - 1" :class="ui.separator({ class: props.ui?.separator })" />
</template>
<slot name="list-trailing" />
<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 })" />

View File

@@ -126,6 +126,8 @@ export interface SelectSlots<
'item-leading': SlotProps<T>
'item-label': SlotProps<T>
'item-trailing': SlotProps<T>
'content-top': (props?: {}) => any
'content-bottom': (props?: {}) => any
}
</script>
@@ -264,6 +266,8 @@ function isSelectItem(item: SelectItem): item is SelectItemBase {
<SelectPortal v-bind="portalProps">
<SelectContent :class="ui.content({ class: props.ui?.content })" v-bind="contentProps">
<slot name="content-top" />
<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}`">
@@ -313,6 +317,8 @@ function isSelectItem(item: SelectItem): item is SelectItemBase {
</SelectGroup>
</SelectViewport>
<slot name="content-bottom" />
<SelectArrow v-if="!!arrow" v-bind="arrowProps" :class="ui.arrow({ class: props.ui?.arrow })" />
</SelectContent>
</SelectPortal>

View File

@@ -156,6 +156,8 @@ export interface SelectMenuSlots<
'item-leading': SlotProps<T>
'item-label': SlotProps<T>
'item-trailing': SlotProps<T>
'content-top': (props?: {}) => any
'content-bottom': (props?: {}) => any
'create-item-label'(props: { item: string }): any
}
</script>
@@ -401,6 +403,8 @@ function isSelectItem(item: SelectMenuItem): item is _SelectMenuItem {
<ComboboxPortal v-bind="portalProps">
<ComboboxContent :class="ui.content({ class: props.ui?.content })" v-bind="contentProps">
<FocusScope trapped :class="ui.focusScope({ class: props.ui?.focusScope })">
<slot name="content-top" />
<ComboboxInput v-if="!!searchInput" v-model="searchTerm" :display-value="() => searchTerm" as-child>
<UInput autofocus autocomplete="off" v-bind="searchInputProps" :class="ui.input({ class: props.ui?.input })" />
</ComboboxInput>
@@ -463,6 +467,8 @@ function isSelectItem(item: SelectMenuItem): item is _SelectMenuItem {
<ReuseCreateItemTemplate v-if="createItem && createItemPosition === 'bottom'" />
</ComboboxViewport>
<slot name="content-bottom" />
</FocusScope>
<ComboboxArrow v-if="!!arrow" v-bind="arrowProps" :class="ui.arrow({ class: props.ui?.arrow })" />