Создание компонентной библиотеки: атомарный дизайн, варианты, документация, Storybook. Use when: новый UI-компонент, библиотека переиспользуемых элементов, дизайн-система.
Скилл для создания переиспользуемых UI-компонентов по методологии атомарного дизайна. Каждый компонент: типизирован, документирован, покрывает все состояния.
| Уровень | Пример | Директория |
|---|---|---|
| Atom | Button, Badge, Avatar | src/components/ui/ |
| Molecule | SearchInput, UserCard | src/components/shared/ |
| Organism | ChatHeader, ProductGrid | src/components/[module]/ |
| Template | PageLayout, ModalLayout | src/components/layout/ |
grep по имени, не дублируй существующиеany, discriminated unions для вариантовdark: классы Tailwind на каждый визуальный элементaria-label, role, keyboard navigationindex.ts модуля, named exportimport { cva, type VariantProps } from 'class-variance-authority'
const buttonVariants = cva(
'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: {
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
outline: 'border border-input bg-background hover:bg-accent',
ghost: 'hover:bg-accent hover:text-accent-foreground',
},
size: {
sm: 'h-9 px-3 text-sm',
md: 'h-11 px-4 py-2',
lg: 'h-12 px-8 text-lg',
icon: 'h-11 w-11',
},
},
defaultVariants: { variant: 'default', size: 'md' },
}
)
interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
loading?: boolean
}
export function Button({ className, variant, size, loading, children, ...props }: ButtonProps) {
return (
<button
className={cn(buttonVariants({ variant, size }), className)}
disabled={loading || props.disabled}
{...props}
>
{loading ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : null}
{children}
</button>
)
}
interface CardProps {
children: React.ReactNode
className?: string
}
export function Card({ children, className }: CardProps) {
return <div className={cn('rounded-lg border bg-card p-4', className)}>{children}</div>
}
Card.Header = function CardHeader({ children, className }: CardProps) {
return <div className={cn('mb-3 font-semibold', className)}>{children}</div>
}
Card.Body = function CardBody({ children, className }: CardProps) {
return <div className={cn('text-sm text-muted-foreground', className)}>{children}</div>
}
anydark: классы)aria-label на иконочных кнопкахstyle={{ color: '#333' }}. Только Tailwind классыif (type === 'a') ... else if (type === 'b'). Используй cvadisabled состояния при loading