import type { ToastPosition } from '@/components/toast/toast'; import { useParam } from '~/router'; /** * Which corner the showcase's one `` sits in, kept in the URL. * * A `Toaster` is app chrome — one per application, mounted near the root, and * `position` is its prop rather than the individual `toast()` call's. That makes * the corner impossible to demonstrate from inside a page: mounting a second * `Toaster` to show `top-center` would not move anything, it would *duplicate* * every toast, because both subscribe to the same module-singleton queue and * each renders the whole of it. * * So the corner lives in the hash instead and the one real `Toaster` follows it. * `/toasts` writes the param, and the toast that appears is the toast the * snippet on that page produces — the showcase's usual rule that what you copy * is what you are looking at, applied to a prop that is nowhere near the copy. * * The param is deliberately *not* `p.`-prefixed: `router.tsx` drops those on * navigation because they configure the component you are leaving, while this * one configures the chrome you are taking with you. It behaves like `theme`. * * Its own file rather than `toaster.tsx` because it has two consumers, and the * two constants travel with it: `/toasts` reads all three, so leaving them * behind would have the page import from `toaster.tsx` and the hook from here — * a cycle through the module that mounts the component. */ const PARAM = 'toast'; export const DEFAULT_POSITION = 'bottom-right' satisfies ToastPosition; /** In the order the picker draws them: the two rows of a viewport, left to right. */ export const TOAST_POSITIONS = [ 'top-left', 'top-center', 'top-right', 'bottom-left', 'bottom-center', 'bottom-right', ] as const satisfies readonly ToastPosition[]; function isPosition(value: string): value is ToastPosition { return (TOAST_POSITIONS as readonly string[]).includes(value); } /** * Read and write the corner. Unknown text in the hash falls back to the default * rather than reaching the component — a hand-edited link should not be able to * put `position={undefined}` on the mounted `Toaster`. */ export function useToastPosition(): [ToastPosition, (next: ToastPosition) => void] { const [raw, set] = useParam(PARAM, DEFAULT_POSITION, true); return [isPosition(raw) ? raw : DEFAULT_POSITION, set]; }