/**
* 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()
}}
/>
)
}