Skip to content

Commit cc549df

Browse files
committed
refactor(frontend): split breadcrumb path between header and sidebar
On desktop (lg+), render the breadcrumb in the header above the title as before. On mobile/tablet (below lg), render it in the sidebar drawer below the logo so the header stays compact (title only, no path). - Header: gate breadcrumb on lg+, allow title to wrap (no truncation) - Sidebar: gate breadcrumb on lg:hidden, auto-expand the active section's parent group so the highlighted nav item is always visible - Breadcrumbs: wrap naturally, full text, no truncation or ellipsis
1 parent 8fe6f7c commit cc549df

3 files changed

Lines changed: 51 additions & 13 deletions

File tree

frontend/src/components/layout/Header.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -175,12 +175,14 @@ function Header() {
175175
)}
176176
<div className="flex flex-col min-w-0">
177177
{pageHeaderConfig?.breadcrumbs && (
178-
<Breadcrumbs
179-
items={pageHeaderConfig.breadcrumbs}
180-
currentLabel={pageHeaderConfig.title}
181-
/>
178+
<div className="hidden lg:block">
179+
<Breadcrumbs
180+
items={pageHeaderConfig.breadcrumbs}
181+
currentLabel={pageHeaderConfig.title}
182+
/>
183+
</div>
182184
)}
183-
<h1 className="text-base md:text-lg font-black text-brand-navy dark:text-dark-text tracking-tight leading-tight sm:leading-none sm:truncate break-words">
185+
<h1 className="text-sm sm:text-base md:text-lg font-black text-brand-navy dark:text-dark-text tracking-tight leading-tight sm:leading-none break-words">
184186
{getPageTitle()}
185187
</h1>
186188
{pageHeaderConfig?.subtitle && (

frontend/src/components/layout/Sidebar.tsx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { useIsTablet } from '../../hooks/useMediaQuery';
2020
import { useState, useMemo, useRef, Fragment, useEffect } from 'react';
2121
import AppVersion from '../ui/AppVersion';
2222
import CreateMenu from '../ui/CreateMenu';
23+
import { Breadcrumbs } from '../ui/Breadcrumbs';
2324

2425
interface SubItem {
2526
path: string;
@@ -56,6 +57,7 @@ function Sidebar() {
5657
const { currentPatient } = usePatientStore();
5758
const user = useAuthStore(state => state.user);
5859
const theme = useSettingsStore(state => state.theme);
60+
const pageHeaderConfig = useUIStore(state => state.pageHeaderConfig);
5961
const [expandedItems, setExpandedItems] = useState<string[]>(['/patient-record']);
6062
const [hoveredMenu, setHoveredMenu] = useState<{ path: string; rect: DOMRect, items?: SubItem[], labelKey: string } | null>(null);
6163
const [isHovered, setIsHovered] = useState(false);
@@ -200,6 +202,21 @@ function Sidebar() {
200202
);
201203
};
202204

205+
// Auto-expand the parent group of the active sub-item so the highlighted
206+
// navigation entry is always visible without manual toggling.
207+
useEffect(() => {
208+
setExpandedItems(prev => {
209+
const next = [...prev];
210+
for (const item of menuItems) {
211+
if (item.subItems && item.subItems.some(isSubActive) && !next.includes(item.path)) {
212+
next.push(item.path);
213+
}
214+
}
215+
return next;
216+
});
217+
// eslint-disable-next-line react-hooks/exhaustive-deps
218+
}, [location.pathname, location.search, menuItems]);
219+
203220
const filteredMenuItems = useMemo(() => {
204221
return menuItems.filter(item => {
205222
if (item.roles && user && !item.roles.includes(user.role)) return false;
@@ -257,6 +274,18 @@ function Sidebar() {
257274
</Link>
258275
</div>
259276

277+
{/* Current page breadcrumb path — mobile/tablet only. On desktop (lg+)
278+
the path renders in the header bar instead. Shown only when the
279+
sidebar drawer is open; hidden in icon-only collapsed mode. */}
280+
{!effectiveCollapsed && pageHeaderConfig?.breadcrumbs && pageHeaderConfig.breadcrumbs.length > 0 && (
281+
<div className="lg:hidden px-6 pb-3 -mt-1 mb-1 border-b border-gray-50 dark:border-white/5">
282+
<Breadcrumbs
283+
items={pageHeaderConfig.breadcrumbs}
284+
currentLabel={pageHeaderConfig.title}
285+
/>
286+
</div>
287+
)}
288+
260289
{/* Invisible hover bridge — keeps the handle visible when the cursor is near the sidebar edge */}
261290
<div className="hidden lg:block absolute inset-y-0 left-full w-5 z-[955]" aria-hidden="true" />
262291

frontend/src/components/ui/Breadcrumbs.tsx

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,27 +19,34 @@ export const Breadcrumbs: React.FC<BreadcrumbsProps> = ({ items = [], currentLab
1919
if (items.length === 0 && !currentLabel) return null;
2020

2121
return (
22-
<nav className={clsx("flex flex-wrap items-center gap-x-1.5 gap-y-0.5 mb-1", className)} aria-label="Breadcrumb">
23-
<Link
24-
to="/"
25-
className="text-gray-400 hover:text-blue-500 transition-colors p-0.5 shrink-0"
22+
<nav
23+
className={clsx(
24+
"flex flex-wrap items-center gap-x-1.5 gap-y-0.5 leading-tight",
25+
className
26+
)}
27+
aria-label="Breadcrumb"
28+
>
29+
<Link
30+
to="/"
31+
className="text-gray-400 hover:text-blue-500 dark:text-dark-muted dark:hover:text-blue-400 transition-colors p-0.5 shrink-0"
2632
title="Home"
33+
aria-label="Home"
2734
>
2835
<Home className="w-3 h-3" />
2936
</Link>
30-
37+
3138
{items.map((item, index) => (
3239
<React.Fragment key={index}>
3340
<ChevronRight className="w-2.5 h-2.5 text-gray-300 dark:text-dark-muted shrink-0" />
3441
{item.path ? (
3542
<Link
3643
to={item.path}
37-
className="text-[10px] font-bold text-gray-400 hover:text-blue-500 transition-colors uppercase tracking-wider whitespace-nowrap"
44+
className="text-[10px] font-bold text-gray-400 hover:text-blue-500 dark:text-dark-muted dark:hover:text-blue-400 transition-colors uppercase tracking-wider"
3845
>
3946
{item.label}
4047
</Link>
4148
) : (
42-
<span className="text-[10px] font-bold text-gray-400 uppercase tracking-wider whitespace-nowrap">
49+
<span className="text-[10px] font-bold text-gray-400 dark:text-dark-muted uppercase tracking-wider">
4350
{item.label}
4451
</span>
4552
)}
@@ -49,7 +56,7 @@ export const Breadcrumbs: React.FC<BreadcrumbsProps> = ({ items = [], currentLab
4956
{currentLabel && (
5057
<>
5158
<ChevronRight className="w-2.5 h-2.5 text-gray-300 dark:text-dark-muted shrink-0" />
52-
<span className="text-[10px] font-black text-brand-navy dark:text-dark-text uppercase tracking-wider min-w-0 break-words">
59+
<span className="text-[10px] font-black text-brand-navy dark:text-dark-text uppercase tracking-wider">
5360
{currentLabel}
5461
</span>
5562
</>

0 commit comments

Comments
 (0)