Width, instead of a negative number to match by hand

Inside the band, whole width of main, or whole page width. The negatives are
gone from the padding select — padding is padding again.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-01 20:23:40 +01:00
co-authored by Claude Opus 5
parent 173e917838
commit 96d917f9e1
2 changed files with 47 additions and 19 deletions
+45 -19
View File
@@ -27,7 +27,12 @@ export const BlockSettings = ({
}: {
pageId: number;
sectionId: number;
block: { id: number; title?: string | null; padding?: Record<string, { x?: number; y?: number }> };
block: {
id: number;
title?: string | null;
width?: string | null;
padding?: Record<string, { x?: number; y?: number }>;
};
/** The padding scale, sent by the API so this file holds no copy of it. */
steps: number[];
onSaved: () => void;
@@ -39,13 +44,20 @@ export const BlockSettings = ({
const [device] = useDevice();
const breakpoint = ({ desktop: 'lg', tablet: 'md', mobile: 'base' } as const)[device];
const [draft, setDraft] = useState({ title: block.title ?? '', padding: block.padding ?? {} });
const [draft, setDraft] = useState({
title: block.title ?? '',
width: block.width ?? '',
padding: block.padding ?? {},
});
const [busy, setBusy] = useState(false);
const [error, setError] = useState<string | null>(null);
const [saved, flashSaved] = useFlash();
// Keyed on the id, so re-reading the same block after a save does not throw away what is being typed.
useEffect(() => setDraft({ title: block.title ?? '', padding: block.padding ?? {} }), [block.id]);
useEffect(
() => setDraft({ title: block.title ?? '', width: block.width ?? '', padding: block.padding ?? {} }),
[block.id],
);
/** What an axis falls back to at this width: the nearest smaller one that has a value. */
const inherited = (axis: 'x' | 'y'): number | null => {
@@ -78,7 +90,7 @@ export const BlockSettings = ({
// Empty is sent as an empty string, which the API reads as "take the heading off".
await clientRef.current.patch(
`/fofos-admin/pages/${pageId}/sections/${sectionId}/components/${block.id}`,
{ title: draft.title, padding: draft.padding },
{ title: draft.title, width: draft.width || null, padding: draft.padding },
);
flashSaved();
onSaved();
@@ -89,7 +101,11 @@ export const BlockSettings = ({
}
};
const stored = JSON.stringify({ title: block.title ?? '', padding: block.padding ?? {} });
const stored = JSON.stringify({
title: block.title ?? '',
width: block.width ?? '',
padding: block.padding ?? {},
});
const pending = JSON.stringify(draft);
useAutosave(pending !== stored ? pending : null, busy, save);
@@ -108,6 +124,29 @@ export const BlockSettings = ({
}}
/>
{/*
How wide it is allowed to be.
It was a NEGATIVE horizontal padding, which worked and asked the wrong thing: you had to know
the band's gutter and type its opposite, and it broke quietly the day somebody changed the
gutter. These two say what is wanted, and the page works out the number.
*/}
<div className="mt-3 flex flex-col gap-1">
<label className="text-xs">Width</label>
<select
value={draft.width}
onChange={(event) => {
const value = event.currentTarget.value;
setDraft((was) => ({ ...was, width: value }));
}}
className="h-8 rounded-md border border-input bg-transparent px-2 text-xs"
>
<option value="">Inside the band</option>
<option value="main">Whole width of main</option>
<option value="page">Whole page width</option>
</select>
</div>
{/*
The block's own spacing, at the width the toggle above is showing.
@@ -139,20 +178,7 @@ export const BlockSettings = ({
<option value="">
{breakpoint === 'base' ? 'None' : from === null ? 'Inherit (none)' : `Inherit (${from})`}
</option>
{/*
Horizontal offers NEGATIVES, vertical does not.
A negative horizontal is how a block escapes the gutter its band applied — a video
that runs to the edge of the window inside an inset band. It becomes `-mx-*`, which
cancels the band's `px-*` exactly, so the value to pick is the band's own number.
Vertically there is nothing to cancel: a band's vertical padding is the room around
the whole band, and a block pulling itself into the one above is a bug.
*/}
{(axis === 'x'
? [...steps.filter((step) => step > 0).map((step) => -step).reverse(), ...steps]
: steps
).map((step) => (
{steps.map((step) => (
<option key={step} value={String(step)}>
{step}
</option>