Files
portfolio/Ralph/refs/ref-07-interactions.md
T
admin c88ceba136 Update progress: Task 1 completed (design tokens)
Also includes manual intervention files: updated CLAUDE.md,
IMPLEMENTATION_PLAN.md, and ref files for GP System Dashboard redesign.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 17:01:29 +00:00

9.1 KiB

Reference: Tasks 16-18 — Interactions

Task 16: Tile Expansion System

Overview

Three tiles have expandable items: CareerActivity (roles), Projects, and CoreSkills. Clicking an item expands it in-place to reveal detail, like expanding a clinical record entry.

Expansion Pattern (consistent across all tiles)

Animation:

  • Framer Motion AnimatePresence + motion.div
  • Height-only animation: 200ms, ease-out
  • No opacity fade on content (guardrail)
  • overflow: hidden on the animated container
<AnimatePresence initial={false}>
  {isExpanded && (
    <motion.div
      initial={{ height: 0 }}
      animate={{ height: 'auto' }}
      exit={{ height: 0 }}
      transition={prefersReducedMotion ? { duration: 0 } : { duration: 0.2, ease: 'easeOut' }}
      style={{ overflow: 'hidden' }}
    >
      {/* expanded content */}
    </motion.div>
  )}
</AnimatePresence>

Behavior:

  • Single-expand accordion: only one item expanded at a time within each tile
  • Click expanded item again to collapse
  • Click different item: collapses current, expands new
  • State: expandedItemId: string | null in each tile component

Keyboard:

  • Enter/Space: toggle expand/collapse
  • Escape: collapse current item
  • aria-expanded on each clickable item

Visual:

  • Expanded content has slightly different background (var(--bg) or subtle border-left)
  • Colored left border on expanded panel (accent color for roles, amber for projects, teal for skills)
  • Content padding: 12-16px

CareerActivity Expansion (roles)

When a role-type activity item is expanded:

  • Show full role details from corresponding consultation entry
  • Structure: role title, organization, date range
  • Achievement bullets (examination array from consultation)
  • Coded entries if available
  • Match expanded content to consultations data by mapping activity item to consultation

Projects Expansion

When a project item is expanded:

  • Show from investigation data:
    • Methodology
    • Tech stack (as tags or inline list)
    • Results (bulleted)
    • External URL link if available ("View Results" button)

CoreSkills Expansion

When a skill item is expanded:

  • Show "prescribing history" — a timeline of skill development
  • Data source: import { medications } from '@/data/medications' (NOT skills.ts). The medications.ts file has 18 entries, each with a prescribingHistory array of { year, description } entries. Map from skills.ts to medications.ts by matching skill name to medication name (e.g., "Data Analysis" in skills.ts → find the medication with name: "Data Analysis" in medications.ts to get its prescribingHistory).
  • Format: vertical timeline with year markers and descriptions
    • Timeline dots: accent color, 6px, with connecting line
    • Year: mono font, 12px, semibold
    • Description: 12px, regular

Task 17: KPI Flip Cards

Overview

In the LatestResults tile, each metric card can be clicked to "flip" and reveal an explanation of that KPI.

Flip Animation

CSS Perspective approach:

.metric-card {
  perspective: 1000px;
  cursor: pointer;
}

.metric-card-inner {
  transition: transform 0.4s ease-in-out;
  transform-style: preserve-3d;
  position: relative;
}

.metric-card-inner.flipped {
  transform: rotateY(180deg);
}

.metric-card-front,
.metric-card-back {
  backface-visibility: hidden;
  position: absolute;
  inset: 0;
}

.metric-card-back {
  transform: rotateY(180deg);
}

Or use Framer Motion animate={{ rotateY: isFlipped ? 180 : 0 }} with perspective on parent.

Behavior:

  • Click to flip front → back
  • Click again to flip back → front
  • Only one card flipped at a time (clicking another card flips the current one back)
  • State: flippedCardId: string | null in LatestResultsTile

Front face: Current metric display (value + label + sub) — same as Task 10.

Back face:

  • background: var(--accent-light) (subtle teal tint)
  • padding: 14px
  • Text: 12px, text-secondary, line-height: 1.5
  • The explanation text from KPI data's explanation field

Reduced motion:

  • No 3D flip animation
  • Instant content swap (front → back)
  • Could use a simple crossfade or just replace content immediately

Keyboard:

  • Enter/Space to flip
  • Each metric card should be tabIndex={0} with appropriate aria-label

KPI Explanations (from src/data/kpis.ts):

  • £220M: Budget management with forecasting models
  • £14.6M: Efficiency programme through data analysis
  • 9+ Years: NHS service progression since 2016
  • 12: Cross-functional team leadership

