Desktop: compact app hero on every tab but Releases; Ctrl+K lists the current app's menu actions; Alt+Up/Down switches apps; shortcuts list; no focus ring on code-focused headings

This commit is contained in:
CRTkafa 2026-10-23 09:26:01 +08:00
parent a656240bfb
commit 7c18b3f7dd
7 changed files with 63 additions and 10 deletions

View file

@ -179,16 +179,19 @@ 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, onContextMenu }: {
export function Hero({ name, icon, projectId, ids, badges, links, all, ios, android, cta, onCta, next, children, onContextMenu, compact }: {
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;
/** One slim row (icon, name, ids, next step): every tab but the overview, so the tab's content starts high. */
compact?: boolean;
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);
const shots = useShots(compact ? null : projectId, 4);
const ctaBtn = cta && <button className={`btn ${cta.primary ? 'btn-primary' : ''} pf-cta`} onClick={() => onCta?.(cta.tab)}><Icon name={cta.icon} />{cta.text}</button>;
return (
<section className="ds-hero" style={tintStyle(hue)} aria-label={name} onContextMenu={onContextMenu}>
<section className={`ds-hero${compact ? ' compact' : ''}`} 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" />
@ -197,18 +200,19 @@ export function Hero({ name, icon, projectId, ids, badges, links, all, ios, andr
{ids && <div className="ds-hero-ids">{ids}</div>}
{links && <div className="ds-hero-links">{links}</div>}
</div>
{compact && ctaBtn && <div className="ds-hero-cta">{ctaBtn}</div>}
</div>
<div className="ds-hero-pipe">
{!compact && <div className="ds-hero-pipe">
<Pipeline all={all} ios={ios} android={android} size="lg" />
<div className="ds-hero-foot">
<div className="ds-hero-next"><span className="pf-next-l">{t('pf.next')}</span><span className="ds-hero-next-v num">{next ?? '–'}</span></div>
{cta && <button className={`btn ${cta.primary ? 'btn-primary' : ''} pf-cta`} onClick={() => onCta?.(cta.tab)}><Icon name={cta.icon} />{cta.text}</button>}
{ctaBtn}
</div>
</div>
</div>}
{children}
</div>
{/* no screenshots: no second (cropped) copy of the icon, which already leads the hero */}
{shots.length > 0 && <div className="ds-hero-shots" aria-hidden="true">
{!compact && shots.length > 0 && <div className="ds-hero-shots" aria-hidden="true">
{shots.map((s, i) => <img key={i} src={s} alt="" draggable={false} />)}
</div>}
</section>