+
{String(
auth.user.profile.preferred_username ||
@@ -164,11 +188,90 @@ export function Sidebar() {
)}
-
+ >
)
}
-function SidebarLink({ to, label, icon: Icon, badge }: NavItem) {
+/**
+ * MobileSidebar — Sheet с тем же содержимым что desktop Sidebar. Открывается
+ * через hamburger button в TopBar (<1024px), закрывается на: backdrop click,
+ * Escape, или клик на любой nav item (auto-close, привычная mobile UX).
+ *
+ *
Hooks logic дублируется (auth, dictsCount etc.) — нужно для filter
+ * auth-required sections. Можно вынести в useSidebarSections() hook
+ * следующим refactor.
+ */
+export function MobileSidebar({ open, onClose }: { open: boolean; onClose: () => void }) {
+ const { t } = useTranslation()
+ const auth = useAuth()
+ const canMutate = useCanMutate()
+ const dictsQuery = useDictionaries()
+ const myDrafts = useMyDrafts(0, 1)
+
+ const dictsCount = dictsQuery.data?.length ?? 0
+ const myDraftsCount = myDrafts.data?.totalElements ?? 0
+ const reviewsCount = 0
+
+ const sections: NavSection[] = [
+ {
+ items: [
+ { to: '/', label: t('nav.home'), icon: Home },
+ {
+ to: '/dictionaries',
+ label: t('nav.dictionaries'),
+ icon: Database,
+ badge: dictsCount > 0 ? dictsCount : undefined,
+ },
+ { to: '/search', label: t('nav.search'), icon: Search },
+ { to: '/graph', label: t('nav.graph'), icon: GitBranch },
+ ],
+ },
+ {
+ label: t('nav.section.workflow'),
+ items: [
+ {
+ to: '/my-drafts',
+ label: t('nav.myDrafts'),
+ icon: FileText,
+ badge: myDraftsCount > 0 ? myDraftsCount : undefined,
+ authRequired: true,
+ },
+ {
+ to: '/reviews',
+ label: t('nav.reviews'),
+ icon: ClipboardList,
+ badge: reviewsCount > 0 ? reviewsCount : undefined,
+ authRequired: true,
+ },
+ ],
+ },
+ {
+ label: t('nav.section.admin'),
+ items: [
+ { to: '/audit', label: t('nav.audit'), icon: ScrollText, authRequired: true },
+ { to: '/outbox', label: t('nav.outbox'), icon: Inbox, authRequired: true },
+ { to: '/webhooks', label: t('nav.webhooks'), icon: Webhook, authRequired: true },
+ ],
+ },
+ ]
+ const visibleSections = sections
+ .map((s) => ({ ...s, items: s.items.filter((i) => !i.authRequired || canMutate) }))
+ .filter((s) => s.items.length > 0)
+
+ return (
+ { if (!v) onClose() }}>
+
+
+
+
+ )
+}
+
+function SidebarLink({ to, label, icon: Icon, badge, onClick }: NavItem & { onClick?: () => void }) {
const location = useLocation()
const active =
to === '/'
@@ -178,6 +281,7 @@ function SidebarLink({ to, label, icon: Icon, badge }: NavItem) {
return (
void }) {
const { t, i18n } = useTranslation()
const navigate = useNavigate()
const [searchValue, setSearchValue] = useState('')
@@ -59,6 +60,18 @@ export function TopBar() {
return (
+ {/* Hamburger (lg:hidden) — toggle MobileSidebar */}
+ {onMenuClick && (
+
+ )}
+
{/* Breadcrumb — left */}