Task 18: Command Palette

File: src/components/CommandPalette.tsx

Trigger

  • Ctrl+K (global keydown listener on document)
  • Click on TopBar search bar (or focus on search input)
  • The TopBar search input does NOT do inline search — it opens the palette

Overlay

  • position: fixed, inset: 0
  • background: rgba(26,43,42,0.45)
  • backdrop-filter: blur(4px)
  • z-index: 1000
  • Fade in: opacity: 0 → 1, visibility: hidden → visible, 200ms transition
  • Click overlay (outside modal) to close

Palette Modal

  • width: 580px, max-height: 520px
  • background: var(--surface) (#FFFFFF)
  • border-radius: 12px
  • box-shadow: 0 20px 60px rgba(26,43,42,0.2), 0 0 0 1px rgba(26,43,42,0.08)
  • overflow: hidden
  • Entrance: transform: scale(0.97) translateY(-8px)scale(1) translateY(0), 200ms cubic-bezier

Search Input

  • Flex row: search icon (18px, accent) + input + "ESC" hint badge
  • padding: 14px 18px, border-bottom: 1px solid var(--border-light)
  • Input: 15px, font-body, placeholder "Search records, experience, skills..."
  • ESC badge: mono 10px, tertiary, bg var(--bg), border, padding 2px 7px, radius 4px

Results Area

  • overflow-y: auto, padding: 8px, flex: 1
  • Custom scrollbar (4px)

Result Sections

Section label: 10px, 600 weight, uppercase, letter-spacing: 0.08em, text-tertiary, padding: 8px 10px 5px

Result Items

  • display: flex, align-items: center, gap: 10px
  • padding: 9px 10px, border-radius: var(--radius-sm) (6px)
  • cursor: pointer, transition: background 0.1s
  • 13px, text-primary
  • Hover/selected: background: var(--accent-light)
  • Selected also gets: outline: 1.5px solid var(--accent-border)

Item structure:

  • Icon container: 28px square, 6px radius, colored bg per section
    • Experience: teal
    • Core Skills: green
    • Active Projects: amber
    • Achievements: amber
    • Education: purple
    • Quick Actions: teal
  • Text: title (500 weight) + subtitle (11px, tertiary, truncated)
  • Optional badge: 10px, mono, tertiary

Adapt existing src/lib/search.ts (fuse.js v7.0.0, already installed):

Existing code: src/lib/search.ts has buildSearchIndex() which creates a Fuse index from consultations, medications, problems, investigations, and documents. It groups results by sectionLabel via groupResultsBySection(). The SearchResult interface has { id, title, section: ViewId, sectionLabel, highlight }.

What needs changing:

  • The section: ViewId field is designed for view-switching navigation (navigating to #consultations, #medications, etc.). The new dashboard has no views — it's a single scrollable page. Results should either scroll to the relevant tile or expand an item within a tile.
  • Add skills.ts data to the index (currently only medications.ts is indexed, not the new 5-skill entries)
  • Add kpis.ts data to the index
  • Add Quick Actions (Download CV, Send Email, View LinkedIn, View Projects)
  • Update section labels to match palette grouping: "Experience", "Core Skills", "Active Projects", "Achievements", "Education", "Quick Actions"
  • Add an action field to SearchResult so each result knows what to do when selected (scroll to tile, expand item, open link, etc.)

Config (keep existing):

  • threshold: 0.3, weighted keys (title: 2, content: 1)
  • minMatchCharLength: 2
  • Group results by section
  • Highlight matching text in titles using <mark> with accent-light background

Keyboard Navigation

  • Arrow Down/Up: move selection through results
  • Enter: select highlighted result (navigate to section or trigger action)
  • Escape: close palette
  • selectedIndex state tracks which result is highlighted
  • Auto-scroll highlighted result into view

Quick Actions Section

Title Subtitle Action
Download CV Export as PDF Trigger download
Send Email andy@charlwood.xyz mailto: link
View LinkedIn Professional profile External link
View Projects GitHub & portfolio External link
  • display: flex, gap: 12px
  • padding: 10px 18px, border-top: 1px solid var(--border-light)
  • 11px, text-tertiary
  • Keyboard hints: ↑ ↓ Navigate, Enter Select, Esc Close
  • Each key in <kbd> styled element

Reduced Motion

  • No scale/translate entrance animation
  • Instant show/hide (opacity only, or immediate)

State Management

const [isOpen, setIsOpen] = useState(false)
const [query, setQuery] = useState('')
const [selectedIndex, setSelectedIndex] = useState(-1)

Render the palette at the DashboardLayout level so it overlays everything.