Урок 22. Теория: архитектура дашборда

📁 Раздел: Итоговый проект ⏱️ Время изучения: ~3–4 часа 🎯 Сложность: Продвинутая

⚡ Кратко

Дашборд — это каркас (сайдбар, шапка, контент), сетка блоков и три состояния данных: загрузка, пусто, ошибка. Каркас держит Grid, блоки — карточки, состояния — скелетоны и пустые экраны. Всё оформление — семантические токены, чтобы тема работала целиком.

Архитектура экрана

Любой дашборд раскладывается на три уровня:

  1. Каркас — сайдбар, шапка, область контента. Живёт один раз на всё приложение.
  2. Сетка блоков — метрики, графики, таблицы. Меняется от экрана к экрану.
  3. Блок — карточка с заголовком, содержимым и, возможно, действиями.
// src/components/app-shell.tsx
export function AppShell({ children }: { children: React.ReactNode }) {
  return (
    <div className="grid min-h-svh grid-rows-[auto_1fr] bg-background text-foreground
                    lg:grid-cols-[16rem_1fr] lg:grid-rows-[auto_1fr]">
      <Sidebar className="hidden lg:col-start-1 lg:row-span-2 lg:block" />
      <AppHeader className="lg:col-start-2" />
      <main className="overflow-y-auto p-4 lg:col-start-2 lg:p-6">{children}</main>
    </div>
  )
}

Почему Grid, а не Flexbox: у каркаса две оси — колонка сайдбара и строки «шапка / контент». Ровно тот случай, о котором говорил урок 09.

Каркас — как несущие стены дома: их ставят один раз, и дальше внутри можно как угодно переставлять мебель. Ошибка новичка — делать «стены» заново на каждом экране: тогда двери оказываются в разных местах, и человек каждый раз ищет выход заново.

Адаптивность каркаса

ЭкранСайдбарШапкаСетка метрик
мобильныйскрыт, открывается Sheetкнопка меню + поиск1–2 колонки
планшетскрыт или узкийполная2 колонки
десктопвиден постояннополная4 колонки

Заказы

348

Выручка

1,2 млн

Конверсия

3,4%

Возвраты

12

<div class="grid h-64 grid-rows-[auto_1fr] overflow-hidden rounded-xl bg-background ring-1 ring-border lg:grid-cols-[12rem_1fr]">
  <aside class="hidden bg-sidebar p-3 text-sidebar-foreground lg:col-start-1 lg:row-span-2 lg:block">
    <p class="text-xs font-semibold">Дашборд</p>
    <nav class="mt-3 space-y-1 text-sm">
      <a class="block rounded-md bg-sidebar-accent px-2 py-1.5 text-sidebar-accent-foreground" href="#">Обзор</a>
      <a class="block rounded-md px-2 py-1.5 hover:bg-sidebar-accent" href="#">Заказы</a>
      <a class="block rounded-md px-2 py-1.5 hover:bg-sidebar-accent" href="#">Клиенты</a>
    </nav>
  </aside>
  <header class="flex items-center justify-between gap-3 border-b border-border px-3 py-2 lg:col-start-2">
    <button class="inline-flex size-7 items-center justify-center rounded-lg hover:bg-muted lg:hidden" aria-label="Меню">☰</button>
    <input class="h-7 min-w-0 flex-1 rounded-lg border border-input bg-transparent px-2 text-sm outline-none placeholder:text-muted-foreground" placeholder="Поиск">
    <div class="size-7 shrink-0 rounded-full bg-muted"></div>
  </header>
  <main class="overflow-y-auto p-3 text-sm lg:col-start-2">
    <div class="grid grid-cols-2 gap-3 lg:grid-cols-4">
      <div class="rounded-lg bg-card p-3 ring-1 ring-border"><p class="text-xs text-muted-foreground">Заказы</p><p class="mt-1 text-lg font-semibold tabular-nums">348</p></div>
      <div class="rounded-lg bg-card p-3 ring-1 ring-border"><p class="text-xs text-muted-foreground">Выручка</p><p class="mt-1 text-lg font-semibold tabular-nums">1,2 млн</p></div>
      <div class="rounded-lg bg-card p-3 ring-1 ring-border"><p class="text-xs text-muted-foreground">Конверсия</p><p class="mt-1 text-lg font-semibold tabular-nums">3,4%</p></div>
      <div class="rounded-lg bg-card p-3 ring-1 ring-border"><p class="text-xs text-muted-foreground">Возвраты</p><p class="mt-1 text-lg font-semibold tabular-nums">12</p></div>
    </div>
  </main>
</div>

Три состояния каждого блока

Данные приходят не мгновенно и не всегда. Спроектируйте блок сразу в трёх состояниях — иначе они появятся сами, в виде пустого белого пятна.

СостояниеЧто показываемИз какого урока
Загрузкаскелетон той же геометрииурок 14
Пустообъяснение и действиешаблон «Пустое состояние»
Ошибкачто случилось и кнопка «Повторить»урок 18 (Alert)
Данныесобственно содержимое—

Загрузка

📭

Пока нет заказов

Они появятся здесь после первой продажи

<div class="grid gap-3 sm:grid-cols-3">
  <div class="rounded-xl bg-card p-3 ring-1 ring-border">
    <p class="text-xs text-muted-foreground">Загрузка</p>
    <div class="mt-2 space-y-2">
      <div class="h-3 w-1/2 animate-pulse rounded bg-muted"></div>
      <div class="h-6 w-2/3 animate-pulse rounded bg-muted"></div>
    </div>
  </div>
  <div class="rounded-xl bg-card p-3 text-center ring-1 ring-border">
    <p class="text-2xl">📭</p>
    <p class="mt-1 text-sm font-medium text-card-foreground">Пока нет заказов</p>
    <p class="mt-1 text-xs text-muted-foreground">Они появятся здесь после первой продажи</p>
  </div>
  <div class="rounded-xl bg-card p-3 ring-1 ring-border">
    <div role="alert" class="rounded-lg bg-destructive/10 p-2 text-xs text-destructive">Не удалось загрузить данные</div>
    <button class="mt-2 inline-flex h-7 w-full items-center justify-center rounded-lg border border-border text-xs font-medium hover:bg-muted">Повторить</button>
  </div>
</div>

Иерархия информации

На дашборде легко получить «стену цифр», где всё одинаково важно. Правила, которые помогают:

  • Главное — крупно. Ключевая метрика — text-2xl, подпись — text-xs text-muted-foreground.
  • Цвет — только для смысла. Зелёный и красный — рост и падение, а не украшение.
  • Цифры — tabular-nums. Иначе колонки «пляшут» при обновлении данных.
  • Одна главная кнопка на экран. Всё остальное — вторичные варианты.

Порядок сборки

Собирать удобно снаружи внутрь — так каркас не приходится переделывать:

  1. каркас: сетка, сайдбар, шапка;
  2. навигация: пункты, активное состояние, мобильное меню;
  3. сетка блоков: метрики;
  4. крупные блоки: график, таблица;
  5. интерактив: меню строк, диалоги, фильтры;
  6. состояния: скелетоны, пустые экраны, ошибки;
  7. тема и финальное ревью.
Совет: первые два шага сделайте на «рыбе» — прямоугольниках вместо содержимого. Так вы поймаете проблемы раскладки до того, как в них утонет реальный контент.