Урок 15. Решения

📁 Раздел: Организация кода ⏱️ Время изучения: ~55 мин 🎯 Сложность: Средняя

⚡ Кратко

Готовый компонент, разбор конфликта классов и ответы про утилиты.

Решение задания 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-свойств — утилита; нужны разметка, состояния и данные — компонент. Уже есть встроенная утилита — не изобретайте свою.