import { Monitor, Smartphone, Tablet } from 'lucide-react'; import { useCallback } from 'react'; import { useSearchParams } from 'react-router'; import { Button } from '@/components/ui/button'; import { DEVICES, type DeviceName } from './ViewMode'; /** * Which width everything is showing, and the control for changing it. * * ── Why this reads the URL and not the context ── * * `useViewMode` is a context, and the editor is a SIBLING panel: an element built inside the detail * panel resolves its context where it is RENDERED, not where it was created, so a toggle handed to the * editor would look up a tree that has no provider in it and quietly show the default forever. * * The device already lives in `?device=`, which every panel can read. So this is the shared controller * the whole screen wants and there is nothing to share: the URL is the state, three components read it, * and they cannot disagree because there is only one of it. * * That is also why the preview follows along for free. It is not subscribed to this control; it reads * the same parameter. */ export const useDevice = (): [DeviceName, (next: DeviceName) => void] => { const [params, setParams] = useSearchParams(); const raw = params.get('device'); const device: DeviceName = raw === 'tablet' || raw === 'mobile' ? raw : 'desktop'; const setDevice = useCallback( (next: DeviceName) => { const search = new URLSearchParams(params); search.set('device', next); setParams(search, { replace: true }); }, [params, setParams], ); return [device, setDevice]; }; const OPTIONS: { value: DeviceName; Icon: typeof Monitor }[] = [ { value: 'desktop', Icon: Monitor }, { value: 'tablet', Icon: Tablet }, { value: 'mobile', Icon: Smartphone }, ]; /** * The control itself: three tabs across the width of whatever holds it. * * ── One shape, not two ── * * There was a `size` prop. `bar` was a small icon-only cluster for the panel bar across the top of the * detail panel, and that bar is gone — so what is left is the one shape, and a prop with a single * value is a decision nobody makes. * * ── Labelled, and spread ── * * The icons alone were a rebus: a rectangle, a slightly narrower rectangle, and a narrower one still, * telling three things apart by proportion at 12 pixels. They read as tabs now because they behave * like tabs — one of three, the whole width, the chosen one filled — and the word is what makes the * 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 (