mirror of
https://github.com/olehomelchenko/astrolabe.git
synced 2026-08-08 02:02:33 +00:00
Theme Builder: accordion panels across the full config surface, vertical-tab nav, reflective gallery
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* Theme Builder — Formats panel (docs/chart-theming-scope.md §5).
|
||||
*
|
||||
* Default number and date formatting (`config.numberFormat`,
|
||||
* `normalizedNumberFormat`, `timeFormat`) plus the count-field title — the
|
||||
* brand-level "always show currency / thousands / short dates" knobs Vega-Lite
|
||||
* applies to guide labels, text marks, and tooltips. The values are d3-format /
|
||||
* d3-time-format strings; each field is free text with quick-fill presets for
|
||||
* the common patterns, since the pattern space is open (the JSON stays the way
|
||||
* to write any string). Each writes a minimal diff via the shared setter.
|
||||
*/
|
||||
|
||||
import type { JsonObject } from '@core/spec-config';
|
||||
import { asString, type ConfigPath, countSet, getConfigValue } from '@core/theme-controls';
|
||||
import { useConfigSetter } from '../stores/CustomThemeStore';
|
||||
import { Button } from './Button';
|
||||
import { Accordion, type AccordionSection, TextRow } from './ThemeFields';
|
||||
import styles from './ThemeFields.module.css';
|
||||
|
||||
const NUMBER: ConfigPath = ['numberFormat'];
|
||||
const NORMALIZED: ConfigPath = ['normalizedNumberFormat'];
|
||||
const TIME: ConfigPath = ['timeFormat'];
|
||||
const COUNT_TITLE: ConfigPath = ['countTitle'];
|
||||
|
||||
interface Preset {
|
||||
/** The d3 pattern written into the config. */
|
||||
code: string;
|
||||
/** A rendered example, shown as the chip label. */
|
||||
sample: string;
|
||||
}
|
||||
|
||||
const NUMBER_PRESETS: Preset[] = [
|
||||
{ code: ',', sample: '1,234' },
|
||||
{ code: '$,.2f', sample: '$1,234.00' },
|
||||
{ code: '.0%', sample: '12%' },
|
||||
{ code: '.2s', sample: '1.2k' },
|
||||
{ code: '+,', sample: '+1,234' },
|
||||
];
|
||||
|
||||
const TIME_PRESETS: Preset[] = [
|
||||
{ code: '%b %Y', sample: 'Jan 2026' },
|
||||
{ code: '%Y-%m-%d', sample: '2026-01-01' },
|
||||
{ code: '%b %-d', sample: 'Jan 1' },
|
||||
{ code: '%-d %b %Y', sample: '1 Jan 2026' },
|
||||
];
|
||||
|
||||
/** A row of quick-fill chips that write a preset pattern into a config key. */
|
||||
function Presets({ presets, onPick }: { presets: Preset[]; onPick: (code: string) => void }) {
|
||||
return (
|
||||
<div className={styles.presets}>
|
||||
{presets.map((p) => (
|
||||
<Button key={p.code} variant="ghost" onClick={() => onPick(p.code)} title={p.code}>
|
||||
{p.sample}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function FormatControls({ config }: { config: JsonObject }) {
|
||||
const set = useConfigSetter();
|
||||
|
||||
const sections: AccordionSection[] = [
|
||||
{
|
||||
id: 'numbers',
|
||||
title: 'Numbers',
|
||||
hint: 'd3-format pattern for labels, text, and tooltips.',
|
||||
badge: countSet(config, [NUMBER, NORMALIZED]),
|
||||
children: (
|
||||
<>
|
||||
<TextRow
|
||||
label="Format"
|
||||
name="Number format"
|
||||
value={asString(getConfigValue(config, NUMBER))}
|
||||
placeholder="e.g. $,.2f"
|
||||
onChange={(v) => set(NUMBER, v)}
|
||||
/>
|
||||
<Presets presets={NUMBER_PRESETS} onPick={(code) => set(NUMBER, code)} />
|
||||
<TextRow
|
||||
label="Stacked %"
|
||||
name="Normalized number format"
|
||||
value={asString(getConfigValue(config, NORMALIZED))}
|
||||
placeholder="e.g. .0%"
|
||||
onChange={(v) => set(NORMALIZED, v)}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'dates',
|
||||
title: 'Dates',
|
||||
hint: 'd3-time-format pattern for raw time values.',
|
||||
badge: countSet(config, [TIME]),
|
||||
children: (
|
||||
<>
|
||||
<TextRow
|
||||
label="Format"
|
||||
name="Date format"
|
||||
value={asString(getConfigValue(config, TIME))}
|
||||
placeholder="e.g. %b %Y"
|
||||
onChange={(v) => set(TIME, v)}
|
||||
/>
|
||||
<Presets presets={TIME_PRESETS} onPick={(code) => set(TIME, code)} />
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'labels',
|
||||
title: 'Labels',
|
||||
badge: countSet(config, [COUNT_TITLE]),
|
||||
children: (
|
||||
<TextRow
|
||||
label="Count title"
|
||||
name="Count title"
|
||||
value={asString(getConfigValue(config, COUNT_TITLE))}
|
||||
placeholder="Count of Records"
|
||||
onChange={(v) => set(COUNT_TITLE, v)}
|
||||
/>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return <Accordion sections={sections} idPrefix="formats" />;
|
||||
}
|
||||
Reference in New Issue
Block a user