Task 6: Rebuild PMRInterface layout + Breadcrumb

Changes made:
- Created Breadcrumb.tsx component with Patient Record > [View] > [Expanded Item] navigation
- Integrated Breadcrumb into PMRInterface (desktop/tablet only, not mobile)
- Breadcrumb receives currentView, expandedItem props and handles navigation callbacks
- Updated all font references from font-inter to font-ui (Elvaro Grotesque)
- Added shadow-pmr to default view placeholder card
- Mobile back button updated to use font-ui

Visual verification:
- Breadcrumb renders correctly with gray-400 text, chevron separators, 13px font size
- Navigation updates breadcrumb path correctly (tested Summary → Experience)
- Layout: fixed sidebar, sticky banner, scrollable content all working
- View switching is instant (no animation between views)
- Premium font (Elvaro Grotesque) rendering throughout interface

Quality checks: All passed (typecheck, lint, build — 396.39 KB bundle)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-13 00:39:41 +00:00
parent 803c4f8a48
commit 8f6bfd0b5e
2 changed files with 117 additions and 5 deletions
+96
View File
@@ -0,0 +1,96 @@
import { ChevronRight } from 'lucide-react'
import type { ViewId } from '../types/pmr'
interface BreadcrumbProps {
currentView: ViewId
expandedItem?: {
name: string
type: string
}
onNavigateToView?: (view: ViewId) => void
onCollapseItem?: () => void
}
const viewLabels: Record<ViewId, string> = {
summary: 'Summary',
consultations: 'Experience',
medications: 'Skills',
problems: 'Achievements',
investigations: 'Projects',
documents: 'Education',
referrals: 'Contact',
}
export function Breadcrumb({
currentView,
expandedItem,
onNavigateToView,
onCollapseItem,
}: BreadcrumbProps) {
const handleNavigateToPatientRecord = () => {
if (onNavigateToView) {
onNavigateToView('summary')
}
}
const handleNavigateToCurrentView = () => {
if (onCollapseItem) {
onCollapseItem()
}
}
return (
<nav
className="flex items-center gap-2 mb-6"
aria-label="Breadcrumb"
>
<ol className="flex items-center gap-2">
{/* Patient Record (root) */}
<li>
<button
type="button"
onClick={handleNavigateToPatientRecord}
className="text-[13px] font-ui font-normal text-gray-400 hover:text-pmr-nhsblue transition-colors"
>
Patient Record
</button>
</li>
<li>
<ChevronRight size={14} className="text-gray-300" />
</li>
{/* Current view */}
<li>
{expandedItem ? (
<button
type="button"
onClick={handleNavigateToCurrentView}
className="text-[13px] font-ui font-normal text-gray-400 hover:text-pmr-nhsblue transition-colors"
>
{viewLabels[currentView]}
</button>
) : (
<span className="text-[13px] font-ui font-normal text-gray-600">
{viewLabels[currentView]}
</span>
)}
</li>
{/* Expanded item (if any) */}
{expandedItem && (
<>
<li>
<ChevronRight size={14} className="text-gray-300" />
</li>
<li>
<span className="text-[13px] font-ui font-normal text-gray-600">
{expandedItem.name}
</span>
</li>
</>
)}
</ol>
</nav>
)
}
+21 -5
View File
@@ -5,6 +5,7 @@ import type { ViewId } from '../types/pmr'
import { ClinicalSidebar } from './ClinicalSidebar'
import { PatientBanner } from './PatientBanner'
import { MobileBottomNav } from './MobileBottomNav'
import { Breadcrumb } from './Breadcrumb'
import { SummaryView } from './views/SummaryView'
import { ConsultationsView } from './views/ConsultationsView'
import { MedicationsView } from './views/MedicationsView'
@@ -130,11 +131,11 @@ function PMRContent({ children }: PMRInterfaceProps) {
return <ReferralsView />
default:
return (
<div className="bg-white border border-gray-200 rounded p-6">
<h1 className="font-inter font-semibold text-lg text-gray-900 capitalize">
<div className="bg-white border border-gray-200 rounded p-6 shadow-pmr">
<h1 className="font-ui font-semibold text-lg text-gray-900 capitalize">
{activeView} View
</h1>
<p className="font-inter text-sm text-gray-500 mt-2">
<p className="font-ui text-sm text-gray-500 mt-2">
Content for {activeView} will be implemented in a separate task.
</p>
</div>
@@ -201,11 +202,26 @@ function PMRContent({ children }: PMRInterfaceProps) {
<h1 className="sr-only">{viewLabels[activeView]}</h1>
</div>
{/* Breadcrumb (desktop/tablet only) */}
{!isMobile && (
<Breadcrumb
currentView={activeView}
expandedItem={
expandedItemId
? { name: expandedItemId, type: activeView }
: undefined
}
onNavigateToView={handleNavigate}
onCollapseItem={() => setExpandedItem(null)}
/>
)}
{/* Mobile back button (mobile only) */}
{isMobile && activeView !== 'summary' && (
<button
type="button"
onClick={handleBackToSummary}
className="flex items-center gap-1 text-pmr-nhsblue text-sm font-inter font-medium mb-4 hover:underline"
className="flex items-center gap-1 text-pmr-nhsblue text-sm font-ui font-medium mb-4 hover:underline"
>
<ArrowLeft size={14} />
Back to Summary
@@ -246,7 +262,7 @@ function MobileSearchBar({ query, onChange }: MobileSearchBarProps) {
placeholder="Search record..."
value={query}
onChange={e => onChange(e.target.value)}
className="w-full h-10 pl-10 pr-10 bg-white border border-gray-200 rounded text-sm font-inter text-gray-900 placeholder-gray-400 focus:outline-none focus:border-pmr-nhsblue focus:ring-1 focus:ring-pmr-nhsblue/20 transition-colors"
className="w-full h-10 pl-10 pr-10 bg-white border border-gray-200 rounded text-sm font-ui text-gray-900 placeholder-gray-400 focus:outline-none focus:border-pmr-nhsblue focus:ring-1 focus:ring-pmr-nhsblue/20 transition-colors"
/>
{query && (
<button