mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-08 09:54:26 +00:00
feat(ui): add Classic, Smart, and Compact desktop navigation styles (#1642)
* feat(ui): add Classic, Smart, and Compact desktop navigation styles Introduce a shared app-nav registry and reachable model so TopBar, the command palette, and mobile menus share one destination source. Smart bar is the default; Appearance gains a Navigation subsection with quick links. * fix(e2e): stop clearing top-nav prefs on every reload The desktop-navigation suite used addInitScript to wipe mode storage, which re-ran on reload and undid the classic/compact values under test. * fix(ui): harden nav PR docs and Compact quick-link coverage Drop the partial docs-refresh import that left missing image assets and a stale Display screenshot, keep navigation-scoped operator docs against main, and add an E2E path that proves Compact quick-link add, persist, and render after reload. * fix(ui): polish Compact quick links and menu mastheads Align Smart/Compact menus with Theme chrome, keep pin labels always visible, and drive add capacity from persisted pins (max five) with a trailing + picker and per-pin remove. * test(e2e): exact-match Compact Networking pin locator Avoid Playwright strict-mode clash with Actions for Networking. * fix(ui): simplify Compact quick link removal and fix + button trailing Remove the (...) dropdown per quick link in Compact mode. Right-click context menu remains as the sole on-bar removal affordance. Fix the + add-button to trail quick links rather than pinning to the far right by removing flex-1 from the quick-link rail.
This commit is contained in:
@@ -18,6 +18,7 @@ import type { ActiveView } from './hooks/useViewNavigationState';
|
||||
import type { StackUpdateInfo } from '@/types/imageUpdates';
|
||||
import type { SecurityTab, FleetTab } from '@/lib/events';
|
||||
import { isStackEditorDeepLink } from '@/lib/router/readUrlRouteState';
|
||||
import type { NavDestination } from '@/lib/navigation/appNavRegistry';
|
||||
|
||||
// Paid-tier views are loaded on demand. Their internal PaidGate /
|
||||
// CapabilityGate wrappers render
|
||||
@@ -109,6 +110,7 @@ export interface ViewRouterProps {
|
||||
stackUpdates: Record<string, StackUpdateInfo>;
|
||||
urlHydratingStack: string | null;
|
||||
isFileLoading: boolean;
|
||||
quickLinkCandidates?: NavDestination[];
|
||||
}
|
||||
|
||||
export function ViewRouter({
|
||||
@@ -142,6 +144,7 @@ export function ViewRouter({
|
||||
stackUpdates,
|
||||
urlHydratingStack,
|
||||
isFileLoading,
|
||||
quickLinkCandidates,
|
||||
}: ViewRouterProps): ReactNode {
|
||||
const { can } = useAuth();
|
||||
const { experimental, experimentalReady } = useExperimental();
|
||||
@@ -153,6 +156,7 @@ export function ViewRouter({
|
||||
muteRulePrefill={muteRulePrefill}
|
||||
onMutePrefillConsumed={onMutePrefillConsumed}
|
||||
onOpenMuteRulesWithPrefill={onOpenMuteRulesWithPrefill}
|
||||
quickLinkCandidates={quickLinkCandidates}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
import { useState, useEffect, useMemo, useCallback } from 'react';
|
||||
import {
|
||||
Terminal, CloudDownload, Home, HardDrive, ScrollText,
|
||||
Activity, Radar, RefreshCw, Clock, ShieldCheck, Network,
|
||||
} from 'lucide-react';
|
||||
import type { LucideIcon } from 'lucide-react';
|
||||
import { useAuth } from '@/context/AuthContext';
|
||||
import { useLicense } from '@/context/LicenseContext';
|
||||
import { useNodes } from '@/context/NodeContext';
|
||||
@@ -18,20 +13,18 @@ import { HUB_ONLY_VIEWS } from '@/lib/router/routeTypes';
|
||||
import { readUrlRouteState } from '@/lib/router/readUrlRouteState';
|
||||
import {
|
||||
authzReady,
|
||||
isViewHidden,
|
||||
normalizeHiddenView,
|
||||
type ReachabilityContext,
|
||||
} from '@/lib/routing/reachability';
|
||||
import { useExperimental } from '@/hooks/useExperimental';
|
||||
import { buildNavigationModel } from '@/lib/navigation/buildNavigationModel';
|
||||
import type { NavDestination } from '@/lib/navigation/appNavRegistry';
|
||||
|
||||
export type { ActiveView };
|
||||
export { HUB_ONLY_VIEWS };
|
||||
|
||||
export interface NavItem {
|
||||
value: ActiveView;
|
||||
label: string;
|
||||
icon: LucideIcon;
|
||||
}
|
||||
/** @deprecated Prefer NavDestination from appNavRegistry; alias kept for mobile/palette imports. */
|
||||
export type NavItem = NavDestination;
|
||||
|
||||
interface UseViewNavigationStateOptions {
|
||||
onNavigateToDashboard?: () => void;
|
||||
@@ -124,39 +117,8 @@ export function useViewNavigationState(options?: UseViewNavigationStateOptions)
|
||||
return () => window.removeEventListener(SENCHO_NAVIGATE_EVENT, handler);
|
||||
}, []);
|
||||
|
||||
const navItems = useMemo((): NavItem[] => {
|
||||
const items: NavItem[] = [
|
||||
{ value: 'dashboard', label: 'Home', icon: Home },
|
||||
];
|
||||
if (!isViewHidden('fleet', reachCtx)) {
|
||||
items.push({ value: 'fleet', label: 'Fleet', icon: Radar });
|
||||
}
|
||||
items.push(
|
||||
{ value: 'resources', label: 'Resources', icon: HardDrive },
|
||||
{ value: 'networking', label: 'Networking', icon: Network },
|
||||
{ value: 'security', label: 'Security', icon: ShieldCheck },
|
||||
{ value: 'templates', label: 'App Store', icon: CloudDownload },
|
||||
);
|
||||
if (!isViewHidden('global-observability', reachCtx)) {
|
||||
items.push({ value: 'global-observability', label: 'Logs', icon: Activity });
|
||||
}
|
||||
if (!isViewHidden('auto-updates', reachCtx)) {
|
||||
items.push({ value: 'auto-updates', label: 'Update', icon: RefreshCw });
|
||||
}
|
||||
if (!isViewHidden('scheduled-ops', reachCtx)) {
|
||||
items.push({ value: 'scheduled-ops', label: 'Schedules', icon: Clock });
|
||||
}
|
||||
// Visual discovery fail-closed: omit Console until /meta settles and the
|
||||
// flag is on. URL normalization still waits on experimentalReady inside
|
||||
// isViewHidden so enabled deep links are not rewritten during cold load.
|
||||
if (experimentalReady && experimental && !isViewHidden('host-console', reachCtx)) {
|
||||
items.push({ value: 'host-console', label: 'Console', icon: Terminal });
|
||||
}
|
||||
if (!isViewHidden('audit-log', reachCtx)) {
|
||||
items.push({ value: 'audit-log', label: 'Audit', icon: ScrollText });
|
||||
}
|
||||
return items;
|
||||
}, [reachCtx, experimentalReady, experimental]);
|
||||
const navModel = useMemo(() => buildNavigationModel(reachCtx), [reachCtx]);
|
||||
const navItems = navModel.allPageItems;
|
||||
|
||||
useEffect(() => {
|
||||
if (!authzReady(reachCtx)) return;
|
||||
@@ -183,6 +145,7 @@ export function useViewNavigationState(options?: UseViewNavigationStateOptions)
|
||||
openMuteRulesWithPrefill,
|
||||
handleNavigate,
|
||||
navItems,
|
||||
navModel,
|
||||
reachCtx,
|
||||
} as const;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user