// Боковая навигация панелей — кабинета и админки. // // Одна на обе: разделы у них разные, а поведение одинаковое, и когда навигация // была написана дважды, дровер пришлось бы чинить дважды тоже. // // На широком экране это колонка слева. На узком — выдвижная панель за кнопкой: // лентой сверху разделы всё так же уезжали за край, и в мини-аппе, где экран // узкий всегда, половина кабинета была недоступна без горизонтальной прокрутки, // о которой никто не догадывается. // PanelNav — groups: [{ title?, items: [{ id, label, badge?, cta? }] }]. // footer — что показать под списком (профиль, выход); может быть пустым. const PanelNav = ({ groups, subPage, onSubPage, label, brand, footer }) => { const [open, setOpen] = React.useState(false); // Раздел выбран — панель закрывается: на узком экране она перекрывает то, // ради чего её открыли. const pick = (id) => { onSubPage(id); setOpen(false); }; // Esc закрывает — привычка, которой ждут от любой выдвижной панели. React.useEffect(() => { if (!open) return undefined; const onKey = (e) => { if (e.key === 'Escape') setOpen(false); }; document.addEventListener('keydown', onKey); return () => document.removeEventListener('keydown', onKey); }, [open]); // Пока панель открыта, страница под ней не прокручивается: иначе палец // листает фон, а не меню. React.useEffect(() => { if (!open) return undefined; const prev = document.body.style.overflow; document.body.style.overflow = 'hidden'; return () => { document.body.style.overflow = prev; }; }, [open]); const current = groups.flatMap(g => g.items).find(it => it.id === subPage); return ( <> {/* Полоса с кнопкой — только на узком экране (скрыта в CSS на широком). Показывает текущий раздел: иначе, закрыв панель, человек не видит, где он находится. */}
{current ? current.label : label}
{open &&
setOpen(false)} aria-hidden="true" />} ); }; Object.assign(window, { PanelNav });