mirror of
https://github.com/olehomelchenko/astrolabe.git
synced 2026-08-08 02:02:33 +00:00
125 lines
4.0 KiB
TypeScript
125 lines
4.0 KiB
TypeScript
/**
|
|
* 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" />;
|
|
}
|