UX second pass: icon-only header, pane-toggle cue, schema columns, facet cleanup

This commit is contained in:
2026-06-13 10:13:40 +03:00
parent e0806bf3db
commit 92bfe888b5
14 changed files with 124 additions and 154 deletions
+15 -9
View File
@@ -41,10 +41,12 @@ import styles from './LivePreview.module.css';
/** The four fit modes in display order (spec §04 → Fit / Sizing Modes). */
const FIT_OPTIONS: ReadonlyArray<SegmentedOption<FitMode>> = [
{ value: 'default', label: 'Original' },
{ value: 'width', label: 'Width' },
{ value: 'height', label: 'Height' },
{ value: 'full', label: 'Full' },
// `title` doubles as the accessible name, so it leads with the visible label
// (WCAG 2.5.3 label-in-name; see SegmentedOption.title).
{ value: 'default', label: 'Original', title: 'Original — the natural size from the spec' },
{ value: 'width', label: 'Width', title: 'Width — fit to the pane width' },
{ value: 'height', label: 'Height', title: 'Height — fit to the pane height' },
{ value: 'full', label: 'Full', title: 'Full — fill the pane' },
];
/**
@@ -81,9 +83,11 @@ function FitControl() {
* snippet's own `config` still overrides it property by property. Lives in the
* header, not inside PreviewSettings: SelectControl and SettingsPopover share
* the one-open-popover registry, so a select nested in the popover would close
* (and unmount) its own parent on open.
* (and unmount) its own parent on open (the contract: arch 10 §5 — a control
* needing its own popover sits beside the gear, never inside the panel). The
* trigger's 16ch label cap keeps long preset/custom names from crowding a
* narrow header.
*/
// TODO: header placement/crowding parked for the batched council pass (docs/ux-second-pass.md).
/** Sentinel option that opens the Theme Builder instead of selecting a theme. */
const EDIT_THEMES = 'edit-themes';
type ThemePickerValue = ChartThemeSelection | typeof EDIT_THEMES;
@@ -103,14 +107,16 @@ function ChartThemeControl() {
// the entries it manages. The roster boundary itself (the divider) is set by
// chartThemeOptions where the order is decided, so the splice is the only
// index this component owns. The first preset is the divider-carrying option,
// so splicing right before it needs no count arithmetic.
// TODO: action row inside a value picker (visual separation? AT surprise?)
// parked for the batched council pass (docs/ux-second-pass.md).
// so splicing right before it needs no count arithmetic. The row needs no
// special styling: SelectControl options are real buttons (not listbox
// options), the "…" is the opens-further-UI convention, and `hasPopup`
// announces the dialog to AT.
const firstPreset = list.findIndex((o) => o.dividerBefore);
list.splice(firstPreset === -1 ? list.length : firstPreset, 0, {
value: EDIT_THEMES,
label: 'Edit themes…',
detail: 'Create and manage custom themes',
hasPopup: 'dialog',
});
return list;
}, [customThemes]);