desktop: five sections by job (Release, Store page, Revenue, Players, Health), Game services view, sidebar by attention

- app view: 12 tabs become 5 sections, each with a header (what it is for, the one next step from state
  already read) and its sub views in a segmented control; 1-5 jump, Ctrl+Shift+[ ] cycle, Ctrl+K and the
  context menu list sections and sub views; old tab ids, section and section/sub links all resolve
- Players > Game services (games only): both stores, one row per leaderboard / achievement with its
  languages and each store's state, Game Center locale map, Play API-disabled state with the console plan
- games status --json: items, localeMap, playApiDisabled; inspect summary: games
- sidebar: apps grouped by attention / in progress / live / not on a store, one status line each, studio
  filter, workspace as a footer icon row
- boot check opens places like a person (section, then sub view)
This commit is contained in:
CRTkafa 2026-10-24 13:01:50 +08:00
parent 98cd1d3c92
commit 0ef27cf2e4
22 changed files with 918 additions and 115 deletions

View file

@ -275,7 +275,7 @@ export function Toolbar({ label, children }: { label: string; children: ReactNod
return <div className="ds-toolbar" role="toolbar" aria-label={label}>{children}</div>;
}
export interface TabItem<T extends string> { id: T; label: string; icon?: IconName; count?: number | null; dot?: boolean; dotTitle?: string }
export interface TabItem<T extends string> { id: T; label: string; icon?: IconName; count?: number | null; dot?: boolean; dotTitle?: string; /** A keyboard hint shown in the tab ("1"). */ hint?: string }
/** Pill tabs with icons and counts; arrows / Home / End move between them (roving tabindex). One row always: tabs that
* do not fit go into a "More" menu at the end, the active tab always stays in the row. */
@ -323,11 +323,12 @@ export function Tabs<T extends string>({ items, value, onChange, label, panelId
return (
<div className="ds-tabs" ref={row} role="tablist" aria-label={label} onKeyDown={onKey}>
{items.map((x, i) => (
<button key={x.id} ref={el => { refs.current[i] = el; }} role="tab" id={`tab-${x.id}`} aria-selected={value === x.id} aria-controls={panelId}
<button key={x.id} ref={el => { refs.current[i] = el; }} role="tab" id={`tab-${x.id}`} aria-selected={value === x.id} aria-controls={panelId} aria-keyshortcuts={x.hint}
tabIndex={value === x.id ? 0 : -1} className="ds-tab" onClick={() => onChange(x.id)} hidden={!visible(i)}>
{x.icon && <Icon name={x.icon} weight={value === x.id ? 'fill' : 'regular'} />}{x.label}
{x.count != null && x.count > 0 && <span className="ds-tab-n num">{x.count}</span>}
{x.dot && <span className="ed-dot" title={x.dotTitle} />}
{x.hint && <kbd className="ds-tab-k" aria-hidden="true">{x.hint}</kbd>}
</button>
))}
{hidden.length > 0 && (