Skip to content

Commit c30c026

Browse files
committed
feat: add list-leading and list-trailing slots to ContextMenu, DropdownMenu, InputMenu, NavigationMenu, Select, SelectMenu
1 parent d5bcb0d commit c30c026

14 files changed

+290
-2
lines changed

playground/app/pages/components/context-menu.vue

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,5 +116,47 @@ defineShortcuts(extractShortcuts(items.value))
116116
Color right click here
117117
</div>
118118
</UContextMenu>
119+
120+
<UContextMenu :items="itemsWithColor" :ui="{ content: 'w-48' }" :size="size">
121+
<div class="flex items-center justify-center rounded-md border border-dashed border-(--ui-border-accented) text-sm aspect-video w-72">
122+
With list slots
123+
</div>
124+
125+
<template #list-leading>
126+
<div class="p-1">
127+
<UButtonGroup
128+
size="xs"
129+
class="w-full"
130+
orientation="horizontal"
131+
>
132+
<UButton
133+
label="OR"
134+
color="primary"
135+
block
136+
size="xs"
137+
class="flex-1"
138+
/>
139+
<UButton
140+
label="AND"
141+
color="neutral"
142+
variant="subtle"
143+
block
144+
size="xs"
145+
class="flex-1"
146+
/>
147+
</UButtonGroup>
148+
</div>
149+
</template>
150+
151+
<template #list-trailing>
152+
<div class="p-1">
153+
<UButton
154+
block
155+
variant="ghost"
156+
label="Load more"
157+
/>
158+
</div>
159+
</template>
160+
</UContextMenu>
119161
</div>
120162
</template>

playground/app/pages/components/dropdown-menu.vue

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,46 @@ defineShortcuts(extractShortcuts(items.value))
151151
<UDropdownMenu :items="itemsWithColor" :size="size" arrow :content="{ side: 'bottom', align: 'start' }" :ui="{ content: 'w-48' }">
152152
<UButton label="Color" color="neutral" variant="outline" icon="i-lucide-menu" />
153153
</UDropdownMenu>
154+
155+
<UDropdownMenu :items="itemsWithColor" :size="size" arrow :content="{ side: 'bottom', align: 'start' }" :ui="{ content: 'w-48' }">
156+
<UButton label="With list slots" color="neutral" variant="outline" icon="i-lucide-menu" />
157+
158+
<template #list-leading>
159+
<div class="p-1">
160+
<UButtonGroup
161+
size="xs"
162+
class="w-full"
163+
orientation="horizontal"
164+
>
165+
<UButton
166+
label="OR"
167+
color="primary"
168+
block
169+
size="xs"
170+
class="flex-1"
171+
/>
172+
<UButton
173+
label="AND"
174+
color="neutral"
175+
variant="subtle"
176+
block
177+
size="xs"
178+
class="flex-1"
179+
/>
180+
</UButtonGroup>
181+
</div>
182+
</template>
183+
184+
<template #list-trailing>
185+
<div class="p-1">
186+
<UButton
187+
block
188+
variant="ghost"
189+
label="Load more"
190+
/>
191+
</div>
192+
</template>
193+
</UDropdownMenu>
154194
</div>
155195
</div>
156196
</template>

playground/app/pages/components/input-menu.vue

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,5 +145,51 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
145145
class="w-48"
146146
/>
147147
</div>
148+
<div class="flex items-center gap-4">
149+
<UInputMenu
150+
:items="items"
151+
:model-value="[]"
152+
multiple
153+
icon="i-lucide-search"
154+
placeholder="With list slots..."
155+
class="w-48"
156+
>
157+
<template #list-leading>
158+
<div class="p-1">
159+
<UButtonGroup
160+
size="xs"
161+
class="w-full"
162+
orientation="horizontal"
163+
>
164+
<UButton
165+
label="OR"
166+
color="primary"
167+
block
168+
size="xs"
169+
class="flex-1"
170+
/>
171+
<UButton
172+
label="AND"
173+
color="neutral"
174+
variant="subtle"
175+
block
176+
size="xs"
177+
class="flex-1"
178+
/>
179+
</UButtonGroup>
180+
</div>
181+
</template>
182+
183+
<template #list-trailing>
184+
<div class="p-1">
185+
<UButton
186+
block
187+
variant="ghost"
188+
label="Load more"
189+
/>
190+
</div>
191+
</template>
192+
</UInputMenu>
193+
</div>
148194
</div>
149195
</template>

