16. Компоненты и @apply
Tailwind CSS: Компоненты и @apply
Заголовок раздела «Tailwind CSS: Компоненты и @apply»
Когда одни и те же классы повторяются везде — пора создавать компоненты. В React/Vue/Svelte это делается через JS-компоненты. В обычном CSS — через @apply. В React с Tailwind — ещё и через CVA.
Проблема дублирования
Заголовок раздела «Проблема дублирования»<!-- Кнопка встречается везде, и везде одинаковые классы --><button class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white font-semibold rounded-lg transition-colors"> Кнопка 1</button><button class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white font-semibold rounded-lg transition-colors"> Кнопка 2</button>Решение 1: React/Vue компонент (рекомендуется)
Заголовок раздела «Решение 1: React/Vue компонент (рекомендуется)»interface ButtonProps { children: React.ReactNode; variant?: 'primary' | 'secondary' | 'ghost'; size?: 'sm' | 'md' | 'lg'; onClick?: () => void; disabled?: boolean;}
export function Button({ children, variant = 'primary', size = 'md', ...props }: ButtonProps) { const variants = { primary: 'bg-blue-600 hover:bg-blue-700 text-white', secondary: 'bg-gray-100 hover:bg-gray-200 text-gray-700', ghost: 'hover:bg-gray-100 text-gray-700', }
const sizes = { sm: 'px-3 py-1.5 text-sm', md: 'px-4 py-2', lg: 'px-6 py-3 text-lg', }
return ( <button className={`${variants[variant]} ${sizes[size]} font-semibold rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed`} {...props} > {children} </button> )}
// Использование<Button>Кнопка</Button><Button variant="secondary" size="lg">Большая вторичная</Button>Решение 2: CVA (Class Variance Authority)
Заголовок раздела «Решение 2: CVA (Class Variance Authority)»CVA — специализированная библиотека для вариантов компонентов с TypeScript-поддержкой:
npm install class-variance-authorityimport { cva } from 'class-variance-authority'import { cn } from '@/lib/utils'
const buttonVariants = cva( // Базовые классы (всегда применяются) 'inline-flex items-center justify-center font-semibold rounded-lg transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed', { variants: { variant: { primary: 'bg-blue-600 hover:bg-blue-700 text-white focus:ring-blue-500', secondary: 'bg-gray-100 hover:bg-gray-200 text-gray-700 focus:ring-gray-400', destructive: 'bg-red-600 hover:bg-red-700 text-white focus:ring-red-500', outline: 'border border-gray-300 hover:bg-gray-50 text-gray-700 focus:ring-gray-400', ghost: 'hover:bg-gray-100 text-gray-700', }, size: { sm: 'px-3 py-1.5 text-sm', md: 'px-4 py-2 text-sm', lg: 'px-6 py-3 text-base', icon: 'w-9 h-9', }, }, defaultVariants: { variant: 'primary', size: 'md', }, })
export function Button({ variant, size, className, ...props }) { return ( <button className={cn(buttonVariants({ variant, size }), className)} {...props} /> )}
// Использование<Button>По умолчанию</Button><Button variant="destructive" size="lg">Удалить</Button><Button variant="outline">Outline</Button><Button size="icon"><TrashIcon /></Button>Решение 3: @apply (только для небольших утилит)
Заголовок раздела «Решение 3: @apply (только для небольших утилит)»@apply позволяет использовать Tailwind-утилиты в CSS-файлах. Подходит для глобальных стилей:
@layer components { .btn { @apply inline-flex items-center justify-center px-4 py-2 font-semibold rounded-lg transition-colors; }
.btn-primary { @apply bg-blue-600 hover:bg-blue-700 text-white; }
.btn-secondary { @apply bg-gray-100 hover:bg-gray-200 text-gray-700; }
.card { @apply bg-white rounded-2xl p-6 shadow-sm border border-gray-100; }
.input { @apply w-full px-4 py-2.5 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors; }}<!-- Использование --><button class="btn btn-primary">Кнопка</button><div class="card">Карточка</div><input class="input" type="text">Когда использовать @apply:
- Глобальные базовые стили (
.input,.card) - Не-компонентный код (WordPress, статические сайты)
- Избегай в компонентных проектах — лучше JS-компоненты
Паттерн Card компонента
Заголовок раздела «Паттерн Card компонента»import { cn } from '@/lib/utils'
interface CardProps { className?: string; children: React.ReactNode;}
export function Card({ className, children }: CardProps) { return ( <div className={cn('bg-white rounded-2xl p-6 border border-gray-100 shadow-sm', className)}> {children} </div> )}
export function CardHeader({ className, children }: CardProps) { return ( <div className={cn('flex items-center justify-between mb-4', className)}> {children} </div> )}
export function CardTitle({ className, children }: CardProps) { return ( <h3 className={cn('font-semibold text-gray-900', className)}> {children} </h3> )}