Тестовое задание: headless-компонент бокового меню на React + TypeScript + Tailwind.
Демо: https://evvlboro.github.io/helloclient-test-task/
ТЗ: HelloClient Notion
| Слой | Папка | Роль |
|---|---|---|
| HeadlessMenu | src/headless-menu |
Поведение и a11y, без дизайна и без роутера |
| RouterMenu | src/router-menu |
Обёртка: роутер + брендинг/стили HelloClient |
| Бизнес | src/layout/AppMenu.tsx |
Только структура: label + to (+ icon) |
Пример бизнес-уровня:
<RouterMenu>
<RouterMenu.Group label="Clients" icon={<IconClients />}>
<RouterMenu.Item label="List" to="/clients/list" />
<RouterMenu.Item label="Reviews" to="/clients/reviews" />
</RouterMenu.Group>
<RouterMenu.Item label="Payments" to="/payments" icon={<IconPayments />} />
</RouterMenu>Без id, activeId, обработчиков и className — это спрятано в RouterMenu.
Headless без роутера можно гонять отдельно (пример: src/examples/UseStateMenuExample.tsx).
| Требование | Реализация |
|---|---|
| Headless API без стилей | src/headless-menu |
| Меню описывается JSX, не JSON | Compound components |
| Controlled state снаружи | activeId, collapsed, openGroupIds |
| Роутер не внутри headless | Только в RouterMenu |
| Desktop: wide / narrow | Collapse, accordion, tooltip, flyout |
| Mobile ≤767px | Tab bar + sheet |
npm install
npm run devnpm run build
npm run preview
npm run deployСтатика в dist/. GitHub Pages: HashRouter + base: './', ветка gh-pages.
src/
headless-menu/ # слой 1 — логика меню
router-menu/ # слой 2 — роутер + дизайн
layout/ # слой 3 — AppMenu + AppShell
pages/
components/
data/
examples/
Menu.Root / Item / Group / GroupTrigger / GroupItems
Menu.CollapseTrigger / Tooltip / MobileSheet / Brand / Label / Icon
Стек: React 19, TypeScript, Vite, Tailwind CSS 4, React Router 7.