--- name: react-ui-components description: UI component patterns với React Aria, Radix UI, và Storybook cho GoodGo. Use for accessible components, composition patterns, animations, và component documentation. compatibility: "react-aria>=3, @radix-ui/*>=1, storybook>=10, framer-motion>=12" metadata: author: Velik Ho version: "1.0" --- # React UI Components / Components UI React ## When to Use This Skill / Khi Nào Sử Dụng Use this skill when: - Creating reusable components / Tạo components tái sử dụng - Implementing accessible UI / Triển khai UI accessible - Writing Storybook stories / Viết Storybook stories - Adding animations / Thêm animations - Styling with CVA & Tailwind / Styling với CVA & Tailwind ## Core Principles / Nguyên Tắc Cốt Lõi 1. **Accessibility First**: Use React Aria/Radix for keyboard & screen reader support 2. **Composition over Props**: Build flexible components via composition 3. **Variant-based Styling**: Use CVA for maintainable variant logic 4. **Document with Stories**: Every component needs Storybook stories ## Key Patterns / Mẫu Chính ### Component Structure ``` features/shared/components/ ├── Button/ │ ├── Button.tsx # Main component │ ├── Button.stories.tsx # Storybook stories │ ├── Button.test.tsx # Unit tests │ └── index.ts # Barrel export └── index.ts # Public exports ``` ### CVA for Variants (Class Variance Authority) ```tsx // components/Button/Button.tsx import { cva, type VariantProps } from 'class-variance-authority'; import { clsx } from 'clsx'; /** * EN: Button variants using CVA * VI: Variants cho Button sử dụng CVA */ const buttonVariants = cva( // Base styles 'inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50', { variants: { variant: { primary: 'bg-accent-primary text-white hover:bg-accent-primary/90', secondary: 'bg-bg-secondary text-text-primary hover:bg-bg-tertiary', ghost: 'hover:bg-bg-secondary', destructive: 'bg-accent-error text-white hover:bg-accent-error/90', }, size: { sm: 'h-8 px-3 text-sm', md: 'h-10 px-4 text-base', lg: 'h-12 px-6 text-lg', }, }, defaultVariants: { variant: 'primary', size: 'md', }, } ); interface ButtonProps extends React.ButtonHTMLAttributes, VariantProps { isLoading?: boolean; } export function Button({ className, variant, size, isLoading, children, ...props }: ButtonProps) { return ( ); } ``` ### React Aria Pattern ```tsx // components/Switch/Switch.tsx import { useToggleState } from 'react-stately'; import { useSwitch, useFocusRing, VisuallyHidden } from 'react-aria'; import { useRef } from 'react'; /** * EN: Accessible switch using React Aria * VI: Switch accessible sử dụng React Aria */ export function Switch({ children, ...props }) { const state = useToggleState(props); const ref = useRef(null); const { inputProps } = useSwitch(props, state, ref); const { focusProps, isFocusVisible } = useFocusRing(); return (