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:
Anso
2026-07-16 21:48:03 -04:00
committed by GitHub
parent d8e4ede94f
commit 25586fc8ab
24 changed files with 1871 additions and 216 deletions
+27 -1
View File
@@ -1,4 +1,4 @@
import { lazy, Suspense, useCallback, useEffect, useRef, useState, type ReactNode } from 'react';
import { lazy, Suspense, useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from 'react';
import { Button } from './ui/button';
import { Plus, Loader2, ChevronLeft, AlertCircle, RefreshCw } from 'lucide-react';
import { UserProfileDropdown } from './UserProfileDropdown';
@@ -44,6 +44,9 @@ import type { SidebarActivityAction } from '@/components/sidebar/SidebarActivity
import { useComposeDiffPreviewEnabled } from '@/hooks/use-compose-diff-preview-enabled';
import { useTopNavLabels } from '@/hooks/use-top-nav-labels';
import { useTopNavAlign } from '@/hooks/use-top-nav-align';
import { useTopNavMode } from '@/hooks/use-top-nav-mode';
import { useTopNavQuickLinks } from '@/hooks/use-top-nav-quick-links';
import { getAppNavItem } from '@/lib/navigation/appNavRegistry';
import { useStackMuteActions } from '@/hooks/useMuteRuleActions';
import { toast } from '@/components/ui/toast-store';
import { useIsMobile } from '@/hooks/use-is-mobile';
@@ -195,6 +198,8 @@ export default function EditorLayout() {
const [diffPreviewEnabled] = useComposeDiffPreviewEnabled();
const [topNavLabels] = useTopNavLabels();
const [topNavAlign] = useTopNavAlign();
const [topNavMode] = useTopNavMode();
const { persistedIds: quickLinkIds, addQuickLink, removeQuickLink } = useTopNavQuickLinks();
// Use a ref to break the circular dependency:
// useViewNavigationState needs onNavigateToDashboard -> resetEditorState
@@ -220,10 +225,22 @@ export default function EditorLayout() {
handleMutePrefillConsumed,
handleNavigate,
navItems,
navModel,
openMuteRulesWithPrefill,
reachCtx,
} = navState;
const visibleQuickLinks = useMemo(() => {
const candidateSet = new Set(navModel.quickLinkCandidates.map((item) => item.value));
return quickLinkIds
.filter((id) => candidateSet.has(id))
.map((id) => {
const item = getAppNavItem(id);
return item ? { value: item.value, label: item.label, icon: item.icon } : null;
})
.filter((item): item is NonNullable<typeof item> => item !== null);
}, [quickLinkIds, navModel.quickLinkCandidates]);
const {
notifications,
tickerConnected,
@@ -908,6 +925,13 @@ export default function EditorLayout() {
userMenu={userMenuEl}
showLabels={topNavLabels}
navAlign={topNavAlign}
navMode={topNavMode}
navModel={navModel}
quickLinks={visibleQuickLinks}
persistedQuickLinkIds={quickLinkIds}
onAddQuickLink={(value) => addQuickLink(value as typeof quickLinkIds[number])}
onRemoveQuickLink={(value) => removeQuickLink(value as typeof quickLinkIds[number])}
onOpenSettings={() => openSettings()}
/>
);
@@ -962,6 +986,7 @@ export default function EditorLayout() {
stackUpdates={stackUpdates}
urlHydratingStack={urlHydratingStack}
isFileLoading={isFileLoading}
quickLinkCandidates={navModel.quickLinkCandidates}
/>
</div>
);
@@ -1033,6 +1058,7 @@ export default function EditorLayout() {
headerActions={mobileMastheadActions}
selectedSection={mobileSettingsSection}
onSelectedSectionChange={setMobileSettingsSection}
quickLinkCandidates={navModel.quickLinkCandidates}
/>
);
case 'security':