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

@ -2,7 +2,7 @@
// the portfolio home as reusable parts. PageHeader, Hero (app hero with screenshots, two-store pipeline, next step),
// Section, StatTile, Pipeline, StatusPill, EmptyState, Tabs, Toolbar, Dialog, plus the per-app tint (useTint / tintStyle)
// and the rendered-screenshot loader (useShots) the cards and the hero share.
import { type CSSProperties, type KeyboardEvent, type ReactNode, useEffect, useId, useRef, useState } from 'react';
import { type CSSProperties, type KeyboardEvent, type MouseEvent as ReactMouseEvent, type ReactNode, useEffect, useId, useRef, useState } from 'react';
import { api, reads, type RenderImage, type StoreRelease, type ToolResult } from '../api';
import { type Key, t } from '../i18n';
import { share } from '../lib/format';
@ -179,14 +179,16 @@ export function PageHeader({ title, subtitle, eyebrow, actions, icon }: { title:
}
/** App hero: screenshots strip, big icon, name, ids, store links, the two-store pipeline and the next step. */
export function Hero({ name, icon, projectId, ids, badges, links, all, ios, android, cta, onCta, next, children }: {
export function Hero({ name, icon, projectId, ids, badges, links, all, ios, android, cta, onCta, next, children, onContextMenu }: {
name: string; icon: string | null; projectId: string | null; ids?: ReactNode; badges?: ReactNode; links?: ReactNode;
/** The app's context menu (useContextMenu props). */
onContextMenu?: (e: ReactMouseEvent) => void;
all: StoreRelease[]; ios: boolean; android: boolean; cta?: Cta | null; onCta?: (tab: AppTab) => void; next?: string | null; children?: ReactNode;
}) {
const hue = useTint(icon, name);
const shots = useShots(projectId, 4);
return (
<section className="ds-hero" style={tintStyle(hue)} aria-label={name}>
<section className="ds-hero" style={tintStyle(hue)} aria-label={name} onContextMenu={onContextMenu}>
<div className="ds-hero-main">
<div className="ds-hero-id">
<AppIcon name={name} src={icon} className="ds-hero-icon" />