/** * Liquid glass: the readings. * * Lit fills over translucent tracks, and every number written out beside the * picture it is drawn as — an angle or a length nobody can measure is not a * reading. */ import { motion, useTransform } from "motion/react" import { cn } from "@/lib/utils" import { arcPath, GAUGE_START, GAUGE_SWEEP, GAUGE_TRACK } from "../core/arc" import { cssOf } from "../core/color" import { format } from "../core/config" import type { BarProps, ColorDiskProps, GaugeProps, ReadoutProps, } from "../core/contract" import { TESTID } from "../core/contract" import { useColorDisk } from "../core/disc" import { useAnimatedFraction, useAnimatedNumber } from "../core/values" import { Slider } from "./Controls" export function Readout({ value, precision, unit, size = "hero", }: ReadoutProps) { const { label, numeric } = useAnimatedNumber(value, precision) return ( {numeric ? ( {label} ) : ( format(value, precision) )} {unit ? ( {unit} ) : null} ) } export function Gauge({ value, min, max, precision, unit, label }: GaugeProps) { const fraction = useAnimatedFraction( value === null ? 0 : Math.min(1, Math.max(0, (value - min) / (max - min || 1))), ) const { label: reading, numeric } = useAnimatedNumber(value, precision) return (
{/* The same full arc as the track, revealed rather than re-pathed: `d` is not animatable, so a reading that redrew the arc could only jump. `pathLength` normalises it, which makes the reveal the fraction itself. */} {numeric ? ( {reading} ) : ( format(value, precision) )} {unit ?? ""}
) } function Row({ row }: { row: BarProps["rows"][number] }) { const fraction = useAnimatedFraction(row.fraction) const width = useTransform(fraction, (at) => `${at * 100}%`) return (
{row.label} {/* Beside the track rather than on it: a number written on a fill has to clear the fill it sits on and the track it slides onto, and one that reads across a room cannot do both. */} {format(row.value, row.precision)} {row.unit ?? ""}
) } export function Bar({ rows }: BarProps) { // No group role of its own: each row already announces what it reads and // what it is worth, and the tile's title is on the frame around them. return (
{rows.map((row, index) => ( // Two rows can read the same message under different labels; position // is the identity, as it is for chart series. ))}
) } export function ColorDisk({ name, hsv, disabled, onChange, onCommit, }: ColorDiskProps) { const { discProps, thumb, shade } = useColorDisk({ name, hsv, disabled, onChange, onCommit, }) const [hue, saturation, brightness] = hsv return (
{ onChange([hue, saturation, next]) onCommit() }} />
) }