mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-14 20:19:34 +01:00
fix(ContextMenu/DropdownMenu): wrap groups in a viewport
Resolves #3315
This commit is contained in:
@@ -109,68 +109,70 @@ const groups = computed<ContextMenuItem[][]>(() =>
|
||||
<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, item.ui?.label, item.class] })">
|
||||
<ReuseItemTemplate :item="item" :index="index" />
|
||||
</ContextMenu.Label>
|
||||
<ContextMenu.Separator v-else-if="item.type === 'separator'" :class="ui.separator({ class: [uiOverride?.separator, item.ui?.separator, item.class] })" />
|
||||
<ContextMenu.Sub v-else-if="item?.children?.length" :open="item.open" :default-open="item.defaultOpen">
|
||||
<ContextMenu.SubTrigger
|
||||
as="button"
|
||||
type="button"
|
||||
<div role="presentation" :class="ui.viewport({ class: props.ui?.viewport })">
|
||||
<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, item.ui?.label, item.class] })">
|
||||
<ReuseItemTemplate :item="item" :index="index" />
|
||||
</ContextMenu.Label>
|
||||
<ContextMenu.Separator v-else-if="item.type === 'separator'" :class="ui.separator({ class: [uiOverride?.separator, item.ui?.separator, item.class] })" />
|
||||
<ContextMenu.Sub v-else-if="item?.children?.length" :open="item.open" :default-open="item.defaultOpen">
|
||||
<ContextMenu.SubTrigger
|
||||
as="button"
|
||||
type="button"
|
||||
:disabled="item.disabled"
|
||||
:text-value="get(item, props.labelKey as string)"
|
||||
:class="ui.item({ class: [uiOverride?.item, item.ui?.item, item.class], color: item?.color })"
|
||||
>
|
||||
<ReuseItemTemplate :item="item" :index="index" />
|
||||
</ContextMenu.SubTrigger>
|
||||
|
||||
<UContextMenuContent
|
||||
sub
|
||||
:class="props.class"
|
||||
:ui="ui"
|
||||
:ui-override="uiOverride"
|
||||
:portal="portal"
|
||||
:items="(item.children as T)"
|
||||
:align-offset="-4"
|
||||
:label-key="labelKey"
|
||||
:checked-icon="checkedIcon"
|
||||
:loading-icon="loadingIcon"
|
||||
:external-icon="externalIcon"
|
||||
v-bind="item.content"
|
||||
>
|
||||
<template v-for="(_, name) in proxySlots" #[name]="slotData">
|
||||
<slot :name="(name as keyof ContextMenuSlots<T>)" v-bind="slotData" />
|
||||
</template>
|
||||
</UContextMenuContent>
|
||||
</ContextMenu.Sub>
|
||||
<ContextMenu.CheckboxItem
|
||||
v-else-if="item.type === 'checkbox'"
|
||||
:model-value="item.checked"
|
||||
:disabled="item.disabled"
|
||||
:text-value="get(item, props.labelKey as string)"
|
||||
:class="ui.item({ class: [uiOverride?.item, item.ui?.item, item.class], color: item?.color })"
|
||||
@update:model-value="item.onUpdateChecked"
|
||||
@select="item.onSelect"
|
||||
>
|
||||
<ReuseItemTemplate :item="item" :index="index" />
|
||||
</ContextMenu.SubTrigger>
|
||||
|
||||
<UContextMenuContent
|
||||
sub
|
||||
:class="props.class"
|
||||
:ui="ui"
|
||||
:ui-override="uiOverride"
|
||||
:portal="portal"
|
||||
:items="(item.children as T)"
|
||||
:align-offset="-4"
|
||||
:label-key="labelKey"
|
||||
:checked-icon="checkedIcon"
|
||||
:loading-icon="loadingIcon"
|
||||
:external-icon="externalIcon"
|
||||
v-bind="item.content"
|
||||
</ContextMenu.CheckboxItem>
|
||||
<ContextMenu.Item
|
||||
v-else
|
||||
as-child
|
||||
:disabled="item.disabled"
|
||||
:text-value="get(item, props.labelKey as string)"
|
||||
@select="item.onSelect"
|
||||
>
|
||||
<template v-for="(_, name) in proxySlots" #[name]="slotData">
|
||||
<slot :name="(name as keyof ContextMenuSlots<T>)" v-bind="slotData" />
|
||||
</template>
|
||||
</UContextMenuContent>
|
||||
</ContextMenu.Sub>
|
||||
<ContextMenu.CheckboxItem
|
||||
v-else-if="item.type === 'checkbox'"
|
||||
:model-value="item.checked"
|
||||
:disabled="item.disabled"
|
||||
:text-value="get(item, props.labelKey as string)"
|
||||
:class="ui.item({ class: [uiOverride?.item, item.ui?.item, item.class], color: item?.color })"
|
||||
@update:model-value="item.onUpdateChecked"
|
||||
@select="item.onSelect"
|
||||
>
|
||||
<ReuseItemTemplate :item="item" :index="index" />
|
||||
</ContextMenu.CheckboxItem>
|
||||
<ContextMenu.Item
|
||||
v-else
|
||||
as-child
|
||||
:disabled="item.disabled"
|
||||
:text-value="get(item, props.labelKey as string)"
|
||||
@select="item.onSelect"
|
||||
>
|
||||
<ULink v-slot="{ active, ...slotProps }" v-bind="pickLinkProps(item as Omit<ContextMenuItem, 'type'>)" custom>
|
||||
<ULinkBase v-bind="slotProps" :class="ui.item({ class: [uiOverride?.item, item.ui?.item, item.class], active, color: item?.color })">
|
||||
<ReuseItemTemplate :item="item" :active="active" :index="index" />
|
||||
</ULinkBase>
|
||||
</ULink>
|
||||
</ContextMenu.Item>
|
||||
</template>
|
||||
</ContextMenu.Group>
|
||||
<ULink v-slot="{ active, ...slotProps }" v-bind="pickLinkProps(item as Omit<ContextMenuItem, 'type'>)" custom>
|
||||
<ULinkBase v-bind="slotProps" :class="ui.item({ class: [uiOverride?.item, item.ui?.item, item.class], active, color: item?.color })">
|
||||
<ReuseItemTemplate :item="item" :active="active" :index="index" />
|
||||
</ULinkBase>
|
||||
</ULink>
|
||||
</ContextMenu.Item>
|
||||
</template>
|
||||
</ContextMenu.Group>
|
||||
</div>
|
||||
|
||||
<slot />
|
||||
|
||||
|
||||
@@ -115,70 +115,72 @@ const groups = computed<DropdownMenuItem[][]>(() =>
|
||||
<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, item.ui?.label, item.class] })">
|
||||
<ReuseItemTemplate :item="item" :index="index" />
|
||||
</DropdownMenu.Label>
|
||||
<DropdownMenu.Separator v-else-if="item.type === 'separator'" :class="ui.separator({ class: [uiOverride?.separator, item.ui?.separator, item.class] })" />
|
||||
<DropdownMenu.Sub v-else-if="item?.children?.length" :open="item.open" :default-open="item.defaultOpen">
|
||||
<DropdownMenu.SubTrigger
|
||||
as="button"
|
||||
type="button"
|
||||
<div role="presentation" :class="ui.viewport({ class: props.ui?.viewport })">
|
||||
<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, item.ui?.label, item.class] })">
|
||||
<ReuseItemTemplate :item="item" :index="index" />
|
||||
</DropdownMenu.Label>
|
||||
<DropdownMenu.Separator v-else-if="item.type === 'separator'" :class="ui.separator({ class: [uiOverride?.separator, item.ui?.separator, item.class] })" />
|
||||
<DropdownMenu.Sub v-else-if="item?.children?.length" :open="item.open" :default-open="item.defaultOpen">
|
||||
<DropdownMenu.SubTrigger
|
||||
as="button"
|
||||
type="button"
|
||||
:disabled="item.disabled"
|
||||
:text-value="get(item, props.labelKey as string)"
|
||||
:class="ui.item({ class: [uiOverride?.item, item.ui?.item, item.class], color: item?.color })"
|
||||
>
|
||||
<ReuseItemTemplate :item="item" :index="index" />
|
||||
</DropdownMenu.SubTrigger>
|
||||
|
||||
<UDropdownMenuContent
|
||||
sub
|
||||
:class="props.class"
|
||||
:ui="ui"
|
||||
:ui-override="uiOverride"
|
||||
:portal="portal"
|
||||
:items="(item.children as T)"
|
||||
align="start"
|
||||
:align-offset="-4"
|
||||
:side-offset="3"
|
||||
:label-key="labelKey"
|
||||
:checked-icon="checkedIcon"
|
||||
:loading-icon="loadingIcon"
|
||||
:external-icon="externalIcon"
|
||||
v-bind="item.content"
|
||||
>
|
||||
<template v-for="(_, name) in proxySlots" #[name]="slotData">
|
||||
<slot :name="(name as keyof DropdownMenuContentSlots<T>)" v-bind="slotData" />
|
||||
</template>
|
||||
</UDropdownMenuContent>
|
||||
</DropdownMenu.Sub>
|
||||
<DropdownMenu.CheckboxItem
|
||||
v-else-if="item.type === 'checkbox'"
|
||||
:model-value="item.checked"
|
||||
:disabled="item.disabled"
|
||||
:text-value="get(item, props.labelKey as string)"
|
||||
:class="ui.item({ class: [uiOverride?.item, item.ui?.item, item.class], color: item?.color })"
|
||||
@update:model-value="item.onUpdateChecked"
|
||||
@select="item.onSelect"
|
||||
>
|
||||
<ReuseItemTemplate :item="item" :index="index" />
|
||||
</DropdownMenu.SubTrigger>
|
||||
|
||||
<UDropdownMenuContent
|
||||
sub
|
||||
:class="props.class"
|
||||
:ui="ui"
|
||||
:ui-override="uiOverride"
|
||||
:portal="portal"
|
||||
:items="(item.children as T)"
|
||||
align="start"
|
||||
:align-offset="-4"
|
||||
:side-offset="3"
|
||||
:label-key="labelKey"
|
||||
:checked-icon="checkedIcon"
|
||||
:loading-icon="loadingIcon"
|
||||
:external-icon="externalIcon"
|
||||
v-bind="item.content"
|
||||
</DropdownMenu.CheckboxItem>
|
||||
<DropdownMenu.Item
|
||||
v-else
|
||||
as-child
|
||||
:disabled="item.disabled"
|
||||
:text-value="get(item, props.labelKey as string)"
|
||||
@select="item.onSelect"
|
||||
>
|
||||
<template v-for="(_, name) in proxySlots" #[name]="slotData">
|
||||
<slot :name="(name as keyof DropdownMenuContentSlots<T>)" v-bind="slotData" />
|
||||
</template>
|
||||
</UDropdownMenuContent>
|
||||
</DropdownMenu.Sub>
|
||||
<DropdownMenu.CheckboxItem
|
||||
v-else-if="item.type === 'checkbox'"
|
||||
:model-value="item.checked"
|
||||
:disabled="item.disabled"
|
||||
:text-value="get(item, props.labelKey as string)"
|
||||
:class="ui.item({ class: [uiOverride?.item, item.ui?.item, item.class], color: item?.color })"
|
||||
@update:model-value="item.onUpdateChecked"
|
||||
@select="item.onSelect"
|
||||
>
|
||||
<ReuseItemTemplate :item="item" :index="index" />
|
||||
</DropdownMenu.CheckboxItem>
|
||||
<DropdownMenu.Item
|
||||
v-else
|
||||
as-child
|
||||
:disabled="item.disabled"
|
||||
:text-value="get(item, props.labelKey as string)"
|
||||
@select="item.onSelect"
|
||||
>
|
||||
<ULink v-slot="{ active, ...slotProps }" v-bind="pickLinkProps(item as Omit<DropdownMenuItem, 'type'>)" custom>
|
||||
<ULinkBase v-bind="slotProps" :class="ui.item({ class: [uiOverride?.item, item.ui?.item, item.class], color: item?.color, active })">
|
||||
<ReuseItemTemplate :item="item" :active="active" :index="index" />
|
||||
</ULinkBase>
|
||||
</ULink>
|
||||
</DropdownMenu.Item>
|
||||
</template>
|
||||
</DropdownMenu.Group>
|
||||
<ULink v-slot="{ active, ...slotProps }" v-bind="pickLinkProps(item as Omit<DropdownMenuItem, 'type'>)" custom>
|
||||
<ULinkBase v-bind="slotProps" :class="ui.item({ class: [uiOverride?.item, item.ui?.item, item.class], color: item?.color, active })">
|
||||
<ReuseItemTemplate :item="item" :active="active" :index="index" />
|
||||
</ULinkBase>
|
||||
</ULink>
|
||||
</DropdownMenu.Item>
|
||||
</template>
|
||||
</DropdownMenu.Group>
|
||||
</div>
|
||||
|
||||
<slot />
|
||||
|
||||
|
||||
@@ -2,7 +2,8 @@ import type { ModuleOptions } from '../module'
|
||||
|
||||
export default (options: Required<ModuleOptions>) => ({
|
||||
slots: {
|
||||
content: 'min-w-32 bg-default shadow-lg rounded-md ring ring-default divide-y divide-default overflow-y-auto scroll-py-1 data-[state=open]:animate-[scale-in_100ms_ease-out] data-[state=closed]:animate-[scale-out_100ms_ease-in] origin-(--reka-context-menu-content-transform-origin)',
|
||||
content: 'min-w-32 bg-default shadow-lg rounded-md ring ring-default overflow-hidden data-[state=open]:animate-[scale-in_100ms_ease-out] data-[state=closed]:animate-[scale-out_100ms_ease-in] origin-(--reka-context-menu-content-transform-origin) flex flex-col',
|
||||
viewport: 'relative divide-y divide-default scroll-py-1 overflow-y-auto flex-1',
|
||||
group: 'p-1 isolate',
|
||||
label: 'w-full flex items-center font-semibold text-highlighted',
|
||||
separator: '-mx-1 my-1 h-px bg-border',
|
||||
|
||||
@@ -2,7 +2,8 @@ import type { ModuleOptions } from '../module'
|
||||
|
||||
export default (options: Required<ModuleOptions>) => ({
|
||||
slots: {
|
||||
content: 'min-w-32 bg-default shadow-lg rounded-md ring ring-default divide-y divide-default overflow-y-auto scroll-py-1 data-[state=open]:animate-[scale-in_100ms_ease-out] data-[state=closed]:animate-[scale-out_100ms_ease-in] origin-(--reka-dropdown-menu-content-transform-origin)',
|
||||
content: 'min-w-32 bg-default shadow-lg rounded-md ring ring-default overflow-hidden data-[state=open]:animate-[scale-in_100ms_ease-out] data-[state=closed]:animate-[scale-out_100ms_ease-in] origin-(--reka-dropdown-menu-content-transform-origin) flex flex-col',
|
||||
viewport: 'relative divide-y divide-default scroll-py-1 overflow-y-auto flex-1',
|
||||
arrow: 'fill-default',
|
||||
group: 'p-1 isolate',
|
||||
label: 'w-full flex items-center font-semibold text-highlighted',
|
||||
|
||||
Reference in New Issue
Block a user