Урок 15. Домашнее задание: мини-набор компонентов
⚡ Кратко
Собрать мини-набор компонентов проекта: Button,
Badge, Input, Card — с вариантами,
функцией cn() и сортировкой классов.
Задание
В проекте курса создайте папку src/components/ui и перенесите
в неё повторяющиеся элементы:
Button: вариантыdefault,outline,ghost,destructive; размерыsm,md,lg;Badge: четыре тона;Input: состояние ошибки черезaria-invalid;Cardиз подкомпонентов:CardHeader,CardContent,CardFooter;- функция
cn()вsrc/lib/utils.ts; - настроенный
prettier-plugin-tailwindcss.
Подготовка окружения
cd tailwind-course
npm install clsx tailwind-merge class-variance-authority
npm install -D prettier prettier-plugin-tailwindcss
Пошаговое решение
Шаг 1. Функция cn
📄 src/lib/utils.ts
// src/lib/utils.ts
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
Шаг 2. Button
📄 src/components/ui/button.tsx
// src/components/ui/button.tsx
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors " +
"focus-visible:outline-2 focus-visible:outline-offset-2 disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default: "bg-slate-900 text-white hover:bg-slate-700",
outline: "border border-slate-300 bg-white text-slate-700 hover:bg-slate-50",
ghost: "text-slate-700 hover:bg-slate-100",
destructive: "bg-rose-600 text-white hover:bg-rose-700",
},
size: {
sm: "h-8 px-3 text-xs",
md: "h-9 px-4",
lg: "h-11 px-6 text-base",
},
},
defaultVariants: { variant: "default", size: "md" },
}
)
type ButtonProps = React.ComponentProps<"button"> & VariantProps<typeof buttonVariants>
export function Button({ className, variant, size, ...props }: ButtonProps) {
return <button className={cn(buttonVariants({ variant, size }), className)} {...props} />
}
Проверьте: <Button className="w-full"> должен
растягиваться, а <Button className="px-10"> —
переопределять внутренний отступ.
Шаг 3. Card из частей
📄 src/components/ui/card.tsx
// src/components/ui/card.tsx
import { cn } from "@/lib/utils"
export function Card({ className, ...props }: React.ComponentProps<"div">) {
return <div className={cn("rounded-xl border border-slate-200 bg-white", className)} {...props} />
}
export function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
return <div className={cn("border-b border-slate-200 px-4 py-3", className)} {...props} />
}
export function CardContent({ className, ...props }: React.ComponentProps<"div">) {
return <div className={cn("px-4 py-3", className)} {...props} />
}
export function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
return <div className={cn("flex justify-end gap-2 border-t border-slate-200 px-4 py-3", className)} {...props} />
}
Шаг 4. Замена в страницах
Пройдитесь по страницам проекта и замените повторяющуюся разметку на новые компоненты. Строки классов должны заметно укоротиться.
Как проверить
- В страницах не осталось длинных строк классов для кнопок и бейджей.
- Внешний
classNameпереопределяет базовые классы. - Prettier сортирует классы при сохранении файла.
- TypeScript подсказывает доступные варианты компонента.
Ориентир
Отчёт за июль
Готов
Карточка собрана из компонентов Card, CardHeader, CardContent и CardFooter.
<div class="max-w-md rounded-xl border border-slate-200 bg-white">
<div class="flex items-center justify-between border-b border-slate-200 px-4 py-3">
<h3 class="text-sm font-semibold text-slate-900">Отчёт за июль</h3>
<span class="inline-flex items-center rounded-full bg-emerald-100 px-2.5 py-0.5 text-xs font-medium text-emerald-800">Готов</span>
</div>
<div class="px-4 py-3 text-sm text-slate-600">
Карточка собрана из компонентов Card, CardHeader, CardContent и CardFooter.
</div>
<div class="flex justify-end gap-2 border-t border-slate-200 px-4 py-3">
<button class="inline-flex h-9 items-center justify-center rounded-md border border-slate-300 bg-white px-4 text-sm font-medium text-slate-700 hover:bg-slate-50">Отмена</button>
<button class="inline-flex h-9 items-center justify-center rounded-md bg-slate-900 px-4 text-sm font-medium text-white hover:bg-slate-700">Открыть</button>
</div>
</div>