the toggle drops its labels when the panel is narrow
A container query, not a screen one: this sits in a workspace panel whose width is a divider somebody drags, so the window says nothing about the room. Below 240px the icons stand alone and the word is the tooltip. Every editor section starts open. It opened on one, from ?tab=, which is what tabs want — these are not tabs, and a collapsed column asks somebody to go looking for the setting they came for. ?tab= is gone with it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+13
-2
@@ -60,12 +60,20 @@ const OPTIONS: { value: DeviceName; Icon: typeof Monitor }[] = [
|
||||
* choice legible without hovering for a tooltip.
|
||||
*
|
||||
* The label comes from DEVICES, which is also what the preview names in its own caption. One list.
|
||||
*
|
||||
* ── Narrow, it goes back to icons ──
|
||||
*
|
||||
* A CONTAINER query, not a screen one: this sits in a workspace panel whose width is a divider the
|
||||
* user drags, so the window's width says nothing about the room there is. `@container` on the row
|
||||
* makes the row itself the thing measured.
|
||||
*
|
||||
* The word is then the `title`, so the answer is still one hover away rather than gone.
|
||||
*/
|
||||
export const DeviceToggle = () => {
|
||||
const [device, setDevice] = useDevice();
|
||||
|
||||
return (
|
||||
<div className="flex w-full items-center rounded-md border border-border p-0.5">
|
||||
<div className="@container flex w-full items-center rounded-md border border-border p-0.5">
|
||||
{OPTIONS.map(({ value, Icon }) => (
|
||||
<Button
|
||||
key={value}
|
||||
@@ -74,9 +82,12 @@ export const DeviceToggle = () => {
|
||||
className="h-7 flex-1 gap-1.5 text-xs"
|
||||
onClick={() => setDevice(value)}
|
||||
aria-label={DEVICES[value].label}
|
||||
title={DEVICES[value].label}
|
||||
>
|
||||
<Icon className="size-3.5" />
|
||||
{DEVICES[value].label}
|
||||
{/* Three labels need about 240px between them. Below that the icons stand alone and the
|
||||
word is the tooltip. */}
|
||||
<span className="hidden @[15rem]:inline">{DEVICES[value].label}</span>
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/components/ui/accordion';
|
||||
import { useSearchParams } from 'react-router';
|
||||
import { useClient } from 'hooks/useClient';
|
||||
import { readableError } from '../errors';
|
||||
import { useFlash } from '../useFlash';
|
||||
@@ -110,6 +109,9 @@ const themeProperties = (draft: Layout): Record<string, string | null> => ({
|
||||
'--font-display': draft.font_display && `"${draft.font_display}", sans-serif`,
|
||||
});
|
||||
|
||||
/** Every section, open from the start. The values below must match the AccordionItems. */
|
||||
const SECTIONS_OPEN = ['colours', 'typefaces', 'padding'];
|
||||
|
||||
const COLOURS = [
|
||||
{ key: 'background' as const, label: 'Background', fallback: '#ffffff' },
|
||||
{ key: 'foreground' as const, label: 'Text', fallback: '#000000' },
|
||||
@@ -124,23 +126,7 @@ export const LayoutPanel = () => {
|
||||
const [stored, setStored] = useState<Layout | null>(null);
|
||||
const [draft, setDraft] = useState<Layout | null>(null);
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [params, setParams] = useSearchParams();
|
||||
const devPort = useDevPort();
|
||||
/** Controlled, because the grid stack below decides visibility rather than Radix unmounting. */
|
||||
/*
|
||||
The open tab, in the URL — `?tab=padding`.
|
||||
|
||||
Same rule as the mode and the device: it is addressable. "Look at the typefaces" is a link somebody
|
||||
sends, and a reload that drops you back on Colours loses the place you were working in.
|
||||
|
||||
Absent means Colours, the first one, so an ordinary link carries nothing.
|
||||
*/
|
||||
const tab = params.get('tab') ?? 'colours';
|
||||
const setTab = (next: string) => {
|
||||
const search = new URLSearchParams(params);
|
||||
search.set('tab', next);
|
||||
setParams(search, { replace: true });
|
||||
};
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [saved, flashSaved] = useFlash();
|
||||
|
||||
@@ -263,7 +249,14 @@ export const LayoutPanel = () => {
|
||||
}
|
||||
|
||||
setEditorRef.current(
|
||||
<Accordion type="multiple" defaultValue={[tab]} className="flex flex-col gap-2">
|
||||
/*
|
||||
Every section open to begin with.
|
||||
|
||||
It opened on one, from `?tab=`, which is what a set of TABS wants — one at a time, and which
|
||||
one is addressable. These are not tabs: they are the whole of what this screen decides, and a
|
||||
column that starts collapsed asks somebody to go looking for the setting they came for.
|
||||
*/
|
||||
<Accordion type="multiple" defaultValue={SECTIONS_OPEN} className="flex flex-col gap-2">
|
||||
<AccordionItem value="colours" className="rounded-lg border border-border px-4">
|
||||
<AccordionTrigger className="text-xs">Colors</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
@@ -357,7 +350,7 @@ export const LayoutPanel = () => {
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
);
|
||||
}, [draft, tab, device]);
|
||||
}, [draft, device]);
|
||||
|
||||
// Cleared on unmount, or the panel goes on offering Layout's controls over somebody else's page.
|
||||
useEffect(() => () => setEditorRef.current(null), []);
|
||||
|
||||
Reference in New Issue
Block a user