Desktop: custom context menus everywhere, never the browser's menu

One ContextMenu in the design system (keyboard, type-ahead, submenus, check items,
disabled reasons, Shift+F10 / Menu key, viewport placement, light and dark). The
native menu is prevented globally; text fields keep Cut / Copy / Paste / Select all.
Menus on sidebar and portfolio apps, app hero, releases, store art, live art,
reviews, checks, editor fields, catalog, prices, accounts, app folders, ignored.
This commit is contained in:
CRTkafa 2026-10-23 08:59:14 +08:00
parent 944b24fe59
commit dece83c3e8
23 changed files with 839 additions and 28 deletions

View file

@ -196,3 +196,27 @@
/* ---------- reduced motion ---------- */
@media (prefers-reduced-motion: reduce) { .ds-hero-shots img, .ds-stat.clickable, .ds-scrim, .ds-dialog { transform: none !important; transition: none; animation: none; } }
:root[data-motion="reduce"] .ds-hero-shots img, :root[data-motion="reduce"] .ds-stat.clickable, :root[data-motion="reduce"] .ds-scrim, :root[data-motion="reduce"] .ds-dialog { transform: none !important; transition: none; animation: none; }
/* ---------- context menu (ui/ContextMenu.tsx) ---------- */
.ds-menu { position: fixed; z-index: 1000; min-width: 200px; max-width: 320px; max-height: calc(100vh - 16px); overflow: auto; padding: 5px;
border-radius: 12px; background: color-mix(in srgb, var(--ds-raised) 94%, transparent); border: 1px solid var(--ds-stroke-strong);
box-shadow: 0 1px 0 rgba(255, 255, 255, .05) inset, 0 14px 38px -10px rgba(10, 14, 30, .38), 0 3px 10px -4px rgba(10, 14, 30, .22);
-webkit-backdrop-filter: blur(18px) saturate(1.4); backdrop-filter: blur(18px) saturate(1.4);
font-size: var(--ds-t-body); color: var(--ink); outline: none; user-select: none;
opacity: 0; transform: scale(.97); transform-origin: top left; transition: opacity 120ms var(--ease), transform 120ms var(--ease); }
.ds-menu.in { opacity: 1; transform: none; }
.ds-menu-item { display: flex; align-items: center; gap: 9px; height: 30px; padding: 0 10px 0 8px; border-radius: 7px; cursor: default; white-space: nowrap; }
.ds-menu-item.active { background: var(--accent-soft); color: var(--ink); }
.ds-menu-item.active .ds-menu-ic { color: var(--accent); }
.ds-menu-item.danger { color: var(--bad-solid); }
.ds-menu-item.danger.active { background: var(--bad-bg); color: var(--bad-ink); }
.ds-menu-item.danger .ds-menu-ic, .ds-menu-item.danger.active .ds-menu-ic { color: inherit; }
.ds-menu-item.disabled { opacity: .45; }
.ds-menu-ic { width: 16px; height: 16px; flex: none; display: grid; place-items: center; color: var(--muted); }
.ds-menu-ic svg { width: 15px; height: 15px; }
.ds-menu-l { flex: 1; overflow: hidden; text-overflow: ellipsis; }
.ds-menu-k { max-width: 150px; overflow: hidden; text-overflow: ellipsis; margin-left: 18px; color: var(--faint); font-size: var(--ds-t-micro); letter-spacing: .02em; font-variant-numeric: tabular-nums; }
.ds-menu-sub { width: 12px; height: 12px; color: var(--faint); margin-left: 10px; }
.ds-menu-sep { height: 1px; margin: 5px 6px; background: var(--ds-stroke); }
@media (prefers-reduced-motion: reduce) { .ds-menu { transition: none; transform: none; } }
:root[data-motion="reduce"] .ds-menu { transition: none; transform: none; }