Layout's controls are stacked sections, not tabs
Three tabs became three collapsible sections in a column, because there are going to be more than three. A tab strip works while the names fit on one line and degrades the moment they do not — it wraps, or it scrolls, and either way the set stops being visible at a glance. `type="multiple"`, so more than one can be open. Tabs enforced exactly one, which is right when the panels are alternatives and wrong when they are parts of the same thing: the colours and the padding of a layout are read together as often as separately. The grid overlay went with them. Every panel shared one cell so the block stayed as tall as the tallest and did not jump when the tab changed — a real problem that stacked sections do not have, because a section that is closed occupies nothing and one that is open is as tall as it is. `?tab=` still seeds which section starts open, so an old link keeps working and lands on the thing it named. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/components/ui/accordion';
|
||||||
import { useSearchParams } from 'react-router';
|
import { useSearchParams } from 'react-router';
|
||||||
import { useClient } from 'hooks/useClient';
|
import { useClient } from 'hooks/useClient';
|
||||||
import { readableError } from '../errors';
|
import { readableError } from '../errors';
|
||||||
@@ -275,37 +275,10 @@ export const LayoutPanel = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setEditorRef.current(
|
setEditorRef.current(
|
||||||
<Tabs value={tab} onValueChange={setTab} className="shrink-0 gap-2">
|
<Accordion type="multiple" defaultValue={[tab]} className="flex flex-col gap-2">
|
||||||
<TabsList className="h-7">
|
<AccordionItem value="colours" className="rounded-lg border border-border px-4">
|
||||||
<TabsTrigger value="colours" className="text-xs">
|
<AccordionTrigger className="text-xs">Colours</AccordionTrigger>
|
||||||
Colours
|
<AccordionContent>
|
||||||
</TabsTrigger>
|
|
||||||
<TabsTrigger value="typefaces" className="text-xs">
|
|
||||||
Typefaces
|
|
||||||
</TabsTrigger>
|
|
||||||
<TabsTrigger value="padding" className="text-xs">
|
|
||||||
Padding
|
|
||||||
</TabsTrigger>
|
|
||||||
</TabsList>
|
|
||||||
|
|
||||||
{/*
|
|
||||||
EVERY panel in the same grid cell, so the block is as tall as the TALLEST of them and stops
|
|
||||||
moving when the tab changes.
|
|
||||||
|
|
||||||
Measuring the panels and setting a height would need a ResizeObserver, a re-measure on every
|
|
||||||
edit, and a wrong answer for one frame after each. `grid-area: 1/1` gets it from the layout
|
|
||||||
engine for nothing, and it stays right when a tab is added or one of them grows a row —
|
|
||||||
which is the case that made the first fix insufficient.
|
|
||||||
|
|
||||||
`invisible` and not `hidden`: an invisible element still takes its space, which IS the
|
|
||||||
mechanism. It is also removed from the tab order, so the controls behind the front panel
|
|
||||||
cannot be reached by keyboard.
|
|
||||||
*/}
|
|
||||||
<div className="grid rounded-lg border border-border p-4">
|
|
||||||
<div
|
|
||||||
className={`col-start-1 row-start-1 ${tab === 'colours' ? '' : 'invisible'}`}
|
|
||||||
aria-hidden={tab !== 'colours'}
|
|
||||||
>
|
|
||||||
<>
|
<>
|
||||||
<div className="flex flex-wrap gap-4">
|
<div className="flex flex-wrap gap-4">
|
||||||
{COLOURS.map(({ key, label, fallback }) => (
|
{COLOURS.map(({ key, label, fallback }) => (
|
||||||
@@ -336,12 +309,11 @@ export const LayoutPanel = () => {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
</div>
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
<div
|
<AccordionItem value="typefaces" className="rounded-lg border border-border px-4">
|
||||||
className={`col-start-1 row-start-1 ${tab === 'typefaces' ? '' : 'invisible'}`}
|
<AccordionTrigger className="text-xs">Typefaces</AccordionTrigger>
|
||||||
aria-hidden={tab !== 'typefaces'}
|
<AccordionContent>
|
||||||
>
|
|
||||||
<>
|
<>
|
||||||
{/*
|
{/*
|
||||||
Four across, one row.
|
Four across, one row.
|
||||||
@@ -382,12 +354,11 @@ export const LayoutPanel = () => {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
</div>
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
<div
|
<AccordionItem value="padding" className="rounded-lg border border-border px-4">
|
||||||
className={`col-start-1 row-start-1 ${tab === 'padding' ? '' : 'invisible'}`}
|
<AccordionTrigger className="text-xs">Padding</AccordionTrigger>
|
||||||
aria-hidden={tab !== 'padding'}
|
<AccordionContent>
|
||||||
>
|
|
||||||
{/*
|
{/*
|
||||||
The site's gutter, not a per-band padding.
|
The site's gutter, not a per-band padding.
|
||||||
|
|
||||||
@@ -404,9 +375,9 @@ export const LayoutPanel = () => {
|
|||||||
steps={draft.padding_steps}
|
steps={draft.padding_steps}
|
||||||
onChange={(padding_x) => patch({ padding_x })}
|
onChange={(padding_x) => patch({ padding_x })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</AccordionContent>
|
||||||
</div>
|
</AccordionItem>
|
||||||
</Tabs>
|
</Accordion>
|
||||||
);
|
);
|
||||||
}, [draft, tab, device]);
|
}, [draft, tab, device]);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user