From 0ebad759f960562ee59cd93ecee56c71c36a93f5 Mon Sep 17 00:00:00 2001 From: adilallo <39313955+adilallo@users.noreply.github.com> Date: Wed, 4 Feb 2026 13:57:51 -0700 Subject: [PATCH] Update radio group component --- app/components/RadioGroup/RadioGroup.types.ts | 1 + app/components/RadioGroup/RadioGroup.view.tsx | 46 +++ stories/RadioGroup.stories.js | 278 ++++++++---------- 3 files changed, 172 insertions(+), 153 deletions(-) diff --git a/app/components/RadioGroup/RadioGroup.types.ts b/app/components/RadioGroup/RadioGroup.types.ts index c3d5aef..b044e1b 100644 --- a/app/components/RadioGroup/RadioGroup.types.ts +++ b/app/components/RadioGroup/RadioGroup.types.ts @@ -1,6 +1,7 @@ export interface RadioOption { value: string; label: string; + subtext?: string; ariaLabel?: string; } diff --git a/app/components/RadioGroup/RadioGroup.view.tsx b/app/components/RadioGroup/RadioGroup.view.tsx index aef4b30..e82df93 100644 --- a/app/components/RadioGroup/RadioGroup.view.tsx +++ b/app/components/RadioGroup/RadioGroup.view.tsx @@ -21,6 +21,52 @@ export function RadioGroupView({ {options.map((option) => { const isSelected = value === option.value; + // If there's subtext, render radio button without label and handle layout separately + if (option.subtext) { + return ( +
+ { + if (checked) { + onOptionChange(option.value); + } + }} + /> +
+ + {option.label} + + + {option.subtext} + +
+
+ ); + } + + // If no subtext, use RadioButton's built-in label return ( { - const canvas = within(canvasElement); - const radioGroup = canvas.getByRole("radiogroup"); - const radioButtons = canvas.getAllByRole("radio"); - await expect(radioGroup).toBeInTheDocument(); - await expect(radioButtons).toHaveLength(3); - await expect(radioButtons[0]).toHaveAttribute("aria-checked", "true"); - await expect(radioButtons[1]).toHaveAttribute("aria-checked", "false"); - await expect(radioButtons[2]).toHaveAttribute("aria-checked", "false"); - }, -}; - -const StandardInteraction = { - play: async ({ canvasElement }) => { - const canvas = within(canvasElement); - const radioGroup = canvas.getByRole("radiogroup"); - const radioButtons = canvas.getAllByRole("radio"); - await expect(radioGroup).toBeInTheDocument(); - await expect(radioButtons[0]).toHaveAttribute("aria-checked", "false"); - await expect(radioButtons[1]).toHaveAttribute("aria-checked", "true"); - await expect(radioButtons[2]).toHaveAttribute("aria-checked", "false"); - await userEvent.click(radioButtons[0]); - await expect(radioButtons[0]).toHaveAttribute("aria-checked", "true"); - await expect(radioButtons[1]).toHaveAttribute("aria-checked", "false"); - await expect(radioButtons[2]).toHaveAttribute("aria-checked", "false"); - }, -}; - -const InverseInteraction = { - play: async ({ canvasElement }) => { - const canvas = within(canvasElement); - const radioGroup = canvas.getByRole("radiogroup"); - const radioButtons = canvas.getAllByRole("radio"); - await expect(radioGroup).toBeInTheDocument(); - await expect(radioButtons[0]).toHaveAttribute("aria-checked", "true"); - await expect(radioButtons[1]).toHaveAttribute("aria-checked", "false"); - await expect(radioButtons[2]).toHaveAttribute("aria-checked", "false"); - await userEvent.click(radioButtons[1]); - await expect(radioButtons[0]).toHaveAttribute("aria-checked", "false"); - await expect(radioButtons[1]).toHaveAttribute("aria-checked", "true"); - await expect(radioButtons[2]).toHaveAttribute("aria-checked", "false"); - }, -}; - -const InteractiveInteraction = { - play: async ({ canvasElement }) => { - const canvas = within(canvasElement); - const radioGroup = canvas.getByRole("radiogroup"); - const radioButtons = canvas.getAllByRole("radio"); - await expect(radioGroup).toBeInTheDocument(); - await expect(canvas.getByText("Selected: option1")).toBeVisible(); - await userEvent.click(radioButtons[1]); - await expect(canvas.getByText("Selected: option2")).toBeVisible(); - await userEvent.click(radioButtons[2]); - await expect(canvas.getByText("Selected: option3")).toBeVisible(); - }, -}; - -const meta = { +export default { title: "Forms/RadioGroup", component: RadioGroup, parameters: { layout: "centered", backgrounds: { default: "dark", - values: [{ name: "dark", value: "black" }], + values: [ + { name: "light", value: "#ffffff" }, + { name: "dark", value: "#000000" }, + ], }, }, - tags: ["autodocs"], argTypes: { mode: { - control: { type: "select" }, + control: "select", options: ["standard", "inverse"], + description: "Visual mode of the radio group", }, - state: { - control: { type: "select" }, - options: ["default", "hover", "focus"], + disabled: { + control: "boolean", + description: "Whether the radio group is disabled", }, - value: { control: "text" }, - }, - args: { - mode: "standard", - state: "default", - value: "option1", - options: [ - { value: "option1", label: "Option 1" }, - { value: "option2", label: "Option 2" }, - { value: "option3", label: "Option 3" }, - ], }, }; -export default meta; - export const Default = { - args: { - mode: "standard", - state: "default", - value: "option1", - options: [ - { value: "option1", label: "Option 1" }, - { value: "option2", label: "Option 2" }, - { value: "option3", label: "Option 3" }, - ], - }, - play: DefaultInteraction.play, - render: (args) => { - const [value, setValue] = React.useState(args.value); + render: () => { + const [value, setValue] = React.useState(""); + return ( setValue(newValue)} + mode="standard" + options={[ + { value: "option1", label: "Option 1" }, + { value: "option2", label: "Option 2" }, + { value: "option3", label: "Option 3" }, + ]} /> ); }, }; -export const Standard = { +export const WithSubtext = { render: () => { - const [value, setValue] = React.useState("option2"); + const [value, setValue] = React.useState(""); return ( -
-
-

Standard Mode

- setValue(newValue)} - /> -
-
+ setValue(newValue)} + mode="standard" + options={[ + { value: "option1", label: "Option 1" }, + { + value: "option2", + label: "Option 2", + subtext: "Lorem ipsum dolor sit amet consectetur", + }, + ]} + /> ); }, - play: StandardInteraction.play, }; export const Inverse = { render: () => { - const [value, setValue] = React.useState("option1"); + const [value, setValue] = React.useState(""); return ( -
-
-

Inverse Mode

- setValue(newValue)} - /> -
-
+ setValue(newValue)} + mode="inverse" + options={[ + { value: "option1", label: "Option 1" }, + { value: "option2", label: "Option 2" }, + { value: "option3", label: "Option 3" }, + ]} + /> ); }, - play: InverseInteraction.play, }; -export const Interactive = { +export const InverseWithSubtext = { render: () => { - const [value, setValue] = React.useState("option1"); + const [value, setValue] = React.useState(""); return ( -
-
-

Interactive Example

-

Selected: {value}

- setValue(value)} - /> -
-
+ setValue(newValue)} + mode="inverse" + options={[ + { value: "option1", label: "Option 1" }, + { + value: "option2", + label: "Option 2", + subtext: "Lorem ipsum dolor sit amet consectetur", + }, + ]} + /> ); }, - play: InteractiveInteraction.play, +}; + +export const Disabled = { + render: () => ( + + ), +}; + +export const AllModes = () => { + const [standardValue, setStandardValue] = React.useState(""); + const [inverseValue, setInverseValue] = React.useState(""); + + return ( +
+
+

Standard Mode

+ setStandardValue(value)} + mode="standard" + options={[ + { value: "option1", label: "Option 1" }, + { + value: "option2", + label: "Option 2", + subtext: "Lorem ipsum dolor sit amet consectetur", + }, + ]} + /> +
+ +
+

Inverse Mode

+ setInverseValue(value)} + mode="inverse" + options={[ + { value: "option3", label: "Option 1" }, + { + value: "option4", + label: "Option 2", + subtext: "Lorem ipsum dolor sit amet consectetur", + }, + ]} + /> +
+
+ ); };