Урок 21. Домашнее задание: оверлеи в проекте
⚡ Кратко
Добавить в проект курса три оверлея: диалог создания записи, меню действий в списке и боковую панель фильтров — с проверкой клавиатурой.
Задание
- диалог «Новая запись» с формой из урока 20 внутри;
- меню действий у каждой строки списка: открыть, дублировать, удалить (с подтверждением);
- боковая панель фильтров, открывающаяся кнопкой в шапке;
- вкладки «Активные / Архив» над списком;
- проверка доступности каждого оверлея.
Подготовка окружения
cd tailwind-course
npx shadcn@latest add dialog sheet dropdown-menu tabs
npm run dev
Пошаговое решение
Шаг 1. Диалог с формой
Положите форму внутрь DialogContent, а кнопку отправки —
в DialogFooter с атрибутом form. После успешной
отправки закрывайте окно управляемым состоянием
(open / onOpenChange).
const [open, setOpen] = useState(false)
const onSubmit = async (values: Values) => {
await api.create(values)
setOpen(false)
}
<Dialog open={open} onOpenChange={setOpen}>…</Dialog>
Шаг 2. Меню и подтверждение
Пункт «Удалить» открывает AlertDialog. Помните про
вложенность: закройте меню перед открытием подтверждения — примитивы
обычно делают это сами, но проверьте поведение.
Шаг 3. Панель фильтров
На мобильном — Sheet снизу или справа, на десктопе можно
показывать фильтры прямо в раскладке. Условие переключения — обычный
адаптивный класс, а не отдельная страница.
Шаг 4. Проверка клавиатурой
Для каждого оверлея: открытие с клавиатуры, фокус внутри, обход по кругу, Esc, возврат фокуса. Запишите результат — это и есть отчёт по заданию.
Как проверить
- У каждого диалога есть заголовок (видимый или
sr-only). - Опасные действия требуют подтверждения и названы глаголом.
- Фокус после закрытия возвращается на триггер.
- Длинное содержимое прокручивается внутри окна.
- Всё работает в тёмной теме.
Ориентир
- Отчёт по продажам за июль
- Смета на оборудование
<div class="w-full max-w-2xl space-y-3">
<div class="flex items-center justify-between">
<div class="inline-flex h-8 items-center justify-center rounded-lg bg-muted p-[3px] text-muted-foreground">
<button class="inline-flex h-full items-center justify-center rounded-md bg-background px-3 text-sm font-medium text-foreground shadow-sm">Активные</button>
<button class="inline-flex h-full items-center justify-center rounded-md px-3 text-sm font-medium">Архив</button>
</div>
<div class="flex gap-2">
<button class="inline-flex h-8 items-center justify-center rounded-lg border border-border bg-background px-3 text-sm font-medium hover:bg-muted">Фильтры</button>
<button class="inline-flex h-8 items-center justify-center rounded-lg bg-primary px-3 text-sm font-medium text-primary-foreground">Новая запись</button>
</div>
</div>
<ul class="divide-y divide-border rounded-xl bg-card text-sm text-card-foreground ring-1 ring-border">
<li class="flex items-center justify-between gap-3 p-3">
<span class="min-w-0 truncate">Отчёт по продажам за июль</span>
<button aria-label="Действия" class="inline-flex size-7 shrink-0 items-center justify-center rounded-lg text-muted-foreground hover:bg-muted hover:text-foreground">⋯</button>
</li>
<li class="flex items-center justify-between gap-3 p-3">
<span class="min-w-0 truncate">Смета на оборудование</span>
<button aria-label="Действия" class="inline-flex size-7 shrink-0 items-center justify-center rounded-lg text-muted-foreground hover:bg-muted hover:text-foreground">⋯</button>
</li>
</ul>
</div>