// The segmented shape's thumb transition lives beside the dashboard's own // widgets, and CSS is chunked per entry — so the rule is pulled in wherever // this picker is used, or the two copies of one shape would move differently. import "@/components/Dashboard/dashboard.css" import { cn } from "@/lib/utils" /** * A window of history, and everything a query needs to ask for it. * * `bucketS` keeps the number of points a chart draws roughly constant: a week * of minute buckets is ten thousand readings nobody can see and a megabyte of * JSON every refresh, so the longer windows are folded coarser server-side. */ export type Range = { label: string; hours: number; bucketS: number } /** * The windows on offer. * * Bounded by what the collector keeps: it prunes buckets, failures and runs at * `OBS_RETENTION_DAYS` (30 by default), so a week is behind the last preset * rather than an empty chart. */ export const RANGES: Range[] = [ { label: "1h", hours: 1, bucketS: 60 }, { label: "6h", hours: 6, bucketS: 60 }, { label: "24h", hours: 24, bucketS: 60 }, { label: "7d", hours: 168, bucketS: 900 }, ] /** A day: long enough to hold a night's worth of trouble, short enough to read. */ export const DEFAULT_RANGE = RANGES[2] /** * Where the window starts, as the stamp the history endpoints take. * * Read at fetch time rather than when the options are built, so the window * slides with the clock instead of freezing where the screen opened. */ export const rangeStart = (range: Range) => new Date(Date.now() - range.hours * 3600_000).toISOString() /** * The window a screen is showing, as presets. * * The one segmented shape: a single border pill, transparent segments, * bg-accent on the selected one (root DESIGN-GUIDELINES.md). */ export function RangePicker({ value, onChange, }: { value: Range onChange: (range: Range) => void }) { const chosen = RANGES.findIndex((range) => range.hours === value.hours) return ( // A `fieldset` carries `min-inline-size: min-content` from the UA sheet, // which no width utility overrides. Equal tracks and no gap put the // sliding thumb at its share of the padded box without measuring — a grid // rather than a flex row because `flex-1` under `w-fit` sizes the segments // to a share of the widest label instead of to the label itself.
Time range {chosen >= 0 ? ( ) : null} {RANGES.map((range, index) => ( ))}
) }