reference files updated
This commit is contained in:
@@ -58,7 +58,6 @@ All data types live in `src/types/index.ts`. Strict TypeScript — no `any` type
|
||||
- ECG animation timing/amplitudes/color transitions must match the concept reference.
|
||||
- CV content sourced from `References/CV_v4.md` — roles, dates, and achievement numbers must be accurate.
|
||||
- Icons via `lucide-react`, not unicode symbols.
|
||||
- When writing components with visual styling or animations, invoke the `/frontend-design` skill first.
|
||||
|
||||
## Project Structure
|
||||
|
||||
|
||||
@@ -0,0 +1,331 @@
|
||||
# Agent Output to Ralph Files Integration Plan
|
||||
|
||||
## Overview
|
||||
|
||||
This plan documents how to decode the agent output files from `AgentOutput/` and place the design recommendations into the relevant Ralph reference files in `Tasks/07-the-clinical-record/Ralph/refs/`.
|
||||
|
||||
---
|
||||
|
||||
## Agent to Ralph File Mapping
|
||||
|
||||
| Agent File | Component | Target Ralph File | Status |
|
||||
|------------|-----------|-------------------|--------|
|
||||
| `agent-a19ebc6.jsonl` | LoginScreen | `ref-transition-login.md` | ✅ Has existing design guidance |
|
||||
| `agent-a1dd546.jsonl` | PatientBanner | `ref-banner-sidebar.md` | Needs design guidance appended |
|
||||
| `agent-aa811d1.jsonl` | PatientBanner (detailed) | `ref-banner-sidebar.md` | Use for design decisions |
|
||||
| `agent-aff857c.jsonl` | ClinicalSidebar | `ref-banner-sidebar.md` | Add to same file |
|
||||
| `agent-a8a7a9b.jsonl` | PMRInterface + Breadcrumb | `ref-banner-sidebar.md` | Add layout guidance |
|
||||
| `agent-a2bbd7e.jsonl` | InvestigationsView + DocumentsView | `ref-investigations-documents.md` | Needs design guidance |
|
||||
| `agent-acompact-a5ee2e.jsonl` | Investigations + Documents (compact) | `ref-investigations-documents.md` | Alternative source |
|
||||
| `agent-a32017e.jsonl` | SummaryView + Clinical Alert | `ref-summary-alert.md` | Needs alert interaction details |
|
||||
| `agent-a403d5f.jsonl` | ReferralsView | `ref-referrals.md` | Needs form styling guidance |
|
||||
| `agent-aab6185.jsonl` | MedicationsView | `ref-medications.md` | Needs table styling guidance |
|
||||
| `agent-aeb60ce.jsonl` | ProblemsView | `ref-problems.md` | Needs traffic light guidance |
|
||||
| `agent-afebbe0.jsonl` | ConsultationsView | `ref-consultations.md` | Needs H/E/P structure guidance |
|
||||
|
||||
---
|
||||
|
||||
## Design Guidance Structure to Add
|
||||
|
||||
Each Ralph file should have a "## Design Guidance (from /frontend-design)" section appended with:
|
||||
|
||||
1. **Aesthetic Direction** - The design philosophy/tone
|
||||
2. **Key Design Decisions** - Specific implementation choices
|
||||
3. **Implementation Patterns** - Code snippets and patterns
|
||||
|
||||
---
|
||||
|
||||
## File-by-File Plan
|
||||
|
||||
### 1. ref-transition-login.md
|
||||
|
||||
**Already contains design guidance** - verify it matches agent output:
|
||||
|
||||
From agent-a19ebc6:
|
||||
- ✅ Aesthetic: Institutional Utilitarian
|
||||
- ✅ Shadow: `0 1px 2px rgba(0,0,0,0.03)`
|
||||
- ✅ Border: `1px solid #E5E7EB`
|
||||
- ✅ Font: Geist Mono for credentials
|
||||
- ✅ Accessibility: role="status", aria-label, prefers-reduced-motion
|
||||
|
||||
**Action**: Already complete - no changes needed.
|
||||
|
||||
---
|
||||
|
||||
### 2. ref-banner-sidebar.md
|
||||
|
||||
**Current content**: Patient Banner specs, Sidebar navigation, URL routing
|
||||
|
||||
**Needs to add from agents**:
|
||||
|
||||
From agent-aa811d1 (PatientBanner detailed):
|
||||
- Animation refinements: Framer Motion AnimatePresence for condensed/full state swap
|
||||
- Badge styling: True pill shape (rounded-full)
|
||||
- NHS Number tooltip: Custom styled tooltip with Framer Motion
|
||||
- Mobile overflow menu: AnimatePresence for enter/exit
|
||||
- Action buttons: Loading state with spinner
|
||||
- Layout measurements: Exact padding, margin values
|
||||
|
||||
From agent-aff857c (ClinicalSidebar):
|
||||
- Keyboard navigation details: Alt+1-7, arrow keys, / for search
|
||||
- Search input integration
|
||||
- Tooltip positioning for tablet mode
|
||||
- Focus management patterns
|
||||
- Accessibility: aria-current for active item
|
||||
|
||||
From agent-a8a7a9b (PMRInterface + Breadcrumb):
|
||||
- Layout timing: Patient banner (200ms), sidebar (250ms, 50ms delay), content (300ms, 100ms delay)
|
||||
- View switching: INSTANT - no crossfade
|
||||
- Breadcrumb deepening: When item expanded, show item name
|
||||
- Breadcrumb styling: Inter 400, 13px, gray-400, chevron separators
|
||||
|
||||
**Action**: Append comprehensive design guidance section.
|
||||
|
||||
---
|
||||
|
||||
### 3. ref-investigations-documents.md
|
||||
|
||||
**Current content**: Table layouts, status badges, expanded views
|
||||
|
||||
**Needs to add from agent-a2bbd7e/acompact**:
|
||||
|
||||
Shared Pattern: ExpandableRow
|
||||
- Component structure for row expansion
|
||||
- Height animation only (200ms ease-out) - no opacity fade
|
||||
- Chevron rotation (180 degrees)
|
||||
- Only one expanded at a time
|
||||
|
||||
InvestigationsView specific:
|
||||
- Status badges: Complete (green), Ongoing (amber), Live (pulsing green)
|
||||
- Pulsing animation for Live status (CSS keyframes)
|
||||
- Tree-indented structure with box-drawing characters
|
||||
- View Results button pattern for external links
|
||||
|
||||
DocumentsView specific:
|
||||
- Lucide icons mapping: FileText, Award, GraduationCap, FlaskConical
|
||||
- Document type indicators
|
||||
- Same tree-indented structure
|
||||
|
||||
**Action**: Append design guidance with expandable row patterns.
|
||||
|
||||
---
|
||||
|
||||
### 4. ref-summary-alert.md
|
||||
|
||||
**Current content**: Summary cards layout, Clinical Alert behavior
|
||||
|
||||
**Needs to add from agent-a32017e**:
|
||||
|
||||
Clinical Alert (signature interaction):
|
||||
- Spring animation (not ease-out) for slide down
|
||||
- Acknowledge button flow:
|
||||
1. Warning icon cross-fades to green checkmark (200ms)
|
||||
2. Holds 200ms
|
||||
3. Alert height collapses (200ms ease-out)
|
||||
- Session-only state (resets on refresh)
|
||||
|
||||
Summary Cards:
|
||||
- Two-column key-value layout exact specs
|
||||
- Card header styling: Inter 600, 14px, uppercase, #F9FAFB bg
|
||||
- Traffic light dots: 8px circles with text labels (WCAG)
|
||||
- "View Full List" link patterns
|
||||
|
||||
Second Alert (Consultations view):
|
||||
- Trigger: First navigation to Consultations
|
||||
- Same dismiss pattern as main alert
|
||||
- Positioning beneath patient banner
|
||||
|
||||
**Action**: Append alert animation details and card styling specs.
|
||||
|
||||
---
|
||||
|
||||
### 5. ref-referrals.md
|
||||
|
||||
**Current content**: Form layout, priority toggle, direct contact
|
||||
|
||||
**Needs to add from agent-a403d5f**:
|
||||
|
||||
Referral Form:
|
||||
- Priority radio button styling: Urgent (red), Routine (blue), Two-Week Wait (amber)
|
||||
- Tooltip content: "All enquiries are welcome..." and "NHS cancer referral pathway..."
|
||||
- Input focus state: NHS blue border + box-shadow `0 0 0 3px rgba(0,94,184,0.15)`
|
||||
- Form validation patterns
|
||||
|
||||
Submit Button States:
|
||||
- Default: NHS blue (#005EB8)
|
||||
- Loading: Spinner icon
|
||||
- Success: Checkmark + reference number generation (REF-YYYY-MMDD-NNN)
|
||||
- Success message styling: 24-48 hours response time
|
||||
|
||||
**Action**: Append form interaction and state management patterns.
|
||||
|
||||
---
|
||||
|
||||
### 6. ref-medications.md
|
||||
|
||||
**Current content**: Table layout, medication categories, prescribing history
|
||||
|
||||
**Needs to add from agent-aab6185**:
|
||||
|
||||
Table Styling:
|
||||
- Semantic HTML: table, thead, th (scope=col), tbody, tr, td
|
||||
- Headers: Inter 600, 13px, uppercase, #F9FAFB bg
|
||||
- Row height: 40px exactly
|
||||
- Alternating backgrounds: #FFFFFF / #F9FAFB
|
||||
- Hover: #EFF6FF (no transform, no lift)
|
||||
- Status dots: 6px green circles + 'Active' text
|
||||
|
||||
Category Tabs:
|
||||
- Active tab: White bg + NHS blue bottom border
|
||||
- Inactive tab: Transparent bg
|
||||
- Tab switching animation (instant)
|
||||
|
||||
Sortable Columns:
|
||||
- Click header to sort
|
||||
- Arrow indicator (up/down) in active column
|
||||
- Default: category grouping
|
||||
|
||||
Prescribing History:
|
||||
- Expand animation: Height only, 200ms ease-out
|
||||
- Geist Mono 12px for history entries
|
||||
- Year markers bold, descriptions regular
|
||||
|
||||
**Action**: Append table semantics and sorting patterns.
|
||||
|
||||
---
|
||||
|
||||
### 7. ref-problems.md
|
||||
|
||||
**Current content**: Active/resolved problems tables, traffic lights
|
||||
|
||||
**Needs to add from agent-aeb60ce**:
|
||||
|
||||
Traffic Light Status:
|
||||
- 8px circles: Green (resolved), Amber (in-progress)
|
||||
- MUST ALWAYS be paired with text labels (WCAG requirement)
|
||||
- Never dots alone
|
||||
- Code column: Geist Mono (e.g., [EFF001], [MGT001])
|
||||
|
||||
Expandable Rows:
|
||||
- Full narrative: Problem, approach, tools, quantified outcome
|
||||
- 'Linked consultations' buttons that navigate to Consultations view
|
||||
- Row hover: #EFF6FF
|
||||
|
||||
Table Structure:
|
||||
- Active Problems: Status | Code | Problem | Since
|
||||
- Resolved Problems: Status | Code | Problem | Resolved | Outcome
|
||||
|
||||
**Action**: Append traffic light accessibility requirements.
|
||||
|
||||
---
|
||||
|
||||
### 8. ref-consultations.md
|
||||
|
||||
**Current content**: Journal layout, H/E/P structure, coded entries
|
||||
|
||||
**Needs to add from agent-afebbe0**:
|
||||
|
||||
Collapsed Entry:
|
||||
- Date in Geist Mono 13px gray-500
|
||||
- Organization in Inter 400 13px NHS blue
|
||||
- Role in Inter 600 15px gray-900
|
||||
- 'Key:' prefix in Inter 500 gray-500
|
||||
- Status dot: Green (current), Gray (historical)
|
||||
- 3px left border color-coded: NHS blue (#005EB8) or Tesco teal (#00897B)
|
||||
|
||||
Expanded Entry:
|
||||
- Duration line
|
||||
- HISTORY / EXAMINATION / PLAN section headers
|
||||
- Headers: Inter 600, 12px, uppercase, letter-spacing 0.05em, gray-400
|
||||
- Plan items as bullet lists
|
||||
- CODED ENTRIES section at bottom: Geist Mono 12px, gray-500, bracket codes
|
||||
|
||||
Animation:
|
||||
- Height-only expand animation, 200ms ease-out
|
||||
- NO opacity fade on content
|
||||
- Only one expanded at a time
|
||||
- Chevron rotates 180 degrees when expanded
|
||||
|
||||
Second Alert:
|
||||
- Appears on first navigation to Consultations view
|
||||
- Same Acknowledge pattern as main alert
|
||||
- Warning icon → Checkmark → Collapse
|
||||
|
||||
**Action**: Append H/E/P styling and animation details.
|
||||
|
||||
---
|
||||
|
||||
## Implementation Order
|
||||
|
||||
1. **Start with**: ref-banner-sidebar.md (most complex, multiple agents)
|
||||
2. **Then**: ref-investigations-documents.md (shared patterns)
|
||||
3. **Then**: ref-summary-alert.md (signature interaction)
|
||||
4. **Then**: ref-consultations.md (core content view)
|
||||
5. **Then**: ref-medications.md, ref-problems.md, ref-referrals.md (specialized views)
|
||||
|
||||
---
|
||||
|
||||
## Token-Efficient Approach
|
||||
|
||||
For each file:
|
||||
|
||||
1. Read the agent output file (focused read, only design guidance section)
|
||||
2. Extract: Aesthetic direction, Key design decisions, Implementation patterns
|
||||
3. Read the target Ralph file
|
||||
4. Append the design guidance in the standard format
|
||||
5. Write back
|
||||
6. Clear context, move to next
|
||||
|
||||
This keeps context window usage minimal by processing one file at a time.
|
||||
|
||||
---
|
||||
|
||||
## Design Guidance Section Template
|
||||
|
||||
```markdown
|
||||
|
||||
---
|
||||
|
||||
## Design Guidance (from /frontend-design)
|
||||
|
||||
> Pre-baked design direction. Do NOT invoke `/frontend-design` at runtime — this section contains the output.
|
||||
|
||||
### Aesthetic Direction: [Name]
|
||||
|
||||
[Description of the design philosophy]
|
||||
|
||||
### Key Design Decisions
|
||||
|
||||
1. **[Decision name]**: [Description with specific values]
|
||||
2. **[Decision name]**: [Description with specific values]
|
||||
...
|
||||
|
||||
### Implementation Patterns
|
||||
|
||||
```tsx
|
||||
// Key code patterns and snippets
|
||||
```
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Completion Checklist
|
||||
|
||||
- [ ] ref-transition-login.md - Verify existing content (no changes needed)
|
||||
- [ ] ref-banner-sidebar.md - Add PatientBanner, ClinicalSidebar, PMRInterface guidance
|
||||
- [ ] ref-investigations-documents.md - Add InvestigationsView + DocumentsView guidance
|
||||
- [ ] ref-summary-alert.md - Add SummaryView + Clinical Alert guidance
|
||||
- [ ] ref-referrals.md - Add ReferralsView guidance
|
||||
- [ ] ref-medications.md - Add MedicationsView guidance
|
||||
- [ ] ref-problems.md - Add ProblemsView guidance
|
||||
- [ ] ref-consultations.md - Add ConsultationsView guidance
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- All design guidance should be appending (not replacing) existing content
|
||||
- Existing content in Ralph files has the "what" and "why"
|
||||
- Design guidance adds the "how" with specific implementation details
|
||||
- Keep code snippets minimal but complete enough to guide implementation
|
||||
- Reference specific values: colors, fonts, sizes, timing functions
|
||||
@@ -143,3 +143,400 @@ The breadcrumb updates as the user navigates deeper (e.g., expanding a consultat
|
||||
- **Investigations:** Click to expand results.
|
||||
- **Documents:** Click to expand preview.
|
||||
- **Referrals:** No sub-navigation.
|
||||
|
||||
---
|
||||
|
||||
## Design Guidance (from /frontend-design)
|
||||
|
||||
### Aesthetic Direction
|
||||
|
||||
**Clinical Institutional Precision** — The NHS Patient Administration System (PAS) header bar, faithfully reproduced as personal branding. This is not a "medical theme" website. It is a clinical system UI that happens to contain career data instead of patient data. The fidelity to real NHS IT systems (EMIS Web, SystmOne, Lorenzo) is the entire point.
|
||||
|
||||
- **Tone**: Utilitarian, institutional, information-dense. No decoration. No gradients. No shadows. The beauty is in the data density, the pipe separators, the monospaced identifiers, the surname-first convention, the green status dot.
|
||||
- **Typography Discipline**:
|
||||
- Inter 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
|
||||
- Inter at normal weight for demographic text
|
||||
- The pipe character `|` as a data separator is a deliberate NHS PAS 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 | `Inter` | All text content |
|
||||
| Font Data | `Geist Mono` | Monospaced identifiers |
|
||||
|
||||
### Key Design Decisions
|
||||
|
||||
1. **220px Sidebar Width**: Fixed, always visible on desktop. No hamburger menu. This is how clinical systems work — persistent direct access.
|
||||
|
||||
2. **Alt+1-7 Keyboard Shortcuts**: Each sidebar item has a keyboard shortcut for power users. Arrow key navigation and `/` for search focus.
|
||||
|
||||
3. **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)
|
||||
|
||||
4. **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
|
||||
|
||||
5. **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)`, Inter 600 weight
|
||||
|
||||
6. **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
|
||||
|
||||
7. **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
|
||||
|
||||
### Implementation Patterns
|
||||
|
||||
#### PatientBanner Component Structure
|
||||
|
||||
```tsx
|
||||
// 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
|
||||
|
||||
```tsx
|
||||
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
|
||||
|
||||
```tsx
|
||||
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-inter 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
|
||||
|
||||
```tsx
|
||||
// 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
|
||||
|
||||
```tsx
|
||||
// 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-motion` is 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-200` with Framer Motion's `AnimatePresence` and `motion.div` for smoother layout animations
|
||||
- Enable cross-fade content between full and condensed banner states
|
||||
- Use `motion.div` with `initial`, `animate`, `exit` props for content swapping
|
||||
|
||||
#### Badge Styling
|
||||
- Current: `rounded-sm` — Change to true pill shape: `rounded-full` for "Open to opportunities" badge
|
||||
- Blue pill shape per NHS design system
|
||||
|
||||
#### NHS Number Tooltip
|
||||
- Replace native `title` attribute with custom styled tooltip
|
||||
- Use Framer Motion for controlled hover reveal
|
||||
- Tooltip text: "GPhC Registration Number"
|
||||
|
||||
#### Mobile Overflow Menu
|
||||
- Current: raw `useState` toggle with no animation
|
||||
- Use `AnimatePresence` for enter/exit animations
|
||||
- Three-dot menu button triggers slide-down panel
|
||||
|
||||
#### Action Button Hover States
|
||||
```tsx
|
||||
// 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
|
||||
```tsx
|
||||
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
|
||||
```tsx
|
||||
// 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
|
||||
|
||||
```tsx
|
||||
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: Inter 400, 13px, gray-400
|
||||
// Chevron separators using Lucide ChevronRight
|
||||
// Clickable links navigate back
|
||||
```
|
||||
|
||||
### Mobile Bottom Navigation
|
||||
|
||||
```tsx
|
||||
// 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
|
||||
|
||||
```tsx
|
||||
// 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'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -107,3 +107,103 @@ This visual grouping helps the user quickly scan which organization each entry b
|
||||
- **Expand/collapse:** Height animation, 200ms, ease-out. No opacity fade — the content simply grows/shrinks.
|
||||
- Only one consultation can be expanded at a time. Expanding a new entry collapses the previous one.
|
||||
- The expand chevron rotates 180 degrees (pointing up when expanded).
|
||||
|
||||
---
|
||||
|
||||
## Design Guidance (from /frontend-design)
|
||||
|
||||
### Aesthetic Direction
|
||||
|
||||
**Utilitarian clinical information system.** Zero decorative flourish. Border-heavy, table-heavy, functional. Light-mode only. The design language of EMIS Web and SystmOne—systems that every NHS pharmacist interacts with daily.
|
||||
|
||||
This is a **faithful digital clinical information system**—structured, database-driven, tabular, with the distinctive UI patterns of actual NHS clinical software: patient banner, tabbed clinical views, consultation history as a reverse-chronological journal, coded entries with SNOMED-style references, traffic-light status indicators, and action buttons styled as clinical system controls.
|
||||
|
||||
**What makes this unforgettable:** The History / Examination / Plan structure maps perfectly to Context / Analysis / Outcome. A clinician seeing this will immediately recognize the consultation journal format. A recruiter gets highly structured, scannable career data. The accordion behavior, coded entries, and status indicators all draw from real clinical software patterns.
|
||||
|
||||
### Key Design Decisions
|
||||
|
||||
1. **SOAP Notes Format (H/E/P Structure)**
|
||||
- Maps clinical consultation format to career content:
|
||||
- **History** → Context / Background (why the role existed, reporting lines)
|
||||
- **Examination** → Analysis / Findings (what was discovered, built, analyzed)
|
||||
- **Plan** → Outcomes / Delivery (what was achieved, impact measured)
|
||||
- Section headers styled as clinical system dividers: Inter 600, 12px, uppercase, letter-spacing 0.05em, gray-400
|
||||
|
||||
2. **Height-Only Expand Animation (200ms)**
|
||||
- No opacity fade on content—content simply grows/shrinks
|
||||
- Duration: 200ms with ease-out timing
|
||||
- Only one entry expanded at a time
|
||||
- Chevron rotates 180 degrees when expanded
|
||||
|
||||
3. **Color-Coded Left Border (3px)**
|
||||
- NHS Norfolk & Waveney ICB: NHS blue (`#005EB8`)
|
||||
- Tesco PLC: Teal (`#00897B`)
|
||||
|
||||
4. **Typography System**
|
||||
- Inter for text content
|
||||
- Geist Mono for dates, codes, timestamps
|
||||
- Border radius: 4px throughout
|
||||
- Borders: 1px solid #E5E7EB
|
||||
|
||||
### Implementation Patterns
|
||||
|
||||
**ConsultationEntry Type:**
|
||||
```typescript
|
||||
interface ConsultationEntry {
|
||||
id: string
|
||||
date: string // e.g., "14 May 2025"
|
||||
organization: string // e.g., "NHS Norfolk & Waveney ICB"
|
||||
role: string // e.g., "Interim Head, Population Health & Data Analysis"
|
||||
duration: string // e.g., "May 2025 - Nov 2025"
|
||||
isCurrent: boolean // Green dot if true, gray if false
|
||||
borderColor: '#005EB8' | '#00897B'
|
||||
keyAchievement: { code: string; description: string }
|
||||
history: string[] // Paragraphs for HISTORY section
|
||||
examination: string[] // Bullet points for EXAMINATION section
|
||||
plan: string[] // Bullet points for PLAN section
|
||||
codedEntries: { code: string; description: string }[]
|
||||
}
|
||||
```
|
||||
|
||||
**Animation Pattern:**
|
||||
```typescript
|
||||
// Height-only animation via Framer Motion
|
||||
<motion.div
|
||||
initial={false}
|
||||
animate={{ height: isExpanded ? 'auto' : 0 }}
|
||||
transition={{ duration: 0.2, ease: 'easeOut' }}
|
||||
className="overflow-hidden"
|
||||
>
|
||||
{/* Expanded content */}
|
||||
</motion.div>
|
||||
|
||||
// Chevron rotation
|
||||
<motion.div
|
||||
animate={{ rotate: isExpanded ? 180 : 0 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
>
|
||||
<ChevronDown />
|
||||
</motion.div>
|
||||
```
|
||||
|
||||
**Section Header Pattern:**
|
||||
```tsx
|
||||
<h4 className="font-sans text-xs font-semibold uppercase tracking-wider text-gray-400">
|
||||
History
|
||||
</h4>
|
||||
```
|
||||
|
||||
**Coded Entry Pattern:**
|
||||
```tsx
|
||||
<span className="font-mono text-xs text-gray-500">
|
||||
[EFF001] Efficiency programme: £14.6M identified
|
||||
</span>
|
||||
```
|
||||
|
||||
**Status Dot Pattern:**
|
||||
```tsx
|
||||
<div className={cn(
|
||||
"w-2 h-2 rounded-full",
|
||||
isCurrent ? "bg-green-500" : "bg-gray-400"
|
||||
)} />
|
||||
```
|
||||
|
||||
@@ -106,3 +106,171 @@ MPharm (Hons) 2:1 - University of East Anglia
|
||||
```
|
||||
|
||||
The preview panel uses the same tree-indented structure as the Investigations expanded view, maintaining visual consistency across the PMR interface.
|
||||
|
||||
---
|
||||
|
||||
## Design Guidance (from /frontend-design)
|
||||
|
||||
### Aesthetic Direction
|
||||
|
||||
**Tone:** Clinical utilitarian — faithful reproduction of NHS clinical software (EMIS Web / SystmOne). Zero decorative flourish. Borders as the dominant structuring element. Dense, scannable, table-first. Light-mode only. The visual language is institutional and familiar to any NHS clinician.
|
||||
|
||||
**Differentiation:** The expanded-row tree-indented monospace structure using box-drawing characters is the signature element. It transforms a flat data table into something that reads like a lab report or radiology result — structured, indented, with labelled fields in `Geist Mono`. The pipe-and-branch characters (`├─`, `│`, `└─`) create a distinctly clinical-system aesthetic that no standard portfolio site would ever use.
|
||||
|
||||
### Key Design Decisions
|
||||
|
||||
#### ExpandableRow Component Pattern
|
||||
|
||||
Both views share an identical expand/collapse mechanic:
|
||||
|
||||
1. **Collapsed State:** Standard table row with hover feedback (slight background tint)
|
||||
2. **Expand Trigger:** Click anywhere on the row
|
||||
3. **Expanded State:** Full-width panel slides down below the row with `AnimatePresence`
|
||||
4. **Visual Connection:** Expanded panel has left border matching the row's status color
|
||||
5. **Tree Structure:** Expanded content uses box-drawing characters for clinical report aesthetic
|
||||
|
||||
**Status Badge System:**
|
||||
- **Complete** (green dot): `#10B981` background, used for finished investigations
|
||||
- **Ongoing** (amber dot): `#F59E0B` background, used for in-progress work
|
||||
- **Live** (pulsing green dot): `#10B981` with CSS pulse animation, used for active/live URLs
|
||||
|
||||
#### Typography & Spacing
|
||||
|
||||
- **Primary font:** Inter (text, labels, table headers)
|
||||
- **Monospace font:** Geist Mono (tree-indented expanded content)
|
||||
- **Border radius:** 4px throughout
|
||||
- **Border color:** `#E5E7EB` (Tailwind gray-200)
|
||||
- **NHS Blue:** `#005EB8` (action buttons, links)
|
||||
|
||||
### Implementation Patterns
|
||||
|
||||
#### StatusBadge Component
|
||||
|
||||
```tsx
|
||||
interface StatusBadgeProps {
|
||||
status: 'complete' | 'ongoing' | 'live';
|
||||
label: string;
|
||||
}
|
||||
|
||||
const StatusBadge = ({ status, label }: StatusBadgeProps) => {
|
||||
const styles = {
|
||||
complete: 'bg-emerald-100 text-emerald-800 border-emerald-200',
|
||||
ongoing: 'bg-amber-100 text-amber-800 border-amber-200',
|
||||
live: 'bg-emerald-100 text-emerald-800 border-emerald-200 animate-pulse',
|
||||
};
|
||||
|
||||
const dotColors = {
|
||||
complete: 'bg-emerald-500',
|
||||
ongoing: 'bg-amber-500',
|
||||
live: 'bg-emerald-500 animate-ping',
|
||||
};
|
||||
|
||||
return (
|
||||
<span className={`inline-flex items-center gap-1.5 px-2.5 py-1 rounded text-xs font-medium border ${styles[status]}`}>
|
||||
<span className={`w-1.5 h-1.5 rounded-full ${dotColors[status]}`} />
|
||||
{label}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
#### Tree-Indented Content Structure
|
||||
|
||||
```tsx
|
||||
const TreeLine = ({ label, value, isLast = false }: TreeLineProps) => (
|
||||
<div className="font-mono text-sm text-gray-700">
|
||||
<span className="text-gray-400">{isLast ? '└─ ' : '├─ '}</span>
|
||||
<span className="text-gray-500">{label}:</span>
|
||||
<span className="ml-2">{value}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
// Usage in expanded view:
|
||||
<div className="bg-gray-50 border-l-4 border-emerald-400 pl-4 py-3">
|
||||
<TreeLine label="Date Requested" value="2024" />
|
||||
<TreeLine label="Status" value="Complete" />
|
||||
<TreeLine label="Methodology" value="Power BI dashboard..." isLast />
|
||||
</div>
|
||||
```
|
||||
|
||||
#### ExpandableRow with Framer Motion
|
||||
|
||||
```tsx
|
||||
const ExpandableRow = ({
|
||||
children,
|
||||
expandedContent,
|
||||
isExpanded,
|
||||
onToggle
|
||||
}: ExpandableRowProps) => {
|
||||
return (
|
||||
<>
|
||||
<tr
|
||||
onClick={onToggle}
|
||||
className="cursor-pointer hover:bg-gray-50 transition-colors"
|
||||
>
|
||||
{children}
|
||||
</tr>
|
||||
<AnimatePresence>
|
||||
{isExpanded && (
|
||||
<motion.tr
|
||||
initial={{ height: 0, opacity: 0 }}
|
||||
animate={{ height: 'auto', opacity: 1 }}
|
||||
exit={{ height: 0, opacity: 0 }}
|
||||
transition={{ duration: 0.2, ease: 'easeInOut' }}
|
||||
>
|
||||
<td colSpan={4} className="p-0 border-b">
|
||||
{expandedContent}
|
||||
</td>
|
||||
</motion.tr>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
#### Document Type Icons
|
||||
|
||||
```tsx
|
||||
import { FileText, Award, GraduationCap, FlaskConical } from 'lucide-react';
|
||||
|
||||
const documentIcons = {
|
||||
certificate: FileText,
|
||||
registration: Award,
|
||||
academic: GraduationCap,
|
||||
research: FlaskConical,
|
||||
};
|
||||
|
||||
const DocumentIcon = ({ type }: { type: keyof typeof documentIcons }) => {
|
||||
const Icon = documentIcons[type];
|
||||
return <Icon className="w-4 h-4 text-gray-500" />;
|
||||
};
|
||||
```
|
||||
|
||||
#### Mobile Card Layout
|
||||
|
||||
On mobile (<768px), both views switch to card layouts:
|
||||
|
||||
```tsx
|
||||
// Mobile: Card layout with vertical stacking
|
||||
<div className="md:hidden space-y-3">
|
||||
{investigations.map((inv) => (
|
||||
<div key={inv.id} className="bg-white rounded border p-4">
|
||||
{/* Card content */}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
// Desktop: Table layout
|
||||
<table className="hidden md:table w-full">
|
||||
{/* Table content */}
|
||||
</table>
|
||||
```
|
||||
|
||||
### Tech Stack Integration
|
||||
|
||||
- **React 18** with TypeScript strict mode
|
||||
- **Tailwind CSS** for all styling (no CSS-in-JS)
|
||||
- **Framer Motion 11** for expand/collapse animations
|
||||
- **Lucide React** for document type icons
|
||||
- **Geist Mono** font for tree-indented content (add to index.html)
|
||||
|
||||
@@ -91,3 +91,180 @@ The history entries are styled in Geist Mono, 12px, with year markers as bold an
|
||||
## Sortable Columns
|
||||
|
||||
Table columns are sortable by clicking the header. Clicking "Dose" sorts by proficiency descending. Clicking "Start" sorts chronologically. A small sort indicator arrow appears in the active sort column header. Default sort: by category grouping.
|
||||
|
||||
---
|
||||
|
||||
## Design Guidance (from /frontend-design)
|
||||
|
||||
### Aesthetic Direction
|
||||
|
||||
**Clinical-Utilitarian / NHS PMR Fidelity**
|
||||
|
||||
This implementation follows the Clinical Record (Design 7) medications-as-skills metaphor with absolute fidelity to the specification. The aesthetic is clinical-utilitarian: light-mode only, border-heavy, table-driven, zero decorative flourish. Every design decision mirrors real NHS PMR systems (EMIS Web, SystmOne). The component is not themed loosely -- it is a faithful reproduction of how medications lists appear in actual GP clinical software, repurposed to present skills as active prescriptions.
|
||||
|
||||
**Purpose:** Present 18 professional skills as an active medications list that clinicians will instantly recognize and recruiters will find navigable and information-dense.
|
||||
|
||||
**Tone:** Institutional, functional, border-heavy. No shadows, no rounded corners beyond 4px, no gradients. Clinical systems are designed for rapid information retrieval under time pressure -- that same quality makes this an efficient skills display.
|
||||
|
||||
**Constraints followed:**
|
||||
- Light-mode ONLY (clinical systems are light-mode by design)
|
||||
- NHS blue `#005EB8` as the sole accent color
|
||||
- Border radius capped at 4px
|
||||
- Inter for all text, Geist Mono for prescribing history data
|
||||
- All borders `1px solid #E5E7EB`
|
||||
- No decorative elements whatsoever
|
||||
|
||||
**Differentiation:** The medications-as-skills mapping provides richer data than any traditional "skills list." Dose maps to proficiency, Frequency maps to usage patterns, Start maps to when the skill was acquired, and prescribing history shows the skill's evolution over time. This is not decoration -- it is genuinely useful information architecture.
|
||||
|
||||
### Key Design Decisions
|
||||
|
||||
#### 1. Three Category Tabs
|
||||
- **"Active Medications"** (8 technical skills), **"Clinical Medications"** (6 healthcare domain skills), **"PRN (As Required)"** (4 strategic/leadership skills)
|
||||
- Active tab: white background + NHS blue (`#005EB8`) 2px bottom border
|
||||
- Inactive tabs: `#F9FAFB` background, gray text, hover brightens
|
||||
- Count badges show the number of items per category
|
||||
- Full ARIA `role="tablist"`, `role="tab"`, `aria-selected`, `aria-controls` semantics
|
||||
|
||||
#### 2. Semantic HTML Table
|
||||
- Proper `<table>`, `<thead>`, `<th scope="col">`, `<tbody>`, `<tr>`, `<td>` markup
|
||||
- Five columns: Drug Name, Dose, Frequency, Start, Status
|
||||
- Headers: Inter 600, 13px, uppercase, 0.03em tracking, `#F9FAFB` background
|
||||
- Row height: 40px
|
||||
- Alternating `#FFFFFF` / `#F9FAFB` row backgrounds via CSS `:nth-child(even)`
|
||||
- Hover state: `#EFF6FF` (subtle blue tint) -- **no transform, no lift, no shadow**
|
||||
- Status dots: 6px green circles inline with "Active" text
|
||||
- All borders: `1px solid #E5E7EB`
|
||||
|
||||
#### 3. Sortable Columns
|
||||
- Click any header to sort (ascending/descending toggle)
|
||||
- ChevronUp, ChevronDown, or ChevronsUpDown indicator in header
|
||||
- Sorting logic handles string, numeric (dose %), and date (year) columns
|
||||
- Default: no sort (original order preserved)
|
||||
|
||||
#### 4. Expandable Prescribing History
|
||||
- Click any row (or arrow at row end) to expand
|
||||
- Uses Framer Motion `<AnimatePresence>` for smooth height animation (0.2s)
|
||||
- History entries styled in Geist Mono 12px
|
||||
- Year markers bold, descriptions regular weight
|
||||
- Format: `2017 Started: Self-taught for data analysis automation`
|
||||
- Vertical timeline with connecting line on left
|
||||
|
||||
#### 5. Mobile: Card Layout
|
||||
- Below 640px: Table hidden, cards displayed
|
||||
- Each card is a bordered block with stacked key-value pairs
|
||||
- No horizontal scroll required
|
||||
- Same expandable history behavior
|
||||
|
||||
### Implementation Patterns / Code Snippets
|
||||
|
||||
#### Types
|
||||
```typescript
|
||||
interface MedicationEntry {
|
||||
drugName: string
|
||||
dose: string
|
||||
frequency: string
|
||||
start: string
|
||||
status: 'Active'
|
||||
prescribingHistory: PrescribingEvent[]
|
||||
}
|
||||
|
||||
interface PrescribingEvent {
|
||||
year: string
|
||||
label: string
|
||||
description: string
|
||||
}
|
||||
|
||||
type MedicationCategory = 'active' | 'clinical' | 'prn'
|
||||
```
|
||||
|
||||
#### Tab Implementation
|
||||
```typescript
|
||||
const tabs: { key: MedicationCategory; label: string }[] = [
|
||||
{ key: 'active', label: 'Active Medications' },
|
||||
{ key: 'clinical', label: 'Clinical Medications' },
|
||||
{ key: 'prn', label: 'PRN (As Required)' },
|
||||
]
|
||||
|
||||
{tabs.map(({ key, label }) => (
|
||||
<button
|
||||
key={key}
|
||||
role="tab"
|
||||
aria-selected={category === key}
|
||||
aria-controls={`${key}-panel`}
|
||||
onClick={() => setCategory(key)}
|
||||
className={cn(
|
||||
'px-4 py-2 font-inter text-sm font-medium transition-colors',
|
||||
category === key
|
||||
? 'bg-white text-[#005EB8] border-b-2 border-[#005EB8]'
|
||||
: 'bg-[#F9FAFB] text-gray-600 hover:bg-white'
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
))}
|
||||
```
|
||||
|
||||
#### Table Row with Expand
|
||||
```typescript
|
||||
<tr
|
||||
onClick={() => toggleExpanded(drugName)}
|
||||
className="h-[40px] border-b border-[#E5E7EB] cursor-pointer transition-colors hover:bg-[#EFF6FF]"
|
||||
>
|
||||
<td className="px-4 py-2 text-sm font-medium text-gray-900">
|
||||
{drugName}
|
||||
</td>
|
||||
{/* ... other cells ... */}
|
||||
</tr>
|
||||
|
||||
<AnimatePresence>
|
||||
{isExpanded && (
|
||||
<motion.tr
|
||||
initial={{ height: 0, opacity: 0 }}
|
||||
animate={{ height: 'auto', opacity: 1 }}
|
||||
exit={{ height: 0, opacity: 0 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
>
|
||||
<td colSpan={5} className="px-4 py-3 bg-[#F9FAFB]">
|
||||
<div className="font-mono text-xs space-y-1">
|
||||
{prescribingHistory.map((event) => (
|
||||
<div key={event.year} className="flex gap-3">
|
||||
<span className="font-bold text-gray-700">{event.year}</span>
|
||||
<span className="text-gray-600">{event.label}:</span>
|
||||
<span className="text-gray-500">{event.description}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</td>
|
||||
</motion.tr>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
```
|
||||
|
||||
#### Sort Indicator
|
||||
```typescript
|
||||
const SortIndicator = ({ column }: { column: SortColumn }) => {
|
||||
if (sort.column !== column) {
|
||||
return <ChevronsUpDown className="w-3.5 h-3.5 text-gray-400" />
|
||||
}
|
||||
return sort.direction === 'asc'
|
||||
? <ChevronUp className="w-3.5 h-3.5 text-[#005EB8]" />
|
||||
: <ChevronDown className="w-3.5 h-3.5 text-[#005EB8]" />
|
||||
}
|
||||
```
|
||||
|
||||
#### Status Dot
|
||||
```typescript
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-green-500" />
|
||||
<span className="text-sm text-gray-700">Active</span>
|
||||
</div>
|
||||
```
|
||||
|
||||
#### Tailwind Classes Summary
|
||||
- **Container:** `border border-[#E5E7EB] rounded`
|
||||
- **Table headers:** `bg-[#F9FAFB] text-xs font-semibold uppercase tracking-wide text-gray-600`
|
||||
- **Row hover:** `hover:bg-[#EFF6FF]`
|
||||
- **Alternating rows:** `even:bg-[#F9FAFB] bg-white`
|
||||
- **Tab active:** `bg-white text-[#005EB8] border-b-2 border-[#005EB8]`
|
||||
- **Tab inactive:** `bg-[#F9FAFB] text-gray-600 hover:bg-white`
|
||||
- **Mono text:** `font-mono text-xs`
|
||||
|
||||
@@ -58,3 +58,141 @@ Each problem row can be expanded to show a full narrative: what the problem was,
|
||||
## Traffic Light Status Indicators
|
||||
|
||||
Traffic lights are 8px circles with the status colors (green, amber, red, gray). They appear inline before the code column. This is exactly how clinical systems indicate problem severity/status — it's an immediately scannable visual language.
|
||||
|
||||
---
|
||||
|
||||
## Design Guidance (from /frontend-design)
|
||||
|
||||
### Aesthetic Direction
|
||||
|
||||
**Clinical Utilitarian** — This is not a place for flashy gradients or playful animation. The metaphor demands the disciplined, functional aesthetic of an actual NHS clinical system. Think EMIS/SystmOne: white backgrounds, precise table layouts, 1px borders, quiet colour discipline. The visual power comes from the *content structure*, not decoration. The traffic light dots and expandable narratives do all the heavy lifting. Every pixel serves a clinical purpose.
|
||||
|
||||
The distinctiveness comes from the *concept itself* — framing career achievements as a Problem List is the creative act. The execution must be restrained to sell the metaphor.
|
||||
|
||||
### Key Design Decisions
|
||||
|
||||
1. **Traffic Light Status Indicators (WCAG Critical)**
|
||||
- 8px circles: green (`#22C55E`) for resolved, amber (`#F59E0B`) for in-progress
|
||||
- **MUST ALWAYS be paired with text labels** — never dots alone (WCAG 1.4.1 requirement)
|
||||
- Each status shows both the colored dot AND the text label (e.g., "● Resolved", "● In Progress")
|
||||
- Implementation uses flexbox with gap-2 for dot-label pairing
|
||||
|
||||
2. **Typography System**
|
||||
- **Inter** for all body text, headers, and UI labels — clean, clinical, readable
|
||||
- **Geist Mono** for codes and dates — SNOMED-style codes like `[EFF001]`, `[MGT001]` must be monospace
|
||||
- Font sizes: 13px for table headers (uppercase, tracking-wider), 14px for body text
|
||||
- Header styling: `font-inter font-semibold text-xs uppercase tracking-wider text-gray-400`
|
||||
|
||||
3. **Color Palette (Locked)**
|
||||
- Light-mode ONLY (clinical systems are light-mode by design)
|
||||
- NHS Blue: `#005EB8` (Tailwind `text-pmr-nhsblue`) — used for links and accents
|
||||
- Borders: `1px solid #E5E7EB` (gray-200) — consistent table borders
|
||||
- Row hover: `#EFF6FF` (blue-50) — subtle clinical highlight
|
||||
- Background: White cards on `#F5F7FA` (pmr-content) background
|
||||
- Border radius: 4px max — clinical systems use sharp corners
|
||||
|
||||
4. **Table Structure**
|
||||
- Semantic HTML: `<table>`, `<thead>`, `<th scope="col">`, `<tbody>`, `<tr>`, `<td>`
|
||||
- Two separate tables: Active Problems (4 columns) and Resolved Problems (6 columns)
|
||||
- Column widths fixed for Status (w-28), Code (w-28), Since/Resolved (w-28)
|
||||
- Alternating row backgrounds not used — clean white with hover state only
|
||||
|
||||
5. **Expandable Rows Pattern**
|
||||
- Chevron icon in rightmost column indicates expandability
|
||||
- Expanded content shows in a full-width sub-row below
|
||||
- Animation: height transition 200ms ease-out (respects prefers-reduced-motion)
|
||||
- Expanded background: `#F9FAFB` (gray-50) with narrative text and linked consultations
|
||||
|
||||
6. **Mobile Layout**
|
||||
- Card-based layout below breakpoint (isMobile from useBreakpoint hook)
|
||||
- Each problem becomes a rounded card with stacked information
|
||||
- Status and code on same line, problem description prominent
|
||||
- Expandable via button press, showing narrative and linked consultations
|
||||
|
||||
### Implementation Patterns
|
||||
|
||||
**TrafficLight Component (WCAG Compliant):**
|
||||
```tsx
|
||||
function TrafficLight({ status }: { status: ProblemStatus }) {
|
||||
const colorMap: Record<ProblemStatus, { bg: string; label: string }> = {
|
||||
Active: { bg: 'bg-green-500', label: 'Active' },
|
||||
'In Progress': { bg: 'bg-amber-500', label: 'In Progress' },
|
||||
Resolved: { bg: 'bg-green-500', label: 'Resolved' },
|
||||
}
|
||||
|
||||
const { bg, label } = colorMap[status]
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<span
|
||||
className={`w-2 h-2 rounded-full ${bg}`}
|
||||
aria-label={`Status: ${label}`}
|
||||
role="img"
|
||||
/>
|
||||
<span className="text-xs text-gray-600">{label}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
**Code Column (Geist Mono):**
|
||||
```tsx
|
||||
<td className="border border-gray-200 px-3 py-2.5">
|
||||
<span className="font-mono text-xs text-gray-500">[{problem.code}]</span>
|
||||
</td>
|
||||
```
|
||||
|
||||
**Row Hover Effect:**
|
||||
```tsx
|
||||
<tr className={`cursor-pointer hover:bg-blue-50 transition-colors ${
|
||||
isExpanded ? 'bg-blue-50' : ''
|
||||
}`}>
|
||||
```
|
||||
|
||||
**Expandable Row Animation:**
|
||||
```tsx
|
||||
<div
|
||||
style={{
|
||||
height: isExpanded ? contentHeight : 0,
|
||||
overflow: 'hidden',
|
||||
transition: prefersReducedMotion ? 'none' : 'height 200ms ease-out',
|
||||
}}
|
||||
>
|
||||
<div ref={contentRef} className="bg-gray-50 p-4">
|
||||
{/* Narrative content */}
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
**Linked Consultations Navigation:**
|
||||
```tsx
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
handleLinkedClick(consultation.id)
|
||||
}}
|
||||
className="inline-flex items-center gap-1 text-xs text-pmr-nhsblue hover:underline"
|
||||
>
|
||||
<ExternalLink className="w-3 h-3" />
|
||||
{consultation.organization} — {consultation.role}
|
||||
</button>
|
||||
```
|
||||
|
||||
### Mobile Card Layout
|
||||
|
||||
On mobile devices (`isMobile` from useBreakpoint hook), the table transforms into cards:
|
||||
- White background cards with `border border-gray-200 rounded`
|
||||
- Status dot + code on one line
|
||||
- Problem description as card title
|
||||
- Since/Resolved date below
|
||||
- Chevron indicates expandability
|
||||
- Expanded state shows narrative and linked consultations below
|
||||
|
||||
### Accessibility Requirements
|
||||
|
||||
1. **WCAG 1.4.1 Use of Color**: Never rely on color alone — traffic lights MUST have text labels
|
||||
2. **Semantic HTML**: Proper `<table>` structure with `<th scope="col">` for headers
|
||||
3. **ARIA**: `aria-expanded` on toggle buttons, `aria-label` on status dots
|
||||
4. **Motion**: Respect `prefers-reduced-motion` for expand/collapse animations
|
||||
5. **Focus management**: Linked consultation buttons are keyboard navigable
|
||||
|
||||
|
||||
+145
-58
@@ -1,72 +1,159 @@
|
||||
# Reference: Referrals View (= Contact)
|
||||
|
||||
> Extracted from goal.md — Referrals View section. Contact information presented as a clinical referral form.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
## Design Guidance (from /frontend-design)
|
||||
|
||||
Contact information presented as a clinical referral form — the mechanism for "referring" a patient (Andy) to another service.
|
||||
### Aesthetic Direction
|
||||
|
||||
## Referral Form Layout
|
||||
**Tone: Clinical Utilitarian** — This is an NHS-styled patient medical record system. The aesthetic is intentionally institutional: clean, functional, bureaucratic-in-a-charming-way. The humor comes from the deadpan application of clinical form conventions to a personal contact form. No flourishes, no gradients, no decorative frills. The beauty is in the precision of the grid, the crispness of the type hierarchy, and the tongue-in-cheek seriousness of it all.
|
||||
|
||||
```
|
||||
+--[ New Referral ]-------------------------------------------------------+
|
||||
| |
|
||||
| Referring to: CHARLWOOD, Andrew (Mr) |
|
||||
| NHS Number: 221 181 0 |
|
||||
| |
|
||||
| Priority: ( ) Urgent (*) Routine ( ) Two-Week Wait |
|
||||
| |
|
||||
| Referrer Name: [________________________] |
|
||||
| Referrer Email: [________________________] |
|
||||
| Referrer Org: [________________________] (optional) |
|
||||
| |
|
||||
| Reason for Referral: |
|
||||
| [ ] |
|
||||
| [ ] |
|
||||
| [ ] |
|
||||
| |
|
||||
| Contact Method: ( ) Email ( ) Phone ( ) LinkedIn |
|
||||
| |
|
||||
| [ Cancel ] [ Send Referral ] |
|
||||
+-------------------------------------------------------------------------+
|
||||
**What makes it memorable**: The collision between NHS clinical form seriousness and the fact that you are "referring" to a person's contact page. The pre-filled "patient" header, the priority radio buttons with their wry tooltips, the reference number generation — all of this is a joke delivered with a completely straight face.
|
||||
|
||||
### Key Design Decisions
|
||||
|
||||
**Priority Radio Buttons (urgent/routine/2-week-wait)**
|
||||
|
||||
| Priority | Color | Tooltip |
|
||||
|----------|-------|---------|
|
||||
| Urgent | Red (`#EF4444`) | "All enquiries are welcome, urgent or not." |
|
||||
| Routine | NHS Blue (`#005EB8`) | (default, no tooltip needed) |
|
||||
| Two-Week Wait | Amber (`#F59E0B`) | "NHS cancer referral pathway — this isn't that, but the spirit of promptness applies." |
|
||||
|
||||
Each priority option features a colored dot indicator and supports hover tooltips via a tooltip component pattern.
|
||||
|
||||
**Form Validation Patterns**
|
||||
|
||||
- Real-time validation on blur
|
||||
- Error messages appear below invalid fields in red (`text-red-600`)
|
||||
- Disabled submit button until required fields are valid
|
||||
- Required fields: Referrer Name, Referrer Email
|
||||
- Email validation uses standard pattern matching
|
||||
- Organization field is optional
|
||||
|
||||
**Design System Constraints (Locked)**
|
||||
|
||||
| Token | Value | Tailwind Class |
|
||||
|-------|-------|----------------|
|
||||
| NHS Blue | `#005EB8` | `text-pmr-nhsblue` / `bg-pmr-nhsblue` |
|
||||
| Card border | `1px solid #E5E7EB` | `border-pmr-border` |
|
||||
| Input border | `1px solid #D1D5DB` | `border-pmr-border-dark` |
|
||||
| Border radius | `4px` | `rounded` |
|
||||
| Label font | Inter 500, 13px, gray-600 | `font-inter font-medium text-sm text-gray-600` |
|
||||
| Mono font | Geist Mono | `font-mono` (reference numbers) |
|
||||
| Input padding | `8px 12px` | `py-2 px-3` |
|
||||
| Focus state | NHS blue border + glow | `focus:border-pmr-nhsblue focus:ring-2 focus:ring-pmr-nhsblue/15` |
|
||||
|
||||
### Implementation Patterns/Code Snippets
|
||||
|
||||
**Priority Option Component Pattern**
|
||||
|
||||
```tsx
|
||||
type Priority = 'urgent' | 'routine' | 'two-week-wait'
|
||||
|
||||
const dotColors: Record<Priority, string> = {
|
||||
urgent: 'bg-red-500',
|
||||
routine: 'bg-pmr-nhsblue',
|
||||
'two-week-wait': 'bg-amber-500',
|
||||
}
|
||||
|
||||
const labelColors: Record<Priority, string> = {
|
||||
urgent: 'text-red-600',
|
||||
routine: 'text-pmr-nhsblue',
|
||||
'two-week-wait': 'text-amber-600',
|
||||
}
|
||||
```
|
||||
|
||||
## Priority Toggle (tongue-in-cheek)
|
||||
**Form Input Styling Pattern**
|
||||
|
||||
Three radio options styled like clinical referral priorities:
|
||||
- **Urgent**: Red label, red dot. Selectable but the tooltip reads "All enquiries are welcome, urgent or not."
|
||||
- **Routine**: Blue label, blue dot. Default selected.
|
||||
- **Two-Week Wait**: Amber label. Tooltip: "NHS cancer referral pathway - this isn't that, but the spirit of promptness applies."
|
||||
```tsx
|
||||
// Standard clinical form input
|
||||
<input
|
||||
className="w-full px-3 py-2 border border-pmr-border-dark rounded
|
||||
text-sm text-gray-900 placeholder-gray-400
|
||||
focus:outline-none focus:border-pmr-nhsblue
|
||||
focus:ring-2 focus:ring-pmr-nhsblue/15
|
||||
transition-all duration-200"
|
||||
/>
|
||||
|
||||
This is the design's main moment of humor. The priority options are visually authentic to clinical referral forms, and the tongue-in-cheek tooltips reward exploration without undermining the professional tone.
|
||||
|
||||
## Form Fields
|
||||
|
||||
Standard clinical form inputs: `1px solid #D1D5DB` border, `4px` radius, `8px 12px` padding. Labels in Inter 500, 13px, gray-600, positioned above inputs. Focus state: border changes to NHS blue, subtle blue glow (`box-shadow: 0 0 0 3px rgba(0, 94, 184, 0.15)`).
|
||||
|
||||
## Submit Button
|
||||
|
||||
"Send Referral" in NHS blue (`#005EB8`), white text, full width of the right half of the form. On hover: darkens to `#004494`. On click: brief loading state (spinner icon), then a success message:
|
||||
|
||||
```
|
||||
Checkmark Referral sent successfully
|
||||
Reference: REF-2026-0210-001
|
||||
Expected response time: 24-48 hours
|
||||
// Label styling
|
||||
<label className="block text-sm font-medium text-gray-600 mb-1.5 font-inter">
|
||||
Field Label
|
||||
</label>
|
||||
```
|
||||
|
||||
The reference number is generated from the current date. The success state mimics the confirmation screen shown after submitting a clinical referral in EMIS.
|
||||
**Reference Number Generation**
|
||||
|
||||
## Alternative Contact Methods (below the form)
|
||||
|
||||
```
|
||||
+--[ Direct Contact ]-----------------------------------------------------+
|
||||
| Email: andy@charlwood.xyz [Send Email ->] |
|
||||
| Phone: 07795553088 [Call ->] |
|
||||
| LinkedIn: linkedin.com/in/andycharlwood [View Profile ->] |
|
||||
| Location: Norwich, UK |
|
||||
+-------------------------------------------------------------------------+
|
||||
```tsx
|
||||
function generateRefNumber(): string {
|
||||
const now = new Date()
|
||||
const year = now.getFullYear()
|
||||
const month = String(now.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(now.getDate()).padStart(2, '0')
|
||||
const seq = String(Math.floor(Math.random() * 999) + 1).padStart(3, '0')
|
||||
return `REF-${year}-${month}${day}-${seq}`
|
||||
}
|
||||
```
|
||||
|
||||
Styled as a simple key-value table, same format as the Patient Demographics card in Summary view.
|
||||
**Form State Management Pattern**
|
||||
|
||||
```tsx
|
||||
interface FormData {
|
||||
priority: Priority
|
||||
referrerName: string
|
||||
referrerEmail: string
|
||||
referrerOrg: string
|
||||
reason: string
|
||||
contactMethod: ContactMethod
|
||||
}
|
||||
|
||||
interface FormErrors {
|
||||
referrerName?: string
|
||||
referrerEmail?: string
|
||||
}
|
||||
|
||||
// Validation on submit
|
||||
const validate = (): boolean => {
|
||||
const errors: FormErrors = {}
|
||||
if (!formData.referrerName.trim()) {
|
||||
errors.referrerName = 'Referrer name is required'
|
||||
}
|
||||
if (!formData.referrerEmail.trim()) {
|
||||
errors.referrerEmail = 'Email is required'
|
||||
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.referrerEmail)) {
|
||||
errors.referrerEmail = 'Please enter a valid email'
|
||||
}
|
||||
setErrors(errors)
|
||||
return Object.keys(errors).length === 0
|
||||
}
|
||||
```
|
||||
|
||||
**Success State Pattern**
|
||||
|
||||
```tsx
|
||||
// After form submission
|
||||
<div className="text-center py-8">
|
||||
<CheckCircle className="w-12 h-12 text-green-500 mx-auto mb-4" />
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-1">
|
||||
Referral sent successfully
|
||||
</h3>
|
||||
<p className="text-sm text-gray-600 font-mono mb-1">
|
||||
Reference: {refNumber}
|
||||
</p>
|
||||
<p className="text-sm text-gray-500">
|
||||
Expected response time: 24-48 hours
|
||||
</p>
|
||||
</div>
|
||||
```
|
||||
|
||||
**Contact Method Radio Pattern**
|
||||
|
||||
```tsx
|
||||
type ContactMethod = 'email' | 'phone' | 'linkedin'
|
||||
|
||||
// Radio button with icon
|
||||
<div className="flex items-center gap-3 p-3 border border-pmr-border-dark rounded
|
||||
cursor-pointer hover:bg-gray-50 transition-colors">
|
||||
<input type="radio" className="sr-only" />
|
||||
<Mail className="w-4 h-4 text-gray-500" />
|
||||
<span className="text-sm text-gray-700">Email</span>
|
||||
</div>
|
||||
```
|
||||
@@ -114,3 +114,196 @@ WARNING NOTE: Patient has developed a Python-based switching algorithm
|
||||
```
|
||||
|
||||
This second alert reinforces the key technical achievement in clinical language. It appears only once (on first navigation to Consultations) and is dismissible with the same "Acknowledge" interaction.
|
||||
|
||||
---
|
||||
|
||||
## Design Guidance (from /frontend-design)
|
||||
|
||||
### Aesthetic Direction
|
||||
|
||||
**Clinical Precision Meets Professional Polish**
|
||||
|
||||
The NHS clinical record aesthetic draws from real-world electronic patient record systems (EPR), balancing institutional gravitas with polished execution. Key visual principles:
|
||||
|
||||
- **Light-mode ONLY** — Consistent with clinical systems that prioritize readability over dark aesthetics
|
||||
- **NHS blue (#005EB8)** — Institutional anchor color for headers and accents
|
||||
- **Card-based architecture** — All information lives in contained, bordered cards (1px solid #E5E7EB, 4px border-radius)
|
||||
- **Monospace for data** — Geist Mono for all coded entries, dates, and numerical values (creates clinical system authenticity)
|
||||
- **High information density** — Compact layouts that maximize data visibility (16px card padding, tight line-heights)
|
||||
- **Status dots** — Green/amber/red traffic light indicators for at-a-glance status assessment
|
||||
|
||||
### Key Design Decisions
|
||||
|
||||
**1. Spring Animation for Alert Slide-Down**
|
||||
|
||||
The Clinical Alert uses a spring animation (Framer Motion `type: 'spring'`) rather than ease-out. This creates a subtle overshoot effect that feels "alive" — mimicking how real clinical alerts materialize in systems like EMIS or SystmOne.
|
||||
|
||||
```
|
||||
Initial state: y: -100%, opacity: 0
|
||||
Animate to: y: 0, opacity: 1
|
||||
type: 'spring', stiffness: 300, damping: 25
|
||||
```
|
||||
|
||||
**2. Acknowledge → Checkmark → Collapse Sequence**
|
||||
|
||||
The dismissal interaction follows a deliberate three-phase sequence:
|
||||
|
||||
1. **Acknowledge click** (0ms) — Button triggers dismissal state
|
||||
2. **Icon cross-fade** (200ms) — AlertTriangle fades out, CheckCircle fades in (green-600)
|
||||
3. **Hold beat** (200ms) — Checkmark holds briefly to confirm action completion
|
||||
4. **Height collapse** (200ms ease-out) — Alert height animates to 0, content slides up
|
||||
|
||||
This sequence transforms dismissal from a jarring disappearance into a satisfying confirmation action.
|
||||
|
||||
**3. Typography Hierarchy**
|
||||
|
||||
- **Card headers**: Inter 600, 14px, uppercase, letter-spacing-wide — creates clear section delineation
|
||||
- **Labels**: Inter 500, 13px, gray-500, right-aligned — mimics clinical form layout
|
||||
- **Values**: Inter 400, 14px, gray-900, left-aligned — primary data focus
|
||||
- **Coded values**: Geist Mono, 12px — all dates, IDs, percentages, status codes
|
||||
|
||||
### Implementation Patterns
|
||||
|
||||
**ClinicalAlert Component**
|
||||
|
||||
```typescript
|
||||
// State machine for alert lifecycle
|
||||
type AlertState = 'visible' | 'acknowledging' | 'dismissing' | 'dismissed'
|
||||
|
||||
// Props interface
|
||||
interface ClinicalAlertProps {
|
||||
variant: 'warning' | 'note'
|
||||
icon: typeof AlertTriangle | typeof Info
|
||||
message: string
|
||||
onDismiss: () => void
|
||||
storageKey?: string // For session persistence
|
||||
}
|
||||
|
||||
// Animation variants
|
||||
const alertVariants = {
|
||||
hidden: { y: '-100%', opacity: 0 },
|
||||
visible: {
|
||||
y: 0,
|
||||
opacity: 1,
|
||||
transition: { type: 'spring', stiffness: 300, damping: 25 }
|
||||
},
|
||||
exit: {
|
||||
height: 0,
|
||||
opacity: 0,
|
||||
transition: { duration: 0.2, ease: 'easeOut' }
|
||||
}
|
||||
}
|
||||
|
||||
const iconVariants = {
|
||||
warning: { scale: 1, opacity: 1 },
|
||||
acknowledged: {
|
||||
scale: [1, 1.1, 1],
|
||||
opacity: [1, 0],
|
||||
transition: { duration: 0.2 }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**SummaryView Component**
|
||||
|
||||
```typescript
|
||||
// Grid layout structure
|
||||
const layoutConfig = {
|
||||
container: 'grid grid-cols-1 md:grid-cols-2 gap-4',
|
||||
demographics: 'col-span-full', // Spans both columns
|
||||
problems: 'col-span-1',
|
||||
medications: 'col-span-1',
|
||||
consultation: 'col-span-full'
|
||||
}
|
||||
|
||||
// Card header pattern
|
||||
const CardHeader = ({ title }: { title: string }) => (
|
||||
<div className="bg-[#F9FAFB] border-b border-[#E5E7EB] px-4 py-3">
|
||||
<h3 className="font-inter font-semibold text-sm uppercase tracking-wide">
|
||||
{title}
|
||||
</h3>
|
||||
</div>
|
||||
)
|
||||
|
||||
// Key-value row pattern (for Demographics)
|
||||
interface KeyValueRowProps {
|
||||
label: string
|
||||
value: string
|
||||
isMono?: boolean
|
||||
}
|
||||
|
||||
const KeyValueRow = ({ label, value, isMono }: KeyValueRowProps) => (
|
||||
<div className="grid grid-cols-[1fr_auto] gap-4 py-1">
|
||||
<span className="font-inter font-medium text-[13px] text-gray-500 text-right">
|
||||
{label}
|
||||
</span>
|
||||
<span className={`font-inter text-sm text-gray-900 text-left ${isMono ? 'font-geist-mono' : ''}`}>
|
||||
{value}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
|
||||
// Problem list pattern with traffic lights
|
||||
interface ProblemItemProps {
|
||||
status: 'active' | 'in-progress'
|
||||
title: string
|
||||
date: string
|
||||
onClick?: () => void
|
||||
}
|
||||
|
||||
const ProblemItem = ({ status, title, date, onClick }: ProblemItemProps) => (
|
||||
<div
|
||||
onClick={onClick}
|
||||
className="flex items-center gap-3 py-2 hover:bg-gray-50 cursor-pointer transition-colors"
|
||||
>
|
||||
<div className={cn(
|
||||
'w-2 h-2 rounded-full',
|
||||
status === 'active' ? 'bg-green-500' : 'bg-amber-500'
|
||||
)} />
|
||||
<span className="flex-1 font-inter font-medium text-sm">{title}</span>
|
||||
<span className="font-geist-mono text-xs text-gray-500">{date}</span>
|
||||
</div>
|
||||
)
|
||||
```
|
||||
|
||||
**Animation Constants**
|
||||
|
||||
```typescript
|
||||
// Timing constants (ms)
|
||||
export const ANIMATION = {
|
||||
SPRING_DURATION: 250,
|
||||
ICON_CROSSFADE: 200,
|
||||
HOLD_BEAT: 200,
|
||||
COLLAPSE_DURATION: 200
|
||||
} as const
|
||||
|
||||
// Easing
|
||||
export const EASING = {
|
||||
spring: { type: 'spring', stiffness: 300, damping: 25 },
|
||||
easeOut: { ease: 'easeOut' }
|
||||
} as const
|
||||
```
|
||||
|
||||
### Color Palette
|
||||
|
||||
```css
|
||||
/* NHS System Colors */
|
||||
--nhs-blue: #005EB8;
|
||||
--nhs-light-blue: #41B6E6;
|
||||
|
||||
/* Alert Colors */
|
||||
--amber-100: #FEF3C7;
|
||||
--amber-500: #F59E0B;
|
||||
--amber-600: #D97706;
|
||||
--amber-800: #92400E;
|
||||
--green-500: #22C55E;
|
||||
--green-600: #16A34A;
|
||||
|
||||
/* UI Colors */
|
||||
--gray-50: #F9FAFB;
|
||||
--gray-100: #F3F4F6;
|
||||
--gray-400: #9CA3AF;
|
||||
--gray-500: #6B7280;
|
||||
--gray-900: #111827;
|
||||
--border: #E5E7EB;
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user