Урок 15. Решения
⚡ Кратко
Готовый компонент, разбор конфликта классов и ответы про утилиты.
Решение задания 1
// src/components/ui/badge.tsx
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const badgeVariants = cva(
"inline-flex items-center rounded-full font-medium",
{
variants: {
tone: {
neutral: "bg-slate-100 text-slate-700",
success: "bg-emerald-100 text-emerald-800",
warning: "bg-amber-100 text-amber-800",
danger: "bg-rose-100 text-rose-800",
},
size: {
sm: "px-2 py-0.5 text-[11px]",
md: "px-2.5 py-0.5 text-xs",
},
},
defaultVariants: { tone: "neutral", size: "md" },
}
)
type BadgeProps = React.ComponentProps<"span"> & VariantProps<typeof badgeVariants>
export function Badge({ className, tone, size, ...props }: BadgeProps) {
return <span className={cn(badgeVariants({ tone, size }), className)} {...props} />
}
Ключевая деталь: className передаётся
в cn() последним — только тогда внешние классы
побеждают базовые.
sm neutral
md success
md warning
md danger
<div class="flex flex-wrap items-center gap-2">
<span class="inline-flex items-center rounded-full bg-slate-100 px-2 py-0.5 text-[11px] font-medium text-slate-700">sm neutral</span>
<span class="inline-flex items-center rounded-full bg-emerald-100 px-2.5 py-0.5 text-xs font-medium text-emerald-800">md success</span>
<span class="inline-flex items-center rounded-full bg-amber-100 px-2.5 py-0.5 text-xs font-medium text-amber-800">md warning</span>
<span class="inline-flex items-center rounded-full bg-rose-100 px-2.5 py-0.5 text-xs font-medium text-rose-800">md danger</span>
</div>
Решение задания 2
В атрибуте окажется "px-4 py-2 bg-slate-900 px-8 bg-emerald-600".
Что увидит пользователь — неизвестно: победят те утилиты,
которые стоят ниже в сгенерированном CSS, а этот порядок задаёт Tailwind,
а не ваш атрибут. На практике почти всегда «побеждает» не то, что ожидалось.
// ✅ предсказуемо
import { cn } from "@/lib/utils"
export function Button({ className, ...props }: React.ComponentProps<"button">) {
return <button className={cn("px-4 py-2 bg-slate-900 text-white", className)} {...props} />
}
// <Button className="px-8 bg-emerald-600" /> → "py-2 text-white px-8 bg-emerald-600"
Решение задания 3
| Случай | Решение | Почему |
|---|---|---|
| (а) тонкая полоса прокрутки | @utility | одно свойство, нужно применять с вариантами |
| (б) карточка товара | компонент | структура + данные, а не только стиль |
| (в) обрезка в две строки | ничего | уже есть line-clamp-2 |
| (г) шапка страницы | компонент | сложная разметка, повторяется на всех страницах |
| (д) градиентная подложка | @utility или компонент | если только стиль — утилита; если со структурой — компонент |
Правило выбора: нужна только пара
CSS-свойств — утилита; нужны разметка, состояния и данные — компонент.
Уже есть встроенная утилита — не изобретайте свою.