From 75c09236362e8207b43476507ef09f5d2df7ff2a Mon Sep 17 00:00:00 2001 From: MyPrototypeWhat Date: Wed, 12 Nov 2025 14:26:25 +0800 Subject: [PATCH] feat(radioGroup): enhance RadioGroup component with size variants and add Storybook examples - Introduced size variants for RadioGroupItem using class-variance-authority for better customization. - Updated RadioGroupItem to accept size prop and adjusted styles accordingly. - Added comprehensive Storybook stories for various use cases, including default, disabled, and size variations, to demonstrate component functionality and usage. --- .../src/components/primitives/radioGroup.tsx | 43 +- .../primitives/RadioGroup.stories.tsx | 534 ++++++++++++++++++ 2 files changed, 570 insertions(+), 7 deletions(-) create mode 100644 packages/ui/stories/components/primitives/RadioGroup.stories.tsx diff --git a/packages/ui/src/components/primitives/radioGroup.tsx b/packages/ui/src/components/primitives/radioGroup.tsx index 99b0601aac..0d4b95b6c9 100644 --- a/packages/ui/src/components/primitives/radioGroup.tsx +++ b/packages/ui/src/components/primitives/radioGroup.tsx @@ -1,28 +1,57 @@ import { cn } from '@cherrystudio/ui/utils/index' import * as RadioGroupPrimitive from '@radix-ui/react-radio-group' +import { cva, type VariantProps } from 'class-variance-authority' import { CircleIcon } from 'lucide-react' import * as React from 'react' +const radioGroupItemVariants = cva( + cn( + 'aspect-square shrink-0 rounded-full border transition-all outline-none', + 'border-primary text-primary', + 'hover:bg-primary/10', + 'aria-checked:ring-3 aria-checked:ring-primary/20', + 'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive', + 'disabled:cursor-not-allowed disabled:border-gray-500/10 disabled:bg-background-subtle', + 'dark:bg-input/30 shadow-xs' + ), + { + variants: { + size: { + sm: 'size-4', + md: 'size-5', + lg: 'size-6' + } + }, + defaultVariants: { + size: 'md' + } + } +) + function RadioGroup({ className, ...props }: React.ComponentProps) { return } -function RadioGroupItem({ className, ...props }: React.ComponentProps) { +function RadioGroupItem({ + className, + size = 'md', + ...props +}: React.ComponentProps & VariantProps) { return ( - + ) } -export { RadioGroup, RadioGroupItem } +export { RadioGroup, RadioGroupItem, radioGroupItemVariants } diff --git a/packages/ui/stories/components/primitives/RadioGroup.stories.tsx b/packages/ui/stories/components/primitives/RadioGroup.stories.tsx new file mode 100644 index 0000000000..b3c64f2549 --- /dev/null +++ b/packages/ui/stories/components/primitives/RadioGroup.stories.tsx @@ -0,0 +1,534 @@ +import type { Meta, StoryObj } from '@storybook/react' +import { Bell, Check, Moon, Palette, Sun } from 'lucide-react' +import { useState } from 'react' + +import { RadioGroup, RadioGroupItem } from '../../../src/components/primitives/radioGroup' + +const meta: Meta = { + title: 'Components/Primitives/RadioGroup', + component: RadioGroup, + parameters: { + layout: 'centered', + docs: { + description: { + component: + 'A radio group component based on Radix UI, allowing users to select a single option from a set. Supports three sizes (sm, md, lg) as defined in the Figma design system.' + } + } + }, + tags: ['autodocs'], + argTypes: { + disabled: { + control: { type: 'boolean' }, + description: 'Whether the radio group is disabled' + }, + defaultValue: { + control: { type: 'text' }, + description: 'Default selected value' + }, + value: { + control: { type: 'text' }, + description: 'Value in controlled mode' + }, + orientation: { + control: { type: 'select' }, + options: ['horizontal', 'vertical'], + description: 'The orientation of the radio group' + } + } +} + +export default meta +type Story = StoryObj + +// Default +export const Default: Story = { + render: () => ( + +
+ + +
+
+ + +
+
+ + +
+
+ ) +} + +// With Default Value +export const WithDefaultValue: Story = { + render: () => ( + +
+ + +
+
+ + +
+
+ + +
+
+ ) +} + +// Horizontal Layout +export const HorizontalLayout: Story = { + render: () => ( + +
+ + +
+
+ + +
+
+ + +
+
+ ) +} + +// Disabled +export const Disabled: Story = { + render: () => ( + +
+ + +
+
+ + +
+
+ + +
+
+ ) +} + +// Disabled Items +export const DisabledItems: Story = { + render: () => ( + +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ ) +} + +// With Descriptions +export const WithDescriptions: Story = { + render: () => ( + +
+ + +
+
+ + +
+
+ + +
+
+ ) +} + +// Controlled +export const Controlled: Story = { + render: function ControlledExample() { + const [value, setValue] = useState('option1') + + return ( +
+ +
+ + +
+
+ + +
+
+ + +
+
+
Current value: {value}
+
+ ) + } +} + +// Sizes +export const Sizes: Story = { + render: () => ( +
+
+

Small (sm)

+ +
+ + +
+
+ + +
+
+
+ +
+

Medium (md) - Default

+ +
+ + +
+
+ + +
+
+
+ +
+

Large (lg)

+ +
+ + +
+
+ + +
+
+
+
+ ) +} + +// All States +export const AllStates: Story = { + render: function AllStatesExample() { + const [normalValue, setNormalValue] = useState('') + const [selectedValue, setSelectedValue] = useState('option2') + + return ( +
+ {/* Normal State */} +
+

Normal State

+ +
+ + +
+
+
+ + {/* Selected State */} +
+

Selected State

+ +
+ + +
+
+
+ + {/* Disabled State */} +
+

Disabled State

+ +
+ + +
+
+
+ + {/* Error State */} +
+

Error State

+ +
+ + +
+
+

Please select an option

+
+
+ ) + } +} + +// Real World Examples +export const RealWorldExamples: Story = { + render: function RealWorldExample() { + const [theme, setTheme] = useState('light') + const [notifications, setNotifications] = useState('all') + const [visibility, setVisibility] = useState('public') + + return ( +
+ {/* Theme Selection */} +
+

Theme Preference

+ +
+ + +
+
+ + +
+
+ + +
+
+
+ + {/* Notification Settings */} +
+

Notification Settings

+ +
+ + +
+
+ + +
+
+ + +
+
+
+ + {/* Visibility Settings */} +
+

Profile Visibility

+ +
+ + +
+
+ + +
+
+ + +
+
+
+ + {/* Required Field Example */} +
+

+ Payment Method * +

+ +
+ + +
+
+ + +
+
+ + +
+
+

Please select a payment method

+
+
+ ) + } +} + +// Card Style +export const CardStyle: Story = { + render: () => ( + + + + + + + + ) +}