/** * Liquid glass: the controls. * * A press is answered by the light moving — a soft glow that follows the * pointer across the surface — and by the control giving very slightly. Every * one of these is a `ui/core` hook in a different coat. */ import * as SelectPrimitive from "@radix-ui/react-select" import { Check, ChevronDown } from "lucide-react" import { motion } from "motion/react" import { cn } from "@/lib/utils" import { asOriginal, text } from "../core/config" import type { ButtonProps, InputProps, SegmentedProps, SelectProps, SliderProps, SwitchProps, } from "../core/contract" import { usePress, useSegmented, useSliderDrag, useSwitch, } from "../core/controls" import { useLook } from "../core/look" import { LOOK } from "../core/motion" export function Button({ variant = "tonal", pressed, disabled, label, onClick, children, }: ButtonProps) { const { onPointerDown } = usePress() return ( {children} ) } export function Switch(props: SwitchProps) { const { buttonProps } = useSwitch(props) const { onPointerDown } = usePress() return ( ) } export function Slider(props: SliderProps) { const { fraction, inputProps, marks } = useSliderDrag(props) const orientation = props.orientation ?? "horizontal" return (
{/* The control itself, laid transparent over what is drawn: it keeps the keyboard, the pointer and every `aria-`, and owes the look nothing. */}
{marks.length > 0 ? ( // Decoration: the input itself announces min, max and where it stands.
{marks.map((mark) => ( {mark.label} ))}
) : null}
) } export function Segmented(props: SegmentedProps) { const { chosen, thumbId, groupProps, itemProps } = useSegmented(props) const vertical = props.orientation === "vertical" const { onPointerDown } = usePress() return (
{props.options.map(([value, label], index) => ( ))}
) } export function Select({ value, options, label, disabled, onChange, }: SelectProps) { // The menu is portalled to `body`, which is outside the canvas — so it // re-states the dashboard's own colours rather than borrowing the app's. const { style } = useLook() return ( onChange(asOriginal(selected, options))} > {options.map((option) => ( {option.label ?? text(option.value)} ))} ) } export function Input({ value, type, label, disabled, onChange, onCommit, }: InputProps) { return ( onChange(event.target.value)} onBlur={onCommit} onKeyDown={(event) => { if (event.key === "Enter") onCommit() }} /> ) }