From 43aab55bfbdeb8d86d86a10125c73d82ca0f5279 Mon Sep 17 00:00:00 2001 From: Guilhem Lemouel Date: Fri, 9 Oct 2026 07:45:07 -0700 Subject: [PATCH] feat: add a navigation picker after the breadcrumb's last segment Co-Authored-By: Claude Opus 5.5 (1M context) --- .../lib/components/BreadcrumbNavPicker.svelte | 98 +++++++++++++++++++ .../src/lib/components/NavBreadcrumb.svelte | 82 +++++++++------- .../src/lib/components/PageHeaderBar.svelte | 5 + 3 files changed, 152 insertions(+), 33 deletions(-) create mode 100644 frontend/src/lib/components/BreadcrumbNavPicker.svelte diff --git a/frontend/src/lib/components/BreadcrumbNavPicker.svelte b/frontend/src/lib/components/BreadcrumbNavPicker.svelte new file mode 100644 index 0000000000..c12ee93a3b --- /dev/null +++ b/frontend/src/lib/components/BreadcrumbNavPicker.svelte @@ -0,0 +1,98 @@ + + + + + {#snippet trigger()} + + {/snippet} + {#snippet content()} + + {/snippet} + diff --git a/frontend/src/lib/components/NavBreadcrumb.svelte b/frontend/src/lib/components/NavBreadcrumb.svelte index 889fe17377..38614f220f 100644 --- a/frontend/src/lib/components/NavBreadcrumb.svelte +++ b/frontend/src/lib/components/NavBreadcrumb.svelte @@ -18,7 +18,7 @@ not this component) — so the lead can run the whole line. import MeltButton from '$lib/components/meltComponents/MeltButton.svelte' import BreadcrumbItemContent from './BreadcrumbItemContent.svelte' import WorkspacePickerBody from '$lib/components/sidebar/WorkspacePickerBody.svelte' - import BreadcrumbSegment from '$lib/components/BreadcrumbSegment.svelte' + import BreadcrumbNavPicker from './BreadcrumbNavPicker.svelte' import { userWorkspaces, workspaceStore, workspaceColor, superadmin } from '$lib/stores' import { findWorkspaceRoot } from '$lib/utils/workspaceHierarchy' import { useForkableWorkspaces } from '$lib/utils/useForkableWorkspaces.svelte' @@ -31,7 +31,7 @@ not this component) — so the lead can run the whole line. import { page } from '$app/state' import { navPageFor } from './sidebar/navPages' import { copyToClipboard, getContrastTextColor } from '$lib/utils' - import { KIND_LABEL_LOWER, kindKey } from '$lib/components/workspacePicker' + import { KIND_LABEL_LOWER } from '$lib/components/workspacePicker' import type { PageHeaderItem, PageHeaderSection } from './pageHeaderRegistry.svelte' import type { Snippet } from 'svelte' @@ -236,6 +236,14 @@ not this component) — so the lead can run the whole line. {/if} {/snippet} +{#snippet navPicker()} + + {#if !nameOnly} + + {/if} +{/snippet} + {#snippet slash(extra = '')} @@ -307,10 +315,10 @@ not this component) — so the lead can run the whole line. {/snippet} {#snippet children({ item: menuItem })} workspaceMenu?.close()} - /> + item={menuItem} + collapseFamilies={actingWorkspaceId != undefined} + closeMenu={() => workspaceMenu?.close()} + /> {/snippet} {/snippet} @@ -361,6 +369,7 @@ not this component) — so the lead can run the whole line. {@render pathLabel()} {/if} + {#if itemIsLast}{@render navPicker()}{/if} {#if afterName}{@render afterName()}{/if} {#if pathCopied} {/if} {:else if section?.kind} - {}} - /> +
+ + {@render sectionKindIcon()} + {KIND_LABEL_LOWER[section.kind]} + + {@render navPicker()} +
{:else if section} - - {#if section.content}{@render section.content()}{:else}{section.label}{/if} - +
+ + {#if section.content}{@render section.content()}{:else}{section.label}{/if} + + {@render navPicker()} +
{#if afterName}{@render afterName()}{/if} {:else if routePage} {@const RouteIcon = routePage.icon} - - {#if RouteIcon}{/if} - {routePage.label} - +
+ + {#if RouteIcon}{/if} + {routePage.label} + + {@render navPicker()} +
{#if afterName}{@render afterName()}{/if} {/if} diff --git a/frontend/src/lib/components/PageHeaderBar.svelte b/frontend/src/lib/components/PageHeaderBar.svelte index 7a97d60f2e..16a16dbf4a 100644 --- a/frontend/src/lib/components/PageHeaderBar.svelte +++ b/frontend/src/lib/components/PageHeaderBar.svelte @@ -12,6 +12,7 @@ The row's height matches the sidebar's own header row, so the two read as one ba import { navDetached } from './sidebar/navDetached.svelte' import { navHandleSlot } from './sidebar/navHandlePlacement.svelte' import NavBreadcrumb from './NavBreadcrumb.svelte' + import BreadcrumbNavPicker from './BreadcrumbNavPicker.svelte' import { BAR_HEIGHT, pageHeader, PHONE_BAR } from './pageHeaderRegistry.svelte' import ContextBridge from './ContextBridge.svelte' import { afterNavigate } from '$app/navigation' @@ -179,6 +180,10 @@ The row's height matches the sidebar's own header row, so the two read as one ba {:else} {item.summary} {/if} + + {#if !navHidden} + + {/if} {/if}