From d1ba921141e5952d7d7b61374c9fece0dec57c4e Mon Sep 17 00:00:00 2001 From: Oleh Omelchenko Date: Fri, 12 Jun 2026 21:46:53 +0300 Subject: [PATCH] SelectControl: 32px trigger scale + option-group divider in the theme picker --- docs/spec/04-live-preview.md | 2 +- docs/ux-second-pass.md | 10 ++-- src/app/components/LivePreview.tsx | 8 ++- src/app/components/SelectControl.module.css | 19 ++++++-- src/app/components/SelectControl.test.tsx | 54 +++++++++++++++++++++ src/app/components/SelectControl.tsx | 38 +++++++++------ src/core/vega-themes.test.ts | 13 ++++- src/core/vega-themes.ts | 22 +++++++-- 8 files changed, 136 insertions(+), 30 deletions(-) create mode 100644 src/app/components/SelectControl.test.tsx diff --git a/docs/spec/04-live-preview.md b/docs/spec/04-live-preview.md index 561748d..3d6ee63 100644 --- a/docs/spec/04-live-preview.md +++ b/docs/spec/04-live-preview.md @@ -44,7 +44,7 @@ The preview pane header carries a **Chart theme** picker — a value-select disc - **Stock Vega-Lite** — injects nothing; charts render exactly as plain Vega-Lite defaults would anywhere else (white background, default palette and fonts). - **Custom themes** — the user's saved themes (see _Theme Builder_ below), listed by name between the built-ins and the presets. - **Edit themes…** — closes the custom-themes block (before the long preset roster, so it's visible without scrolling); opens the Theme Builder instead of changing the selection. -- **Presets** — the `vega-themes` preset configs (Excel, ggplot2, FiveThirtyEight, LA Times, Power BI, the Carbon family, …), rendered verbatim and independent of the app's light/dark theme. +- **Presets** — the `vega-themes` preset configs (Excel, ggplot2, FiveThirtyEight, LA Times, Power BI, the Carbon family, …), rendered verbatim and independent of the app's light/dark theme. A **divider** separates the preset roster from everything above it — the built-ins, the user's themes, and the manage entry read as "ours"; the presets as the imported catalogue. Behavior: diff --git a/docs/ux-second-pass.md b/docs/ux-second-pass.md index 819a43e..c3220bd 100644 --- a/docs/ux-second-pass.md +++ b/docs/ux-second-pass.md @@ -15,15 +15,17 @@ record the resolution into the contract (`docs/architecture/09`+`10` and the rel pane widths the long trigger labels ("FiveThirtyEight", "Urban Institute") may crowd it. Council questions: does the picker deserve header prominence (the "transform your chart" showcase) or settings-cluster placement (a persistent global pref); should the popover - registry learn nesting; 16 flat options — group presets under a heading? + registry learn nesting; should the preset block also carry a group _heading_ (a + divider now bounds it — 2026-06-12, user-decided; `SelectControl` `dividerBefore`)? - **"Edit themes…" action row inside the value picker** (`LivePreview.tsx` — ChartThemeControl). A non-value action lives inside a single-select disclosure (the VS Code theme-picker pattern), placed after the custom-themes block and before the preset roster (first-use feedback: at the very bottom it was invisible without - scrolling). Council questions: should an action be visually separated from the - values (divider, distinct styling); is a mid-list row that opens a modal instead of - selecting surprising to AT users? + scrolling). The ours-vs-presets divider (2026-06-12) now bounds it below; remaining + council questions: should the action row itself be styled distinctly from the value + rows; is a mid-list row that opens a modal instead of selecting surprising to AT + users? - **Theme Builder config editor is a plain textarea** (`ThemeBuilderModal.tsx`). Monaco (with the Vega-Lite config schema for completions) would match the main editor but is diff --git a/src/app/components/LivePreview.tsx b/src/app/components/LivePreview.tsx index 158987d..a064302 100644 --- a/src/app/components/LivePreview.tsx +++ b/src/app/components/LivePreview.tsx @@ -100,10 +100,14 @@ function ChartThemeControl() { // pattern); choosing it opens the builder and leaves the selection alone. // It closes the custom-themes block — right after the built-ins, BEFORE the // long preset roster — so it is visible without scrolling and sits next to - // the entries it manages. + // 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). - list.splice(2 + customThemes.length, 0, { + 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', diff --git a/src/app/components/SelectControl.module.css b/src/app/components/SelectControl.module.css index 7a6afe9..f3d7a79 100644 --- a/src/app/components/SelectControl.module.css +++ b/src/app/components/SelectControl.module.css @@ -1,17 +1,19 @@ /* SelectControl — the app's value-picker disclosure (replaces native