Архитектура экрана
Любой дашборд раскладывается на три уровня:
- Каркас — сайдбар, шапка, область контента. Живёт один раз на всё приложение.
- Сетка блоков — метрики, графики, таблицы. Меняется от экрана к экрану.
- Блок — карточка с заголовком, содержимым и, возможно, действиями.
// 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. Иначе колонки «пляшут» при обновлении данных. - Одна главная кнопка на экран. Всё остальное — вторичные варианты.
Порядок сборки
Собирать удобно снаружи внутрь — так каркас не приходится переделывать:
- каркас: сетка, сайдбар, шапка;
- навигация: пункты, активное состояние, мобильное меню;
- сетка блоков: метрики;
- крупные блоки: график, таблица;
- интерактив: меню строк, диалоги, фильтры;
- состояния: скелетоны, пустые экраны, ошибки;
- тема и финальное ревью.