From 2b2d60f45c1246981806b278a1147c2a3365c978 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Mon, 31 Aug 2026 05:32:01 +0100 Subject: [PATCH] the gutter shows all three widths at once MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit One control, three values, horizontal only — Mobile, Tablet, Desktop on one row, smallest first because that is the direction they cascade. Not tied to the device toggle, and the difference from the per-band control is the reason. That one has two axes and three bands, where showing every width would be eighteen inputs, so it writes to whichever width the preview is showing. Here there is one axis and one value, so all three fit — and seeing them together is the point. A gutter is a progression, 4 then 8 then 12, judged as a whole; reading it off three separate visits to the same control is how a site ends up tighter on a tablet than on a phone. Inherit still says what it is inheriting, and Mobile still says "None", because there is nothing below it to inherit from. Co-Authored-By: Claude Opus 5 (1M context) --- web/sections/GutterTab.tsx | 108 +++++++++++++++++++---------------- web/sections/LayoutPanel.tsx | 7 ++- 2 files changed, 63 insertions(+), 52 deletions(-) diff --git a/web/sections/GutterTab.tsx b/web/sections/GutterTab.tsx index 85f3bc1..65f1d47 100644 --- a/web/sections/GutterTab.tsx +++ b/web/sections/GutterTab.tsx @@ -1,9 +1,7 @@ -import { DEVICES, type DeviceName } from '../ViewMode'; - /** * The site's horizontal gutter: how far the content sits from the edge of the window. * - * ── One setting, not one per band ── + * ── 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 @@ -12,20 +10,35 @@ import { DEVICES, type DeviceName } from '../ViewMode'; * 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. * - * ── The device toggle picks the width ── + * ── All three widths at once, and NOT tied to the device toggle ── * - * Same as everywhere else in this panel: Mobile writes `base`, Tablet `md`, Desktop `lg`, and they - * cascade — so one gutter at every width is one choice rather than three. + * 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>; -const BREAKPOINT: Record = { mobile: 'base', tablet: 'md', desktop: 'lg' }; +/** + * 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 this breakpoint falls back to: the nearest smaller one that has a value. */ -const inherited = (gutter: Gutter, breakpoint: 'base' | 'md' | 'lg'): number | null => { +/** 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[breakpoint]) { + for (const smaller of below[key]) { const step = gutter[smaller]; if (typeof step === 'number') return step; } @@ -34,25 +47,20 @@ const inherited = (gutter: Gutter, breakpoint: 'base' | 'md' | 'lg'): number | n export const GutterTab = ({ gutter, - device, steps, onChange, }: { gutter: Gutter; - device: DeviceName; + /** The scale, sent by the API so this file holds no copy of it. */ steps: number[]; onChange: (gutter: Gutter) => void; }) => { - const breakpoint = BREAKPOINT[device]; - const own = gutter[breakpoint]; - const from = inherited(gutter, breakpoint); - - const set = (value: string) => { + const set = (key: 'base' | 'md' | 'lg', value: string) => { const next = { ...gutter }; - // Removed rather than stored as null: an absence is what "inherit" means, and storing a marker for - // it would leave two ways to say the same thing. - if (value === '') delete next[breakpoint]; - else next[breakpoint] = Number(value); + // 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); }; @@ -61,38 +69,38 @@ export const GutterTab = ({

Horizontal padding

- The space between the content and the edge of the window - - {/* Which width is being set, said here because the device buttons are at the top of the panel - and the effect of pressing one is not obvious from down here. */} - - editing {DEVICES[device].label.toLowerCase()} — {breakpoint} + The space between the content and the edge of the window — the header, the page and the footer
-
-
- - - - Applies to the header, the page and the footer - -
+
+ {WIDTHS.map(({ key, label, hint }) => { + const own = gutter[key]; + const from = inherited(gutter, key); + + return ( +
+ + + {hint} +
+ ); + })}
); diff --git a/web/sections/LayoutPanel.tsx b/web/sections/LayoutPanel.tsx index 5936e59..bc16ce2 100644 --- a/web/sections/LayoutPanel.tsx +++ b/web/sections/LayoutPanel.tsx @@ -364,11 +364,14 @@ export const LayoutPanel = () => { PaddingTab is the per-band control and it stays — for the pages, where a band really does want its own spacing. Here the three wrappers share one measure and should share - one number, so this is one select rather than three sections' worth. + one number. + + All three widths at once, and deliberately NOT tied to the device toggle: 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. */} patch({ padding_x })} />