From 9276955fa8250f0231af32c5cf1f5afaa4ff130b Mon Sep 17 00:00:00 2001 From: Andy Charlwood Date: Mon, 16 Feb 2026 14:35:15 +0000 Subject: [PATCH] refactor: extract PlayPauseButton + screen-reader-description from orchestrator Reduces CareerConstellation orchestrator from 334 to 285 lines to meet the <300 line success criterion. --- .../constellation/CareerConstellation.tsx | 65 +++---------------- .../constellation/PlayPauseButton.tsx | 48 ++++++++++++++ .../screen-reader-description.ts | 25 +++++++ 3 files changed, 81 insertions(+), 57 deletions(-) create mode 100644 src/components/constellation/PlayPauseButton.tsx create mode 100644 src/components/constellation/screen-reader-description.ts diff --git a/src/components/constellation/CareerConstellation.tsx b/src/components/constellation/CareerConstellation.tsx index e98f266..9d6e9e2 100644 --- a/src/components/constellation/CareerConstellation.tsx +++ b/src/components/constellation/CareerConstellation.tsx @@ -1,6 +1,6 @@ import React, { useRef, useEffect, useState, useCallback, useMemo } from 'react' import * as d3 from 'd3' -import { constellationNodes, roleSkillMappings } from '@/data/constellation' +import { constellationNodes } from '@/data/constellation' import { timelineEntities } from '@/data/timeline' import { useForceSimulation, getHeight } from '@/hooks/useForceSimulation' import { useConstellationHighlight } from '@/hooks/useConstellationHighlight' @@ -9,6 +9,8 @@ import { useTimelineAnimation } from '@/hooks/useTimelineAnimation' import { MobileAccordion } from './MobileAccordion' import { ConstellationLegend } from './ConstellationLegend' import { AccessibleNodeOverlay } from './AccessibleNodeOverlay' +import { PlayPauseButton } from './PlayPauseButton' +import { srDescription } from './screen-reader-description' import { MIN_HEIGHT, SKILL_RADIUS_DEFAULT, SKILL_RADIUS_ACTIVE, @@ -26,29 +28,6 @@ interface CareerConstellationProps { const nodeById = new Map(constellationNodes.map(node => [node.id, node])) const careerEntityById = new Map(timelineEntities.map(entity => [entity.id, entity])) -const srDescription = buildScreenReaderDescription() - -function buildScreenReaderDescription(): string { - const entities = constellationNodes.filter(n => n.type === 'role' || n.type === 'education') - const skills = constellationNodes.filter(n => n.type === 'skill') - - const entityDescriptions = entities.map(entity => { - const mapping = roleSkillMappings.find(m => m.roleId === entity.id) - const skillNames = mapping - ? mapping.skillIds - .map(sid => skills.find(s => s.id === sid)?.label) - .filter(Boolean) - .join(', ') - : '' - const yearRange = entity.endYear - ? `${entity.startYear}-${entity.endYear}` - : `${entity.startYear}-present` - return `${entity.label} at ${entity.organization} (${yearRange}): ${skillNames}` - }) - - return `Career constellation graph showing ${entities.length} roles and ${skills.length} skills in reverse-chronological order along a vertical timeline, with the most recent role at the top. ` + - entityDescriptions.join('. ') + '.' -} const CareerConstellation: React.FC = ({ onRoleClick, @@ -252,39 +231,11 @@ const CareerConstellation: React.FC = ({ {!prefersReducedMotion && ( - + )}

void + isMobile: boolean +} + +export const PlayPauseButton: React.FC = ({ isPlaying, onToggle, isMobile }) => { + const size = isMobile ? 44 : 36 + const offset = isMobile ? 8 : 12 + + return ( + + ) +} diff --git a/src/components/constellation/screen-reader-description.ts b/src/components/constellation/screen-reader-description.ts new file mode 100644 index 0000000..0c084e9 --- /dev/null +++ b/src/components/constellation/screen-reader-description.ts @@ -0,0 +1,25 @@ +import { constellationNodes, roleSkillMappings } from '@/data/constellation' + +function buildScreenReaderDescription(): string { + const entities = constellationNodes.filter(n => n.type === 'role' || n.type === 'education') + const skills = constellationNodes.filter(n => n.type === 'skill') + + const entityDescriptions = entities.map(entity => { + const mapping = roleSkillMappings.find(m => m.roleId === entity.id) + const skillNames = mapping + ? mapping.skillIds + .map(sid => skills.find(s => s.id === sid)?.label) + .filter(Boolean) + .join(', ') + : '' + const yearRange = entity.endYear + ? `${entity.startYear}-${entity.endYear}` + : `${entity.startYear}-present` + return `${entity.label} at ${entity.organization} (${yearRange}): ${skillNames}` + }) + + return `Career constellation graph showing ${entities.length} roles and ${skills.length} skills in reverse-chronological order along a vertical timeline, with the most recent role at the top. ` + + entityDescriptions.join('. ') + '.' +} + +export const srDescription = buildScreenReaderDescription()