import * as stylex from "@stylexjs/stylex"; import { type FormEvent, memo, useId } from "react"; import { sliderConfig, state } from "./Slider.vars.stylex"; import { controlColor } from "./theme.stylex"; const webkitThumb = "::-webkit-slider-thumb"; const firefoxThumb = "::-moz-range-thumb"; const webkitTack = "::-webkit-slider-runnable-track"; const firefoxTack = "::-moz-range-track"; const firefoxProgress = "::-moz-range-progress"; // Refs: // - https://www.smashingmagazine.com/2021/12/create-custom-range-input-consistent-browsers/ // - https://css-tricks.com/styling-cross-browser-compatible-range-inputs-css/ // - https://stackoverflow.com/questions/18389224/how-to-style-html5-range-input-to-have-different-color-before-and-after-slider const styles = stylex.create({ base: { width: "100%", margin: 0, padding: 0, appearance: "none", // Hides the slider so that custom slider can be made backgroundColor: "transparent", // Otherwise white in Chrome accentColor: controlColor.sliderPassedTrackBackground, outline: { ":focus": "none", // Removes the blue border. You should probably do some kind of focus styling for accessibility reasons though. }, [state.cursor]: { default: "pointer", ":disabled": "not-allowed", }, [state.thumbBackground]: { default: sliderConfig.thumbBackground, ":hover:not(:disabled)": controlColor.sliderThumbHoverBackground, ":disabled": controlColor.sliderThumbDisabledBackground, }, [state.trackBackground]: { default: controlColor.sliderTrackBackground, ":disabled": controlColor.sliderTrackDisabledBackground, }, [state.progressBackground]: { default: controlColor.sliderPassedTrackBackground, ":disabled": controlColor.sliderDisabledPassedTrackBackground, }, [webkitThumb]: { appearance: "none", cursor: state.cursor, width: sliderConfig.width, height: sliderConfig.height, borderStyle: sliderConfig.borderStyle, borderRadius: sliderConfig.borderRadius, background: state.thumbBackground, // This has to be a specified margin in Chrome, but in Firefox it is automatic marginTop: `calc(-1* ${sliderConfig.height} / 3)`, }, // Note: DON'T use comma-separated selectors here. It won't work, since a browser will drop the entire rule if it doesn't understand one part of it. [firefoxThumb]: { cursor: state.cursor, width: sliderConfig.width, height: sliderConfig.height, borderStyle: sliderConfig.borderStyle, borderRadius: sliderConfig.borderRadius, background: state.thumbBackground, }, [webkitTack]: { width: "100%", height: sliderConfig.trackHeight, cursor: state.cursor, background: state.trackBackground, borderStyle: sliderConfig.trackBorderStyle, borderRadius: sliderConfig.trackBorderRadius, }, [firefoxTack]: { width: "100%", height: sliderConfig.trackHeight, cursor: state.cursor, background: state.trackBackground, borderStyle: sliderConfig.trackBorderStyle, borderRadius: sliderConfig.trackBorderRadius, }, [firefoxProgress]: { background: state.progressBackground, }, }, }); export interface SliderProps { name?: string; disabled?: boolean; defaultValue?: number; value?: number; onChange?: (value: number) => void; min?: number; max?: number; step?: number; list?: number[]; } export default memo(function Slider({ name, disabled, defaultValue, value, onChange, min, max, step, list, }: SliderProps) { const markerId = useId(); const onChangeWrapped = onChange ? (e: FormEvent) => onChange?.(e.currentTarget.valueAsNumber) : undefined; return ( <> {list && ( {list.map((value) => ( )} ); });