19 KiB
Reference: Patient Banner + Sidebar + Navigation
Extracted from goal.md — Patient Banner, Left Sidebar, and Navigation sections. These are the persistent UI chrome that defines the clinical system feel.
Patient Banner (Persistent Top Chrome)
The patient banner is the most recognizable element of any PMR system. It spans the full viewport width above the main content area and provides constant demographic context.
Full Banner (80px height, visible at top of page)
+---------------------------------------------------------------------------+
| CHARLWOOD, Andrew (Mr) Active (green dot) Open to opps. |
| DOB: 14/02/1993 | NHS No: 2211810 | Norwich, NR1 |
| 07795553088 | andy@charlwood.xyz [Download CV] [Email] [LinkedIn] |
+---------------------------------------------------------------------------+
Content Mapping
| PMR Field | Actual Content | Notes |
|---|---|---|
| Patient name | CHARLWOOD, Andrew (Mr) | Surname first, comma-separated — exactly as in clinical systems |
| DOB | 14/02/1993 | DD/MM/YYYY format (UK clinical standard) |
| NHS Number | 221 181 0 | Andy's GPhC registration number formatted like an NHS number (with spaces). Hover tooltip: "GPhC Registration Number" |
| GP Practice | Self-Referred | Tongue-in-cheek — Andy referred himself to this record |
| Address | Norwich, NR1 | Abbreviated postcode area |
| Phone | 07795553088 | Clickable (tel: link) |
| andy@charlwood.xyz | Clickable (mailto: link) | |
| Status | Active (green dot) | Like the "registered" status in a PMR |
| Badge | Open to opportunities | Styled as a clinical banner tag (blue background, white text, small pill shape) |
Action Buttons (top right of banner)
| Button | PMR Equivalent | Action |
|---|---|---|
| Download CV | Print Summary | Downloads PDF version of CV |
| Send Letter | Opens mailto: link | |
| External Link | Opens LinkedIn profile in new tab |
Buttons are styled as small outlined rectangles with NHS blue text and 1px NHS blue border, 4px radius. On hover: filled NHS blue background with white text.
Condensed Banner (48px, sticky after scroll)
When the user scrolls past 100px of content, the banner smoothly condenses to show only the essential information on a single line:
CHARLWOOD, Andrew (Mr) | NHS No: 2211810 | Active (green dot) [Download CV] [Email]
The condensed banner sticks to the top of the viewport (position: sticky) with a z-index above the content area but below modals/alerts.
Left Sidebar — Clinical Navigation
The sidebar replicates the dark navigation panel found in EMIS Web and similar clinical systems. It provides category-based access to different "record views."
Width: 220px (desktop), dark blue-gray (#1E293B) background.
Navigation Items
IMPORTANT: Sidebar labels use CV-friendly terms, NOT clinical jargon. The clinical metaphor lives in the LAYOUT of each view, not the labels.
| Icon | Label | View Layout Style | Description |
|---|---|---|---|
ClipboardList |
Summary | Patient summary screen | Demographics, active items, current skills, recent role |
FileText |
Experience | Consultation journal layout | Reverse-chronological journal of roles with H/E/P format |
Pill |
Skills | Medications table layout | Skills table with proficiency dosages and frequency |
AlertTriangle |
Achievements | Problems list layout | Challenges resolved and ongoing, with traffic lights |
FlaskConical |
Projects | Investigation results layout | Project outcomes with status badges |
FolderOpen |
Education | Attached documents layout | Certificates and qualifications |
Send |
Contact | Referral form layout | Contact/message form styled as clinical referral |
Styling
- Each item: 44px height, 16px left padding, icon (18px,
lucide-react) + label in [UI font] 500, 14px - Default state: white text at 70% opacity, transparent background
- Hover state: white text at 100% opacity, background
rgba(255,255,255,0.08) - Active state: white text at 100%, NHS blue left border (3px), background
rgba(255,255,255,0.12), label in [UI font] 600 - A thin horizontal separator line (
1px solid rgba(255,255,255,0.1)) appears between "Summary" and "Consultations" (separating the overview from the detail views)
Sidebar Footer
At the bottom of the sidebar, in small text ([UI font] 400, 11px, #64748B):
Session: A.CHARLWOOD
Logged in: [current time]
This updates with the actual current time on mount, reinforcing the "logged in" metaphor.
Sidebar Header
At the top, above the navigation items, a small logo or system name:
CareerRecord PMR
v1.0.0
In [UI font] 500, 13px, white at 50% opacity. Styled like clinical system branding that appears in the top-left of the navigation.
Navigation
Primary Navigation: Left Sidebar
The sidebar is always visible on desktop — this is how clinical systems work. There is no floating nav, no hamburger menu on desktop, and no scroll-based navigation. The sidebar provides persistent, direct access to any record section.
Keyboard Shortcuts
| Sidebar Item | View Layout | Shortcut |
|---|---|---|
| Summary | Patient summary | Alt+1 |
| Experience | Consultation journal | Alt+2 |
| Skills | Medications table | Alt+3 |
| Achievements | Problems list | Alt+4 |
| Projects | Investigation results | Alt+5 |
| Education | Attached documents | Alt+6 |
| Contact | Referral form | Alt+7 |
URL Hash Routing
Each sidebar item updates the URL hash (#summary, #experience, #skills, #achievements, #projects, #education, #contact) for direct linking. On page load, the app reads the hash and navigates to the corresponding view.
Breadcrumb
A breadcrumb appears at the top of the main content area:
Patient Record > Consultations > Interim Head, Population Health
The breadcrumb updates as the user navigates deeper (e.g., expanding a consultation). Clicking "Patient Record" returns to Summary. Clicking "Consultations" collapses any expanded entries and shows the full journal list. The breadcrumb is styled in [UI font] 400, 13px, gray-400, with chevron separators.
Secondary Navigation: Within-View Interactions
- Summary: Clicking "View Full List" or "View Full Record" links navigates to the corresponding sidebar section.
- Consultations: Expand/collapse individual entries. "Linked consultations" in Problems view can deep-link to specific consultation entries.
- Medications: Category tabs (Active, Clinical, PRN) within the view. Click to expand prescribing history.
- Problems: Click to expand. "Linked consultations" navigate to Consultations view.
- Investigations: Click to expand results.
- Documents: Click to expand preview.
- Referrals: No sub-navigation.
Design Guidance
Aesthetic Direction
Clinical Luxury — The patient banner and sidebar draw their structure from NHS clinical systems (PAS headers, EMIS Web navigation), but the execution is premium — refined typography, layered shadows, considered spacing. The clinical metaphor lives in the layout and conventions (surname-first, pipe separators, status dots); the luxury lives in the finish.
- Tone: Precise, information-dense, and refined. Generous whitespace, layered shadows, and premium typography elevate what would otherwise be institutional UI. The clinical conventions (data density, pipe separators, monospaced identifiers, surname-first, green status dot) provide authentic texture.
- Typography Discipline:
- [UI font] at 600 weight for the patient name — the anchor element
- Geist Mono for structured identifiers (NHS Number, DOB) — monospaced data feels like it came from a database
- [UI font] at normal weight for demographic text
- The pipe character
|as a data separator is a deliberate clinical convention
Design System Tokens
| Token | Value | Usage |
|---|---|---|
| NHS Blue | #005EB8 |
Primary accent, buttons, active states, borders |
| Banner Background | #334155 (slate-700) |
Patient banner background — exact EMIS Web header shade |
| Sidebar Background | #1E293B |
Dark navigation panel |
| Content Background | #F5F7FA |
Main content area |
| Border | #E5E7EB |
1px solid borders |
| Border Radius | 4px |
All UI elements |
| Green Status | #22C55E |
Active status dot |
| Font Text | [UI font] | All text content (Elvaro or Blumir — see CLAUDE.md) |
| Font Data | Geist Mono |
Monospaced identifiers |
Key Design Decisions
-
220px Sidebar Width: Fixed, always visible on desktop. No hamburger menu. This is how clinical systems work — persistent direct access.
-
Alt+1-7 Keyboard Shortcuts: Each sidebar item has a keyboard shortcut for power users. Arrow key navigation and
/for search focus. -
CV-Friendly Navigation Labels: Not clinical jargon. The metaphor lives in the layout, not the labels:
- Summary (ClipboardList icon)
- Experience (FileText)
- Skills (Pill)
- Achievements (AlertTriangle)
- Projects (FlaskConical)
- Education (FolderOpen)
- Contact (Send)
-
Scroll-Triggered Banner Condensation:
- Full banner: 80px height with three rows (name, demographics, contact/actions)
- Condensed: 48px sticky after 100px scroll, single line
- 200ms smooth transition
- IntersectionObserver for performance
-
Navigation Item States:
- Default: white text at 70% opacity, transparent background
- Hover: white text at 100%, background
rgba(255,255,255,0.08) - Active: white text at 100%, 3px NHS blue left border, background
rgba(255,255,255,0.12), [UI font] 600 weight
-
Interface Materialization Animations (PMRInterface):
- Patient banner slides down (200ms ease-out)
- Sidebar slides from left (250ms ease-out, 50ms delay)
- Content fades in (300ms, 100ms delay after sidebar)
- View switching is INSTANT — no crossfade or slide between views
-
Mobile Adaptations:
- Banner collapses to minimal:
CHARLWOOD, A (Mr) | 2211810 | dot - Overflow menu for actions
- Bottom nav bar (56px height with safe area padding)
- Sidebar becomes icon-only (56px) with tooltips on tablet
- Banner collapses to minimal:
Implementation Patterns
PatientBanner Component Structure
// Main container with IntersectionObserver sentinel
<>
<div ref={sentinelRef} className="h-0 w-full absolute top-0" aria-hidden="true" />
<header
className={`
sticky top-0 z-40 w-full
bg-pmr-banner border-b border-slate-600
transition-all duration-200 ease-out
${shouldCondense ? 'h-12' : 'h-20'}
`}
role="banner"
>
{shouldCondense ? <CondensedBanner /> : <FullBanner />}
</header>
</>
useScrollCondensation Hook
export function useScrollCondensation(options: UseScrollCondensationOptions = {}) {
const { threshold = 100 } = options
const [isCondensed, setIsCondensed] = useState(false)
const sentinelRef = useRef<HTMLDivElement>(null)
useEffect(() => {
const sentinel = sentinelRef.current
if (!sentinel) return
const observer = new IntersectionObserver(
(entries) => {
const [entry] = entries
setIsCondensed(!entry.isIntersecting)
},
{
rootMargin: `-${threshold}px 0px 0px 0px`,
threshold: 0,
}
)
observer.observe(sentinel)
return () => observer.disconnect()
}, [threshold])
return { isCondensed, sentinelRef }
}
ClinicalSidebar Navigation Items
const navItems: NavItem[] = [
{ id: 'summary', label: 'Summary', icon: <ClipboardList size={18} /> },
{ id: 'consultations', label: 'Experience', icon: <FileText size={18} /> },
{ id: 'medications', label: 'Skills', icon: <Pill size={18} /> },
{ id: 'problems', label: 'Achievements', icon: <AlertTriangle size={18} /> },
{ id: 'investigations', label: 'Projects', icon: <FlaskConical size={18} /> },
{ id: 'documents', label: 'Education', icon: <FolderOpen size={18} /> },
{ id: 'referrals', label: 'Contact', icon: <Send size={18} /> },
]
// Item styling pattern
<button
className={`
w-full h-[44px] px-4 flex items-center gap-3
font-ui text-[14px] font-medium
transition-all duration-150
${isActive
? 'text-white bg-white/[0.12] border-l-[3px] border-pmr-nhsblue font-semibold'
: 'text-white/70 hover:text-white hover:bg-white/[0.08] border-l-[3px] border-transparent'
}
`}
>
<span className="w-[18px] h-[18px]">{icon}</span>
<span>{label}</span>
</button>
PMRInterface Layout
// Main layout structure
<div className="flex h-screen overflow-hidden">
{/* Fixed sidebar */}
<ClinicalSidebar
activeView={activeView}
onViewChange={handleViewChange}
isTablet={isTablet}
/>
{/* Main content area */}
<div className="flex-1 flex flex-col min-w-0">
{/* Sticky patient banner */}
<PatientBanner isMobile={isMobile} isTablet={isTablet} />
{/* Scrollable content */}
<main className="flex-1 overflow-y-auto bg-pmr-content p-6">
{/* View content renders here */}
</main>
</div>
</div>
Action Button Pattern
// Outlined buttons that fill on hover
<button
className="
px-3 py-1.5
text-pmr-nhsblue text-sm font-medium
border border-pmr-nhsblue rounded-[4px]
transition-all duration-150
hover:bg-pmr-nhsblue hover:text-white
"
>
Download CV
</button>
Mobile Considerations
- Banner: Shows only name (truncated), NHS number, and status dot
- Overflow Menu: Three-dot menu reveals hidden actions (Download CV, Email, LinkedIn)
- Bottom Nav: 56px fixed bottom bar with safe area padding for notched devices
- Touch Targets: All interactive elements minimum 44px for accessibility
Accessibility Requirements
- All navigation items keyboard accessible
- Active state has visual indicator (NHS blue left border)
- Reduced motion support: disable animations when
prefers-reduced-motionis set - Focus visible states on all interactive elements
- ARIA labels for icon-only buttons
Additional Implementation Notes (from Agent Analysis)
PatientBanner Component Refinements
Animation Improvements
- Replace raw CSS
transition-all duration-200with Framer Motion'sAnimatePresenceandmotion.divfor smoother layout animations - Enable cross-fade content between full and condensed banner states
- Use
motion.divwithinitial,animate,exitprops for content swapping
Badge Styling
- Current:
rounded-sm— Change to true pill shape:rounded-fullfor "Open to opportunities" badge - Blue pill shape per NHS design system
NHS Number Tooltip
- Replace native
titleattribute with custom styled tooltip - Use Framer Motion for controlled hover reveal
- Tooltip text: "GPhC Registration Number"
Mobile Overflow Menu
- Current: raw
useStatetoggle with no animation - Use
AnimatePresencefor enter/exit animations - Three-dot menu button triggers slide-down panel
Action Button Hover States
// Outlined buttons with NHS blue that fill on hover
className="
px-3 py-1.5
text-[#005EB8] text-sm font-medium
border border-[#005EB8] rounded-[4px]
transition-all duration-150
hover:bg-[#005EB8] hover:text-white
"
ClinicalSidebar Keyboard Navigation
Alt+1-7 Shortcuts Implementation
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.altKey && e.key >= '1' && e.key <= '7') {
const index = parseInt(e.key) - 1
const view = navItems[index]
if (view) onViewChange(view.id)
}
if (e.key === '/') {
e.preventDefault()
searchInputRef.current?.focus()
}
}
window.addEventListener('keydown', handleKeyDown)
return () => window.removeEventListener('keydown', handleKeyDown)
}, [onViewChange])
Arrow Key Navigation
- Up/Down arrows navigate between sidebar items
- Focus trap within sidebar when using keyboard
- Visual focus indicator matches hover state
PMRInterface Layout Structure
Materialization Animation Sequence
// Staggered entrance animations
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.05,
delayChildren: 0.1
}
}
}
// Patient banner: slides down (200ms ease-out)
const bannerVariants = {
hidden: { y: -80, opacity: 0 },
visible: {
y: 0,
opacity: 1,
transition: { duration: 0.2, ease: 'easeOut' }
}
}
// Sidebar: slides from left (250ms ease-out, 50ms delay)
const sidebarVariants = {
hidden: { x: -220, opacity: 0 },
visible: {
x: 0,
opacity: 1,
transition: { duration: 0.25, ease: 'easeOut', delay: 0.05 }
}
}
// Content: fades in (300ms, 100ms delay after sidebar)
const contentVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: { duration: 0.3, delay: 0.15 }
}
}
View Switching Performance
- Views switch INSTANTLY — no crossfade or slide between views
- Content updates immediately on hash change
- No transition/animation between different view components
- Only initial materialization has animation
Breadcrumb Component Pattern
interface BreadcrumbProps {
currentView: ViewId
expandedItem?: { name: string; type: string }
}
// View name mapping (CV-friendly names)
const viewLabels: Record<ViewId, string> = {
summary: 'Summary',
consultations: 'Experience',
medications: 'Skills',
problems: 'Achievements',
investigations: 'Projects',
documents: 'Education',
referrals: 'Contact'
}
// Styling: [UI font] 400, 13px, gray-400
// Chevron separators using Lucide ChevronRight
// Clickable links navigate back
Mobile Bottom Navigation
// 56px height with safe area padding
<div className="fixed bottom-0 left-0 right-0 h-14 bg-pmr-sidebar border-t border-slate-700 pb-safe">
<div className="flex justify-around items-center h-full px-4">
{navItems.map((item) => (
<button
key={item.id}
className={`
flex flex-col items-center gap-1
${isActive ? 'text-pmr-nhsblue' : 'text-white/60'}
`}
>
{item.icon}
<span className="text-[10px]">{item.label}</span>
</button>
))}
</div>
</div>
TypeScript Types Reference
// ViewId type for navigation
export type ViewId =
| 'summary'
| 'consultations'
| 'medications'
| 'problems'
| 'investigations'
| 'documents'
| 'referrals'
// Patient data structure
export interface Patient {
name: string // 'CHARLWOOD, Andrew (Mr)'
displayName: string // 'Andrew Charlwood'
dob: string // '14/02/1993'
nhsNumber: string // '221 181 0'
nhsNumberTooltip: string // 'GPhC Registration Number'
address: string // 'Norwich, NR1'
phone: string
email: string
linkedin: string
status: 'Active' | 'Inactive'
badge?: string // 'Open to opportunities'
}