import { InfoIcon, Undo2Icon } from "lucide-react"; import { useLocation, useNavigate } from "@tanstack/react-router"; import { createContext, type ComponentPropsWithoutRef, type ReactNode, useCallback, useContext, useEffect, useMemo, useState, } from "react"; import { cn } from "../../lib/utils"; import { WorkspacePageContainer, type WorkspacePageWidth } from "../WorkspacePageContainer"; import { Button } from "../ui/button"; import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; interface SettingsSearchTargetContextValue { readonly targetId: string | null; readonly onTargetHandled: () => void; } const noop = () => undefined; const SettingsSearchTargetContext = createContext({ targetId: null, onTargetHandled: noop, }); export function SettingsSearchTargetProvider({ targetId, onTargetHandled = noop, children, }: { targetId: string | null; onTargetHandled?: () => void; children: ReactNode; }) { const value = useMemo(() => ({ targetId, onTargetHandled }), [onTargetHandled, targetId]); return {children}; } function scrollAndFocusSettingsTarget(target: HTMLElement): void { const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches; const scrollTarget = target.tagName === "SECTION" && target.firstElementChild ? (target.firstElementChild as HTMLElement) : target; scrollTarget.scrollIntoView({ behavior: prefersReducedMotion ? "auto" : "smooth", block: "center", }); target.focus({ preventScroll: true }); target.classList.remove("settings-search-target-pulse"); if (prefersReducedMotion) return; void target.offsetWidth; target.classList.add("settings-search-target-pulse"); // The class also suppresses the focus outline (the pulse is the destination // indicator), so drop it once the element is no longer the destination. target.addEventListener("blur", () => target.classList.remove("settings-search-target-pulse"), { once: true, }); } /** The row id a settings-search jump is currently trying to reach, if any. */ export function useSettingsSearchTargetId(): string | null { return useContext(SettingsSearchTargetContext).targetId; } function useSettingsSearchTarget(id: string | undefined) { const { targetId, onTargetHandled } = useContext(SettingsSearchTargetContext); const isSearchTarget = id !== undefined && id === targetId; const targetRef = useCallback( (target: T | null) => { if (target && isSearchTarget) { scrollAndFocusSettingsTarget(target); onTargetHandled(); } }, [isSearchTarget, onTargetHandled], ); return targetRef; } /** Info affordance explaining how a setting interacts with the shared background policy. */ export function PolicyTooltip({ children }: { readonly children: string }) { return ( } /> {children} ); } /** Re-render every `intervalMs`; return a stable timestamp snapshot for render-time relative labels. */ export function useRelativeTimeTick(intervalMs = 1_000) { const [nowMs, setNowMs] = useState(() => Date.now()); useEffect(() => { const id = setInterval(() => setNowMs(Date.now()), intervalMs); return () => clearInterval(id); }, [intervalMs]); return nowMs; } export function SettingsSection({ title, icon, headerAction, children, className, ...sectionProps }: ComponentPropsWithoutRef<"section"> & { title: string; icon?: ReactNode; headerAction?: ReactNode; children: ReactNode; }) { const targetRef = useSettingsSearchTarget(sectionProps.id); return (

{icon} {title}

{headerAction}
{children}
); } export function SettingsRow({ title, description, status, resetAction, control, children, className, ...rowProps }: Omit, "title"> & { title: ReactNode; description?: ReactNode; status?: ReactNode; resetAction?: ReactNode; control?: ReactNode; children?: ReactNode; }) { const targetRef = useSettingsSearchTarget(rowProps.id); return (

{title}

{resetAction}
{description ? (

{description}

) : null} {status ?
{status}
: null}
{control ? (
{control}
) : null}
{children}
); } export function SettingResetButton({ label, disabled = false, onClick, }: { label: string; disabled?: boolean; onClick: () => void; }) { return ( { event.stopPropagation(); onClick(); }} > } /> Reset to default ); } export function SettingsPageContainer({ children, className, width = "readable", }: { children: ReactNode; className?: string; width?: WorkspacePageWidth; }) { const navigate = useNavigate(); const hash = useLocation({ select: (location) => location.hash }); const targetId = hash.replace(/^#/, "") || null; const clearTargetHash = useCallback(() => { void navigate({ hash: "", replace: true, resetScroll: false, hashScrollIntoView: false }); }, [navigate]); return (
{children}
); } export function scrollToSettingsTarget(targetId: string): boolean { const target = document.getElementById(targetId); if (!target) return false; scrollAndFocusSettingsTarget(target); return true; }