import * as stylex from "@stylexjs/stylex";
import { type ChangeEvent, memo, useCallback, useState } from "react";
import { sliderConfig, switchConfig } from "./ToggleSliderInput.vars.stylex";
import { interaction } from "./mixins";
import { controlColor } from "./theme.stylex";
import { color, duration, timingFunction } from "./tokens.stylex";
export interface ToggleSliderInputProps {
name?: string;
disabled?: boolean;
title?: string;
/** `value` and `onChange` are optional because we want to be able to use the control in an uncontrolled manner as well (for example, in forms) */
value?: boolean;
onChange?: (value: boolean) => void;
defaultChecked?: boolean;
}
// Box around the slider
// Loosely based on:
// https://www.w3schools.com/howto/howto_css_switch.asp
// https://kittygiraudel.com/2021/04/05/an-accessible-toggle/#focused-styles
const switchStyle = stylex.create({
base: {
position: "relative",
display: "inline-block",
width: switchConfig.width,
height: switchConfig.height,
},
});
const sliderStyle = stylex.create({
base: {
position: "absolute",
inset: 0,
borderRadius: switchConfig.width,
transition: `background-color ${duration.default} ${timingFunction.fast}`,
"::before": {
position: "absolute",
content: "''",
height: sliderConfig.size,
width: sliderConfig.size,
left: sliderConfig.offset,
bottom: sliderConfig.offset,
borderRadius: "50%",
transition: `
transform ${duration.default} ${timingFunction.fast},
background-color ${duration.default} ${timingFunction.fast}
`,
},
},
off: {
backgroundColor: color.gray400,
"::before": {
backgroundColor: color.gray000,
},
},
on: {
backgroundColor: controlColor.toggleCheckedBackground,
"::before": {
transform: `translateX(${sliderConfig.translationOffset})`,
backgroundColor: controlColor.toggleCheckedPillBackground,
},
},
disabledOff: {
backgroundColor: color.gray400,
"::before": {
backgroundColor: color.gray100,
},
},
disabledOn: {
backgroundColor: controlColor.toggleCheckedDisabledBackground,
"::before": {
transform: `translateX(${sliderConfig.translationOffset})`,
backgroundColor: controlColor.toggleCheckedDisabledPillBackground,
},
},
});
/** @remarks Exported for use in Drawer */
export default memo(function ToggleSliderInput(props: ToggleSliderInputProps) {
return props.value !== undefined && props.onChange !== undefined ? (
) : (
);
});
interface FakeUncontrolledToggleSliderInputProps
extends ToggleSliderInputProps {
value?: undefined;
onChange?: undefined;
}
const FakeUncontrolledToggleSliderInput = memo(
function FakeUncontrolledToggleSliderInput(
props: FakeUncontrolledToggleSliderInputProps,
) {
const [value, setValue] = useState(props.defaultChecked || false);
return (
);
},
);
interface InternalStateUncontrolledToggleSliderInputProps
extends ToggleSliderInputProps {
value: boolean;
onChange: (value: boolean) => void;
}
// We need to split this up into a fake uncontrolled component because StyleX does not support styling using the input's `checked` state.
// See: https://github.com/facebook/stylex/issues/536
const InternalStateUncontrolledToggleSliderInput = memo(
function FakeUncontrolledToggleSliderInput({
value,
onChange,
defaultChecked,
disabled,
name,
title,
}: InternalStateUncontrolledToggleSliderInputProps) {
const onChangeEffective = useCallback(
(e: ChangeEvent) => {
onChange(e.target.checked);
},
[onChange],
);
return (
);
},
);