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:
@@ -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>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import type { ViewId } from '../types/pmr'
|
|||||||
import { ClinicalSidebar } from './ClinicalSidebar'
|
import { ClinicalSidebar } from './ClinicalSidebar'
|
||||||
import { PatientBanner } from './PatientBanner'
|
import { PatientBanner } from './PatientBanner'
|
||||||
import { MobileBottomNav } from './MobileBottomNav'
|
import { MobileBottomNav } from './MobileBottomNav'
|
||||||
|
import { Breadcrumb } from './Breadcrumb'
|
||||||
import { SummaryView } from './views/SummaryView'
|
import { SummaryView } from './views/SummaryView'
|
||||||
import { ConsultationsView } from './views/ConsultationsView'
|
import { ConsultationsView } from './views/ConsultationsView'
|
||||||
import { MedicationsView } from './views/MedicationsView'
|
import { MedicationsView } from './views/MedicationsView'
|
||||||
@@ -130,11 +131,11 @@ function PMRContent({ children }: PMRInterfaceProps) {
|
|||||||
return <ReferralsView />
|
return <ReferralsView />
|
||||||
default:
|
default:
|
||||||
return (
|
return (
|
||||||
<div className="bg-white border border-gray-200 rounded p-6">
|
<div className="bg-white border border-gray-200 rounded p-6 shadow-pmr">
|
||||||
<h1 className="font-inter font-semibold text-lg text-gray-900 capitalize">
|
<h1 className="font-ui font-semibold text-lg text-gray-900 capitalize">
|
||||||
{activeView} View
|
{activeView} View
|
||||||
</h1>
|
</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.
|
Content for {activeView} will be implemented in a separate task.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -201,11 +202,26 @@ function PMRContent({ children }: PMRInterfaceProps) {
|
|||||||
<h1 className="sr-only">{viewLabels[activeView]}</h1>
|
<h1 className="sr-only">{viewLabels[activeView]}</h1>
|
||||||
</div>
|
</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' && (
|
{isMobile && activeView !== 'summary' && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleBackToSummary}
|
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} />
|
<ArrowLeft size={14} />
|
||||||
Back to Summary
|
Back to Summary
|
||||||
@@ -246,7 +262,7 @@ function MobileSearchBar({ query, onChange }: MobileSearchBarProps) {
|
|||||||
placeholder="Search record..."
|
placeholder="Search record..."
|
||||||
value={query}
|
value={query}
|
||||||
onChange={e => onChange(e.target.value)}
|
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 && (
|
{query && (
|
||||||
<button
|
<button
|
||||||
|
|||||||
Reference in New Issue
Block a user