@prenta/admin 2.6.1 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +98 -0
- package/design-system/tokens.css +179 -85
- package/design-system/tokens.json +276 -85
- package/design-system/tokens.scss +148 -44
- package/design-system/tokens.tailwind.cjs +50 -1
- package/dist/AdminRoot.d.ts.map +1 -1
- package/dist/AdminRoot.js +42 -20
- package/dist/AdminRoot.js.map +1 -1
- package/dist/LoginRoot.d.ts.map +1 -1
- package/dist/LoginRoot.js +2 -1
- package/dist/LoginRoot.js.map +1 -1
- package/dist/__tests__/components/author-avatar.test.js +3 -17
- package/dist/__tests__/components/author-avatar.test.js.map +1 -1
- package/dist/__tests__/components/notification-bell.test.js +86 -15
- package/dist/__tests__/components/notification-bell.test.js.map +1 -1
- package/dist/__tests__/components/responsive-tiers.render.test.d.ts +2 -0
- package/dist/__tests__/components/responsive-tiers.render.test.d.ts.map +1 -0
- package/dist/__tests__/components/responsive-tiers.render.test.js +313 -0
- package/dist/__tests__/components/responsive-tiers.render.test.js.map +1 -0
- package/dist/__tests__/components/serp-preview.render.test.js +6 -3
- package/dist/__tests__/components/serp-preview.render.test.js.map +1 -1
- package/dist/__tests__/components/shell-primitives.render.test.d.ts +2 -0
- package/dist/__tests__/components/shell-primitives.render.test.d.ts.map +1 -0
- package/dist/__tests__/components/shell-primitives.render.test.js +716 -0
- package/dist/__tests__/components/shell-primitives.render.test.js.map +1 -0
- package/dist/__tests__/components/theme-provider-accent.test.js +49 -7
- package/dist/__tests__/components/theme-provider-accent.test.js.map +1 -1
- package/dist/__tests__/components/toggle.test.js +10 -3
- package/dist/__tests__/components/toggle.test.js.map +1 -1
- package/dist/__tests__/components/ui-primitives.test.js +3 -7
- package/dist/__tests__/components/ui-primitives.test.js.map +1 -1
- package/dist/__tests__/layout/build-nav-items.test.js +1 -1
- package/dist/__tests__/layout/build-nav-items.test.js.map +1 -1
- package/dist/__tests__/layout/shell-nav.render.test.d.ts +2 -0
- package/dist/__tests__/layout/shell-nav.render.test.d.ts.map +1 -0
- package/dist/__tests__/layout/shell-nav.render.test.js +736 -0
- package/dist/__tests__/layout/shell-nav.render.test.js.map +1 -0
- package/dist/__tests__/layout/sidebar-forms-submenu.render.test.js +8 -4
- package/dist/__tests__/layout/sidebar-forms-submenu.render.test.js.map +1 -1
- package/dist/__tests__/layout/sidebar-groups.render.test.d.ts +2 -0
- package/dist/__tests__/layout/sidebar-groups.render.test.d.ts.map +1 -0
- package/dist/__tests__/layout/sidebar-groups.render.test.js +56 -0
- package/dist/__tests__/layout/sidebar-groups.render.test.js.map +1 -0
- package/dist/__tests__/layout/sidebar-menus.render.test.d.ts +2 -0
- package/dist/__tests__/layout/sidebar-menus.render.test.d.ts.map +1 -0
- package/dist/__tests__/layout/sidebar-menus.render.test.js +82 -0
- package/dist/__tests__/layout/sidebar-menus.render.test.js.map +1 -0
- package/dist/__tests__/layout/sidebar-plugin-nav.test.js +1 -1
- package/dist/__tests__/layout/sidebar-plugin-nav.test.js.map +1 -1
- package/dist/__tests__/layout/sidebar-posts.test.js +48 -38
- package/dist/__tests__/layout/sidebar-posts.test.js.map +1 -1
- package/dist/__tests__/layout/sidebar-submenu.render.test.js +26 -10
- package/dist/__tests__/layout/sidebar-submenu.render.test.js.map +1 -1
- package/dist/__tests__/layout/topbar.render.test.d.ts +2 -0
- package/dist/__tests__/layout/topbar.render.test.d.ts.map +1 -0
- package/dist/__tests__/layout/topbar.render.test.js +406 -0
- package/dist/__tests__/layout/topbar.render.test.js.map +1 -0
- package/dist/__tests__/lib/api.test.js +2 -0
- package/dist/__tests__/lib/api.test.js.map +1 -1
- package/dist/__tests__/lib/job-drawer.test.d.ts +2 -0
- package/dist/__tests__/lib/job-drawer.test.d.ts.map +1 -0
- package/dist/__tests__/lib/job-drawer.test.js +393 -0
- package/dist/__tests__/lib/job-drawer.test.js.map +1 -0
- package/dist/__tests__/lib/job-view.test.d.ts +2 -0
- package/dist/__tests__/lib/job-view.test.d.ts.map +1 -0
- package/dist/__tests__/lib/job-view.test.js +387 -0
- package/dist/__tests__/lib/job-view.test.js.map +1 -0
- package/dist/__tests__/lib/jobs-service.test.d.ts +2 -0
- package/dist/__tests__/lib/jobs-service.test.d.ts.map +1 -0
- package/dist/__tests__/lib/jobs-service.test.js +88 -0
- package/dist/__tests__/lib/jobs-service.test.js.map +1 -0
- package/dist/__tests__/router/admin-route-order.test.d.ts +2 -0
- package/dist/__tests__/router/admin-route-order.test.d.ts.map +1 -0
- package/dist/__tests__/router/admin-route-order.test.js +36 -0
- package/dist/__tests__/router/admin-route-order.test.js.map +1 -0
- package/dist/__tests__/styles/ai-gradient-contrast.test.d.ts +2 -0
- package/dist/__tests__/styles/ai-gradient-contrast.test.d.ts.map +1 -0
- package/dist/__tests__/styles/ai-gradient-contrast.test.js +110 -0
- package/dist/__tests__/styles/ai-gradient-contrast.test.js.map +1 -0
- package/dist/__tests__/styles/breakpoint-order.test.d.ts +2 -0
- package/dist/__tests__/styles/breakpoint-order.test.d.ts.map +1 -0
- package/dist/__tests__/styles/breakpoint-order.test.js +52 -0
- package/dist/__tests__/styles/breakpoint-order.test.js.map +1 -0
- package/dist/__tests__/styles/responsive-layer.test.js +303 -12
- package/dist/__tests__/styles/responsive-layer.test.js.map +1 -1
- package/dist/__tests__/styles/tailwind-preset.test.d.ts +2 -0
- package/dist/__tests__/styles/tailwind-preset.test.d.ts.map +1 -0
- package/dist/__tests__/styles/tailwind-preset.test.js +147 -0
- package/dist/__tests__/styles/tailwind-preset.test.js.map +1 -0
- package/dist/__tests__/styles/theme-token-scope.test.js +9 -3
- package/dist/__tests__/styles/theme-token-scope.test.js.map +1 -1
- package/dist/__tests__/styles/tiers.test.d.ts +2 -0
- package/dist/__tests__/styles/tiers.test.d.ts.map +1 -0
- package/dist/__tests__/styles/tiers.test.js +121 -0
- package/dist/__tests__/styles/tiers.test.js.map +1 -0
- package/dist/__tests__/styles/tokens-drift.test.js +57 -6
- package/dist/__tests__/styles/tokens-drift.test.js.map +1 -1
- package/dist/__tests__/views/admin-lazy-route.render.test.js +27 -1
- package/dist/__tests__/views/admin-lazy-route.render.test.js.map +1 -1
- package/dist/__tests__/views/ai-settings.render.test.js +4 -4
- package/dist/__tests__/views/appearance-menus-route.render.test.d.ts +2 -0
- package/dist/__tests__/views/appearance-menus-route.render.test.d.ts.map +1 -0
- package/dist/__tests__/views/appearance-menus-route.render.test.js +50 -0
- package/dist/__tests__/views/appearance-menus-route.render.test.js.map +1 -0
- package/dist/__tests__/views/appearance-settings.render.test.js +8 -6
- package/dist/__tests__/views/appearance-settings.render.test.js.map +1 -1
- package/dist/__tests__/views/appearance-theme-provider.render.test.d.ts +2 -0
- package/dist/__tests__/views/appearance-theme-provider.render.test.d.ts.map +1 -0
- package/dist/__tests__/views/appearance-theme-provider.render.test.js +286 -0
- package/dist/__tests__/views/appearance-theme-provider.render.test.js.map +1 -0
- package/dist/__tests__/views/dashboard.test.js +37 -0
- package/dist/__tests__/views/dashboard.test.js.map +1 -1
- package/dist/__tests__/views/document-edit-navigation.render.test.d.ts +2 -0
- package/dist/__tests__/views/document-edit-navigation.render.test.d.ts.map +1 -0
- package/dist/__tests__/views/document-edit-navigation.render.test.js +101 -0
- package/dist/__tests__/views/document-edit-navigation.render.test.js.map +1 -0
- package/dist/__tests__/views/document-edit-routes.render.test.d.ts +2 -0
- package/dist/__tests__/views/document-edit-routes.render.test.d.ts.map +1 -0
- package/dist/__tests__/views/document-edit-routes.render.test.js +113 -0
- package/dist/__tests__/views/document-edit-routes.render.test.js.map +1 -0
- package/dist/__tests__/views/email-settings.render.test.js +2 -2
- package/dist/__tests__/views/form-entries.render.test.js +15 -0
- package/dist/__tests__/views/form-entries.render.test.js.map +1 -1
- package/dist/__tests__/views/form-submissions.render.test.js +18 -0
- package/dist/__tests__/views/form-submissions.render.test.js.map +1 -1
- package/dist/__tests__/views/forms-list.render.test.js +12 -0
- package/dist/__tests__/views/forms-list.render.test.js.map +1 -1
- package/dist/__tests__/views/general-settings.render.test.js +26 -10
- package/dist/__tests__/views/general-settings.render.test.js.map +1 -1
- package/dist/__tests__/views/jobs-drawer.render.test.d.ts +2 -0
- package/dist/__tests__/views/jobs-drawer.render.test.d.ts.map +1 -0
- package/dist/__tests__/views/jobs-drawer.render.test.js +908 -0
- package/dist/__tests__/views/jobs-drawer.render.test.js.map +1 -0
- package/dist/__tests__/views/jobs-list.render.test.d.ts +2 -0
- package/dist/__tests__/views/jobs-list.render.test.d.ts.map +1 -0
- package/dist/__tests__/views/jobs-list.render.test.js +695 -0
- package/dist/__tests__/views/jobs-list.render.test.js.map +1 -0
- package/dist/__tests__/views/kpi-grid.test.d.ts +2 -0
- package/dist/__tests__/views/kpi-grid.test.d.ts.map +1 -0
- package/dist/__tests__/views/kpi-grid.test.js +26 -0
- package/dist/__tests__/views/kpi-grid.test.js.map +1 -0
- package/dist/__tests__/views/post-section-editor.test.js +3 -1
- package/dist/__tests__/views/post-section-editor.test.js.map +1 -1
- package/dist/__tests__/views/script-tags.render.test.js +25 -0
- package/dist/__tests__/views/script-tags.render.test.js.map +1 -1
- package/dist/__tests__/views/security-settings.render.test.js +9 -8
- package/dist/__tests__/views/security-settings.render.test.js.map +1 -1
- package/dist/__tests__/views/seo-settings.render.test.js +7 -7
- package/dist/__tests__/views/seo-settings.render.test.js.map +1 -1
- package/dist/__tests__/views/settings-save-bar.render.test.d.ts +2 -0
- package/dist/__tests__/views/settings-save-bar.render.test.d.ts.map +1 -0
- package/dist/__tests__/views/settings-save-bar.render.test.js +68 -0
- package/dist/__tests__/views/settings-save-bar.render.test.js.map +1 -0
- package/dist/__tests__/views/settings-save-footer.render.test.js +23 -15
- package/dist/__tests__/views/settings-save-footer.render.test.js.map +1 -1
- package/dist/__tests__/views/settings-tabs.render.test.d.ts +2 -0
- package/dist/__tests__/views/settings-tabs.render.test.d.ts.map +1 -0
- package/dist/__tests__/views/settings-tabs.render.test.js +319 -0
- package/dist/__tests__/views/settings-tabs.render.test.js.map +1 -0
- package/dist/__tests__/views/updates-tab.render.test.js +4 -3
- package/dist/__tests__/views/updates-tab.render.test.js.map +1 -1
- package/dist/components/Breadcrumbs.d.ts +8 -1
- package/dist/components/Breadcrumbs.d.ts.map +1 -1
- package/dist/components/Breadcrumbs.js +6 -0
- package/dist/components/Breadcrumbs.js.map +1 -1
- package/dist/components/CommandPalette.d.ts.map +1 -1
- package/dist/components/CommandPalette.js +40 -4
- package/dist/components/CommandPalette.js.map +1 -1
- package/dist/components/LocaleSwitcher.d.ts.map +1 -1
- package/dist/components/LocaleSwitcher.js +1 -1
- package/dist/components/LocaleSwitcher.js.map +1 -1
- package/dist/components/NotificationBell.d.ts.map +1 -1
- package/dist/components/NotificationBell.js +44 -3
- package/dist/components/NotificationBell.js.map +1 -1
- package/dist/components/OfflineStatus.js +1 -1
- package/dist/components/OfflineStatus.js.map +1 -1
- package/dist/components/PresenceIndicator.d.ts.map +1 -1
- package/dist/components/PresenceIndicator.js +2 -1
- package/dist/components/PresenceIndicator.js.map +1 -1
- package/dist/components/ReflowMeter.js +2 -2
- package/dist/components/ReflowMeter.js.map +1 -1
- package/dist/components/SEOPanel.js +2 -2
- package/dist/components/SEOPanel.js.map +1 -1
- package/dist/components/ThemeProvider.d.ts +24 -4
- package/dist/components/ThemeProvider.d.ts.map +1 -1
- package/dist/components/ThemeProvider.js +80 -22
- package/dist/components/ThemeProvider.js.map +1 -1
- package/dist/components/TipTapEditor.d.ts.map +1 -1
- package/dist/components/TipTapEditor.js +3 -4
- package/dist/components/TipTapEditor.js.map +1 -1
- package/dist/components/forms/EmbedPanel.js +1 -1
- package/dist/components/forms/EmbedPanel.js.map +1 -1
- package/dist/components/forms/FormEntryDetailPane.js +6 -1
- package/dist/components/forms/FormEntryDetailPane.js.map +1 -1
- package/dist/components/forms/ShortcodeGlossary.d.ts.map +1 -1
- package/dist/components/forms/ShortcodeGlossary.js +6 -1
- package/dist/components/forms/ShortcodeGlossary.js.map +1 -1
- package/dist/components/forms/primitives.d.ts +15 -5
- package/dist/components/forms/primitives.d.ts.map +1 -1
- package/dist/components/forms/primitives.js +11 -11
- package/dist/components/forms/primitives.js.map +1 -1
- package/dist/components/pages/tag-badges.d.ts +2 -1
- package/dist/components/pages/tag-badges.d.ts.map +1 -1
- package/dist/components/pages/tag-badges.js +4 -3
- package/dist/components/pages/tag-badges.js.map +1 -1
- package/dist/components/seo/PageAuditPanel.js +2 -2
- package/dist/components/seo/PageAuditPanel.js.map +1 -1
- package/dist/components/seo/SerpPreview.d.ts +4 -4
- package/dist/components/seo/SerpPreview.js +1 -1
- package/dist/components/seo/SerpPreview.js.map +1 -1
- package/dist/components/seo/primitives.js +2 -2
- package/dist/components/seo/primitives.js.map +1 -1
- package/dist/components/ui/AuthorAvatar.d.ts +4 -4
- package/dist/components/ui/AuthorAvatar.d.ts.map +1 -1
- package/dist/components/ui/AuthorAvatar.js +11 -10
- package/dist/components/ui/AuthorAvatar.js.map +1 -1
- package/dist/components/ui/Badge.d.ts +1 -1
- package/dist/components/ui/Badge.js +1 -1
- package/dist/components/ui/Badge.js.map +1 -1
- package/dist/components/ui/Breadcrumb.d.ts +27 -0
- package/dist/components/ui/Breadcrumb.d.ts.map +1 -0
- package/dist/components/ui/Breadcrumb.js +41 -0
- package/dist/components/ui/Breadcrumb.js.map +1 -0
- package/dist/components/ui/Button.d.ts +2 -2
- package/dist/components/ui/Button.d.ts.map +1 -1
- package/dist/components/ui/Button.js +31 -16
- package/dist/components/ui/Button.js.map +1 -1
- package/dist/components/ui/Card.d.ts +8 -4
- package/dist/components/ui/Card.d.ts.map +1 -1
- package/dist/components/ui/Card.js +21 -13
- package/dist/components/ui/Card.js.map +1 -1
- package/dist/components/ui/CardListControls.d.ts +34 -0
- package/dist/components/ui/CardListControls.d.ts.map +1 -0
- package/dist/components/ui/CardListControls.js +25 -0
- package/dist/components/ui/CardListControls.js.map +1 -0
- package/dist/components/ui/CountChip.d.ts +20 -0
- package/dist/components/ui/CountChip.d.ts.map +1 -0
- package/dist/components/ui/CountChip.js +48 -0
- package/dist/components/ui/CountChip.js.map +1 -0
- package/dist/components/ui/FilterPill.d.ts +2 -1
- package/dist/components/ui/FilterPill.d.ts.map +1 -1
- package/dist/components/ui/FilterPill.js +3 -2
- package/dist/components/ui/FilterPill.js.map +1 -1
- package/dist/components/ui/Input.d.ts +1 -1
- package/dist/components/ui/InspectorPane.d.ts +1 -1
- package/dist/components/ui/InspectorPane.d.ts.map +1 -1
- package/dist/components/ui/InspectorPane.js +15 -4
- package/dist/components/ui/InspectorPane.js.map +1 -1
- package/dist/components/ui/Modal.d.ts.map +1 -1
- package/dist/components/ui/Modal.js +10 -2
- package/dist/components/ui/Modal.js.map +1 -1
- package/dist/components/ui/PageHeader.d.ts +28 -0
- package/dist/components/ui/PageHeader.d.ts.map +1 -0
- package/dist/components/ui/PageHeader.js +18 -0
- package/dist/components/ui/PageHeader.js.map +1 -0
- package/dist/components/ui/RowActions.d.ts +5 -0
- package/dist/components/ui/RowActions.d.ts.map +1 -1
- package/dist/components/ui/RowActions.js +7 -2
- package/dist/components/ui/RowActions.js.map +1 -1
- package/dist/components/ui/Segmented.d.ts +21 -0
- package/dist/components/ui/Segmented.d.ts.map +1 -0
- package/dist/components/ui/Segmented.js +41 -0
- package/dist/components/ui/Segmented.js.map +1 -0
- package/dist/components/ui/Toaster.d.ts +12 -0
- package/dist/components/ui/Toaster.d.ts.map +1 -0
- package/dist/components/ui/Toaster.js +103 -0
- package/dist/components/ui/Toaster.js.map +1 -0
- package/dist/components/ui/Toggle.d.ts +4 -3
- package/dist/components/ui/Toggle.d.ts.map +1 -1
- package/dist/components/ui/Toggle.js +16 -12
- package/dist/components/ui/Toggle.js.map +1 -1
- package/dist/components/ui/UnderlineTabs.d.ts +56 -0
- package/dist/components/ui/UnderlineTabs.d.ts.map +1 -0
- package/dist/components/ui/UnderlineTabs.js +205 -0
- package/dist/components/ui/UnderlineTabs.js.map +1 -0
- package/dist/components/ui/UnsavedBar.d.ts +25 -0
- package/dist/components/ui/UnsavedBar.d.ts.map +1 -0
- package/dist/components/ui/UnsavedBar.js +22 -0
- package/dist/components/ui/UnsavedBar.js.map +1 -0
- package/dist/components/ui/admin-links.d.ts +11 -0
- package/dist/components/ui/admin-links.d.ts.map +1 -0
- package/dist/components/ui/admin-links.js +10 -0
- package/dist/components/ui/admin-links.js.map +1 -0
- package/dist/components/ui/card-table.d.ts +43 -0
- package/dist/components/ui/card-table.d.ts.map +1 -0
- package/dist/components/ui/card-table.js +31 -0
- package/dist/components/ui/card-table.js.map +1 -0
- package/dist/components/virtual-window.d.ts +7 -0
- package/dist/components/virtual-window.d.ts.map +1 -1
- package/dist/components/virtual-window.js +5 -2
- package/dist/components/virtual-window.js.map +1 -1
- package/dist/fields/RelationshipField.js +1 -1
- package/dist/fields/RelationshipField.js.map +1 -1
- package/dist/files/dm-mono-latin-400-normal.woff +0 -0
- package/dist/files/dm-mono-latin-400-normal.woff2 +0 -0
- package/dist/files/dm-mono-latin-500-normal.woff +0 -0
- package/dist/files/dm-mono-latin-500-normal.woff2 +0 -0
- package/dist/files/dm-mono-latin-ext-400-normal.woff +0 -0
- package/dist/files/dm-mono-latin-ext-400-normal.woff2 +0 -0
- package/dist/files/dm-mono-latin-ext-500-normal.woff +0 -0
- package/dist/files/dm-mono-latin-ext-500-normal.woff2 +0 -0
- package/dist/files/dm-sans-latin-400-normal.woff +0 -0
- package/dist/files/dm-sans-latin-400-normal.woff2 +0 -0
- package/dist/files/dm-sans-latin-500-normal.woff +0 -0
- package/dist/files/dm-sans-latin-500-normal.woff2 +0 -0
- package/dist/files/dm-sans-latin-600-normal.woff +0 -0
- package/dist/files/dm-sans-latin-600-normal.woff2 +0 -0
- package/dist/files/dm-sans-latin-700-normal.woff +0 -0
- package/dist/files/dm-sans-latin-700-normal.woff2 +0 -0
- package/dist/files/dm-sans-latin-ext-400-normal.woff +0 -0
- package/dist/files/dm-sans-latin-ext-400-normal.woff2 +0 -0
- package/dist/files/dm-sans-latin-ext-500-normal.woff +0 -0
- package/dist/files/dm-sans-latin-ext-500-normal.woff2 +0 -0
- package/dist/files/dm-sans-latin-ext-600-normal.woff +0 -0
- package/dist/files/dm-sans-latin-ext-600-normal.woff2 +0 -0
- package/dist/files/dm-sans-latin-ext-700-normal.woff +0 -0
- package/dist/files/dm-sans-latin-ext-700-normal.woff2 +0 -0
- package/dist/hooks/useAppearanceBootstrap.d.ts.map +1 -1
- package/dist/hooks/useAppearanceBootstrap.js +9 -3
- package/dist/hooks/useAppearanceBootstrap.js.map +1 -1
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/layout/Header.d.ts +14 -5
- package/dist/layout/Header.d.ts.map +1 -1
- package/dist/layout/Header.js +78 -13
- package/dist/layout/Header.js.map +1 -1
- package/dist/layout/Layout.d.ts +5 -3
- package/dist/layout/Layout.d.ts.map +1 -1
- package/dist/layout/Layout.js +45 -54
- package/dist/layout/Layout.js.map +1 -1
- package/dist/layout/MobileTabBar.d.ts +20 -0
- package/dist/layout/MobileTabBar.d.ts.map +1 -0
- package/dist/layout/MobileTabBar.js +29 -0
- package/dist/layout/MobileTabBar.js.map +1 -0
- package/dist/layout/Sidebar.d.ts +8 -26
- package/dist/layout/Sidebar.d.ts.map +1 -1
- package/dist/layout/Sidebar.js +309 -345
- package/dist/layout/Sidebar.js.map +1 -1
- package/dist/layout/nav-config.d.ts +101 -0
- package/dist/layout/nav-config.d.ts.map +1 -0
- package/dist/layout/nav-config.js +297 -0
- package/dist/layout/nav-config.js.map +1 -0
- package/dist/layout/primitives/AdminShell.d.ts +10 -2
- package/dist/layout/primitives/AdminShell.d.ts.map +1 -1
- package/dist/layout/primitives/AdminShell.js +114 -6
- package/dist/layout/primitives/AdminShell.js.map +1 -1
- package/dist/layout/topbar-classes.d.ts +8 -0
- package/dist/layout/topbar-classes.d.ts.map +1 -0
- package/dist/layout/topbar-classes.js +8 -0
- package/dist/layout/topbar-classes.js.map +1 -0
- package/dist/layout/use-nav-items.d.ts +10 -0
- package/dist/layout/use-nav-items.d.ts.map +1 -0
- package/dist/layout/use-nav-items.js +39 -0
- package/dist/layout/use-nav-items.js.map +1 -0
- package/dist/lib/api.d.ts +6 -0
- package/dist/lib/api.d.ts.map +1 -1
- package/dist/lib/api.js +1 -1
- package/dist/lib/api.js.map +1 -1
- package/dist/lib/appearance-api.d.ts +3 -1
- package/dist/lib/appearance-api.d.ts.map +1 -1
- package/dist/lib/appearance-api.js +1 -0
- package/dist/lib/appearance-api.js.map +1 -1
- package/dist/lib/job-drawer.d.ts +128 -0
- package/dist/lib/job-drawer.d.ts.map +1 -0
- package/dist/lib/job-drawer.js +351 -0
- package/dist/lib/job-drawer.js.map +1 -0
- package/dist/lib/job-view.d.ts +187 -0
- package/dist/lib/job-view.d.ts.map +1 -0
- package/dist/lib/job-view.js +363 -0
- package/dist/lib/job-view.js.map +1 -0
- package/dist/lib/jobs-service.d.ts +134 -0
- package/dist/lib/jobs-service.d.ts.map +1 -0
- package/dist/lib/jobs-service.js +179 -0
- package/dist/lib/jobs-service.js.map +1 -0
- package/dist/lib/sheet-focus.d.ts +24 -0
- package/dist/lib/sheet-focus.d.ts.map +1 -0
- package/dist/lib/sheet-focus.js +61 -0
- package/dist/lib/sheet-focus.js.map +1 -0
- package/dist/lib/tiers.d.ts +30 -0
- package/dist/lib/tiers.d.ts.map +1 -0
- package/dist/lib/tiers.js +43 -0
- package/dist/lib/tiers.js.map +1 -0
- package/dist/prenta-admin.css +1 -1
- package/dist/router/admin-routes.d.ts.map +1 -1
- package/dist/router/admin-routes.js +8 -0
- package/dist/router/admin-routes.js.map +1 -1
- package/dist/router/index.d.ts +5 -1
- package/dist/router/index.d.ts.map +1 -1
- package/dist/router/index.js +7 -0
- package/dist/router/index.js.map +1 -1
- package/dist/views/ApiKeys.js +2 -2
- package/dist/views/ApiKeys.js.map +1 -1
- package/dist/views/AppearanceChrome.d.ts +6 -0
- package/dist/views/AppearanceChrome.d.ts.map +1 -0
- package/dist/views/AppearanceChrome.js +25 -0
- package/dist/views/AppearanceChrome.js.map +1 -0
- package/dist/views/CollectionList.d.ts.map +1 -1
- package/dist/views/CollectionList.js +73 -53
- package/dist/views/CollectionList.js.map +1 -1
- package/dist/views/Dashboard.d.ts.map +1 -1
- package/dist/views/Dashboard.js +14 -8
- package/dist/views/Dashboard.js.map +1 -1
- package/dist/views/DocumentEdit.d.ts +6 -1
- package/dist/views/DocumentEdit.d.ts.map +1 -1
- package/dist/views/DocumentEdit.js +44 -8
- package/dist/views/DocumentEdit.js.map +1 -1
- package/dist/views/FormDeliveries.d.ts.map +1 -1
- package/dist/views/FormDeliveries.js +7 -1
- package/dist/views/FormDeliveries.js.map +1 -1
- package/dist/views/FormEntries.d.ts.map +1 -1
- package/dist/views/FormEntries.js +11 -2
- package/dist/views/FormEntries.js.map +1 -1
- package/dist/views/FormSubmissions.d.ts +1 -1
- package/dist/views/FormSubmissions.d.ts.map +1 -1
- package/dist/views/FormSubmissions.js +15 -4
- package/dist/views/FormSubmissions.js.map +1 -1
- package/dist/views/Forms.d.ts.map +1 -1
- package/dist/views/Forms.js +6 -4
- package/dist/views/Forms.js.map +1 -1
- package/dist/views/FormsSettings.d.ts +2 -0
- package/dist/views/FormsSettings.d.ts.map +1 -0
- package/dist/views/FormsSettings.js +37 -0
- package/dist/views/FormsSettings.js.map +1 -0
- package/dist/views/Jobs/BusyButton.d.ts +15 -0
- package/dist/views/Jobs/BusyButton.d.ts.map +1 -0
- package/dist/views/Jobs/BusyButton.js +26 -0
- package/dist/views/Jobs/BusyButton.js.map +1 -0
- package/dist/views/Jobs/CancelJobDialog.d.ts +21 -0
- package/dist/views/Jobs/CancelJobDialog.d.ts.map +1 -0
- package/dist/views/Jobs/CancelJobDialog.js +66 -0
- package/dist/views/Jobs/CancelJobDialog.js.map +1 -0
- package/dist/views/Jobs/DrawerActivity.d.ts +16 -0
- package/dist/views/Jobs/DrawerActivity.d.ts.map +1 -0
- package/dist/views/Jobs/DrawerActivity.js +59 -0
- package/dist/views/Jobs/DrawerActivity.js.map +1 -0
- package/dist/views/Jobs/DrawerOverview.d.ts +28 -0
- package/dist/views/Jobs/DrawerOverview.d.ts.map +1 -0
- package/dist/views/Jobs/DrawerOverview.js +32 -0
- package/dist/views/Jobs/DrawerOverview.js.map +1 -0
- package/dist/views/Jobs/DrawerResults.d.ts +25 -0
- package/dist/views/Jobs/DrawerResults.d.ts.map +1 -0
- package/dist/views/Jobs/DrawerResults.js +50 -0
- package/dist/views/Jobs/DrawerResults.js.map +1 -0
- package/dist/views/Jobs/DrawerReview.d.ts +44 -0
- package/dist/views/Jobs/DrawerReview.d.ts.map +1 -0
- package/dist/views/Jobs/DrawerReview.js +110 -0
- package/dist/views/Jobs/DrawerReview.js.map +1 -0
- package/dist/views/Jobs/JobDrawer.d.ts +31 -0
- package/dist/views/Jobs/JobDrawer.d.ts.map +1 -0
- package/dist/views/Jobs/JobDrawer.js +353 -0
- package/dist/views/Jobs/JobDrawer.js.map +1 -0
- package/dist/views/Jobs/JobsTable.d.ts +19 -0
- package/dist/views/Jobs/JobsTable.d.ts.map +1 -0
- package/dist/views/Jobs/JobsTable.js +80 -0
- package/dist/views/Jobs/JobsTable.js.map +1 -0
- package/dist/views/Jobs/WorkerStrip.d.ts +22 -0
- package/dist/views/Jobs/WorkerStrip.d.ts.map +1 -0
- package/dist/views/Jobs/WorkerStrip.js +63 -0
- package/dist/views/Jobs/WorkerStrip.js.map +1 -0
- package/dist/views/Jobs/atoms.d.ts +19 -0
- package/dist/views/Jobs/atoms.d.ts.map +1 -0
- package/dist/views/Jobs/atoms.js +52 -0
- package/dist/views/Jobs/atoms.js.map +1 -0
- package/dist/views/Jobs/focus.d.ts +23 -0
- package/dist/views/Jobs/focus.d.ts.map +1 -0
- package/dist/views/Jobs/focus.js +54 -0
- package/dist/views/Jobs/focus.js.map +1 -0
- package/dist/views/Jobs/icons.d.ts +22 -0
- package/dist/views/Jobs/icons.d.ts.map +1 -0
- package/dist/views/Jobs/icons.js +23 -0
- package/dist/views/Jobs/icons.js.map +1 -0
- package/dist/views/Jobs/placeholders.d.ts +6 -0
- package/dist/views/Jobs/placeholders.d.ts.map +1 -0
- package/dist/views/Jobs/placeholders.js +20 -0
- package/dist/views/Jobs/placeholders.js.map +1 -0
- package/dist/views/Jobs/useJobDetail.d.ts +21 -0
- package/dist/views/Jobs/useJobDetail.d.ts.map +1 -0
- package/dist/views/Jobs/useJobDetail.js +39 -0
- package/dist/views/Jobs/useJobDetail.js.map +1 -0
- package/dist/views/Jobs/useJobsFeed.d.ts +35 -0
- package/dist/views/Jobs/useJobsFeed.d.ts.map +1 -0
- package/dist/views/Jobs/useJobsFeed.js +231 -0
- package/dist/views/Jobs/useJobsFeed.js.map +1 -0
- package/dist/views/Jobs/useReviewItems.d.ts +39 -0
- package/dist/views/Jobs/useReviewItems.d.ts.map +1 -0
- package/dist/views/Jobs/useReviewItems.js +140 -0
- package/dist/views/Jobs/useReviewItems.js.map +1 -0
- package/dist/views/Jobs.d.ts +10 -1
- package/dist/views/Jobs.d.ts.map +1 -1
- package/dist/views/Jobs.js +126 -35
- package/dist/views/Jobs.js.map +1 -1
- package/dist/views/MediaBrowser.d.ts.map +1 -1
- package/dist/views/MediaBrowser.js +37 -9
- package/dist/views/MediaBrowser.js.map +1 -1
- package/dist/views/Pages/PagesListView.d.ts +1 -1
- package/dist/views/Pages/PagesListView.d.ts.map +1 -1
- package/dist/views/Pages/PagesListView.js +34 -5
- package/dist/views/Pages/PagesListView.js.map +1 -1
- package/dist/views/Posts/NewPostTypeInstructions.d.ts.map +1 -1
- package/dist/views/Posts/NewPostTypeInstructions.js +12 -2
- package/dist/views/Posts/NewPostTypeInstructions.js.map +1 -1
- package/dist/views/Posts/PostTypesView.d.ts.map +1 -1
- package/dist/views/Posts/PostTypesView.js +10 -1
- package/dist/views/Posts/PostTypesView.js.map +1 -1
- package/dist/views/Posts/PostsListView.d.ts +1 -2
- package/dist/views/Posts/PostsListView.d.ts.map +1 -1
- package/dist/views/Posts/PostsListView.js +31 -3
- package/dist/views/Posts/PostsListView.js.map +1 -1
- package/dist/views/SEO.d.ts.map +1 -1
- package/dist/views/SEO.js +3 -3
- package/dist/views/SEO.js.map +1 -1
- package/dist/views/ScriptTags.d.ts.map +1 -1
- package/dist/views/ScriptTags.js +20 -15
- package/dist/views/ScriptTags.js.map +1 -1
- package/dist/views/Settings.d.ts.map +1 -1
- package/dist/views/Settings.js +115 -51
- package/dist/views/Settings.js.map +1 -1
- package/dist/views/SpamFolder.d.ts +2 -0
- package/dist/views/SpamFolder.d.ts.map +1 -0
- package/dist/views/SpamFolder.js +14 -0
- package/dist/views/SpamFolder.js.map +1 -0
- package/dist/views/Trash.d.ts +6 -1
- package/dist/views/Trash.d.ts.map +1 -1
- package/dist/views/Trash.js +7 -2
- package/dist/views/Trash.js.map +1 -1
- package/dist/views/Users.js +2 -2
- package/dist/views/Users.js.map +1 -1
- package/dist/views/WidgetsPlaceholder.d.ts +2 -0
- package/dist/views/WidgetsPlaceholder.d.ts.map +1 -0
- package/dist/views/WidgetsPlaceholder.js +15 -0
- package/dist/views/WidgetsPlaceholder.js.map +1 -0
- package/dist/views/dashboard/kpi-grid.d.ts +18 -0
- package/dist/views/dashboard/kpi-grid.d.ts.map +1 -0
- package/dist/views/dashboard/kpi-grid.js +36 -0
- package/dist/views/dashboard/kpi-grid.js.map +1 -0
- package/dist/views/lazy-views.d.ts +4 -0
- package/dist/views/lazy-views.d.ts.map +1 -1
- package/dist/views/lazy-views.js +4 -0
- package/dist/views/lazy-views.js.map +1 -1
- package/dist/views/page-editor/PageTab.js +1 -1
- package/dist/views/page-editor/PageTab.js.map +1 -1
- package/dist/views/page-editor/ReviewBar.js +1 -1
- package/dist/views/page-editor/ReviewBar.js.map +1 -1
- package/dist/views/page-editor/VersionHistoryPanel.js +1 -1
- package/dist/views/page-editor/VersionHistoryPanel.js.map +1 -1
- package/dist/views/seo/AuditTab.js +1 -1
- package/dist/views/seo/AuditTab.js.map +1 -1
- package/dist/views/seo/OverviewTab.d.ts.map +1 -1
- package/dist/views/seo/OverviewTab.js +2 -2
- package/dist/views/seo/OverviewTab.js.map +1 -1
- package/dist/views/seo/ProposalsTab.js +1 -1
- package/dist/views/seo/ProposalsTab.js.map +1 -1
- package/dist/views/settings/AISettingsTab.d.ts.map +1 -1
- package/dist/views/settings/AISettingsTab.js +4 -2
- package/dist/views/settings/AISettingsTab.js.map +1 -1
- package/dist/views/settings/AccessProtectionCard.js +1 -1
- package/dist/views/settings/AccessProtectionCard.js.map +1 -1
- package/dist/views/settings/AdminThemeCard.d.ts.map +1 -1
- package/dist/views/settings/AdminThemeCard.js +63 -15
- package/dist/views/settings/AdminThemeCard.js.map +1 -1
- package/dist/views/settings/AppearanceTab.d.ts.map +1 -1
- package/dist/views/settings/AppearanceTab.js +4 -2
- package/dist/views/settings/AppearanceTab.js.map +1 -1
- package/dist/views/settings/BrandPreviewCard.js +1 -1
- package/dist/views/settings/BrandPreviewCard.js.map +1 -1
- package/dist/views/settings/BrandVoiceCard.js +1 -1
- package/dist/views/settings/BrandVoiceCard.js.map +1 -1
- package/dist/views/settings/EmailSettingsTab.d.ts.map +1 -1
- package/dist/views/settings/EmailSettingsTab.js +5 -3
- package/dist/views/settings/EmailSettingsTab.js.map +1 -1
- package/dist/views/settings/GeneralSettingsTab.d.ts.map +1 -1
- package/dist/views/settings/GeneralSettingsTab.js +4 -2
- package/dist/views/settings/GeneralSettingsTab.js.map +1 -1
- package/dist/views/settings/ProviderConnectionCard.js +1 -1
- package/dist/views/settings/ProviderConnectionCard.js.map +1 -1
- package/dist/views/settings/SearchDataCard.js +1 -1
- package/dist/views/settings/SearchDataCard.js.map +1 -1
- package/dist/views/settings/SecurityStatusCard.js +2 -2
- package/dist/views/settings/SecurityStatusCard.js.map +1 -1
- package/dist/views/settings/SecurityTab.d.ts.map +1 -1
- package/dist/views/settings/SecurityTab.js +4 -2
- package/dist/views/settings/SecurityTab.js.map +1 -1
- package/dist/views/settings/SeoAutopilotCard.js +2 -2
- package/dist/views/settings/SeoAutopilotCard.js.map +1 -1
- package/dist/views/settings/SeoSettingsTab.d.ts.map +1 -1
- package/dist/views/settings/SeoSettingsTab.js +5 -3
- package/dist/views/settings/SeoSettingsTab.js.map +1 -1
- package/dist/views/settings/SiteIndexStatusCard.js +1 -1
- package/dist/views/settings/SiteIndexStatusCard.js.map +1 -1
- package/dist/views/settings/SiteThemeCard.js +1 -1
- package/dist/views/settings/UpdatesTab.d.ts.map +1 -1
- package/dist/views/settings/UpdatesTab.js +6 -5
- package/dist/views/settings/UpdatesTab.js.map +1 -1
- package/dist/views/settings/components.d.ts +7 -11
- package/dist/views/settings/components.d.ts.map +1 -1
- package/dist/views/settings/components.js +18 -12
- package/dist/views/settings/components.js.map +1 -1
- package/dist/views/settings/useAppearanceSettings.d.ts +7 -1
- package/dist/views/settings/useAppearanceSettings.d.ts.map +1 -1
- package/dist/views/settings/useAppearanceSettings.js +89 -22
- package/dist/views/settings/useAppearanceSettings.js.map +1 -1
- package/dist/views/users/UserDetailDrawer.js +1 -1
- package/dist/views/users/UserDetailDrawer.js.map +1 -1
- package/package.json +10 -8
- package/src/AdminRoot.tsx +88 -30
- package/src/LoginRoot.tsx +2 -1
- package/src/__tests__/components/author-avatar.test.tsx +3 -20
- package/src/__tests__/components/notification-bell.test.tsx +146 -14
- package/src/__tests__/components/responsive-tiers.render.test.tsx +461 -0
- package/src/__tests__/components/serp-preview.render.test.tsx +6 -3
- package/src/__tests__/components/shell-primitives.render.test.tsx +951 -0
- package/src/__tests__/components/theme-provider-accent.test.tsx +83 -8
- package/src/__tests__/components/toggle.test.tsx +11 -3
- package/src/__tests__/components/ui-primitives.test.tsx +5 -8
- package/src/__tests__/layout/build-nav-items.test.ts +1 -1
- package/src/__tests__/layout/shell-nav.render.test.tsx +983 -0
- package/src/__tests__/layout/sidebar-forms-submenu.render.test.tsx +8 -4
- package/src/__tests__/layout/sidebar-groups.render.test.tsx +116 -0
- package/src/__tests__/layout/sidebar-menus.render.test.tsx +116 -0
- package/src/__tests__/layout/sidebar-plugin-nav.test.ts +1 -1
- package/src/__tests__/layout/sidebar-posts.test.ts +48 -40
- package/src/__tests__/layout/sidebar-submenu.render.test.tsx +35 -10
- package/src/__tests__/layout/topbar.render.test.tsx +464 -0
- package/src/__tests__/lib/api.test.ts +2 -0
- package/src/__tests__/lib/job-drawer.test.ts +493 -0
- package/src/__tests__/lib/job-view.test.ts +437 -0
- package/src/__tests__/lib/jobs-service.test.ts +98 -0
- package/src/__tests__/router/admin-route-order.test.ts +39 -0
- package/src/__tests__/styles/ai-gradient-contrast.test.ts +135 -0
- package/src/__tests__/styles/breakpoint-order.test.ts +68 -0
- package/src/__tests__/styles/responsive-layer.test.ts +367 -12
- package/src/__tests__/styles/tailwind-preset.test.ts +154 -0
- package/src/__tests__/styles/theme-token-scope.test.ts +10 -3
- package/src/__tests__/styles/tiers.test.ts +136 -0
- package/src/__tests__/styles/tokens-drift.test.ts +67 -6
- package/src/__tests__/views/admin-lazy-route.render.test.tsx +41 -1
- package/src/__tests__/views/ai-settings.render.test.tsx +4 -4
- package/src/__tests__/views/appearance-menus-route.render.test.tsx +58 -0
- package/src/__tests__/views/appearance-settings.render.test.tsx +10 -8
- package/src/__tests__/views/appearance-theme-provider.render.test.tsx +347 -0
- package/src/__tests__/views/dashboard.test.tsx +48 -0
- package/src/__tests__/views/document-edit-navigation.render.test.tsx +130 -0
- package/src/__tests__/views/document-edit-routes.render.test.tsx +130 -0
- package/src/__tests__/views/email-settings.render.test.tsx +2 -2
- package/src/__tests__/views/form-entries.render.test.tsx +22 -0
- package/src/__tests__/views/form-submissions.render.test.tsx +22 -0
- package/src/__tests__/views/forms-list.render.test.tsx +13 -0
- package/src/__tests__/views/general-settings.render.test.tsx +38 -11
- package/src/__tests__/views/jobs-drawer.render.test.tsx +1047 -0
- package/src/__tests__/views/jobs-list.render.test.tsx +850 -0
- package/src/__tests__/views/kpi-grid.test.ts +29 -0
- package/src/__tests__/views/post-section-editor.test.tsx +5 -1
- package/src/__tests__/views/script-tags.render.test.tsx +29 -0
- package/src/__tests__/views/security-settings.render.test.tsx +9 -10
- package/src/__tests__/views/seo-settings.render.test.tsx +7 -9
- package/src/__tests__/views/settings-save-bar.render.test.tsx +91 -0
- package/src/__tests__/views/settings-save-footer.render.test.tsx +25 -15
- package/src/__tests__/views/settings-tabs.render.test.tsx +371 -0
- package/src/__tests__/views/updates-tab.render.test.tsx +4 -3
- package/src/components/Breadcrumbs.tsx +7 -1
- package/src/components/CommandPalette.tsx +51 -3
- package/src/components/LocaleSwitcher.tsx +5 -6
- package/src/components/NotificationBell.tsx +74 -18
- package/src/components/OfflineStatus.tsx +1 -1
- package/src/components/PresenceIndicator.tsx +2 -1
- package/src/components/ReflowMeter.tsx +3 -3
- package/src/components/SEOPanel.tsx +2 -2
- package/src/components/ThemeProvider.tsx +122 -42
- package/src/components/TipTapEditor.tsx +3 -4
- package/src/components/forms/EmbedPanel.tsx +1 -1
- package/src/components/forms/FormEntryDetailPane.tsx +7 -1
- package/src/components/forms/ShortcodeGlossary.tsx +7 -1
- package/src/components/forms/primitives.tsx +37 -21
- package/src/components/pages/tag-badges.tsx +4 -3
- package/src/components/seo/PageAuditPanel.tsx +2 -2
- package/src/components/seo/SerpPreview.tsx +5 -5
- package/src/components/seo/primitives.tsx +2 -2
- package/src/components/ui/AuthorAvatar.tsx +11 -10
- package/src/components/ui/Badge.tsx +1 -1
- package/src/components/ui/Breadcrumb.tsx +109 -0
- package/src/components/ui/Button.tsx +40 -15
- package/src/components/ui/Card.tsx +35 -32
- package/src/components/ui/CardListControls.tsx +90 -0
- package/src/components/ui/CountChip.tsx +90 -0
- package/src/components/ui/FilterPill.tsx +5 -3
- package/src/components/ui/InspectorPane.tsx +21 -7
- package/src/components/ui/Modal.tsx +20 -6
- package/src/components/ui/PageHeader.tsx +63 -0
- package/src/components/ui/RowActions.tsx +7 -2
- package/src/components/ui/Segmented.tsx +97 -0
- package/src/components/ui/Toaster.tsx +139 -0
- package/src/components/ui/Toggle.tsx +18 -14
- package/src/components/ui/UnderlineTabs.tsx +373 -0
- package/src/components/ui/UnsavedBar.tsx +119 -0
- package/src/components/ui/admin-links.tsx +20 -0
- package/src/components/ui/card-table.ts +34 -0
- package/src/components/virtual-window.tsx +13 -2
- package/src/fields/RelationshipField.tsx +1 -1
- package/src/hooks/useAppearanceBootstrap.ts +9 -3
- package/src/index.ts +19 -0
- package/src/layout/Header.tsx +210 -135
- package/src/layout/Layout.tsx +61 -108
- package/src/layout/MobileTabBar.tsx +92 -0
- package/src/layout/Sidebar.tsx +555 -593
- package/src/layout/nav-config.tsx +425 -0
- package/src/layout/primitives/AdminShell.tsx +154 -6
- package/src/layout/topbar-classes.ts +8 -0
- package/src/layout/use-nav-items.ts +40 -0
- package/src/lib/api.ts +7 -1
- package/src/lib/appearance-api.ts +4 -0
- package/src/lib/job-drawer.ts +424 -0
- package/src/lib/job-view.ts +456 -0
- package/src/lib/jobs-service.ts +290 -0
- package/src/lib/sheet-focus.ts +71 -0
- package/src/lib/tiers.ts +43 -0
- package/src/router/admin-routes.ts +8 -0
- package/src/router/index.ts +9 -1
- package/src/styles/prenta-tokens.css +263 -82
- package/src/styles/responsive.css +460 -22
- package/src/styles/theme.css +177 -28
- package/src/views/ApiKeys.tsx +2 -2
- package/src/views/AppearanceChrome.tsx +74 -0
- package/src/views/CollectionList.tsx +264 -205
- package/src/views/Dashboard.tsx +26 -13
- package/src/views/DocumentEdit.tsx +111 -56
- package/src/views/FormDeliveries.tsx +30 -17
- package/src/views/FormEntries.tsx +28 -20
- package/src/views/FormSubmissions.tsx +32 -34
- package/src/views/Forms.tsx +55 -34
- package/src/views/FormsSettings.tsx +94 -0
- package/src/views/Jobs/BusyButton.tsx +56 -0
- package/src/views/Jobs/CancelJobDialog.tsx +127 -0
- package/src/views/Jobs/DrawerActivity.tsx +142 -0
- package/src/views/Jobs/DrawerOverview.tsx +139 -0
- package/src/views/Jobs/DrawerResults.tsx +155 -0
- package/src/views/Jobs/DrawerReview.tsx +464 -0
- package/src/views/Jobs/JobDrawer.tsx +556 -0
- package/src/views/Jobs/JobsTable.tsx +260 -0
- package/src/views/Jobs/WorkerStrip.tsx +144 -0
- package/src/views/Jobs/atoms.tsx +93 -0
- package/src/views/Jobs/focus.ts +56 -0
- package/src/views/Jobs/icons.tsx +111 -0
- package/src/views/Jobs/placeholders.tsx +66 -0
- package/src/views/Jobs/useJobDetail.ts +64 -0
- package/src/views/Jobs/useJobsFeed.ts +249 -0
- package/src/views/Jobs/useReviewItems.ts +187 -0
- package/src/views/Jobs.tsx +282 -105
- package/src/views/MediaBrowser.tsx +71 -21
- package/src/views/Pages/PagesListView.tsx +115 -39
- package/src/views/Posts/NewPostTypeInstructions.tsx +15 -10
- package/src/views/Posts/PostTypesView.tsx +28 -6
- package/src/views/Posts/PostsListView.tsx +130 -40
- package/src/views/SEO.tsx +23 -8
- package/src/views/ScriptTags.tsx +233 -153
- package/src/views/Settings.tsx +215 -256
- package/src/views/SpamFolder.tsx +32 -0
- package/src/views/Trash.tsx +12 -1
- package/src/views/Users.tsx +2 -2
- package/src/views/WidgetsPlaceholder.tsx +33 -0
- package/src/views/dashboard/kpi-grid.ts +38 -0
- package/src/views/lazy-views.tsx +12 -0
- package/src/views/page-editor/PageTab.tsx +2 -2
- package/src/views/page-editor/ReviewBar.tsx +1 -1
- package/src/views/page-editor/VersionHistoryPanel.tsx +1 -1
- package/src/views/seo/AuditTab.tsx +1 -1
- package/src/views/seo/OverviewTab.tsx +6 -5
- package/src/views/seo/ProposalsTab.tsx +1 -1
- package/src/views/settings/AISettingsTab.tsx +10 -6
- package/src/views/settings/AccessProtectionCard.tsx +1 -1
- package/src/views/settings/AdminThemeCard.tsx +228 -35
- package/src/views/settings/AppearanceTab.tsx +10 -6
- package/src/views/settings/BrandPreviewCard.tsx +1 -1
- package/src/views/settings/BrandVoiceCard.tsx +1 -1
- package/src/views/settings/EmailSettingsTab.tsx +11 -7
- package/src/views/settings/GeneralSettingsTab.tsx +10 -6
- package/src/views/settings/ProviderConnectionCard.tsx +1 -1
- package/src/views/settings/SearchDataCard.tsx +1 -1
- package/src/views/settings/SecurityStatusCard.tsx +2 -2
- package/src/views/settings/SecurityTab.tsx +10 -6
- package/src/views/settings/SeoAutopilotCard.tsx +2 -2
- package/src/views/settings/SeoSettingsTab.tsx +13 -9
- package/src/views/settings/SiteIndexStatusCard.tsx +1 -1
- package/src/views/settings/SiteThemeCard.tsx +1 -1
- package/src/views/settings/UpdatesTab.tsx +107 -101
- package/src/views/settings/components.tsx +26 -62
- package/src/views/settings/useAppearanceSettings.ts +116 -25
- package/src/views/users/UserDetailDrawer.tsx +1 -1
- package/dist/components/ui/StatPill.d.ts +0 -18
- package/dist/components/ui/StatPill.d.ts.map +0 -1
- package/dist/components/ui/StatPill.js +0 -5
- package/dist/components/ui/StatPill.js.map +0 -1
- package/src/components/ui/StatPill.tsx +0 -37
package/src/layout/Sidebar.tsx
CHANGED
|
@@ -1,58 +1,16 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { useEffect, useRef, useState, type ReactNode } from 'react'
|
|
3
|
+
import { useEffect, useRef, useState, type CSSProperties, type ReactNode } from 'react'
|
|
4
4
|
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
|
|
5
|
+
import { accentPickCssVars, generateAdminThemeTokens } from '@prenta/core/appearance'
|
|
6
|
+
import { ChevronLeft, ChevronRight } from 'lucide-react'
|
|
5
7
|
import { adminPortalContainer } from '../lib/portal-container.js'
|
|
8
|
+
import { TIER_MEDIA } from '../lib/tiers.js'
|
|
6
9
|
import { useTheme } from '../components/ThemeProvider.js'
|
|
7
|
-
import { fetchFormsSidebarCounts, type FormsSidebarCount } from '../lib/forms-service.js'
|
|
8
|
-
import { onFormsChanged } from '../lib/forms-events.js'
|
|
9
|
-
import {
|
|
10
|
-
LayoutDashboard,
|
|
11
|
-
FileText,
|
|
12
|
-
File,
|
|
13
|
-
Image,
|
|
14
|
-
Settings,
|
|
15
|
-
ChevronLeft,
|
|
16
|
-
ChevronDown,
|
|
17
|
-
ClipboardList,
|
|
18
|
-
Inbox,
|
|
19
|
-
AlertTriangle,
|
|
20
|
-
Search as SearchIcon,
|
|
21
|
-
Briefcase,
|
|
22
|
-
FolderOpen,
|
|
23
|
-
BookOpen,
|
|
24
|
-
HelpCircle,
|
|
25
|
-
Newspaper,
|
|
26
|
-
PanelTop,
|
|
27
|
-
PanelBottom,
|
|
28
|
-
Layers,
|
|
29
|
-
Code2,
|
|
30
|
-
ListTree,
|
|
31
|
-
Menu as MenuIcon,
|
|
32
|
-
PanelLeftClose,
|
|
33
|
-
PanelLeftOpen,
|
|
34
|
-
Trash2,
|
|
35
|
-
ListTodo,
|
|
36
|
-
} from 'lucide-react'
|
|
37
|
-
import type { LucideIcon } from 'lucide-react'
|
|
38
10
|
import { PrentaBrandLogo, PrentaMark } from '../assets/prenta-logo.js'
|
|
39
11
|
import { LogoPlaceholder } from '../components/LogoPlaceholder.js'
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
file: File,
|
|
43
|
-
'file-text': FileText,
|
|
44
|
-
briefcase: Briefcase,
|
|
45
|
-
folder: FolderOpen,
|
|
46
|
-
book: BookOpen,
|
|
47
|
-
help: HelpCircle,
|
|
48
|
-
newspaper: Newspaper,
|
|
49
|
-
navigation: ListTree,
|
|
50
|
-
menu: MenuIcon,
|
|
51
|
-
'list-tree': ListTree,
|
|
52
|
-
PanelTop: PanelTop,
|
|
53
|
-
PanelBottom: PanelBottom,
|
|
54
|
-
Layers: Layers,
|
|
55
|
-
}
|
|
12
|
+
import { activeChildPath, hasActiveDescendant, isPathActive, type NavItem } from './nav-config.js'
|
|
13
|
+
import { useNavItems } from './use-nav-items.js'
|
|
56
14
|
|
|
57
15
|
/**
|
|
58
16
|
* Sidebar brand lockup.
|
|
@@ -64,7 +22,8 @@ const ICON_MAP: Record<string, LucideIcon> = {
|
|
|
64
22
|
* un-branded installs. The placeholder replaces the old initials badge, which
|
|
65
23
|
* read as a user avatar (the real account avatar already sits top-right).
|
|
66
24
|
* Dark-mode uses the dark logo when one is configured. Logos are never
|
|
67
|
-
* stretched/cropped (object-contain).
|
|
25
|
+
* stretched/cropped (object-contain). Kit sizes (cms-responsive.css): the
|
|
26
|
+
* full logo is 24px tall and at most 150px wide; the rail mark is 26px.
|
|
68
27
|
*/
|
|
69
28
|
function BrandLogo({ config, collapsed }: { config?: any; collapsed: boolean }) {
|
|
70
29
|
const { adminBrand, resolvedTheme } = useTheme()
|
|
@@ -94,26 +53,32 @@ function BrandLogo({ config, collapsed }: { config?: any; collapsed: boolean })
|
|
|
94
53
|
const cfg = usable(configLogo)
|
|
95
54
|
const alt = brandName ?? 'Admin'
|
|
96
55
|
|
|
97
|
-
// Wide horizontal logos
|
|
98
|
-
//
|
|
99
|
-
|
|
100
|
-
const logoClass = 'h-8 w-auto max-w-full object-contain'
|
|
56
|
+
// Wide horizontal logos scale down to fit the rail / sidebar header instead
|
|
57
|
+
// of overflowing: height-capped, width auto, never wider than the slot.
|
|
58
|
+
const logoClass = 'h-6 w-auto max-w-full object-contain'
|
|
101
59
|
|
|
102
60
|
if (collapsed) {
|
|
103
61
|
// Collapsed rail prefers a dedicated square mark; otherwise it shows the
|
|
104
62
|
// logo (scaled to fit) — never the favicon.
|
|
105
63
|
if (mark)
|
|
106
|
-
return
|
|
64
|
+
return (
|
|
65
|
+
<img
|
|
66
|
+
src={mark}
|
|
67
|
+
alt={alt}
|
|
68
|
+
className="h-[26px] w-[26px] object-contain"
|
|
69
|
+
onError={onErr(mark)}
|
|
70
|
+
/>
|
|
71
|
+
)
|
|
107
72
|
if (primary)
|
|
108
73
|
return <img src={primary} alt={alt} className={logoClass} onError={onErr(primary)} />
|
|
109
74
|
if (cfg) return <img src={cfg} alt={alt} className={logoClass} onError={onErr(cfg)} />
|
|
110
75
|
if (hasInitials) return <LogoPlaceholder tone="sidebar" />
|
|
111
|
-
return <PrentaMark className="h-
|
|
76
|
+
return <PrentaMark className="h-[26px] w-[26px]" />
|
|
112
77
|
}
|
|
113
78
|
|
|
114
79
|
if (primary) {
|
|
115
80
|
return (
|
|
116
|
-
<div className="flex min-w-0 items-center gap-2.5">
|
|
81
|
+
<div className="flex max-w-[150px] min-w-0 items-center gap-2.5">
|
|
117
82
|
<img src={primary} alt={alt} className={logoClass} onError={onErr(primary)} />
|
|
118
83
|
</div>
|
|
119
84
|
)
|
|
@@ -121,7 +86,7 @@ function BrandLogo({ config, collapsed }: { config?: any; collapsed: boolean })
|
|
|
121
86
|
|
|
122
87
|
if (cfg) {
|
|
123
88
|
return (
|
|
124
|
-
<div className="flex min-w-0 items-center gap-2.5">
|
|
89
|
+
<div className="flex max-w-[150px] min-w-0 items-center gap-2.5">
|
|
125
90
|
<img src={cfg} alt={alt} className={logoClass} onError={onErr(cfg)} />
|
|
126
91
|
</div>
|
|
127
92
|
)
|
|
@@ -133,12 +98,10 @@ function BrandLogo({ config, collapsed }: { config?: any; collapsed: boolean })
|
|
|
133
98
|
<img
|
|
134
99
|
src={mark}
|
|
135
100
|
alt={alt}
|
|
136
|
-
className="h-
|
|
101
|
+
className="h-[26px] w-[26px] shrink-0 object-contain"
|
|
137
102
|
onError={onErr(mark)}
|
|
138
103
|
/>
|
|
139
|
-
<span className="text-
|
|
140
|
-
{brandName ?? alt}
|
|
141
|
-
</span>
|
|
104
|
+
<span className="text-body text-txt truncate font-medium">{brandName ?? alt}</span>
|
|
142
105
|
</div>
|
|
143
106
|
)
|
|
144
107
|
}
|
|
@@ -150,445 +113,556 @@ function BrandLogo({ config, collapsed }: { config?: any; collapsed: boolean })
|
|
|
150
113
|
if (brandName) {
|
|
151
114
|
return (
|
|
152
115
|
<div className="flex min-w-0 items-center gap-2.5">
|
|
153
|
-
<PrentaMark className="h-
|
|
154
|
-
<span className="text-
|
|
116
|
+
<PrentaMark className="h-[26px] w-[26px] shrink-0" />
|
|
117
|
+
<span className="text-body text-txt truncate font-medium">{brandName}</span>
|
|
155
118
|
</div>
|
|
156
119
|
)
|
|
157
120
|
}
|
|
158
121
|
|
|
159
|
-
return <PrentaBrandLogo className="h-
|
|
122
|
+
return <PrentaBrandLogo className="h-6 w-auto" />
|
|
160
123
|
}
|
|
161
124
|
|
|
162
|
-
/**
|
|
163
|
-
* Children of the Posts parent. Same shape regardless of how many
|
|
164
|
-
* post-type collections are registered — Post Types are accessed via
|
|
165
|
-
* the `/posts/types` cards page, not as individual sidebar entries.
|
|
166
|
-
*/
|
|
167
|
-
const POSTS_CHILDREN: NavItem[] = [
|
|
168
|
-
{ path: '/posts', label: 'All Posts', icon: FileText },
|
|
169
|
-
{ path: '/posts/types', label: 'Post Types', icon: Layers },
|
|
170
|
-
]
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Nav tree for the default (config-less) install. Dashboard sits on its
|
|
174
|
-
* own at the top, then a CONTENT group holding Pages (no children) above
|
|
175
|
-
* Posts (with All Posts + Post Types), followed by Media / Forms / SEO /
|
|
176
|
-
* Script Tags, then a SETTINGS group (Users / API Keys / Settings).
|
|
177
|
-
*/
|
|
178
|
-
const defaultNavItems: NavItem[] = [
|
|
179
|
-
{ path: '/', label: 'Dashboard', icon: LayoutDashboard },
|
|
180
|
-
{ path: '/pages', label: 'Pages', icon: File, group: 'Content' },
|
|
181
|
-
{ path: '/posts', label: 'Posts', icon: FileText, group: 'Content', children: POSTS_CHILDREN },
|
|
182
|
-
{ path: '/media', label: 'Media', icon: Image, group: 'Content' },
|
|
183
|
-
{ path: '/forms', label: 'Forms', icon: ClipboardList, group: 'Content' },
|
|
184
|
-
{ path: '/seo', label: 'SEO', icon: SearchIcon, group: 'Content' },
|
|
185
|
-
{ path: '/script-tags', label: 'Script Tags', icon: Code2, group: 'Content' },
|
|
186
|
-
{ path: '/trash', label: 'Trash', icon: Trash2, group: 'Content' },
|
|
187
|
-
{ path: '/jobs', label: 'Jobs', icon: ListTodo, group: 'Settings' },
|
|
188
|
-
{ path: '/settings', label: 'Settings', icon: Settings, group: 'Settings' },
|
|
189
|
-
]
|
|
190
|
-
|
|
191
125
|
export interface SidebarProps {
|
|
192
126
|
collapsed: boolean
|
|
193
127
|
onToggleCollapse: () => void
|
|
194
128
|
currentPath: string
|
|
195
129
|
onNavigate: (path: string) => void
|
|
196
130
|
config?: any
|
|
131
|
+
/** Nav tree from `useNavItems`; built (and Forms counts fetched) here when omitted. */
|
|
132
|
+
items?: NavItem[]
|
|
197
133
|
}
|
|
198
134
|
|
|
135
|
+
/**
|
|
136
|
+
* The admin sidebar (SHELL.md §2). Expanded: section labels, rows and
|
|
137
|
+
* collapsible sub-groups. Collapsed or at ≤1024px: a 60px icon rail with no
|
|
138
|
+
* sub-items, the parent active for any child page, children in a flyout and
|
|
139
|
+
* labels kept for screen readers. The nav itself comes from `nav-config`.
|
|
140
|
+
*/
|
|
199
141
|
export function Sidebar({
|
|
200
142
|
collapsed,
|
|
201
143
|
onToggleCollapse,
|
|
202
144
|
currentPath,
|
|
203
145
|
onNavigate,
|
|
204
146
|
config,
|
|
147
|
+
items,
|
|
205
148
|
}: SidebarProps) {
|
|
206
|
-
const
|
|
207
|
-
const navItems =
|
|
208
|
-
const { density } = useTheme()
|
|
209
|
-
const compact = density === 'compact'
|
|
149
|
+
const ownItems = useNavItems(config, !items)
|
|
150
|
+
const navItems = items ?? ownItems
|
|
210
151
|
|
|
211
152
|
return (
|
|
212
153
|
<aside
|
|
213
|
-
className={`bg-
|
|
154
|
+
className={`bg-sb border-bdr flex h-full flex-col border-r transition-[width] duration-[var(--mo-slow)] ease-[cubic-bezier(.4,0,.2,1)] ${
|
|
214
155
|
collapsed ? 'w-[var(--sbw-col)]' : 'w-[var(--sbw)]'
|
|
215
156
|
}`}
|
|
216
157
|
>
|
|
217
|
-
{/* Brand header — no toggle button here anymore; it now lives in the footer. */}
|
|
218
158
|
<div
|
|
219
|
-
className={`border-
|
|
220
|
-
|
|
159
|
+
className={`border-bdr flex h-[var(--tbh)] shrink-0 items-center gap-2.5 overflow-hidden border-b ${
|
|
160
|
+
// Below 769px the drawer's Close button (36px, 44px on touch, at
|
|
161
|
+
// right 10px) sits in this row; keep a long brand name clear of it.
|
|
162
|
+
collapsed ? 'justify-center px-0' : 'mobile:pr-14 px-[14px]'
|
|
221
163
|
}`}
|
|
222
164
|
>
|
|
223
165
|
<BrandLogo config={config} collapsed={collapsed} />
|
|
224
166
|
</div>
|
|
225
167
|
|
|
168
|
+
{/* `relative`: the rail's `sr-only` labels (absolutely positioned)
|
|
169
|
+
would otherwise size the fixed admin root and scroll the shell. */}
|
|
226
170
|
<nav
|
|
227
|
-
className=
|
|
171
|
+
className="[&::-webkit-scrollbar-thumb]:rounded-pill [&::-webkit-scrollbar-thumb]:bg-bdr relative flex-1 overflow-x-hidden overflow-y-auto py-2 [&::-webkit-scrollbar]:w-[3px]"
|
|
228
172
|
aria-label="Primary"
|
|
229
173
|
>
|
|
230
|
-
{
|
|
174
|
+
<NavTree items={navItems} ctx={{ collapsed, currentPath, onNavigate }} />
|
|
231
175
|
</nav>
|
|
232
176
|
|
|
233
|
-
{/*
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
dismiss affordance in <AdminShell>. */}
|
|
237
|
-
<div className="border-sidebar-border hidden shrink-0 border-t p-3 md:block">
|
|
177
|
+
{/* Only at >=1025px: the 769-1024px rail can't expand, and the mobile
|
|
178
|
+
drawer has its own dismiss. */}
|
|
179
|
+
<div className="border-bdr not-narrow:block hidden shrink-0 border-t p-2">
|
|
238
180
|
<button
|
|
181
|
+
type="button"
|
|
239
182
|
onClick={onToggleCollapse}
|
|
240
|
-
className={`text-
|
|
183
|
+
className={`rounded-7 text-meta text-sub hover:bg-acc-l hover:text-acc flex w-full cursor-pointer items-center gap-[9px] overflow-hidden border-0 bg-transparent px-2.5 py-[7px] leading-[normal] font-normal whitespace-nowrap ${
|
|
241
184
|
collapsed ? 'justify-center' : ''
|
|
242
185
|
}`}
|
|
186
|
+
aria-expanded={!collapsed}
|
|
243
187
|
aria-label={collapsed ? 'Expand sidebar' : 'Collapse sidebar'}
|
|
244
|
-
title={collapsed ? 'Expand sidebar' : 'Collapse sidebar'}
|
|
245
188
|
>
|
|
246
|
-
{
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
<
|
|
252
|
-
|
|
253
|
-
|
|
189
|
+
{/* 19px: the kit's inline chevron sits on the 12px text baseline, so the button is 33px. */}
|
|
190
|
+
<span className="flex h-[19px] shrink-0 items-center">
|
|
191
|
+
{collapsed ? (
|
|
192
|
+
<ChevronRight className="h-[15px] w-[15px]" strokeWidth={1.75} aria-hidden />
|
|
193
|
+
) : (
|
|
194
|
+
<ChevronLeft className="h-[15px] w-[15px]" strokeWidth={1.75} aria-hidden />
|
|
195
|
+
)}
|
|
196
|
+
</span>
|
|
197
|
+
<span className={collapsed ? 'sr-only' : ''}>Collapse sidebar</span>
|
|
254
198
|
</button>
|
|
255
199
|
</div>
|
|
256
200
|
</aside>
|
|
257
201
|
)
|
|
258
202
|
}
|
|
259
203
|
|
|
260
|
-
// ───
|
|
204
|
+
// ─── Rendering ──────────────────────────────────────────────────────────────
|
|
261
205
|
|
|
262
|
-
interface
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
forms: FormsSidebarCount[]
|
|
206
|
+
interface NavRenderContext {
|
|
207
|
+
collapsed: boolean
|
|
208
|
+
currentPath: string
|
|
209
|
+
onNavigate: (path: string) => void
|
|
267
210
|
}
|
|
268
211
|
|
|
212
|
+
/** Kit `.ni`: 13px --sub, 7px 10px, margin 1px 8px, radius 7, gap 9. */
|
|
213
|
+
const ROW =
|
|
214
|
+
'rounded-7 text-body flex w-full cursor-pointer items-center gap-[9px] overflow-hidden border-0 leading-[normal] whitespace-nowrap'
|
|
215
|
+
const ROW_IDLE = 'bg-transparent font-[450] text-sub hover:bg-acc-l hover:text-acc'
|
|
216
|
+
// Forced colors drop the fill, so the current row also gets an outline there.
|
|
217
|
+
const FORCED_ACTIVE =
|
|
218
|
+
'forced-colors:outline-solid forced-colors:outline-2 forced-colors:-outline-offset-2'
|
|
219
|
+
const ROW_ACTIVE = `bg-acc-l font-[550] text-acc ${FORCED_ACTIVE}`
|
|
220
|
+
|
|
269
221
|
/**
|
|
270
|
-
*
|
|
271
|
-
*
|
|
272
|
-
*
|
|
273
|
-
*
|
|
274
|
-
*
|
|
222
|
+
* At >=1025px a parent row toggles its group (the kit's `onClickCapture`);
|
|
223
|
+
* below that, in the mobile drawer, it opens the parent page.
|
|
224
|
+
*
|
|
225
|
+
* Starts wide and corrects after mount, so a server render and the browser's
|
|
226
|
+
* first render agree (the row's `aria-expanded` depends on it).
|
|
275
227
|
*/
|
|
276
|
-
function
|
|
277
|
-
const [
|
|
278
|
-
|
|
228
|
+
function useWideNav(): boolean {
|
|
229
|
+
const [wide, setWide] = useState(true)
|
|
279
230
|
useEffect(() => {
|
|
280
|
-
|
|
281
|
-
const
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
totalUnread: counts.reduce((sum, c) => sum + (c.unread ?? 0), 0),
|
|
287
|
-
forms: counts,
|
|
288
|
-
})
|
|
289
|
-
})
|
|
290
|
-
.catch(() => {
|
|
291
|
-
/* keep the last known value; nav must never break on a fetch error */
|
|
292
|
-
})
|
|
293
|
-
}
|
|
294
|
-
load()
|
|
295
|
-
const off = onFormsChanged(load)
|
|
296
|
-
return () => {
|
|
297
|
-
active = false
|
|
298
|
-
off()
|
|
299
|
-
}
|
|
231
|
+
if (typeof window.matchMedia !== 'function') return
|
|
232
|
+
const mq = window.matchMedia(TIER_MEDIA.narrow)
|
|
233
|
+
const apply = () => setWide(!mq.matches)
|
|
234
|
+
apply()
|
|
235
|
+
mq.addEventListener?.('change', apply)
|
|
236
|
+
return () => mq.removeEventListener?.('change', apply)
|
|
300
237
|
}, [])
|
|
301
|
-
|
|
302
|
-
return state
|
|
238
|
+
return wide
|
|
303
239
|
}
|
|
304
240
|
|
|
305
|
-
/**
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
const children: NavItem[] = [
|
|
314
|
-
{ path: '/forms/entries', label: 'All Entries', icon: Inbox },
|
|
315
|
-
{ path: '/forms/deliveries', label: 'Deliveries', icon: AlertTriangle },
|
|
316
|
-
...state.forms.map((f) => ({
|
|
317
|
-
path: `/forms/${f.formId}/submissions`,
|
|
318
|
-
label: f.name,
|
|
319
|
-
icon: ClipboardList,
|
|
320
|
-
badge: f.unread,
|
|
321
|
-
})),
|
|
322
|
-
]
|
|
323
|
-
return {
|
|
324
|
-
...item,
|
|
325
|
-
badge: state.totalUnread > 0 ? state.totalUnread : undefined,
|
|
326
|
-
children,
|
|
327
|
-
}
|
|
328
|
-
})
|
|
241
|
+
/** A parent is current for any page under its path or any of its children. */
|
|
242
|
+
function isSectionActive(currentPath: string, item: NavItem): boolean {
|
|
243
|
+
const path = currentPath.split(/[?#]/)[0]!
|
|
244
|
+
return (
|
|
245
|
+
path === item.path ||
|
|
246
|
+
(item.path !== '/' && path.startsWith(item.path + '/')) ||
|
|
247
|
+
hasActiveDescendant(item.children ?? [], currentPath)
|
|
248
|
+
)
|
|
329
249
|
}
|
|
330
250
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
251
|
+
/** 16px for top-level rows; sub-items default to the kit's 14px. */
|
|
252
|
+
function NavIcon({ item, size: fallback = 16 }: { item: NavItem; size?: number }) {
|
|
253
|
+
const Icon = item.icon
|
|
254
|
+
const size = item.iconSize ?? fallback
|
|
255
|
+
return (
|
|
256
|
+
<span className="flex h-4 w-4 shrink-0 items-center justify-center" aria-hidden>
|
|
257
|
+
<Icon className="shrink-0" size={size} strokeWidth={1.75} />
|
|
258
|
+
</span>
|
|
259
|
+
)
|
|
339
260
|
}
|
|
340
261
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
}
|
|
262
|
+
/** Kit `.ni-badge`: 16px pill, --err fill, 9.5px / 700. */
|
|
263
|
+
function NavBadge({ count, className = '' }: { count?: number; className?: string }) {
|
|
264
|
+
if (!count || count <= 0) return null
|
|
265
|
+
return (
|
|
266
|
+
<span
|
|
267
|
+
className={`rounded-pill bg-err text-badge text-acc-on flex h-4 min-w-4 shrink-0 items-center justify-center px-1 leading-none font-bold tabular-nums ${className}`}
|
|
268
|
+
aria-label={`${count} unread`}
|
|
269
|
+
>
|
|
270
|
+
{count > 99 ? '99+' : count}
|
|
271
|
+
</span>
|
|
272
|
+
)
|
|
273
|
+
}
|
|
354
274
|
|
|
355
|
-
|
|
356
|
-
//
|
|
357
|
-
|
|
275
|
+
function SectionLabel({ label, collapsed }: { label: string; collapsed: boolean }) {
|
|
276
|
+
// Kit: the collapsed sidebar keeps the label's space but hides it; the
|
|
277
|
+
// 769–1024px rail drops it entirely. The mobile drawer shows it.
|
|
278
|
+
return (
|
|
279
|
+
<div
|
|
280
|
+
className={`text-label text-faint tablet:hidden overflow-hidden px-4 pt-2.5 pb-1 leading-[normal] font-semibold tracking-[0.8px] whitespace-nowrap uppercase ${
|
|
281
|
+
collapsed ? 'pointer-events-none opacity-0' : ''
|
|
282
|
+
}`}
|
|
283
|
+
>
|
|
284
|
+
{label}
|
|
285
|
+
</div>
|
|
286
|
+
)
|
|
358
287
|
}
|
|
359
288
|
|
|
360
|
-
function
|
|
289
|
+
function NavTree({ items, ctx }: { items: NavItem[]; ctx: NavRenderContext }) {
|
|
361
290
|
const nodes: ReactNode[] = []
|
|
362
291
|
let lastGroup: string | undefined = undefined
|
|
363
292
|
|
|
364
293
|
for (const item of items) {
|
|
365
294
|
if (item.group && item.group !== lastGroup) {
|
|
366
295
|
nodes.push(
|
|
367
|
-
ctx.collapsed
|
|
368
|
-
<div key={`g-${item.group}`} className="flex justify-center pt-3 pb-1">
|
|
369
|
-
<span className="border-sidebar-foreground/20 w-4 border-t" aria-hidden />
|
|
370
|
-
</div>
|
|
371
|
-
) : (
|
|
372
|
-
<div key={`g-${item.group}`} className="px-3 pt-4 pb-1">
|
|
373
|
-
<span className="text-sidebar-foreground/80 text-[10px] font-medium tracking-wider uppercase">
|
|
374
|
-
{item.group}
|
|
375
|
-
</span>
|
|
376
|
-
</div>
|
|
377
|
-
),
|
|
296
|
+
<SectionLabel key={`g-${item.group}`} label={item.group} collapsed={ctx.collapsed} />,
|
|
378
297
|
)
|
|
379
|
-
lastGroup = item.group
|
|
380
|
-
} else if (!item.group) {
|
|
381
|
-
lastGroup = undefined
|
|
382
298
|
}
|
|
299
|
+
lastGroup = item.group
|
|
383
300
|
|
|
384
301
|
const hasChildren = !!item.children && item.children.length > 0
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
if (hasChildren
|
|
389
|
-
nodes.push(<
|
|
390
|
-
|
|
302
|
+
// Keys are namespaced: a parent and its first child can share a path.
|
|
303
|
+
if (ctx.collapsed) {
|
|
304
|
+
nodes.push(<RailItem key={`rail:${item.label}:${item.path}`} item={item} ctx={ctx} />)
|
|
305
|
+
} else if (hasChildren) {
|
|
306
|
+
nodes.push(<NavGroup key={`group:${item.label}:${item.path}`} item={item} ctx={ctx} />)
|
|
307
|
+
} else {
|
|
308
|
+
nodes.push(<NavRow key={`item:${item.path}`} item={item} ctx={ctx} />)
|
|
391
309
|
}
|
|
392
|
-
|
|
393
|
-
// Expanded parents with children render as a collapsible section
|
|
394
|
-
// (chevron toggle to contract/expand the submenu).
|
|
395
|
-
if (hasChildren) {
|
|
396
|
-
nodes.push(<NavSection key={`section:${item.path}`} item={item} ctx={ctx} />)
|
|
397
|
-
continue
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
// Keys are namespaced because a parent and its first child can share
|
|
401
|
-
// the same path (e.g. the Posts parent and its "All Posts" child are
|
|
402
|
-
// both `/posts`). Using the raw path as the key collided and made
|
|
403
|
-
// React warn about — and risk dropping — duplicate siblings.
|
|
404
|
-
nodes.push(<NavLink key={`item:${item.path}`} item={item} ctx={ctx} />)
|
|
405
310
|
}
|
|
406
311
|
|
|
407
|
-
return nodes
|
|
312
|
+
return <>{nodes}</>
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function NavRow({ item, ctx }: { item: NavItem; ctx: NavRenderContext }) {
|
|
316
|
+
const active = isPathActive(ctx.currentPath, item.path)
|
|
317
|
+
return (
|
|
318
|
+
<div className="mx-2 my-px">
|
|
319
|
+
<button
|
|
320
|
+
type="button"
|
|
321
|
+
onClick={() => ctx.onNavigate(item.path)}
|
|
322
|
+
className={`${ROW} px-2.5 py-[7px] ${active ? ROW_ACTIVE : ROW_IDLE}`}
|
|
323
|
+
aria-current={active ? 'page' : undefined}
|
|
324
|
+
>
|
|
325
|
+
<NavIcon item={item} />
|
|
326
|
+
<span className="truncate">{item.label}</span>
|
|
327
|
+
<NavBadge count={item.badge} className="ml-auto" />
|
|
328
|
+
</button>
|
|
329
|
+
</div>
|
|
330
|
+
)
|
|
408
331
|
}
|
|
409
332
|
|
|
410
333
|
/**
|
|
411
|
-
* Expanded parent with a
|
|
412
|
-
*
|
|
413
|
-
*
|
|
414
|
-
* the
|
|
415
|
-
* section
|
|
334
|
+
* Expanded parent with a sub-group. Groups start closed and open on their
|
|
335
|
+
* own when the current page is one of their children; a manual toggle holds
|
|
336
|
+
* until the route changes. At ≥1025px clicking the row toggles the group, as
|
|
337
|
+
* in the kit; below that (the mobile drawer) it opens the parent page. The
|
|
338
|
+
* parent stays filled while its section holds the current page, and the
|
|
339
|
+
* current child is filled too (Inbox.html).
|
|
416
340
|
*/
|
|
417
|
-
function
|
|
418
|
-
const
|
|
419
|
-
const
|
|
420
|
-
const childActive =
|
|
421
|
-
const [open, setOpen] = useState(
|
|
422
|
-
|
|
341
|
+
function NavGroup({ item, ctx }: { item: NavItem; ctx: NavRenderContext }) {
|
|
342
|
+
const children = item.children!
|
|
343
|
+
const sectionActive = isSectionActive(ctx.currentPath, item)
|
|
344
|
+
const childActive = hasActiveDescendant(children, ctx.currentPath)
|
|
345
|
+
const [open, setOpen] = useState(childActive)
|
|
346
|
+
// Re-sync on navigation. Adjusting state during render (rather than in an
|
|
347
|
+
// effect) avoids painting the old open state for a frame. Query/hash-only
|
|
348
|
+
// changes (filters, tabs) keep the user's toggle.
|
|
349
|
+
const route = ctx.currentPath.split(/[?#]/)[0]!
|
|
350
|
+
const [syncedRoute, setSyncedRoute] = useState(route)
|
|
351
|
+
if (syncedRoute !== route) {
|
|
352
|
+
setSyncedRoute(route)
|
|
353
|
+
setOpen(childActive)
|
|
354
|
+
}
|
|
355
|
+
const listId = `nav-group-${item.label.replace(/[^a-z0-9]+/gi, '-').toLowerCase()}`
|
|
356
|
+
const current = activeChildPath(children, ctx.currentPath)
|
|
423
357
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
358
|
+
const wide = useWideNav()
|
|
359
|
+
const onRowClick = () => {
|
|
360
|
+
if (wide) setOpen((value) => !value)
|
|
361
|
+
else ctx.onNavigate(item.path)
|
|
362
|
+
}
|
|
427
363
|
|
|
364
|
+
// Kit `.ni` with `.ni-tog`: the 22px toggle sits inside the 7px padding, so
|
|
365
|
+
// parent rows are 36px tall; the toggle ends 10px from the row's edge.
|
|
428
366
|
return (
|
|
429
367
|
<div>
|
|
430
368
|
<div
|
|
431
|
-
className={`flex items-center
|
|
432
|
-
|
|
433
|
-
?
|
|
434
|
-
: 'text-
|
|
369
|
+
className={`rounded-7 mx-2 my-px flex h-9 items-center pr-2.5 ${
|
|
370
|
+
sectionActive
|
|
371
|
+
? `bg-acc-l text-acc ${FORCED_ACTIVE}`
|
|
372
|
+
: 'text-sub hover:bg-acc-l hover:text-acc'
|
|
435
373
|
}`}
|
|
436
374
|
>
|
|
437
375
|
<button
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
376
|
+
type="button"
|
|
377
|
+
onClick={onRowClick}
|
|
378
|
+
className={`${ROW} h-full min-w-0 flex-1 bg-transparent py-0 pl-2.5 text-inherit ${
|
|
379
|
+
sectionActive ? 'font-[550]' : 'font-[450]'
|
|
380
|
+
}`}
|
|
381
|
+
aria-current={sectionActive && !open ? 'page' : undefined}
|
|
382
|
+
// When the row itself toggles, it reports the group like the chevron.
|
|
383
|
+
aria-expanded={wide ? open : undefined}
|
|
384
|
+
aria-controls={wide ? listId : undefined}
|
|
441
385
|
>
|
|
442
|
-
<
|
|
443
|
-
<span className="truncate
|
|
386
|
+
<NavIcon item={item} />
|
|
387
|
+
<span className="truncate">{item.label}</span>
|
|
388
|
+
<NavBadge count={item.badge} className="ml-1.5" />
|
|
444
389
|
</button>
|
|
445
|
-
{
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
aria-label={`${item.badge} unread`}
|
|
449
|
-
>
|
|
450
|
-
{item.badge > 99 ? '99+' : item.badge}
|
|
451
|
-
</span>
|
|
452
|
-
)}
|
|
390
|
+
{/* 24px (kit 22) for WCAG 2.5.8. On touch screens the hit area is
|
|
391
|
+
44px wide and exactly the 36px row tall, so it doesn't reach into
|
|
392
|
+
the rows above and below. */}
|
|
453
393
|
<button
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
aria-
|
|
458
|
-
aria-
|
|
394
|
+
type="button"
|
|
395
|
+
onClick={() => setOpen((value) => !value)}
|
|
396
|
+
className="prenta-touch-hit rounded-5 text-faint hover:text-txt relative ml-auto flex h-6 w-6 shrink-0 cursor-pointer items-center justify-center border-0 bg-transparent p-0 [--touch-hit-block:-6px] [--touch-hit-inline:-10px] hover:bg-[color-mix(in_srgb,var(--txt)_8%,transparent)]"
|
|
397
|
+
aria-label={`${open ? 'Collapse' : 'Expand'} ${item.label}`}
|
|
398
|
+
aria-expanded={open}
|
|
399
|
+
aria-controls={listId}
|
|
459
400
|
>
|
|
460
|
-
<
|
|
461
|
-
className={`h-
|
|
401
|
+
<ChevronRight
|
|
402
|
+
className={`h-3 w-3 motion-safe:transition-transform motion-safe:duration-[var(--mo-base)] ${open ? 'rotate-90' : ''}`}
|
|
403
|
+
strokeWidth={2.2}
|
|
462
404
|
aria-hidden
|
|
463
405
|
/>
|
|
464
406
|
</button>
|
|
465
407
|
</div>
|
|
466
408
|
|
|
467
|
-
{
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
409
|
+
{open && (
|
|
410
|
+
// Kit `.sub-group`: margin 2px 8px 4px 18px, a 1.5px guide on the left
|
|
411
|
+
// and a 13px connector into each row.
|
|
412
|
+
<div
|
|
413
|
+
id={listId}
|
|
414
|
+
className="before:rounded-pill before:bg-bdr relative mt-0.5 mr-2 mb-1 ml-[18px] before:absolute before:top-1 before:bottom-[18px] before:left-0 before:w-[1.5px]"
|
|
415
|
+
>
|
|
416
|
+
{children.map((child) =>
|
|
417
|
+
child.children?.length ? (
|
|
418
|
+
<NestedSubGroup
|
|
419
|
+
key={`nest:${child.label}:${child.path}`}
|
|
420
|
+
item={child}
|
|
421
|
+
ctx={ctx}
|
|
422
|
+
current={current}
|
|
423
|
+
/>
|
|
424
|
+
) : (
|
|
425
|
+
<SubRow
|
|
426
|
+
key={`child:${child.label}:${child.path}`}
|
|
427
|
+
item={child}
|
|
428
|
+
ctx={ctx}
|
|
429
|
+
active={child.path === current}
|
|
430
|
+
/>
|
|
431
|
+
),
|
|
432
|
+
)}
|
|
481
433
|
</div>
|
|
482
434
|
)}
|
|
483
435
|
</div>
|
|
484
436
|
)
|
|
485
437
|
}
|
|
486
438
|
|
|
487
|
-
|
|
439
|
+
/** Kit `.sub-group .ni`: 12.5px, inset 20px past the guide, with a connector. */
|
|
440
|
+
function SubRow({
|
|
488
441
|
item,
|
|
489
442
|
ctx,
|
|
490
|
-
|
|
491
|
-
|
|
443
|
+
active,
|
|
444
|
+
kid = false,
|
|
445
|
+
className = '',
|
|
492
446
|
}: {
|
|
493
447
|
item: NavItem
|
|
494
448
|
ctx: NavRenderContext
|
|
495
|
-
|
|
496
|
-
/**
|
|
497
|
-
|
|
449
|
+
active: boolean
|
|
450
|
+
/** Third-level row (`a[data-nest-k]`): inset 30px, icon at half opacity. */
|
|
451
|
+
kid?: boolean
|
|
452
|
+
className?: string
|
|
498
453
|
}) {
|
|
499
|
-
|
|
500
|
-
const isActive = isPathActive(ctx.currentPath, item.path)
|
|
501
|
-
|
|
502
|
-
const badge = item.badge && item.badge > 0 ? item.badge : 0
|
|
503
|
-
|
|
504
|
-
const button = (
|
|
454
|
+
return (
|
|
505
455
|
<button
|
|
456
|
+
type="button"
|
|
506
457
|
onClick={() => ctx.onNavigate(item.path)}
|
|
507
|
-
className={
|
|
508
|
-
|
|
509
|
-
? ctx.compact
|
|
510
|
-
? 'py-1 pr-3 pl-9'
|
|
511
|
-
: 'py-1.5 pr-3 pl-9'
|
|
512
|
-
: ctx.compact
|
|
513
|
-
? 'px-3 py-1.5'
|
|
514
|
-
: 'px-3 py-2.5'
|
|
458
|
+
className={`${ROW} text-sm2 relative my-px py-[7px] pr-2.5 before:absolute before:top-1/2 before:left-0 before:h-[1.5px] before:w-[13px] before:-translate-y-1/2 ${
|
|
459
|
+
kid ? 'pl-[30px]' : 'pl-5'
|
|
515
460
|
} ${
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
} ${ctx.collapsed ? 'justify-center px-3' : ''}`}
|
|
520
|
-
title={ctx.collapsed ? item.label : ''}
|
|
521
|
-
aria-current={isActive ? 'page' : undefined}
|
|
461
|
+
active ? `${ROW_ACTIVE} before:bg-acc-m` : `${ROW_IDLE} before:bg-bdr hover:before:bg-acc-m`
|
|
462
|
+
} ${className}`}
|
|
463
|
+
aria-current={active ? 'page' : undefined}
|
|
522
464
|
>
|
|
523
|
-
<span className=
|
|
524
|
-
|
|
525
|
-
{nested && !ctx.collapsed && (
|
|
526
|
-
<Icon className="text-sidebar-foreground/60 h-4 w-4 shrink-0" aria-hidden />
|
|
527
|
-
)}
|
|
528
|
-
{/* Collapsed rail: a dot signals unread without room for a number. */}
|
|
529
|
-
{ctx.collapsed && badge > 0 && (
|
|
530
|
-
<span
|
|
531
|
-
className="bg-destructive absolute -top-0.5 -right-0.5 h-2 w-2 rounded-full"
|
|
532
|
-
aria-hidden
|
|
533
|
-
/>
|
|
534
|
-
)}
|
|
465
|
+
<span className={kid ? 'flex opacity-50' : 'flex'}>
|
|
466
|
+
<NavIcon item={item} size={14} />
|
|
535
467
|
</span>
|
|
536
|
-
{
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
468
|
+
<span className="truncate">{item.label}</span>
|
|
469
|
+
<NavBadge count={item.badge} className="ml-auto" />
|
|
470
|
+
</button>
|
|
471
|
+
)
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Third level (`cms-layouts.js` "3rd-level nav nesting"): All Entries and All
|
|
476
|
+
* Forms open one row per form. The row itself navigates; the chevron at its
|
|
477
|
+
* right edge toggles the rows, which start closed and open when one of them
|
|
478
|
+
* is the current page.
|
|
479
|
+
*/
|
|
480
|
+
function NestedSubGroup({
|
|
481
|
+
item,
|
|
482
|
+
ctx,
|
|
483
|
+
current,
|
|
484
|
+
}: {
|
|
485
|
+
item: NavItem
|
|
486
|
+
ctx: NavRenderContext
|
|
487
|
+
current: string | undefined
|
|
488
|
+
}) {
|
|
489
|
+
const kids = item.children!
|
|
490
|
+
const kidActive = kids.some((kid) => kid.path === current)
|
|
491
|
+
const [open, setOpen] = useState(kidActive)
|
|
492
|
+
const route = ctx.currentPath.split(/[?#]/)[0]!
|
|
493
|
+
const [syncedRoute, setSyncedRoute] = useState(route)
|
|
494
|
+
if (syncedRoute !== route) {
|
|
495
|
+
setSyncedRoute(route)
|
|
496
|
+
setOpen(kidActive)
|
|
497
|
+
}
|
|
498
|
+
const listId = `nav-nest-${item.label.replace(/[^a-z0-9]+/gi, '-').toLowerCase()}`
|
|
499
|
+
|
|
500
|
+
return (
|
|
501
|
+
<>
|
|
502
|
+
<div className="relative">
|
|
503
|
+
<SubRow item={item} ctx={ctx} active={item.path === current} className="pr-[30px]!" />
|
|
504
|
+
<button
|
|
505
|
+
type="button"
|
|
506
|
+
onClick={() => setOpen((value) => !value)}
|
|
507
|
+
// Touch hit area: 44px wide, the 30px row tall.
|
|
508
|
+
className={`prenta-touch-hit rounded-5 absolute top-1/2 right-1 flex h-6 w-6 -translate-y-1/2 cursor-pointer items-center justify-center border-0 bg-transparent p-0 [--touch-hit-block:-3px] [--touch-hit-inline:-10px] ${
|
|
509
|
+
item.path === current ? 'text-acc' : 'text-sub'
|
|
510
|
+
}`}
|
|
511
|
+
aria-label={`${open ? 'Collapse' : 'Expand'} ${item.label}`}
|
|
512
|
+
aria-expanded={open}
|
|
513
|
+
aria-controls={listId}
|
|
545
514
|
>
|
|
546
|
-
|
|
547
|
-
|
|
515
|
+
<ChevronRight
|
|
516
|
+
className={`h-[14px] w-[14px] opacity-55 motion-safe:transition-transform motion-safe:duration-[var(--mo-base)] ${open ? 'rotate-90' : ''}`}
|
|
517
|
+
strokeWidth={2.4}
|
|
518
|
+
aria-hidden
|
|
519
|
+
/>
|
|
520
|
+
</button>
|
|
521
|
+
</div>
|
|
522
|
+
{open && (
|
|
523
|
+
<div id={listId}>
|
|
524
|
+
{kids.map((kid) => (
|
|
525
|
+
<SubRow
|
|
526
|
+
key={`kid:${kid.label}:${kid.path}`}
|
|
527
|
+
item={kid}
|
|
528
|
+
ctx={ctx}
|
|
529
|
+
active={kid.path === current}
|
|
530
|
+
kid
|
|
531
|
+
/>
|
|
532
|
+
))}
|
|
533
|
+
</div>
|
|
548
534
|
)}
|
|
535
|
+
</>
|
|
536
|
+
)
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* Rail item (collapsed sidebar or ≤1024px). The label stays in the
|
|
541
|
+
* accessibility tree (`sr-only`). Parents never render sub-items here: the
|
|
542
|
+
* parent is active for any child page and the children open in a flyout.
|
|
543
|
+
*/
|
|
544
|
+
function RailItem({ item, ctx }: { item: NavItem; ctx: NavRenderContext }) {
|
|
545
|
+
const hasChildren = !!item.children && item.children.length > 0
|
|
546
|
+
const active = hasChildren
|
|
547
|
+
? isSectionActive(ctx.currentPath, item)
|
|
548
|
+
: isPathActive(ctx.currentPath, item.path)
|
|
549
|
+
|
|
550
|
+
const trigger = (
|
|
551
|
+
<button
|
|
552
|
+
type="button"
|
|
553
|
+
onClick={() => ctx.onNavigate(item.path)}
|
|
554
|
+
className={`${ROW} justify-center px-0 py-[9px] ${active ? ROW_ACTIVE : ROW_IDLE}`}
|
|
555
|
+
aria-current={active ? 'page' : undefined}
|
|
556
|
+
>
|
|
557
|
+
<NavIcon item={item} />
|
|
558
|
+
<span className="sr-only">{item.label}</span>
|
|
559
|
+
<NavBadge count={item.badge} />
|
|
549
560
|
</button>
|
|
550
561
|
)
|
|
551
562
|
|
|
552
|
-
if (
|
|
563
|
+
if (hasChildren) return <RailFlyout item={item} ctx={ctx} trigger={trigger} />
|
|
564
|
+
return <RailLabel label={item.label}>{trigger}</RailLabel>
|
|
565
|
+
}
|
|
553
566
|
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
567
|
+
/** Kit `.flyout-panel` chrome, shared by the label tip and the menu. */
|
|
568
|
+
const FLYOUT_PANEL =
|
|
569
|
+
'bg-card border-bdr rounded-12 shadow-shd2 min-w-[200px] overflow-hidden border pb-2 motion-safe:animate-in motion-safe:fade-in'
|
|
570
|
+
const FLYOUT_LABEL =
|
|
571
|
+
'text-body text-txt flex items-center gap-2 px-[14px] py-2.5 leading-[normal] font-medium'
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* Hover/focus label for a childless rail item (the kit's label-only flyout).
|
|
575
|
+
* Visual only: the button already carries its label for screen readers.
|
|
576
|
+
* WCAG 1.4.13: Esc dismisses it, the pointer can move onto it, and it shows
|
|
577
|
+
* for mouse and keyboard only, so a tap on a touch tablet doesn't leave it
|
|
578
|
+
* stuck after navigating.
|
|
579
|
+
*/
|
|
580
|
+
function RailLabel({ label, children }: { label: string; children: ReactNode }) {
|
|
581
|
+
const [top, setTop] = useState<number | null>(null)
|
|
582
|
+
const hideTimer = useRef<ReturnType<typeof setTimeout> | null>(null)
|
|
583
|
+
const cancelHide = () => {
|
|
584
|
+
if (hideTimer.current) clearTimeout(hideTimer.current)
|
|
585
|
+
hideTimer.current = null
|
|
586
|
+
}
|
|
587
|
+
const show = (el: HTMLElement) => {
|
|
588
|
+
cancelHide()
|
|
589
|
+
setTop(el.getBoundingClientRect().top)
|
|
590
|
+
}
|
|
591
|
+
const hide = () => {
|
|
592
|
+
cancelHide()
|
|
593
|
+
setTop(null)
|
|
594
|
+
}
|
|
595
|
+
// Crossing the 6px gap to the label briefly leaves the trigger.
|
|
596
|
+
const scheduleHide = () => {
|
|
597
|
+
cancelHide()
|
|
598
|
+
hideTimer.current = setTimeout(() => setTop(null), 120)
|
|
599
|
+
}
|
|
600
|
+
const visible = top !== null
|
|
601
|
+
useEffect(() => {
|
|
602
|
+
if (!visible) return
|
|
603
|
+
const onKey = (e: KeyboardEvent) => {
|
|
604
|
+
if (e.key === 'Escape') setTop(null)
|
|
605
|
+
}
|
|
606
|
+
document.addEventListener('keydown', onKey)
|
|
607
|
+
return () => document.removeEventListener('keydown', onKey)
|
|
608
|
+
}, [visible])
|
|
609
|
+
useEffect(() => () => cancelHide(), [])
|
|
559
610
|
return (
|
|
560
|
-
<div
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
{
|
|
566
|
-
|
|
611
|
+
<div
|
|
612
|
+
className="mx-1.5 my-px"
|
|
613
|
+
onPointerEnter={(e) => {
|
|
614
|
+
if (e.pointerType === 'mouse') show(e.currentTarget)
|
|
615
|
+
}}
|
|
616
|
+
onPointerLeave={scheduleHide}
|
|
617
|
+
onFocus={(e) => {
|
|
618
|
+
if (e.currentTarget.querySelector(':focus-visible')) show(e.currentTarget)
|
|
619
|
+
}}
|
|
620
|
+
onBlur={hide}
|
|
621
|
+
onClick={hide}
|
|
622
|
+
>
|
|
623
|
+
{children}
|
|
624
|
+
{visible && (
|
|
625
|
+
<div
|
|
567
626
|
aria-hidden
|
|
568
|
-
className=
|
|
569
|
-
|
|
627
|
+
className={`${FLYOUT_PANEL} fixed left-[var(--sbw-col)] z-[var(--z-menu)] min-w-0 pb-0`}
|
|
628
|
+
style={{ top }}
|
|
629
|
+
onPointerEnter={cancelHide}
|
|
630
|
+
onPointerLeave={scheduleHide}
|
|
631
|
+
>
|
|
632
|
+
<div className={FLYOUT_LABEL}>{label}</div>
|
|
633
|
+
</div>
|
|
570
634
|
)}
|
|
571
|
-
{button}
|
|
572
635
|
</div>
|
|
573
636
|
)
|
|
574
637
|
}
|
|
575
638
|
|
|
576
639
|
/**
|
|
577
|
-
*
|
|
578
|
-
*
|
|
579
|
-
*
|
|
580
|
-
*
|
|
581
|
-
*
|
|
582
|
-
* so hovering doesn't steal focus, while keyboard opening still moves focus
|
|
583
|
-
* into the menu.
|
|
640
|
+
* Rail parent with a flyout listing its children. Built on Radix
|
|
641
|
+
* DropdownMenu so the panel is portaled (escaping the nav's `overflow` clip),
|
|
642
|
+
* dismissable and keyboard-navigable. Hover opens it; Radix's auto-focus is
|
|
643
|
+
* suppressed for pointer opens so hovering doesn't steal focus, while
|
|
644
|
+
* keyboard opens still move focus into the menu.
|
|
584
645
|
*/
|
|
585
|
-
function
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
646
|
+
function RailFlyout({
|
|
647
|
+
item,
|
|
648
|
+
ctx,
|
|
649
|
+
trigger,
|
|
650
|
+
}: {
|
|
651
|
+
item: NavItem
|
|
652
|
+
ctx: NavRenderContext
|
|
653
|
+
trigger: ReactNode
|
|
654
|
+
}) {
|
|
655
|
+
const children = item.children!
|
|
656
|
+
const { resolvedTheme, lightBase, accent } = useTheme()
|
|
589
657
|
const [open, setOpen] = useState(false)
|
|
590
658
|
const openedByPointer = useRef(false)
|
|
659
|
+
const focusEntered = useRef(false)
|
|
660
|
+
const closingByHover = useRef(false)
|
|
661
|
+
const pointerSelect = useRef(false)
|
|
662
|
+
const wrapRef = useRef<HTMLDivElement>(null)
|
|
663
|
+
const contentRef = useRef<HTMLDivElement>(null)
|
|
591
664
|
const closeTimer = useRef<ReturnType<typeof setTimeout> | null>(null)
|
|
665
|
+
const current = activeChildPath(children, ctx.currentPath)
|
|
592
666
|
|
|
593
667
|
const cancelClose = () => {
|
|
594
668
|
if (closeTimer.current) {
|
|
@@ -598,98 +672,148 @@ function CollapsedFlyout({ item, ctx }: { item: NavItem; ctx: NavRenderContext }
|
|
|
598
672
|
}
|
|
599
673
|
const scheduleClose = () => {
|
|
600
674
|
cancelClose()
|
|
601
|
-
closeTimer.current = setTimeout(() =>
|
|
675
|
+
closeTimer.current = setTimeout(() => {
|
|
676
|
+
closingByHover.current = true
|
|
677
|
+
setOpen(false)
|
|
678
|
+
}, 120)
|
|
679
|
+
}
|
|
680
|
+
// Only a hover-opened menu closes when the pointer leaves; a keyboard user
|
|
681
|
+
// inside a menu keeps it while the mouse happens to cross the rail.
|
|
682
|
+
const hoverLeave = () => {
|
|
683
|
+
if (openedByPointer.current) scheduleClose()
|
|
684
|
+
}
|
|
685
|
+
const onOpenChange = (next: boolean) => {
|
|
686
|
+
if (next) closingByHover.current = false
|
|
687
|
+
setOpen(next)
|
|
602
688
|
}
|
|
603
689
|
|
|
690
|
+
// `openedByPointer` records how the menu last opened, set by the opening
|
|
691
|
+
// input itself: a mouse hover on a closed menu sets it, while a key or a
|
|
692
|
+
// touch/pen press on the trigger clears it before Radix toggles. Hovering an
|
|
693
|
+
// already open menu leaves it alone, and so do close paths, so the close
|
|
694
|
+
// focus handling below still knows how the closing menu was opened. (Esc
|
|
695
|
+
// needs no listener here: Radix dismisses the top layer on Esc wherever
|
|
696
|
+
// focus is.)
|
|
604
697
|
return (
|
|
605
|
-
<DropdownMenu.Root
|
|
606
|
-
open={open}
|
|
607
|
-
onOpenChange={(next) => {
|
|
608
|
-
if (!next) openedByPointer.current = false
|
|
609
|
-
setOpen(next)
|
|
610
|
-
}}
|
|
611
|
-
modal={false}
|
|
612
|
-
>
|
|
698
|
+
<DropdownMenu.Root open={open} onOpenChange={onOpenChange} modal={false}>
|
|
613
699
|
<div
|
|
700
|
+
ref={wrapRef}
|
|
701
|
+
className="mx-1.5 my-px"
|
|
614
702
|
onMouseEnter={() => {
|
|
615
|
-
|
|
703
|
+
if (!open) {
|
|
704
|
+
openedByPointer.current = true
|
|
705
|
+
closingByHover.current = false
|
|
706
|
+
}
|
|
616
707
|
cancelClose()
|
|
617
708
|
setOpen(true)
|
|
618
709
|
}}
|
|
619
|
-
onMouseLeave={
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
710
|
+
onMouseLeave={hoverLeave}
|
|
711
|
+
onKeyDownCapture={(e) => {
|
|
712
|
+
openedByPointer.current = false
|
|
713
|
+
// The mouse already opened the menu while focus sat on the trigger:
|
|
714
|
+
// Radix would treat these keys as a no-op (ArrowDown) or a toggle
|
|
715
|
+
// closed (Enter/Space), so take the keyboard user into the menu.
|
|
716
|
+
if (open && (e.key === 'ArrowDown' || e.key === 'Enter' || e.key === ' ')) {
|
|
717
|
+
const first = contentRef.current?.querySelector<HTMLElement>(
|
|
718
|
+
'[role="menuitem"]:not([data-disabled])',
|
|
719
|
+
)
|
|
720
|
+
if (first) {
|
|
721
|
+
e.preventDefault()
|
|
722
|
+
e.stopPropagation()
|
|
723
|
+
first.focus()
|
|
631
724
|
}
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
className="bg-destructive absolute -top-0.5 -right-0.5 h-2 w-2 rounded-full"
|
|
640
|
-
aria-hidden
|
|
641
|
-
/>
|
|
642
|
-
)}
|
|
643
|
-
</span>
|
|
644
|
-
</button>
|
|
645
|
-
</DropdownMenu.Trigger>
|
|
725
|
+
}
|
|
726
|
+
}}
|
|
727
|
+
onPointerDownCapture={(e) => {
|
|
728
|
+
if (e.pointerType !== 'mouse') openedByPointer.current = false
|
|
729
|
+
}}
|
|
730
|
+
>
|
|
731
|
+
<DropdownMenu.Trigger asChild>{trigger}</DropdownMenu.Trigger>
|
|
646
732
|
</div>
|
|
647
733
|
|
|
648
734
|
<DropdownMenu.Portal container={adminPortalContainer()}>
|
|
649
735
|
<DropdownMenu.Content
|
|
736
|
+
ref={contentRef}
|
|
650
737
|
side="right"
|
|
651
738
|
align="start"
|
|
652
|
-
|
|
739
|
+
// The trigger ends 6px inside the 60px rail; the kit panel starts at 60px.
|
|
740
|
+
sideOffset={6}
|
|
653
741
|
onMouseEnter={cancelClose}
|
|
654
|
-
onMouseLeave={
|
|
742
|
+
onMouseLeave={hoverLeave}
|
|
743
|
+
// A hover open must not pull focus out of what the user is typing
|
|
744
|
+
// in (typeahead would then eat their keys). Keyboard opens focus
|
|
745
|
+
// the first item as usual. DropdownMenu forwards Menu's
|
|
746
|
+
// `onOpenAutoFocus` but leaves it out of its types; the shell-nav
|
|
747
|
+
// render test pins the behaviour across Radix upgrades.
|
|
748
|
+
{...({
|
|
749
|
+
onOpenAutoFocus: (e: Event) => {
|
|
750
|
+
focusEntered.current = false
|
|
751
|
+
pointerSelect.current = false
|
|
752
|
+
if (openedByPointer.current) e.preventDefault()
|
|
753
|
+
},
|
|
754
|
+
} as object)}
|
|
755
|
+
onFocusCapture={() => {
|
|
756
|
+
focusEntered.current = true
|
|
757
|
+
}}
|
|
655
758
|
onCloseAutoFocus={(e) => {
|
|
656
|
-
// After a pointer-driven open
|
|
657
|
-
//
|
|
658
|
-
//
|
|
659
|
-
|
|
759
|
+
// After a pointer-driven open that never took focus, leave focus
|
|
760
|
+
// where the user had it (e.g. a field they're typing in) instead
|
|
761
|
+
// of yanking it to the rail trigger. Once focus has been inside
|
|
762
|
+
// the menu (keyboard open, or Radix focusing items under the
|
|
763
|
+
// pointer), Esc or picking an item returns it to the trigger;
|
|
764
|
+
// otherwise it would fall to <body> as the focused item unmounts.
|
|
765
|
+
// A close because the pointer left never moves focus: focusing
|
|
766
|
+
// this trigger would dismiss the next flyout the pointer opened.
|
|
767
|
+
if (openedByPointer.current && (!focusEntered.current || closingByHover.current)) {
|
|
768
|
+
e.preventDefault()
|
|
769
|
+
} else if (pointerSelect.current) {
|
|
770
|
+
// A mouse pick still returns focus to the trigger, but without
|
|
771
|
+
// the keyboard focus ring (Radix's item focus under the pointer
|
|
772
|
+
// would otherwise make Chromium show it).
|
|
773
|
+
e.preventDefault()
|
|
774
|
+
wrapRef.current
|
|
775
|
+
?.querySelector<HTMLElement>('button')
|
|
776
|
+
?.focus({ preventScroll: true, focusVisible: false } as FocusOptions)
|
|
777
|
+
}
|
|
660
778
|
}}
|
|
661
|
-
// Radix portals
|
|
662
|
-
//
|
|
663
|
-
//
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
779
|
+
// Radix portals outside the `.prenta-admin` scope where the theme
|
|
780
|
+
// tokens live; re-assert the scope, dark state, light base and
|
|
781
|
+
// accent on the panel so it paints with the admin's tokens.
|
|
782
|
+
data-light={lightBase}
|
|
783
|
+
style={
|
|
784
|
+
accentPickCssVars(
|
|
785
|
+
generateAdminThemeTokens({ adminAccentColor: accent }, resolvedTheme),
|
|
786
|
+
) as CSSProperties
|
|
787
|
+
}
|
|
788
|
+
className={`prenta-admin ${resolvedTheme === 'dark' ? 'dark' : ''} ${FLYOUT_PANEL} z-[var(--z-menu)]`}
|
|
667
789
|
>
|
|
668
|
-
<div className=
|
|
790
|
+
<div className={`${FLYOUT_LABEL} border-bdr mb-1 border-b pb-2`}>
|
|
791
|
+
<span className="text-acc flex">
|
|
792
|
+
<NavIcon item={item} />
|
|
793
|
+
</span>
|
|
669
794
|
{item.label}
|
|
795
|
+
<NavBadge count={item.badge} />
|
|
670
796
|
</div>
|
|
671
|
-
{
|
|
672
|
-
const
|
|
673
|
-
const childActive = isPathActive(ctx.currentPath, child.path)
|
|
797
|
+
{children.map((child) => {
|
|
798
|
+
const active = child.path === current
|
|
674
799
|
return (
|
|
675
800
|
<DropdownMenu.Item
|
|
676
|
-
key={`flyout
|
|
801
|
+
key={`flyout:${child.label}:${child.path}`}
|
|
802
|
+
// Radix's Enter/Space select clicks the item with detail 0.
|
|
803
|
+
onClick={(e) => {
|
|
804
|
+
pointerSelect.current = e.detail !== 0
|
|
805
|
+
}}
|
|
677
806
|
onSelect={() => ctx.onNavigate(child.path)}
|
|
678
|
-
className={`data-highlighted:bg-
|
|
679
|
-
|
|
807
|
+
className={`text-body rounded-6 data-highlighted:bg-acc-l data-highlighted:text-acc mx-1 my-px flex cursor-pointer items-center gap-2 px-[14px] py-1.5 leading-[normal] outline-none select-none ${
|
|
808
|
+
active ? `bg-acc-l text-acc font-[550] ${FORCED_ACTIVE}` : 'text-sub'
|
|
680
809
|
}`}
|
|
681
|
-
aria-current={
|
|
810
|
+
aria-current={active ? 'page' : undefined}
|
|
682
811
|
>
|
|
683
|
-
<
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
aria-label={`${child.badge} unread`}
|
|
689
|
-
>
|
|
690
|
-
{child.badge > 99 ? '99+' : child.badge}
|
|
691
|
-
</span>
|
|
692
|
-
)}
|
|
812
|
+
<span className="text-faint flex">
|
|
813
|
+
<NavIcon item={child} size={14} />
|
|
814
|
+
</span>
|
|
815
|
+
<span className="flex-1 truncate">{child.label}</span>
|
|
816
|
+
<NavBadge count={child.badge} />
|
|
693
817
|
</DropdownMenu.Item>
|
|
694
818
|
)
|
|
695
819
|
})}
|
|
@@ -698,165 +822,3 @@ function CollapsedFlyout({ item, ctx }: { item: NavItem; ctx: NavRenderContext }
|
|
|
698
822
|
</DropdownMenu.Root>
|
|
699
823
|
)
|
|
700
824
|
}
|
|
701
|
-
|
|
702
|
-
// ─── Config-driven nav builder ──────────────────────────────────────────────
|
|
703
|
-
|
|
704
|
-
function resolveIcon(collection: any): LucideIcon {
|
|
705
|
-
const mapped = collection.admin?.icon ? ICON_MAP[collection.admin.icon] : undefined
|
|
706
|
-
if (mapped) return mapped
|
|
707
|
-
return collection.type === 'page' ? File : FileText
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
export interface NavItem {
|
|
711
|
-
path: string
|
|
712
|
-
label: string
|
|
713
|
-
icon: LucideIcon
|
|
714
|
-
/**
|
|
715
|
-
* Section header. Items sharing the same `group` are grouped together
|
|
716
|
-
* under a header rendered on first encounter. Items without a `group`
|
|
717
|
-
* render at the top of the nav in document order.
|
|
718
|
-
*/
|
|
719
|
-
group?: string
|
|
720
|
-
/** Indented children rendered immediately after this item. */
|
|
721
|
-
children?: NavItem[]
|
|
722
|
-
/**
|
|
723
|
-
* Optional unread/notification count. Rendered as a red pill (expanded)
|
|
724
|
-
* or a dot (collapsed). Falsy / zero values render nothing.
|
|
725
|
-
*/
|
|
726
|
-
badge?: number
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
/**
|
|
730
|
-
* Build the sidebar nav tree from the CMS config. If no collections are
|
|
731
|
-
* configured, returns the curated `defaultNavItems` tree. When
|
|
732
|
-
* collections ARE configured, page-type collections render as Content
|
|
733
|
-
* entries, and every static destination (Media, Forms, SEO, Script Tags,
|
|
734
|
-
* Users, API Keys, Settings) is anchored under the right group.
|
|
735
|
-
*/
|
|
736
|
-
export function buildNavItems(config: any): NavItem[] {
|
|
737
|
-
if (!config?.collections) return defaultNavItems
|
|
738
|
-
|
|
739
|
-
const raw = config.collections
|
|
740
|
-
const collectionsList: any[] = Array.isArray(raw) ? raw : Object.values(raw)
|
|
741
|
-
const visible = collectionsList.filter((c) => !c.admin?.hidden)
|
|
742
|
-
|
|
743
|
-
// Treat `admin.group: 'Posts'` (case-insensitive) as a synonym for
|
|
744
|
-
// `type: 'post'` so consumers can tag arbitrary collections into
|
|
745
|
-
// the Posts area without changing the discriminator they already
|
|
746
|
-
// use elsewhere (sitemaps, schema.org, etc.).
|
|
747
|
-
const isPostsGroup = (c: any) =>
|
|
748
|
-
typeof c.admin?.group === 'string' && c.admin.group.toLowerCase() === 'posts'
|
|
749
|
-
const pages = visible.filter((c) => c.type === 'page' && !c.admin?.group)
|
|
750
|
-
const posts = visible.filter((c) => c.type === 'post' || isPostsGroup(c))
|
|
751
|
-
const otherUngrouped = visible.filter(
|
|
752
|
-
(c) => c.type !== 'page' && c.type !== 'post' && !c.admin?.group,
|
|
753
|
-
)
|
|
754
|
-
// Anything explicitly grouped that isn't a posts collection.
|
|
755
|
-
const grouped = visible.filter((c) => c.admin?.group && !isPostsGroup(c) && c.type !== 'post')
|
|
756
|
-
|
|
757
|
-
const items: NavItem[] = [{ path: '/', label: 'Dashboard', icon: LayoutDashboard }]
|
|
758
|
-
|
|
759
|
-
// Ungrouped collections that are neither page- nor post-type stay at
|
|
760
|
-
// the top level, directly under Dashboard, before the CONTENT group.
|
|
761
|
-
for (const collection of otherUngrouped) {
|
|
762
|
-
items.push({
|
|
763
|
-
label: collection.labels?.plural ?? collection.slug,
|
|
764
|
-
path: `/${collection.slug}`,
|
|
765
|
-
icon: resolveIcon(collection),
|
|
766
|
-
})
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
// CONTENT group. Everything below shares `group: 'Content'`, so the
|
|
770
|
-
// renderer emits the "CONTENT" header once — above Pages. Order:
|
|
771
|
-
// Pages → Posts → static content destinations (Media / Forms / SEO /
|
|
772
|
-
// Script Tags).
|
|
773
|
-
|
|
774
|
-
// Pages-style collections render as plain Content entries (no nested
|
|
775
|
-
// children).
|
|
776
|
-
pages.forEach((collection) => {
|
|
777
|
-
items.push({
|
|
778
|
-
label: collection.labels?.plural ?? collection.slug,
|
|
779
|
-
path: `/${collection.slug}`,
|
|
780
|
-
icon: resolveIcon(collection),
|
|
781
|
-
group: 'Content',
|
|
782
|
-
})
|
|
783
|
-
})
|
|
784
|
-
|
|
785
|
-
// Posts area — collapses every post-type collection under a single
|
|
786
|
-
// "Posts" parent. The aggregated table at /posts and the type cards
|
|
787
|
-
// at /posts/types are the entry points; individual post-type
|
|
788
|
-
// collections are not enumerated in the sidebar to avoid the
|
|
789
|
-
// duplicate-nav problem when a project has many types.
|
|
790
|
-
if (posts.length > 0) {
|
|
791
|
-
items.push({
|
|
792
|
-
path: '/posts',
|
|
793
|
-
label: 'Posts',
|
|
794
|
-
icon: FileText,
|
|
795
|
-
group: 'Content',
|
|
796
|
-
children: POSTS_CHILDREN,
|
|
797
|
-
})
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
// Standard Content destinations (always present — they map to CMS
|
|
801
|
-
// features, not collections).
|
|
802
|
-
items.push(
|
|
803
|
-
{ path: '/media', label: 'Media', icon: Image, group: 'Content' },
|
|
804
|
-
{ path: '/forms', label: 'Forms', icon: ClipboardList, group: 'Content' },
|
|
805
|
-
{ path: '/seo', label: 'SEO', icon: SearchIcon, group: 'Content' },
|
|
806
|
-
{ path: '/script-tags', label: 'Script Tags', icon: Code2, group: 'Content' },
|
|
807
|
-
{ path: '/trash', label: 'Trash', icon: Trash2, group: 'Content' },
|
|
808
|
-
)
|
|
809
|
-
|
|
810
|
-
// Plugin-contributed custom nav links (PluginDefinition.adminNavItems,
|
|
811
|
-
// collected onto `_pluginNavItems` by cms-core's applyPlugins). Deduped by
|
|
812
|
-
// path against everything already present so a plugin can't double a built-in
|
|
813
|
-
// destination or one of its own collections. Emitted HERE — inside the Content
|
|
814
|
-
// block, before the custom collection groups below — so their `group: 'Content'`
|
|
815
|
-
// stays contiguous and doesn't start a second "CONTENT" header after the groups.
|
|
816
|
-
const pluginNav: Array<{ label?: string; icon?: string; path?: string }> = Array.isArray(
|
|
817
|
-
config?._pluginNavItems,
|
|
818
|
-
)
|
|
819
|
-
? config._pluginNavItems
|
|
820
|
-
: []
|
|
821
|
-
for (const item of pluginNav) {
|
|
822
|
-
if (!item?.path || items.some((n) => n.path === item.path)) continue
|
|
823
|
-
items.push({
|
|
824
|
-
label: item.label ?? item.path,
|
|
825
|
-
path: item.path,
|
|
826
|
-
icon: (item.icon && ICON_MAP[item.icon]) || FileText,
|
|
827
|
-
group: 'Content',
|
|
828
|
-
})
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
// Config-supplied custom groups land after the standard Content block and
|
|
832
|
-
// before Settings. Each renders under its own header. Skip any collection
|
|
833
|
-
// whose path a built-in Content destination (or an already-added item) already
|
|
834
|
-
// provides — otherwise a grouped collection like plugin-forms' `forms`
|
|
835
|
-
// (admin.group: 'Forms', path `/forms`) duplicates the static Forms entry, and
|
|
836
|
-
// `applyFormsNav` then attaches "All Entries" to BOTH.
|
|
837
|
-
const groupBuckets = new Map<string, NavItem[]>()
|
|
838
|
-
for (const col of grouped) {
|
|
839
|
-
const path = `/${col.slug}`
|
|
840
|
-
if (items.some((n) => n.path === path)) continue
|
|
841
|
-
const group = col.admin.group as string
|
|
842
|
-
const bucket = groupBuckets.get(group) ?? []
|
|
843
|
-
bucket.push({
|
|
844
|
-
label: col.labels?.plural ?? col.slug,
|
|
845
|
-
path,
|
|
846
|
-
icon: resolveIcon(col),
|
|
847
|
-
group,
|
|
848
|
-
})
|
|
849
|
-
groupBuckets.set(group, bucket)
|
|
850
|
-
}
|
|
851
|
-
for (const bucket of groupBuckets.values()) items.push(...bucket)
|
|
852
|
-
|
|
853
|
-
// Settings destinations — always last. Users + API Keys now live inside
|
|
854
|
-
// the Settings view as tabs (see Settings.tsx), so only Settings is anchored
|
|
855
|
-
// here.
|
|
856
|
-
items.push(
|
|
857
|
-
{ path: '/jobs', label: 'Jobs', icon: ListTodo, group: 'Settings' },
|
|
858
|
-
{ path: '/settings', label: 'Settings', icon: Settings, group: 'Settings' },
|
|
859
|
-
)
|
|
860
|
-
|
|
861
|
-
return items
|
|
862
|
-
}
|