refactor: extract hexToRgba and prefersReducedMotion to shared utils

Move hexToRgba() (3 identical copies) and prefersReducedMotion (5 module-level
copies) to src/lib/utils.ts. Re-export prefersReducedMotion from
constellation/constants.ts to preserve existing importers. Add clarifying
comments to constellation.ts and tags.ts re-export layers (Phase 1.4).
This commit is contained in:
2026-02-17 01:48:43 +00:00
parent 45b87466be
commit 296b18f025
9 changed files with 19 additions and 32 deletions
+1 -2
View File
@@ -12,8 +12,7 @@ import {
import { buildPaletteData } from '@/lib/search'
import type { PaletteItem, PaletteAction } from '@/lib/search'
import { iconByType, iconColorStyles } from '@/lib/palette-icons'
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches
import { prefersReducedMotion } from '@/lib/utils'
const MAX_HISTORY = 10
+1 -2
View File
@@ -9,8 +9,7 @@ import type { PaletteItem, PaletteAction } from '@/lib/search'
import { iconByType, iconColorStyles } from '@/lib/palette-icons'
import { isModelReady, embedQuery } from '@/lib/embedding-model'
import { semanticSearch, loadEmbeddings } from '@/lib/semantic-search'
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches
import { prefersReducedMotion } from '@/lib/utils'
interface CommandPaletteProps {
isOpen: boolean
+1 -9
View File
@@ -17,8 +17,7 @@ import { useDetailPanel } from '@/contexts/DetailPanelContext'
import { timelineConsultations } from '@/data/timeline'
import { skills } from '@/data/skills'
import type { PaletteAction } from '@/lib/search'
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches
import { hexToRgba, prefersReducedMotion } from '@/lib/utils'
const sidebarVariants = {
hidden: prefersReducedMotion ? { x: 0, opacity: 1 } : { x: -272, opacity: 0 },
@@ -41,13 +40,6 @@ const contentVariants = {
},
}
function hexToRgba(hex: string, opacity: number): string {
const r = parseInt(hex.slice(1, 3), 16)
const g = parseInt(hex.slice(3, 5), 16)
const b = parseInt(hex.slice(5, 7), 16)
return `rgba(${r},${g},${b},${opacity})`
}
interface LastConsultationSubsectionProps {
highlightedRoleId?: string | null
}
@@ -5,15 +5,7 @@ import { useDetailPanel } from '@/contexts/DetailPanelContext'
import { timelineEntities, timelineConsultations } from '@/data/timeline'
import { getExperienceEducationUICopy } from '@/lib/profile-content'
import type { TimelineEntity } from '@/types/pmr'
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches
function hexToRgba(hex: string, opacity: number): string {
const r = parseInt(hex.slice(1, 3), 16)
const g = parseInt(hex.slice(3, 5), 16)
const b = parseInt(hex.slice(5, 7), 16)
return `rgba(${r},${g},${b},${opacity})`
}
import { hexToRgba, prefersReducedMotion } from '@/lib/utils'
interface TimelineInterventionItemProps {
entity: TimelineEntity
+1 -9
View File
@@ -4,15 +4,7 @@ import { ChevronRight } from 'lucide-react'
import { CardHeader } from './Card'
import { timelineConsultations } from '@/data/timeline'
import { useDetailPanel } from '@/contexts/DetailPanelContext'
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches
function hexToRgba(hex: string, opacity: number): string {
const r = parseInt(hex.slice(1, 3), 16)
const g = parseInt(hex.slice(3, 5), 16)
const b = parseInt(hex.slice(5, 7), 16)
return `rgba(${r},${g},${b},${opacity})`
}
import { hexToRgba, prefersReducedMotion } from '@/lib/utils'
interface RoleItemProps {
consultation: typeof timelineConsultations[0]
+1 -1
View File
@@ -86,5 +86,5 @@ export const HIDDEN_ENTITY_IDS = new Set([
])
// Media queries (evaluated once at module level)
export const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches
export { prefersReducedMotion } from '@/lib/utils'
export const supportsCoarsePointer = window.matchMedia('(pointer: coarse)').matches
+2
View File
@@ -1,3 +1,5 @@
// Module-level cache: buildConstellationData() is expensive (D3 graph construction).
// 5 consumers import from here instead of calling the builder independently.
import type { ConstellationLink, ConstellationNode, RoleSkillMapping } from '@/types/pmr'
import { buildConstellationData } from '@/data/timeline'
+2
View File
@@ -1,3 +1,5 @@
// Derives sidebar tags from timeline skills with color assignment.
// Separated from Sidebar.tsx to keep data derivation out of UI components.
import type { Tag } from '@/types/pmr'
import { getTopTimelineSkills } from '@/data/timeline'
+9
View File
@@ -6,3 +6,12 @@ export function calculateSkillOffset(level: number, radius: number): number {
export function formatBootLine(text: string): string {
return text
}
export function hexToRgba(hex: string, opacity: number): string {
const r = parseInt(hex.slice(1, 3), 16)
const g = parseInt(hex.slice(3, 5), 16)
const b = parseInt(hex.slice(5, 7), 16)
return `rgba(${r},${g},${b},${opacity})`
}
export const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches