import { type ReactNode } from "react"; import { useTranslation } from "react-i18next"; import { RotateCcw } from "lucide-react"; import { IconButton } from "./ui/IconButton"; import { Slider as Range, Checkbox } from "./ui/NativeControls"; // --- settings-panel building blocks ------------------------------------------------------------ export function PanelHead({ children }: { children: ReactNode }) { return (
{children}
); } export function Slider({ label, value, min, max, step, suffix, onChange, onReset, }: { label: string; value: number; min: number; max: number; step: number; suffix?: string; onChange: (v: number) => void; onReset?: () => void; }) { const { t } = useTranslation(); return (
{label} {Number.isInteger(value) ? value : value.toFixed(1)} {suffix ?? ""} {onReset && ( )}
onChange(v)} />
); } export function Toggle({ label, checked, onChange, }: { label: string; checked: boolean; onChange: (v: boolean) => void; }) { return ( ); } // A control button with a tooltip that opens ABOVE it (so it never clips off the bottom of the // viewport, unlike a native title tooltip on the bottom control bar). export function Ctrl({ label, onClick, disabled, align = "center", children, }: { label: string; onClick: () => void; disabled?: boolean; // Tooltip horizontal anchor: "start"/"end" for edge buttons so it doesn't clip off the viewport. align?: "start" | "center" | "end"; children: ReactNode; }) { const pos = align === "start" ? "left-0" : align === "end" ? "right-0" : "left-1/2 -translate-x-1/2"; // `will-change-[opacity]` keeps each tooltip on its own compositor layer from the start. Without it // the browser promotes a tooltip only while its fade runs, and inside the player's scale(1.25) // the neighbouring glyphs got re-layered with it: every button in the row shimmered as the // pointer crossed one (owner, E28 audit). return (
{children} {label}
); }