fix(sidebar): highlight destructive more menus

This commit is contained in:
zipg authored and GitHub committed 2026-08-14 17:46:54 +08:00
1 parent 518a8b593c
commit 02eed8c683
3 files changed
+52 -2

No files matched your search

@@ -4352,6 +4352,7 @@ function moreActionsSubmenu(children: ContextMenuItem[]): ContextMenuItem {
return {
label: t("common.more"),
icon: ListTree,
variant: "destructive",
children,
};
}
@@ -243,6 +243,11 @@ function itemButtonClass(variant?: "default" | "destructive") {
];
}
function activeSubmenuTriggerClass(item: ContextMenuItem, index: number) {
if (activeSubIndex.value !== index) return "";
return item.variant === "destructive" ? "bg-destructive/10 text-destructive" : "bg-accent text-accent-foreground";
}
function itemIsDisabled(item: ContextMenuItem): boolean {
return typeof item.disabled === "function" ? item.disabled() : !!item.disabled;
}
@@ -271,7 +276,7 @@ onBeforeUnmount(() => {
<div v-if="item.separator" class="-mx-1 my-1 flex items-center px-1">
<div class="h-px flex-1 bg-border/70" />
</div>
<button v-else :disabled="itemIsDisabled(item)" :class="[...itemButtonClass(item.variant), activeSubIndex === index ? 'bg-accent text-accent-foreground' : '']" @click="handleItemClick(item)" @mouseenter="(e) => onItemMouseEnter(index, e)" @mouseleave="onItemMouseLeave">
<button v-else :disabled="itemIsDisabled(item)" :class="[...itemButtonClass(item.variant), activeSubmenuTriggerClass(item, index)]" @click="handleItemClick(item)" @mouseenter="(e) => onItemMouseEnter(index, e)" @mouseleave="onItemMouseLeave">
<span class="flex size-4 shrink-0 items-center justify-center">
<Check v-if="item.checked" class="size-4 text-primary" />
<component :is="item.icon" v-else-if="item.icon" :class="['size-4', item.iconClass]" />
@@ -280,7 +285,7 @@ onBeforeUnmount(() => {
<span v-if="item.shortcut" class="ml-8 inline-flex shrink-0 items-center gap-1 text-muted-foreground">
<kbd v-for="key in shortcutKeys(item.shortcut)" :key="key" class="min-w-4 rounded border border-border/70 bg-muted/60 px-1 py-0.5 text-center font-mono text-[10px] leading-none text-muted-foreground shadow-xs">{{ key }}</kbd>
</span>
<ChevronRight v-if="item.children?.length" class="ml-auto size-4 text-muted-foreground/80" />
<ChevronRight v-if="item.children?.length" :class="['ml-auto size-4', item.variant === 'destructive' ? 'text-destructive' : 'text-muted-foreground/80']" />
</button>
</template>
</template>
@@ -251,6 +251,50 @@ describe("CustomContextMenu lifecycle", () => {
app.unmount();
});
it("keeps a destructive submenu trigger red while expanded", async () => {
const root = defineComponent({
setup() {
return () =>
h(
CustomContextMenu,
{
items: [
{
label: "More",
variant: "destructive",
children: [{ label: "Drop table", variant: "destructive" }],
},
],
},
{
default: ({ onContextMenu }: { onContextMenu: (event: MouseEvent) => void }) => h("div", { id: "context-target", onContextmenu: onContextMenu }, "Target"),
},
);
},
});
const container = document.createElement("div");
mountedContainers.push(container);
document.body.append(container);
const app = createApp(root);
app.mount(container);
container.querySelector("#context-target")?.dispatchEvent(new MouseEvent("contextmenu", { bubbles: true }));
await nextTick();
const trigger = Array.from(document.body.querySelectorAll("button")).find((button) => button.textContent?.includes("More"));
expect(trigger?.classList.contains("text-destructive")).toBe(true);
expect(trigger?.querySelector("svg")?.classList.contains("text-destructive")).toBe(true);
trigger?.dispatchEvent(new MouseEvent("mouseenter", { bubbles: true }));
await nextTick();
expect(trigger?.classList.contains("bg-destructive/10")).toBe(true);
expect(trigger?.classList.contains("text-accent-foreground")).toBe(false);
expect(document.body.textContent).toContain("Drop table");
app.unmount();
});
it("resolves lazy menu items again for every open", async () => {
let copied = false;
const items = vi.fn(() =>