Update radio button component
This commit is contained in:
@@ -14,6 +14,7 @@ export default function ComponentsPreview() {
|
|||||||
const [inverseCheckbox, setInverseCheckbox] = useState(false);
|
const [inverseCheckbox, setInverseCheckbox] = useState(false);
|
||||||
const [checkboxGroupValues, setCheckboxGroupValues] = useState<string[]>([]);
|
const [checkboxGroupValues, setCheckboxGroupValues] = useState<string[]>([]);
|
||||||
const [radioValue, setRadioValue] = useState("");
|
const [radioValue, setRadioValue] = useState("");
|
||||||
|
const [inverseRadioValue, setInverseRadioValue] = useState("");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-[var(--color-surface-default-primary)] p-[var(--spacing-scale-032)]">
|
<div className="min-h-screen bg-[var(--color-surface-default-primary)] p-[var(--spacing-scale-032)]">
|
||||||
@@ -174,12 +175,14 @@ export default function ComponentsPreview() {
|
|||||||
<div className="space-y-[var(--spacing-scale-016)]">
|
<div className="space-y-[var(--spacing-scale-016)]">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="font-inter text-[20px] leading-[24px] font-semibold text-[var(--color-content-default-primary)] mb-[var(--spacing-scale-012)]">
|
<h3 className="font-inter text-[20px] leading-[24px] font-semibold text-[var(--color-content-default-primary)] mb-[var(--spacing-scale-012)]">
|
||||||
States
|
Standard Mode
|
||||||
</h3>
|
</h3>
|
||||||
<div className="space-y-[var(--spacing-scale-016)]">
|
<div className="space-y-[var(--spacing-scale-016)]">
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
name="default-radio"
|
name="default-radio"
|
||||||
value=""
|
value={radioValue}
|
||||||
|
onChange={({ value }) => setRadioValue(value)}
|
||||||
|
mode="standard"
|
||||||
options={[
|
options={[
|
||||||
{ value: "option1", label: "Option 1" },
|
{ value: "option1", label: "Option 1" },
|
||||||
{ value: "option2", label: "Option 2" },
|
{ value: "option2", label: "Option 2" },
|
||||||
@@ -190,6 +193,7 @@ export default function ComponentsPreview() {
|
|||||||
name="interactive-radio"
|
name="interactive-radio"
|
||||||
value={radioValue}
|
value={radioValue}
|
||||||
onChange={({ value }) => setRadioValue(value)}
|
onChange={({ value }) => setRadioValue(value)}
|
||||||
|
mode="standard"
|
||||||
options={[
|
options={[
|
||||||
{ value: "option1", label: "Option 1" },
|
{ value: "option1", label: "Option 1" },
|
||||||
{ value: "option2", label: "Option 2" },
|
{ value: "option2", label: "Option 2" },
|
||||||
@@ -199,6 +203,47 @@ export default function ComponentsPreview() {
|
|||||||
<RadioGroup
|
<RadioGroup
|
||||||
name="disabled-radio"
|
name="disabled-radio"
|
||||||
value=""
|
value=""
|
||||||
|
mode="standard"
|
||||||
|
disabled
|
||||||
|
options={[
|
||||||
|
{ value: "option1", label: "Option 1" },
|
||||||
|
{ value: "option2", label: "Option 2" },
|
||||||
|
{ value: "option3", label: "Option 3" },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="font-inter text-[20px] leading-[24px] font-semibold text-[var(--color-content-default-primary)] mb-[var(--spacing-scale-012)]">
|
||||||
|
Inverse Mode
|
||||||
|
</h3>
|
||||||
|
<div className="space-y-[var(--spacing-scale-016)]">
|
||||||
|
<RadioGroup
|
||||||
|
name="inverse-default-radio"
|
||||||
|
value={inverseRadioValue}
|
||||||
|
onChange={({ value }) => setInverseRadioValue(value)}
|
||||||
|
mode="inverse"
|
||||||
|
options={[
|
||||||
|
{ value: "option1", label: "Option 1" },
|
||||||
|
{ value: "option2", label: "Option 2" },
|
||||||
|
{ value: "option3", label: "Option 3" },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<RadioGroup
|
||||||
|
name="inverse-interactive-radio"
|
||||||
|
value={inverseRadioValue}
|
||||||
|
onChange={({ value }) => setInverseRadioValue(value)}
|
||||||
|
mode="inverse"
|
||||||
|
options={[
|
||||||
|
{ value: "option1", label: "Option 1" },
|
||||||
|
{ value: "option2", label: "Option 2" },
|
||||||
|
{ value: "option3", label: "Option 3" },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<RadioGroup
|
||||||
|
name="inverse-disabled-radio"
|
||||||
|
value=""
|
||||||
|
mode="inverse"
|
||||||
disabled
|
disabled
|
||||||
options={[
|
options={[
|
||||||
{ value: "option1", label: "Option 1" },
|
{ value: "option1", label: "Option 1" },
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import type { RadioButtonProps } from "./RadioButton.types";
|
|||||||
const RadioButtonContainer = ({
|
const RadioButtonContainer = ({
|
||||||
checked = false,
|
checked = false,
|
||||||
mode = "standard",
|
mode = "standard",
|
||||||
state = "default",
|
state = "default", // This state prop is now only for static display in Storybook/Preview
|
||||||
disabled = false,
|
disabled = false,
|
||||||
label,
|
label,
|
||||||
onChange,
|
onChange,
|
||||||
@@ -19,52 +19,90 @@ const RadioButtonContainer = ({
|
|||||||
...props
|
...props
|
||||||
}: RadioButtonProps) => {
|
}: RadioButtonProps) => {
|
||||||
const isInverse = mode === "inverse";
|
const isInverse = mode === "inverse";
|
||||||
|
const isStandard = mode === "standard";
|
||||||
|
|
||||||
// Base tokens (using same design tokens as Checkbox)
|
// Base box styles per Figma - 24px size, circular
|
||||||
const colorContent = isInverse
|
const baseBox = `
|
||||||
? "var(--color-content-inverse-primary)"
|
flex
|
||||||
: "var(--color-content-default-primary)";
|
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, " ");
|
||||||
|
|
||||||
// Visual container depending on state
|
// Get box styles based on mode and checked status per Figma designs
|
||||||
const baseBox = `flex items-center justify-center shrink-0 w-[var(--measures-sizing-024)] h-[var(--measures-sizing-024)] rounded-[var(--measures-radius-medium)] transition-all duration-200 ease-in-out`;
|
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,#fdfaa8)]"
|
||||||
|
: "border-[var(--color-border-default-tertiary,#464646)]";
|
||||||
|
|
||||||
|
// When focused and checked, border should be invert tertiary (#2d2d2d) per Figma
|
||||||
|
const focusBorder = checked
|
||||||
|
? "focus:border-[var(--color-content-invert-tertiary,#2d2d2d)]"
|
||||||
|
: "focus:border-[var(--color-border-default-tertiary,#464646)]";
|
||||||
|
|
||||||
|
return `${baseBox} bg-[var(--color-surface-default-primary)] border border-solid ${defaultBorder} hover:border-[var(--color-border-default-brand-primary,#fdfaa8)] ${focusBorder} focus:shadow-[0px_0px_0px_2px_var(--color-border-invert-primary,white),0px_0px_0px_4px_var(--color-border-default-primary,#141414)] focus:outline-none`;
|
||||||
|
}
|
||||||
|
|
||||||
const stateStyles: Record<string, string> = {
|
// Inverse mode styles
|
||||||
default: "",
|
if (isInverse) {
|
||||||
hover: "",
|
// Default state: white border (or brand primary when checked), transparent background
|
||||||
focus: "",
|
// 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,#fdfaa8)]"
|
||||||
|
: "border-[var(--color-border-invert-primary,white)]";
|
||||||
|
|
||||||
|
// Hover border: inverse brand primary for both selected and unselected per Figma
|
||||||
|
const hoverBorder = "hover:border-[var(--color-border-invert-brand-primary,#6c6701)]";
|
||||||
|
|
||||||
|
// Focus border: when focused and checked, border should be white per Figma
|
||||||
|
const focusBorder = checked
|
||||||
|
? "focus:border-[var(--color-border-invert-primary,white)]"
|
||||||
|
: "focus:border-[var(--color-border-invert-primary,white)]";
|
||||||
|
|
||||||
|
return `${baseBox} bg-transparent border border-solid ${defaultBorder} ${hoverBorder} ${focusBorder} focus:shadow-[0px_0px_0px_2px_var(--color-border-default-primary,#141414),0px_0px_0px_4px_var(--color-border-invert-primary,white)] focus:outline-none`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseBox;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Background behavior:
|
const combinedBoxStyles = getBoxStyles();
|
||||||
// - Standard: background does not change on check; only dot appears
|
|
||||||
// - Inverse: transparent background, dot appears on check
|
|
||||||
const backgroundWhenChecked = isInverse
|
|
||||||
? "var(--color-surface-default-transparent)"
|
|
||||||
: "var(--color-surface-default-primary)";
|
|
||||||
|
|
||||||
// Dot color for selected state
|
// Dot color per Figma
|
||||||
const dotColor = checked
|
// Selected state: light cream/yellow (#fefcc9)
|
||||||
? isInverse
|
// Selected hover state: darker yellow/brown (#333000 or rgba(51, 48, 0, 1))
|
||||||
? "var(--color-content-inverse-primary)"
|
const getDotColor = (): string => {
|
||||||
: "var(--color-border-default-brand-primary)"
|
if (!checked) return "transparent";
|
||||||
: "transparent";
|
|
||||||
const labelColor = colorContent;
|
if (isStandard) {
|
||||||
|
// Use CSS to handle hover state - default is light cream, hover is darker
|
||||||
|
return "var(--color-content-default-brand-primary, #fefcc9)";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inverse mode: black dot
|
||||||
|
return "var(--color-content-default-primary, #000000)";
|
||||||
|
};
|
||||||
|
|
||||||
const combinedBoxStyles = `${baseBox} ${stateStyles[state]}`;
|
const dotColor = getDotColor();
|
||||||
|
|
||||||
// Force visible outline for standard / default / unchecked
|
// Label color
|
||||||
const defaultOutlineClass = isInverse
|
const labelColor = isInverse
|
||||||
? "outline outline-1 outline-[var(--color-border-inverse-primary)]"
|
? "var(--color-content-inverse-primary)"
|
||||||
: "outline outline-1 outline-[var(--color-border-default-tertiary)]";
|
: "var(--color-content-default-primary)";
|
||||||
|
|
||||||
// Apply brand outline only on actual :hover
|
|
||||||
// Standard mode uses default brand primary, inverse mode uses inverse brand primary
|
|
||||||
const conditionalHoverOutlineClass = isInverse
|
|
||||||
? "hover:outline hover:outline-1 hover:outline-[var(--color-border-inverse-brand-primary)]"
|
|
||||||
: "hover:outline hover:outline-1 hover:outline-[var(--color-border-default-brand-primary)]";
|
|
||||||
|
|
||||||
// Focus state for standard/unchecked with brand primary color and specific blur/spread
|
|
||||||
const conditionalFocusClass =
|
|
||||||
"focus:outline focus:outline-1 focus:outline-[var(--color-border-default-utility-info)] focus:shadow-[0_0_10px_1px_var(--color-surface-inverse-brand-primary)]";
|
|
||||||
|
|
||||||
// Generate unique ID for accessibility if not provided
|
// Generate unique ID for accessibility if not provided
|
||||||
const generatedId = useId();
|
const generatedId = useId();
|
||||||
@@ -72,11 +110,13 @@ const RadioButtonContainer = ({
|
|||||||
|
|
||||||
const handleToggle = useCallback(
|
const handleToggle = useCallback(
|
||||||
(_e: React.MouseEvent | React.KeyboardEvent) => {
|
(_e: React.MouseEvent | React.KeyboardEvent) => {
|
||||||
if (!disabled && onChange && !checked) {
|
if (!disabled && onChange) {
|
||||||
|
// Always call onChange when clicked, even if already checked
|
||||||
|
// The parent (RadioGroup) will handle the logic
|
||||||
onChange({ checked: true, value });
|
onChange({ checked: true, value });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[disabled, onChange, checked, value],
|
[disabled, onChange, value],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLSpanElement>) => {
|
const handleKeyDown = (e: React.KeyboardEvent<HTMLSpanElement>) => {
|
||||||
@@ -91,7 +131,7 @@ const RadioButtonContainer = ({
|
|||||||
radioId={radioId}
|
radioId={radioId}
|
||||||
checked={checked}
|
checked={checked}
|
||||||
mode={mode}
|
mode={mode}
|
||||||
state={state}
|
state={state} // Passed for static display in Storybook/Preview
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
label={label}
|
label={label}
|
||||||
name={name}
|
name={name}
|
||||||
@@ -99,15 +139,10 @@ const RadioButtonContainer = ({
|
|||||||
ariaLabel={ariaLabel}
|
ariaLabel={ariaLabel}
|
||||||
className={className}
|
className={className}
|
||||||
combinedBoxStyles={combinedBoxStyles}
|
combinedBoxStyles={combinedBoxStyles}
|
||||||
defaultOutlineClass={defaultOutlineClass}
|
|
||||||
conditionalHoverOutlineClass={conditionalHoverOutlineClass}
|
|
||||||
conditionalFocusClass={conditionalFocusClass}
|
|
||||||
backgroundWhenChecked={backgroundWhenChecked}
|
|
||||||
dotColor={dotColor}
|
dotColor={dotColor}
|
||||||
labelColor={labelColor}
|
labelColor={labelColor}
|
||||||
onToggle={handleToggle}
|
onToggle={handleToggle}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
{...props}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -24,10 +24,6 @@ export interface RadioButtonViewProps {
|
|||||||
ariaLabel?: string;
|
ariaLabel?: string;
|
||||||
className: string;
|
className: string;
|
||||||
combinedBoxStyles: string;
|
combinedBoxStyles: string;
|
||||||
defaultOutlineClass: string;
|
|
||||||
conditionalHoverOutlineClass: string;
|
|
||||||
conditionalFocusClass: string;
|
|
||||||
backgroundWhenChecked: string;
|
|
||||||
dotColor: string;
|
dotColor: string;
|
||||||
labelColor: string;
|
labelColor: string;
|
||||||
onToggle: (_e: React.MouseEvent | React.KeyboardEvent) => void;
|
onToggle: (_e: React.MouseEvent | React.KeyboardEvent) => void;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import type { RadioButtonViewProps } from "./RadioButton.types";
|
|||||||
export function RadioButtonView({
|
export function RadioButtonView({
|
||||||
radioId,
|
radioId,
|
||||||
checked,
|
checked,
|
||||||
|
mode,
|
||||||
disabled,
|
disabled,
|
||||||
label,
|
label,
|
||||||
name,
|
name,
|
||||||
@@ -10,15 +11,10 @@ export function RadioButtonView({
|
|||||||
ariaLabel,
|
ariaLabel,
|
||||||
className,
|
className,
|
||||||
combinedBoxStyles,
|
combinedBoxStyles,
|
||||||
defaultOutlineClass,
|
|
||||||
conditionalHoverOutlineClass,
|
|
||||||
conditionalFocusClass,
|
|
||||||
backgroundWhenChecked,
|
|
||||||
dotColor,
|
dotColor,
|
||||||
labelColor,
|
labelColor,
|
||||||
onToggle,
|
onToggle,
|
||||||
onKeyDown,
|
onKeyDown,
|
||||||
...props
|
|
||||||
}: RadioButtonViewProps) {
|
}: RadioButtonViewProps) {
|
||||||
return (
|
return (
|
||||||
<label
|
<label
|
||||||
@@ -30,25 +26,25 @@ export function RadioButtonView({
|
|||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
onKeyDown={onKeyDown}
|
onKeyDown={onKeyDown}
|
||||||
className={`${combinedBoxStyles} ${defaultOutlineClass} ${conditionalHoverOutlineClass} ${conditionalFocusClass} p-[var(--measures-spacing-004)]`}
|
className={`group ${combinedBoxStyles} ${disabled ? "" : "cursor-pointer"}`}
|
||||||
style={{
|
tabIndex={disabled ? -1 : 0}
|
||||||
backgroundColor: backgroundWhenChecked,
|
|
||||||
}}
|
|
||||||
tabIndex={0}
|
|
||||||
role="radio"
|
role="radio"
|
||||||
aria-checked={checked}
|
aria-checked={checked}
|
||||||
{...(disabled && { "aria-disabled": true })}
|
{...(disabled && { "aria-disabled": true })}
|
||||||
{...(ariaLabel && { "aria-label": ariaLabel })}
|
{...(ariaLabel && { "aria-label": ariaLabel })}
|
||||||
{...(label && !ariaLabel && { "aria-labelledby": `${radioId}-label` })}
|
{...(label && !ariaLabel && { "aria-labelledby": `${radioId}-label` })}
|
||||||
id={radioId}
|
id={radioId}
|
||||||
{...props}
|
|
||||||
>
|
>
|
||||||
{/* Radio dot */}
|
{/* Radio dot - 16px size per Figma */}
|
||||||
|
{/* Selected hover state: darker dot color (#333000) per Figma */}
|
||||||
<div
|
<div
|
||||||
className="w-[16px] h-[16px] rounded-full transition-all duration-200"
|
className={`w-[16px] h-[16px] rounded-full transition-all duration-200 ${
|
||||||
style={{
|
checked && mode === "standard"
|
||||||
backgroundColor: dotColor,
|
? "bg-[var(--color-content-default-brand-primary,#fefcc9)] group-hover:!bg-[#333000]"
|
||||||
}}
|
: checked && mode === "inverse"
|
||||||
|
? "bg-[var(--color-content-default-primary,#000000)]"
|
||||||
|
: "bg-transparent"
|
||||||
|
}`}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
{label && (
|
{label && (
|
||||||
|
|||||||
+155
-104
@@ -1,96 +1,53 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import RadioButton from "../app/components/RadioButton";
|
import RadioButton from "../app/components/RadioButton";
|
||||||
import { expect } from "@storybook/test";
|
|
||||||
import { userEvent, within } from "@storybook/test";
|
|
||||||
|
|
||||||
// Interaction functions for Storybook play functions
|
export default {
|
||||||
const DefaultInteraction = {
|
|
||||||
play: async ({ canvasElement }) => {
|
|
||||||
const canvas = within(canvasElement);
|
|
||||||
const radioButton = canvas.getByRole("radio");
|
|
||||||
await expect(radioButton).toHaveAttribute("aria-checked", "false");
|
|
||||||
await userEvent.click(radioButton);
|
|
||||||
await expect(radioButton).toHaveAttribute("aria-checked", "true");
|
|
||||||
await userEvent.click(radioButton);
|
|
||||||
await expect(radioButton).toHaveAttribute("aria-checked", "true");
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const CheckedInteraction = {
|
|
||||||
play: async ({ canvasElement }) => {
|
|
||||||
const canvas = within(canvasElement);
|
|
||||||
const radioButton = canvas.getByRole("radio");
|
|
||||||
await expect(radioButton).toHaveAttribute("aria-checked", "true");
|
|
||||||
await userEvent.click(radioButton);
|
|
||||||
await expect(radioButton).toHaveAttribute("aria-checked", "true");
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const StandardInteraction = {
|
|
||||||
play: async ({ canvasElement }) => {
|
|
||||||
const canvas = within(canvasElement);
|
|
||||||
const radioButtons = canvas.getAllByRole("radio");
|
|
||||||
await expect(radioButtons[0]).toHaveAttribute("aria-checked", "false");
|
|
||||||
await expect(radioButtons[1]).toHaveAttribute("aria-checked", "true");
|
|
||||||
await userEvent.click(radioButtons[0]);
|
|
||||||
await expect(radioButtons[0]).toHaveAttribute("aria-checked", "true");
|
|
||||||
await expect(radioButtons[1]).toHaveAttribute("aria-checked", "false");
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const InverseInteraction = {
|
|
||||||
play: async ({ canvasElement }) => {
|
|
||||||
const canvas = within(canvasElement);
|
|
||||||
const radioButtons = canvas.getAllByRole("radio");
|
|
||||||
await expect(radioButtons[0]).toHaveAttribute("aria-checked", "false");
|
|
||||||
await expect(radioButtons[1]).toHaveAttribute("aria-checked", "true");
|
|
||||||
await userEvent.click(radioButtons[0]);
|
|
||||||
await expect(radioButtons[0]).toHaveAttribute("aria-checked", "true");
|
|
||||||
await expect(radioButtons[1]).toHaveAttribute("aria-checked", "false");
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const meta = {
|
|
||||||
title: "Forms/RadioButton",
|
title: "Forms/RadioButton",
|
||||||
component: RadioButton,
|
component: RadioButton,
|
||||||
parameters: {
|
parameters: {
|
||||||
layout: "centered",
|
layout: "centered",
|
||||||
backgrounds: {
|
backgrounds: {
|
||||||
default: "dark",
|
default: "dark",
|
||||||
values: [{ name: "dark", value: "black" }],
|
values: [
|
||||||
|
{ name: "light", value: "#ffffff" },
|
||||||
|
{ name: "dark", value: "#000000" },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
tags: ["autodocs"],
|
|
||||||
argTypes: {
|
argTypes: {
|
||||||
checked: { control: "boolean" },
|
checked: {
|
||||||
|
control: "boolean",
|
||||||
|
description: "Whether the radio button is checked",
|
||||||
|
},
|
||||||
mode: {
|
mode: {
|
||||||
control: { type: "select" },
|
control: "select",
|
||||||
options: ["standard", "inverse"],
|
options: ["standard", "inverse"],
|
||||||
|
description: "Visual mode of the radio button",
|
||||||
},
|
},
|
||||||
state: {
|
state: {
|
||||||
control: { type: "select" },
|
control: "select",
|
||||||
options: ["default", "hover", "focus"],
|
options: ["default", "hover", "focus"],
|
||||||
|
description: "Interaction state for static display",
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
control: "boolean",
|
||||||
|
description: "Whether the radio button is disabled",
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
control: "text",
|
||||||
|
description: "Label text for the radio button",
|
||||||
},
|
},
|
||||||
label: { control: "text" },
|
|
||||||
},
|
|
||||||
args: {
|
|
||||||
checked: false,
|
|
||||||
mode: "standard",
|
|
||||||
state: "default",
|
|
||||||
label: "Radio Button Label",
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default meta;
|
|
||||||
|
|
||||||
export const Default = {
|
export const Default = {
|
||||||
args: {
|
args: {
|
||||||
checked: false,
|
checked: false,
|
||||||
mode: "standard",
|
mode: "standard",
|
||||||
state: "default",
|
state: "default",
|
||||||
|
disabled: false,
|
||||||
label: "Default radio button",
|
label: "Default radio button",
|
||||||
},
|
},
|
||||||
play: DefaultInteraction.play,
|
|
||||||
render: (args) => {
|
render: (args) => {
|
||||||
const [checked, setChecked] = React.useState(args.checked);
|
const [checked, setChecked] = React.useState(args.checked);
|
||||||
return (
|
return (
|
||||||
@@ -108,9 +65,9 @@ export const Checked = {
|
|||||||
checked: true,
|
checked: true,
|
||||||
mode: "standard",
|
mode: "standard",
|
||||||
state: "default",
|
state: "default",
|
||||||
|
disabled: false,
|
||||||
label: "Checked radio button",
|
label: "Checked radio button",
|
||||||
},
|
},
|
||||||
play: CheckedInteraction.play,
|
|
||||||
render: (args) => {
|
render: (args) => {
|
||||||
const [checked, setChecked] = React.useState(args.checked);
|
const [checked, setChecked] = React.useState(args.checked);
|
||||||
return (
|
return (
|
||||||
@@ -125,7 +82,7 @@ export const Checked = {
|
|||||||
|
|
||||||
export const Standard = {
|
export const Standard = {
|
||||||
render: () => {
|
render: () => {
|
||||||
const [selectedValue, setSelectedValue] = React.useState("checked");
|
const [checked, setChecked] = React.useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
@@ -133,36 +90,21 @@ export const Standard = {
|
|||||||
<h3 className="text-white font-medium">Standard Mode</h3>
|
<h3 className="text-white font-medium">Standard Mode</h3>
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<RadioButton
|
<RadioButton
|
||||||
label="Unchecked"
|
label="Standard Radio Button"
|
||||||
checked={selectedValue === "unchecked"}
|
checked={checked}
|
||||||
name="standard-example"
|
|
||||||
value="unchecked"
|
|
||||||
mode="standard"
|
mode="standard"
|
||||||
onChange={({ checked }) => {
|
onChange={({ checked: newChecked }) => setChecked(newChecked)}
|
||||||
if (checked) setSelectedValue("unchecked");
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<RadioButton
|
|
||||||
label="Checked"
|
|
||||||
checked={selectedValue === "checked"}
|
|
||||||
name="standard-example"
|
|
||||||
value="checked"
|
|
||||||
mode="standard"
|
|
||||||
onChange={({ checked }) => {
|
|
||||||
if (checked) setSelectedValue("checked");
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
play: StandardInteraction.play,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Inverse = {
|
export const Inverse = {
|
||||||
render: () => {
|
render: () => {
|
||||||
const [selectedValue, setSelectedValue] = React.useState("checked");
|
const [checked, setChecked] = React.useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
@@ -170,29 +112,138 @@ export const Inverse = {
|
|||||||
<h3 className="text-white font-medium">Inverse Mode</h3>
|
<h3 className="text-white font-medium">Inverse Mode</h3>
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<RadioButton
|
<RadioButton
|
||||||
label="Unchecked"
|
label="Inverse Radio Button"
|
||||||
checked={selectedValue === "unchecked"}
|
checked={checked}
|
||||||
name="inverse-example"
|
|
||||||
value="unchecked"
|
|
||||||
mode="inverse"
|
mode="inverse"
|
||||||
onChange={({ checked }) => {
|
onChange={({ checked: newChecked }) => setChecked(newChecked)}
|
||||||
if (checked) setSelectedValue("unchecked");
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<RadioButton
|
|
||||||
label="Checked"
|
|
||||||
checked={selectedValue === "checked"}
|
|
||||||
name="inverse-example"
|
|
||||||
value="checked"
|
|
||||||
mode="inverse"
|
|
||||||
onChange={({ checked }) => {
|
|
||||||
if (checked) setSelectedValue("checked");
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
play: InverseInteraction.play,
|
};
|
||||||
|
|
||||||
|
export const Disabled = {
|
||||||
|
args: {
|
||||||
|
checked: false,
|
||||||
|
mode: "standard",
|
||||||
|
state: "default",
|
||||||
|
disabled: true,
|
||||||
|
label: "Disabled radio button",
|
||||||
|
},
|
||||||
|
render: (args) => <RadioButton {...args} />,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DisabledChecked = {
|
||||||
|
args: {
|
||||||
|
checked: true,
|
||||||
|
mode: "standard",
|
||||||
|
state: "default",
|
||||||
|
disabled: true,
|
||||||
|
label: "Disabled checked radio button",
|
||||||
|
},
|
||||||
|
render: (args) => <RadioButton {...args} />,
|
||||||
|
};
|
||||||
|
|
||||||
|
// All modes comparison
|
||||||
|
export const AllModes = () => {
|
||||||
|
const [standardChecked, setStandardChecked] = React.useState(false);
|
||||||
|
const [inverseChecked, setInverseChecked] = React.useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-semibold mb-4 text-white">Standard Mode</h3>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<RadioButton
|
||||||
|
label="Standard Radio Button"
|
||||||
|
checked={standardChecked}
|
||||||
|
mode="standard"
|
||||||
|
onChange={({ checked }) => setStandardChecked(checked)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-semibold mb-4 text-white">Inverse Mode</h3>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<RadioButton
|
||||||
|
label="Inverse Radio Button"
|
||||||
|
checked={inverseChecked}
|
||||||
|
mode="inverse"
|
||||||
|
onChange={({ checked }) => setInverseChecked(checked)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// All states for standard mode
|
||||||
|
export const StandardAllStates = () => {
|
||||||
|
const [unchecked, setUnchecked] = React.useState(false);
|
||||||
|
const [checked, setChecked] = React.useState(true);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-semibold mb-4 text-white">Standard Mode - Unselected</h3>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<RadioButton
|
||||||
|
label="Unselected (default, hover, focus)"
|
||||||
|
checked={unchecked}
|
||||||
|
mode="standard"
|
||||||
|
onChange={({ checked }) => setUnchecked(checked)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-semibold mb-4 text-white">Standard Mode - Selected</h3>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<RadioButton
|
||||||
|
label="Selected (default, hover, focus)"
|
||||||
|
checked={checked}
|
||||||
|
mode="standard"
|
||||||
|
onChange={({ checked }) => setChecked(checked)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// All states for inverse mode
|
||||||
|
export const InverseAllStates = () => {
|
||||||
|
const [unchecked, setUnchecked] = React.useState(false);
|
||||||
|
const [checked, setChecked] = React.useState(true);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-semibold mb-4 text-white">Inverse Mode - Unselected</h3>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<RadioButton
|
||||||
|
label="Unselected (default, hover, focus)"
|
||||||
|
checked={unchecked}
|
||||||
|
mode="inverse"
|
||||||
|
onChange={({ checked }) => setUnchecked(checked)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-semibold mb-4 text-white">Inverse Mode - Selected</h3>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<RadioButton
|
||||||
|
label="Selected (default, hover, focus)"
|
||||||
|
checked={checked}
|
||||||
|
mode="inverse"
|
||||||
|
onChange={({ checked }) => setChecked(checked)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user