import * as stylex from "@stylexjs/stylex"; import { type FormEvent, type ReactElement, createContext, memo, use, } from "react"; import { controlColor } from "./theme.stylex"; import { color, fontSize, size, transition } from "./tokens.stylex"; import { a11y, interaction } from "./mixins"; import useRippleEffect from "./useRippleEffect"; const styles = stylex.create({ fieldset: { display: "flex", gap: size.px0_5, borderStyle: "none", margin: 0, padding: 0, }, legend: { fontSize: fontSize.body, marginBottom: size.px0_5, }, option: { flex: "1", userSelect: "none", cursor: "pointer", padding: `${size.px2} ${size.px3}`, transition: `${transition.a11yOutline}, ${transition.themeBackground}, ${transition.themeColor}`, backgroundColor: { default: color.gray500, ":has(input:checked)": controlColor.checkboxCheckedBackground, }, fontSize: fontSize.sub, textAlign: "center", textTransform: "lowercase", }, }); type InlineRadioButtonGroupContextType = { /** Name for the HTML data value in forms */ name: string; /** A selection is required */ required: boolean; /** Disable entire group of radio buttons. */ disabled: boolean; /** `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: string | undefined; /** `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) */ onChange: ((value: string) => void) | undefined; }; const InlineRadioButtonGroupContext = createContext( // biome-ignore lint/style/noNonNullAssertion: Context always set in group component undefined!, ); InlineRadioButtonGroupContext.displayName = "InlineRadioButtonGroupContext"; export type InlineRadioButtonGroupProps = { label?: string; /** Name for the HTML data value in forms */ name: string; /** A selection is required */ required?: boolean; /** Disable entire group of radio buttons. */ disabled?: boolean; /** `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?: string | undefined; /** `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) */ onChange?: (value: string) => void; children: | ReactElement | ReactElement[]; }; export const InlineRadioButtonGroup = memo(function InlineRadioButtonGroup( props: InlineRadioButtonGroupProps, ) { const ctx = { value: props.value, onChange: props.onChange, name: props.name, required: !!props.required, disabled: !!props.disabled, }; return (
{props.label && ( {props.label} )} {props.children}
); }); export type InlineRadioButtonProps = { label: string; value: string; /** Disabled this specific radio button in the group. */ disabled?: boolean; defaultChecked?: boolean; }; export const InlineRadioButton = memo(function InlineRadioButton( props: InlineRadioButtonProps, ) { const ctx = use(InlineRadioButtonGroupContext); const disabled = props.disabled || ctx.disabled; const ref = useRippleEffect(disabled); const checked = ctx.value !== undefined ? ctx.value === props.value : undefined; const onChange = ctx.onChange ? (e: FormEvent) => ctx.onChange?.(e.currentTarget.value) : undefined; return ( ); });