playground/app/pages/components/navigation-menu.vue

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,20 @@ const items = [
114114
:highlight-color="highlightColor"
115115
:class="highlight && 'data-[orientation=horizontal]:border-b border-(--ui-border)'"
116116
class="data-[orientation=vertical]:data-[collapsed=false]:w-48"
117-
/>
117+
>
118+
<template #list-leading>
119+
<img src="/favicon.ico">
120+
</template>
121+
122+
<template #list-trailing>
123+
<UModal>
124+
<UButton label="Login" />
125+
126+
<template #body>
127+
<USkeleton class="h-20" />
128+
</template>
129+
</UModal>
130+
</template>
131+
</UNavigationMenu>
118132
</div>
119133
</template>

playground/app/pages/components/select-menu.vue

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,5 +138,51 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
138138
</template>
139139
</USelectMenu>
140140
</div>
141+
<div class="flex items-center gap-4">
142+
<USelectMenu
143+
:items="statuses.slice(0, 3)"
144+
:loading="status === 'pending'"
145+
icon="lucide:list"
146+
placeholder="With list slots..."
147+
class="w-48"
148+
value-key="value"
149+
>
150+
<template #list-leading>
151+
<div class="p-1">
152+
<UButtonGroup
153+
size="xs"
154+
class="w-full"
155+
orientation="horizontal"
156+
>
157+
<UButton
158+
label="OR"
159+
color="primary"
160+
block
161+
size="xs"
162+
class="flex-1"
163+
/>
164+
<UButton
165+
label="AND"
166+
color="neutral"
167+
variant="subtle"
168+
block
169+
size="xs"
170+
class="flex-1"
171+
/>
172+
</UButtonGroup>
173+
</div>
174+
</template>
175+
176+
<template #list-trailing>
177+
<div class="p-1">
178+
<UButton
179+
block
180+
variant="ghost"
181+
label="Load more"
182+
/>
183+
</div>
184+
</template>
185+
</USelectMenu>
186+
</div>
141187
</div>
142188
</template>

playground/app/pages/components/select.vue

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,5 +139,51 @@ function getUserAvatar(value: string) {
139139
</template>
140140
</USelect>
141141
</div>
142+
<div class="flex items-center gap-4">
143+
<USelect
144+
:items="statuses.slice(0, 3)"
145+
:loading="status === 'pending'"
146+
icon="lucide:list"
147+
placeholder="With list slots..."
148+
class="w-48"
149+
value-key="value"
150+
>
151+
<template #list-leading>
152+
<div class="p-1">
153+
<UButtonGroup
154+
size="xs"
155+
class="w-full"
156+
orientation="horizontal"
157+
>
158+
<UButton
159+
label="OR"
160+
color="primary"
161+
block
162+
size="xs"
163+
class="flex-1"
164+
/>
165+
<UButton
166+
label="AND"
167+
color="neutral"
168+
variant="subtle"
169+
block
170+
size="xs"
171+
class="flex-1"
172+
/>
173+
</UButtonGroup>
174+
</div>
175+
</template>
176+
177+
<template #list-trailing>
178+
<div class="p-1">
179+
<UButton
180+
block
181+
variant="ghost"
182+
label="Load more"
183+
/>
184+
</div>
185+
</template>
186+
</USelect>
187+
</div>
142188
</div>
143189
</template>

src/runtime/components/ContextMenu.vue

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ export type ContextMenuSlots<
9090
'item-leading': SlotProps<T>
9191
'item-label': SlotProps<T>
9292
'item-trailing': SlotProps<T>
93+
'list-leading': (props?: {}) => any
94+
'list-trailing': (props?: {}) => any
9395
} & DynamicSlots<MergeTypes<T>, 'leading' | 'label' | 'trailing', { active?: boolean, index: number }>
9496

9597
</script>
@@ -141,9 +143,17 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.contextMenu
141143
:loading-icon="loadingIcon"
142144
:external-icon="externalIcon"
143145
>
146+
<template #list-leading>
147+
<slot name="list-leading" />
148+
</template>
149+
144150
<template v-for="(_, name) in proxySlots" #[name]="slotData">
145151
<slot :name="(name as keyof ContextMenuSlots<T>)" v-bind="slotData" />
146152
</template>
153+
154+
<template #list-trailing>
155+
<slot name="list-trailing" />
156+
</template>
147157
</UContextMenuContent>
148158
</ContextMenuRoot>
149159
</template>

src/runtime/components/ContextMenuContent.vue

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ const groups = computed<ContextMenuItem[][]>(() =>
105105

106106
<ContextMenu.Portal :disabled="!portal">
107107
<component :is="sub ? ContextMenu.SubContent : ContextMenu.Content" :class="props.class" v-bind="contentProps">
108+
<slot name="list-leading" />
109+
108110
<ContextMenu.Group v-for="(group, groupIndex) in groups" :key="`group-${groupIndex}`" :class="ui.group({ class: uiOverride?.group })">
109111
<template v-for="(item, index) in group" :key="`group-${groupIndex}-${index}`">
110112
<ContextMenu.Label v-if="item.type === 'label'" :class="ui.label({ class: uiOverride?.label })">
@@ -169,6 +171,8 @@ const groups = computed<ContextMenuItem[][]>(() =>
169171
</ContextMenu.Group>
170172

171173
<slot />
174+
175+
<slot name="list-trailing" />
172176
</component>
173177
</ContextMenu.Portal>
174178
</template>

src/runtime/components/DropdownMenu.vue

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ export type DropdownMenuSlots<
9898
'item-leading': SlotProps<T>
9999
'item-label': SlotProps<T>
100100
'item-trailing': SlotProps<T>
101+
'list-leading': (props?: {}) => any
102+
'list-trailing': (props?: {}) => any
101103
} & DynamicSlots<MergeTypes<T>, 'leading' | 'label' | 'trailing', { active?: boolean, index: number }>
102104

103105
</script>
@@ -151,10 +153,18 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.dropdownMenu
151153
:loading-icon="loadingIcon"
152154
:external-icon="externalIcon"
153155
>
156+
<template #list-leading>
157+
<slot name="list-leading" />
158+
</template>
159+
154160
<template v-for="(_, name) in proxySlots" #[name]="slotData">
155161
<slot :name="(name as keyof DropdownMenuSlots<T>)" v-bind="slotData" />
156162
</template>
157163

164+
<template #list-trailing>
165+
<slot name="list-trailing" />
166+
</template>
167+
158168
<DropdownMenuArrow v-if="!!arrow" v-bind="arrowProps" :class="ui.arrow({ class: props.ui?.arrow })" />
159169
</UDropdownMenuContent>
160170
</DropdownMenuRoot>

src/runtime/components/DropdownMenuContent.vue

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ interface DropdownMenuContentProps<T extends ArrayOrNested<DropdownMenuItem>> ex
3333
interface DropdownMenuContentEmits extends RekaDropdownMenuContentEmits {}
3434

3535
type DropdownMenuContentSlots<T extends ArrayOrNested<DropdownMenuItem>> = Omit<DropdownMenuSlots<T>, 'default'> & {
36-
default(props?: {}): any
36+
'default'(props?: {}): any
37+
'list-leading': (props?: {}) => any
38+
'list-trailing': (props?: {}) => any
3739
}
3840

3941
</script>
@@ -111,6 +113,8 @@ const groups = computed<DropdownMenuItem[][]>(() =>
111113

112114
<DropdownMenu.Portal :disabled="!portal">
113115
<component :is="sub ? DropdownMenu.SubContent : DropdownMenu.Content" :class="props.class" v-bind="contentProps">
116+
<slot name="list-leading" />
117+
114118
<DropdownMenu.Group v-for="(group, groupIndex) in groups" :key="`group-${groupIndex}`" :class="ui.group({ class: uiOverride?.group })">
115119
<template v-for="(item, index) in group" :key="`group-${groupIndex}-${index}`">
116120
<DropdownMenu.Label v-if="item.type === 'label'" :class="ui.label({ class: uiOverride?.label })">
@@ -177,6 +181,8 @@ const groups = computed<DropdownMenuItem[][]>(() =>
177181
</DropdownMenu.Group>
178182

179183
<slot />
184+
185+
<slot name="list-trailing" />
180186
</component>
181187
</DropdownMenu.Portal>
182188
</template>

0 commit comments

Comments
 (0)