"use client" import * as React from "react" import type { CSSProperties } from "react" import { cn } from "@/lib/utils" function clampValue(value: number, min: number, max: number) { return Math.min(Math.max(value, min), max) } function getProgressPercent(value: number, min: number, max: number) { if (max <= min) return 0 return ((value - min) / (max - min)) * 100 } function getSliderTrackStyle(value: number, min: number, max: number): CSSProperties { const progress = getProgressPercent(clampValue(value, min, max), min, max) return { background: `linear-gradient(90deg, hsl(var(--primary)) 0%, hsl(var(--primary)) ${progress}%, hsl(var(--muted)) ${progress}%, hsl(var(--muted)) 100%)`, } } export type SliderProps = Omit, "type" | "value" | "defaultValue" | "onChange"> & { value?: number defaultValue?: number onValueChange?: (value: number) => void min?: number max?: number step?: number label?: React.ReactNode description?: React.ReactNode showValue?: boolean formatValue?: (value: number) => React.ReactNode } function Slider({ value, defaultValue = 0, onValueChange, min = 0, max = 100, step = 1, label, description, showValue = false, formatValue, className, disabled, ...props }: SliderProps) { const [internalValue, setInternalValue] = React.useState(defaultValue) const currentValue = value ?? internalValue const resolvedValue = formatValue?.(currentValue) ?? currentValue const updateValue = (nextValue: number) => { if (value === undefined) setInternalValue(nextValue) onValueChange?.(nextValue) } return (
{(label || description || showValue) && (
{label && } {description &&
{description}
}
{showValue && (
{resolvedValue}
)}
)} updateValue(event.currentTarget.valueAsNumber)} {...props} />
) } export type RangeSliderValue = [number, number] export type RangeSliderProps = Omit & { value?: RangeSliderValue defaultValue?: RangeSliderValue onValueChange?: (value: RangeSliderValue) => void showValue?: boolean formatValue?: (value: RangeSliderValue) => React.ReactNode } function getRangeFillStyle(value: RangeSliderValue, min: number, max: number): CSSProperties { const start = getProgressPercent(clampValue(value[0], min, max), min, max) const end = getProgressPercent(clampValue(value[1], min, max), min, max) return { left: `${start}%`, width: `${Math.max(end - start, 0)}%`, } } function RangeSlider({ value, defaultValue = [0, 100], onValueChange, min = 0, max = 100, step = 1, label, description, showValue = false, formatValue, className, disabled, ...props }: RangeSliderProps) { const [internalValue, setInternalValue] = React.useState(defaultValue) const currentValue = value ?? internalValue const resolvedValue = formatValue?.(currentValue) ?? `${currentValue[0]} - ${currentValue[1]}` const updateValue = (index: 0 | 1, nextPart: number) => { const nextValue: RangeSliderValue = index === 0 ? [Math.min(nextPart, currentValue[1]), currentValue[1]] : [currentValue[0], Math.max(nextPart, currentValue[0])] if (value === undefined) setInternalValue(nextValue) onValueChange?.(nextValue) } return (
{(label || description || showValue) && (
{label && } {description &&
{description}
}
{showValue && (
{resolvedValue}
)}
)}
updateValue(0, event.currentTarget.valueAsNumber)} {...props} /> updateValue(1, event.currentTarget.valueAsNumber)} {...props} />
) } export { RangeSlider, Slider }