"use client"
import * as React from "react"
import { type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
import { matchesHotkey } from "@/lib/hotkeys"
import { KbdShortcut } from "@/components/ui/kbd-shortcut"
import { Toggle } from "@/components/ui/toggle"
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
import type { toggleVariants } from "@/components/ui/toggle"
export type ChartTimeRangeOption = { value: string; label: string }
/** The ranges a dashboard reaches for first — a week, a month, a quarter, a year. */
export const CHART_TIME_RANGES: ChartTimeRangeOption[] = [
{ value: "7d", label: "7d" },
{ value: "30d", label: "30d" },
{ value: "90d", label: "90d" },
{ value: "12m", label: "12m" },
]
/** The key that turns the previous period on and off. */
export const COMPARE_HOTKEY = "c"
// `multiple` is omitted deliberately: a multi-select group could be emptied,
// and the whole point of this control is that a range is always chosen.
export type ChartTimeRangeProps = Omit<
React.ComponentProps<typeof ToggleGroup>,
"value" | "defaultValue" | "onValueChange" | "multiple" | "size" | "variant"
> & {
value: string
onValueChange: (v: string) => void
options?: ChartTimeRangeOption[]
size?: NonNullable<VariantProps<typeof toggleVariants>["size"]>
/** Adds a Compare toggle beside the ranges. Leave it off and none is drawn. */
compare?: boolean
onCompareChange?: (compare: boolean) => void
/** The keystroke that toggles Compare; false unbinds it. */
compareHotkey?: string | false
/**
* @deprecated Changes nothing: the chosen range wears the kit's selected
* fill, --brand-muted, in either tone — the Toggle's own pressed fill since
* it stopped using the grey that hover paints too. Still recorded as
* `data-tone`, so a page that sets it keeps compiling; leave it out.
*/
tone?: "neutral" | "brand"
className?: string
}
function ChartTimeRange({
className,
value,
onValueChange,
options = CHART_TIME_RANGES,
size = "default",
compare,
onCompareChange,
compareHotkey = COMPARE_HOTKEY,
tone = "neutral",
...props
}: ChartTimeRangeProps) {
// The toggle only exists when a caller is holding the state for it: a Compare
// switch that does nothing is worse than no switch.
const showCompare = compare !== undefined && onCompareChange !== undefined
// The listener is attached once per hotkey and reads the current state
// through a ref, so an inline onCompareChange does not resubscribe it every
// render — the same shape the command palette's ⌘K listener takes.
const latest = React.useRef({ compare, onCompareChange })
React.useEffect(() => {
latest.current = { compare, onCompareChange }
}, [compare, onCompareChange])
React.useEffect(() => {
if (!showCompare || compareHotkey === false) return
const onKeyDown = (event: KeyboardEvent) => {
if (!matchesHotkey(event, compareHotkey)) return
event.preventDefault()
latest.current.onCompareChange?.(!latest.current.compare)
}
window.addEventListener("keydown", onKeyDown)
return () => window.removeEventListener("keydown", onKeyDown)
}, [showCompare, compareHotkey])
return (
<div
data-slot="chart-time-range"
data-size={size}
data-tone={tone}
className={cn("flex w-fit items-center gap-2", className)}
>
<ToggleGroup
aria-label="Time range"
variant="outline"
size={size}
spacing={0}
value={[value]}
// Pressing the selected range hands back an empty group. Ignoring that
// keeps a range always chosen, so the charts it scopes never lose their
// window and blank out.
onValueChange={(next) => {
const [selected] = next
if (selected) onValueChange(selected)
}}
className="w-fit"
{...props}
>
{options.map((option) => (
<ToggleGroupItem
key={option.value}
value={option.value}
className="tabular-nums"
>
{option.label}
</ToggleGroupItem>
))}
</ToggleGroup>
{showCompare ? (
// Described, not only labelled: "Compare" does not say with what,
// and the tooltip — the one place that does — reaches a pointer only.
<Tooltip describeTrigger>
<TooltipTrigger
render={
<Toggle
data-slot="chart-compare-toggle"
variant="outline"
size={size}
pressed={compare}
onPressedChange={(next) => onCompareChange?.(next)}
aria-keyshortcuts={compareHotkey === false ? undefined : compareHotkey}
>
Compare
</Toggle>
}
/>
<TooltipContent className="flex items-center gap-2">
Previous period
{/* Shown, not described: the toggle announces its key through
aria-keyshortcuts already. */}
{compareHotkey === false ? null : <KbdShortcut keys={compareHotkey} aria-hidden="true" />}
</TooltipContent>
</Tooltip>
) : null}
</div>
)
}
export { ChartTimeRange }