/** * The site's horizontal gutter: how far the content sits from the edge of the window. * * ── One setting, three places ── * * The header row, the content column and the footer's inner block all share the site's measure, and all * three want the same space at the edges. That is what makes this a GUTTER rather than three paddings * that happen to agree — and setting it once is the only arrangement in which they cannot drift. * * A section's own padding still exists and composes on top of this. This is the floor everything starts * from; `PaddingTab` is the per-band control, and it stays for the pages. * * ── All three widths at once, and NOT tied to the device toggle ── * * The per-band control does it the other way: one set of inputs writing to whichever width the preview * is showing. That is right when there are two axes and three bands, where showing everything would be * eighteen inputs. * * Here there is one axis and one value, so all three fit on a row — and seeing them together is the * point. A gutter is a progression, 4 then 8 then 12, and it is judged as one: reading it off three * separate visits to the same control is how a site ends up tighter on a tablet than on a phone. */ export type Gutter = Partial>; /** * The three widths, smallest first, with the name the admin uses for each. * * Smallest first because that is the direction they cascade: `md` inherits from `base`, `lg` from `md`. * Read left to right, the row is the progression. */ const WIDTHS = [ { key: 'base' as const, label: 'Mobile', hint: 'every width' }, { key: 'md' as const, label: 'Tablet', hint: '768 and up' }, { key: 'lg' as const, label: 'Desktop', hint: '1024 and up' }, ]; /** What a width falls back to: the nearest smaller one that has a value. */ const inherited = (gutter: Gutter, key: 'base' | 'md' | 'lg'): number | null => { const below = { base: [], md: ['base'], lg: ['md', 'base'] } as const; for (const smaller of below[key]) { const step = gutter[smaller]; if (typeof step === 'number') return step; } return null; }; export const GutterTab = ({ gutter, steps, onChange, }: { gutter: Gutter; /** The scale, sent by the API so this file holds no copy of it. */ steps: number[]; onChange: (gutter: Gutter) => void; }) => { const set = (key: 'base' | 'md' | 'lg', value: string) => { const next = { ...gutter }; // Removed rather than stored as null: an absence is what "inherit" means, and a marker for it would // leave two ways to say the same thing. if (value === '') delete next[key]; else next[key] = Number(value); onChange(next); }; return (

Horizontal padding

The space between the content and the edge of the window — the header, the page and the footer
{WIDTHS.map(({ key, label, hint }) => { const own = gutter[key]; const from = inherited(gutter, key); return (
{hint}
); })}
); };