import { useDevice } from '../DeviceToggle'; import type { SpacingDraft, Steps } from '../canvas'; /** * Where the space goes: three bands, each with its own numbers. * * It was one gutter for the whole site, on the argument that the header, the bands and the footer hold * the same measure and should not drift. They do hold the same measure — and they still want different * edges, because the menu is not the page. So the guarantee moved: three values, chosen in one place, * side by side. * * ── One value per field ── * * The device toggle above says which width is being edited. Showing all three would be eighteen * selects, seventeen of them for a screen you are not looking at. * * Unset inherits the smaller width, so "the same everywhere" is one choice. Zero is the other * instruction — none HERE even though the phone has some — which is why they are separate options. */ const BREAKPOINT = { desktop: 'lg', tablet: 'md', mobile: 'base' } as const; /** The nearest smaller width that has a value, or null. */ const inherited = (steps: Steps | undefined, key: 'base' | 'md' | 'lg'): number | null => { const below = { base: [], md: ['base'], lg: ['md', 'base'] } as const; for (const smaller of below[key]) { const step = steps?.[smaller]; if (typeof step === 'number') return step; } return null; }; /** The vocabulary. A knob is a line here, the class it emits on the site, and a field in the API. */ const BANDS = [ { key: 'header' as const, label: 'Top Nav', fields: [ { key: 'paddingX' as const, label: 'Horizontal padding' }, { key: 'marginBottom' as const, label: 'Margin bottom' }, ], }, { key: 'sections' as const, label: 'Sections', fields: [ { key: 'paddingX' as const, label: 'Horizontal padding' }, { key: 'paddingY' as const, label: 'Vertical padding' }, { key: 'gap' as const, label: 'Gap' }, ], }, { key: 'footer' as const, label: 'Footer', fields: [ { key: 'paddingX' as const, label: 'Horizontal padding' }, { key: 'marginTop' as const, label: 'Margin top' }, { key: 'gap' as const, label: 'Gap' }, ], }, ]; export const SpacingTab = ({ spacing, steps, onChange, }: { spacing: SpacingDraft; /** The scale, sent by the API so this file holds no copy of it. */ steps: number[]; onChange: (spacing: SpacingDraft) => void; }) => { const [device] = useDevice(); const key = BREAKPOINT[device]; const set = (band: string, field: string, value: string) => { const bands = spacing as Record | undefined>; const at: Steps = { ...(bands[band]?.[field] ?? {}) }; // Removed rather than stored as null: an absence is what "inherit" means, all the way up. if (value === '') delete at[key]; else at[key] = Number(value); const nextBand = { ...(bands[band] ?? {}) }; if (Object.keys(at).length) nextBand[field] = at; else delete nextBand[field]; const next = { ...bands }; if (Object.keys(nextBand).length) next[band] = nextBand; else delete next[band]; onChange(next as SpacingDraft); }; return (
{BANDS.map((band) => (

{band.label}

{/* Three across, and the same grid for every band — Top Nav has two fields and leaves the third empty, so the columns line up down the section rather than staggering. */}
{band.fields.map((field) => { const current = (spacing as Record | undefined>)[band.key]?.[field.key]; const own = current?.[key]; const from = inherited(current, key); return (
); })}
))}
); };