import type { CSSProperties, InputHTMLAttributes } from 'react';
import { cn } from './cn';
import styles from './range.module.css';
type RangeStyle = CSSProperties & {
'--range-progress'?: string;
'--range-tick-size'?: string;
'--range-tick-color'?: string;
};
function toNumber(value: string | number | readonly string[] | undefined, fallback: number): number {
if (typeof value === 'number') return Number.isFinite(value) ? value : fallback;
if (typeof value === 'string') {
const parsed = Number.parseFloat(value);
return Number.isFinite(parsed) ? parsed : fallback;
}
return fallback;
}
function resolveRangeProgress(props: InputHTMLAttributes): number {
const min = toNumber(props.min, 0);
const max = toNumber(props.max, 100);
const value = toNumber(props.value, min);
const span = max - min;
if (span <= 0) return 0;
const progress = ((value - min) / span) * 100;
return Math.min(100, Math.max(0, progress));
}
// Discrete sliders get a ruler: one notch per step. Returns the segment width
// (as a %) for the repeating tick gradient, or null for continuous/large ranges.
function resolveTickSize(props: InputHTMLAttributes): string | null {
const min = toNumber(props.min, 0);
const max = toNumber(props.max, 100);
const step = toNumber(props.step, 1);
if (step <= 0) return null;
const segments = Math.round((max - min) / step);
if (!Number.isFinite(segments) || segments < 2 || segments > 24) return null;
return `${100 / segments}%`;
}
export function RangeInput({
className,
style,
...props
}: Omit, 'type'>) {
const tickSize = resolveTickSize(props);
const rangeStyle: RangeStyle = {
...style,
'--range-progress': `${resolveRangeProgress(props)}%`,
...(tickSize
? {
'--range-tick-size': tickSize,
'--range-tick-color': 'color-mix(in srgb, var(--foreground) 22%, transparent)',
}
: {}),
};
return ;
}