Files
community-rule/app/components/controls/RadioButton/RadioButton.container.tsx
T
adilallo ca928e306b Match the 3 Nov color ramps and size-based strokes, and replace leftover palette hex with tokens.
Drop Cookies Settings from the footer, link the Home features that already have pages, and give leftover routes their own descriptions.
2026-11-05 15:37:55 -07:00

144 lines
4.8 KiB
TypeScript

"use client";
import { memo, useCallback, useId } from "react";
import { RadioButtonView } from "./RadioButton.view";
import type { RadioButtonProps } from "./RadioButton.types";
const RadioButtonContainer = ({
checked = false,
mode: modeProp = "standard",
state: stateProp = "default", // This state prop is now only for static display in Storybook/Preview
indicator: _indicator = true, // From Figma: whether to show the indicator dot (currently not used in view)
disabled = false,
label,
onChange,
id,
name,
value,
ariaLabel,
className = "",
}: RadioButtonProps) => {
const mode = modeProp;
const state = stateProp;
// If state is "selected", it means checked in Figma terms
const normalizedState = state === "selected" || checked ? "selected" : state;
const isInverse = mode === "inverse";
const isStandard = mode === "standard";
// Base box styles per Figma - 24px size, circular
const baseBox = `
flex
items-center
justify-center
shrink-0
w-[24px]
h-[24px]
rounded-full
transition-all
duration-200
ease-in-out
p-[4px]
`
.trim()
.replace(/\s+/g, " ");
// Get box styles based on mode and checked status per Figma designs
const getBoxStyles = (): string => {
// Standard mode styles
if (isStandard) {
// Default state: tertiary border (or brand primary when checked), with hover and focus states via CSS
// Hover changes border to brand primary color
// Focus shows shadow (double ring: 2px white inner, 4px dark outer)
// When checked, border is brand primary (but changes to invert tertiary on focus)
const defaultBorder = checked
? "border-[var(--color-border-default-brand-primary)]"
: "border-[var(--color-border-default-control)]";
// When focused and checked, border should be invert tertiary (#2d2d2d) per Figma
const focusBorder = checked
? "focus:border-[var(--color-content-invert-tertiary)]"
: "focus:border-[var(--color-border-default-control)]";
return `${baseBox} bg-[var(--color-surface-default-primary)] border border-solid ${defaultBorder} hover:border-[var(--color-border-default-brand-primary)] ${focusBorder} focus:shadow-[var(--shadow-focus-invert)] focus:outline-none`;
}
// Inverse mode styles
if (isInverse) {
// Default state: white border (or brand primary when checked), transparent background
// Hover changes border to inverse brand primary color (#6c6701) for both selected and unselected
// Focus shows shadow (double ring: 2px dark inner, 4px white outer)
// When checked, border is brand primary (but changes to white on focus)
const defaultBorder = checked
? "border-[var(--color-border-default-brand-primary)]"
: "border-[var(--color-border-invert-primary)]";
// Hover border: inverse brand primary for both selected and unselected per Figma
const hoverBorder =
"hover:border-[var(--color-border-invert-brand-primary)]";
// Focus border: when focused and checked, border should be white per Figma
const focusBorder = checked
? "focus:border-[var(--color-border-invert-primary)]"
: "focus:border-[var(--color-border-invert-primary)]";
return `${baseBox} bg-transparent border border-solid ${defaultBorder} ${hoverBorder} ${focusBorder} focus:shadow-[var(--shadow-focus-default)] focus:outline-none`;
}
return baseBox;
};
const combinedBoxStyles = getBoxStyles();
// Label color
const labelColor = isInverse
? "var(--color-content-invert-primary)"
: "var(--color-content-default-primary)";
// Generate unique ID for accessibility if not provided
const generatedId = useId();
const radioId = id || `radio-${generatedId}`;
const handleToggle = useCallback(
(_e: React.MouseEvent | React.KeyboardEvent) => {
if (!disabled && onChange) {
// Always call onChange when clicked, even if already checked
// The parent (RadioGroup) will handle the logic
onChange({ checked: true, value });
}
},
[disabled, onChange, value],
);
const handleKeyDown = (e: React.KeyboardEvent<HTMLSpanElement>) => {
if (e.key === " " || e.key === "Enter") {
e.preventDefault();
handleToggle(e);
}
};
return (
<RadioButtonView
radioId={radioId}
checked={checked}
mode={mode}
state={normalizedState} // Normalized state (handles "selected" from Figma)
disabled={disabled}
label={label}
name={name}
value={value}
ariaLabel={ariaLabel}
className={className}
combinedBoxStyles={combinedBoxStyles}
labelColor={labelColor}
onToggle={handleToggle}
onKeyDown={handleKeyDown}
/>
);
};
RadioButtonContainer.displayName = "RadioButton";
export default memo(RadioButtonContainer);