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:
@@ -12,8 +12,7 @@ import {
|
|||||||
import { buildPaletteData } from '@/lib/search'
|
import { buildPaletteData } from '@/lib/search'
|
||||||
import type { PaletteItem, PaletteAction } from '@/lib/search'
|
import type { PaletteItem, PaletteAction } from '@/lib/search'
|
||||||
import { iconByType, iconColorStyles } from '@/lib/palette-icons'
|
import { iconByType, iconColorStyles } from '@/lib/palette-icons'
|
||||||
|
import { prefersReducedMotion } from '@/lib/utils'
|
||||||
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
|
||||||
|
|
||||||
const MAX_HISTORY = 10
|
const MAX_HISTORY = 10
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,7 @@ import type { PaletteItem, PaletteAction } from '@/lib/search'
|
|||||||
import { iconByType, iconColorStyles } from '@/lib/palette-icons'
|
import { iconByType, iconColorStyles } from '@/lib/palette-icons'
|
||||||
import { isModelReady, embedQuery } from '@/lib/embedding-model'
|
import { isModelReady, embedQuery } from '@/lib/embedding-model'
|
||||||
import { semanticSearch, loadEmbeddings } from '@/lib/semantic-search'
|
import { semanticSearch, loadEmbeddings } from '@/lib/semantic-search'
|
||||||
|
import { prefersReducedMotion } from '@/lib/utils'
|
||||||
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
|
||||||
|
|
||||||
interface CommandPaletteProps {
|
interface CommandPaletteProps {
|
||||||
isOpen: boolean
|
isOpen: boolean
|
||||||
|
|||||||
@@ -17,8 +17,7 @@ import { useDetailPanel } from '@/contexts/DetailPanelContext'
|
|||||||
import { timelineConsultations } from '@/data/timeline'
|
import { timelineConsultations } from '@/data/timeline'
|
||||||
import { skills } from '@/data/skills'
|
import { skills } from '@/data/skills'
|
||||||
import type { PaletteAction } from '@/lib/search'
|
import type { PaletteAction } from '@/lib/search'
|
||||||
|
import { hexToRgba, prefersReducedMotion } from '@/lib/utils'
|
||||||
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
|
||||||
|
|
||||||
const sidebarVariants = {
|
const sidebarVariants = {
|
||||||
hidden: prefersReducedMotion ? { x: 0, opacity: 1 } : { x: -272, opacity: 0 },
|
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 {
|
interface LastConsultationSubsectionProps {
|
||||||
highlightedRoleId?: string | null
|
highlightedRoleId?: string | null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,15 +5,7 @@ import { useDetailPanel } from '@/contexts/DetailPanelContext'
|
|||||||
import { timelineEntities, timelineConsultations } from '@/data/timeline'
|
import { timelineEntities, timelineConsultations } from '@/data/timeline'
|
||||||
import { getExperienceEducationUICopy } from '@/lib/profile-content'
|
import { getExperienceEducationUICopy } from '@/lib/profile-content'
|
||||||
import type { TimelineEntity } from '@/types/pmr'
|
import type { TimelineEntity } from '@/types/pmr'
|
||||||
|
import { hexToRgba, prefersReducedMotion } from '@/lib/utils'
|
||||||
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})`
|
|
||||||
}
|
|
||||||
|
|
||||||
interface TimelineInterventionItemProps {
|
interface TimelineInterventionItemProps {
|
||||||
entity: TimelineEntity
|
entity: TimelineEntity
|
||||||
|
|||||||
@@ -4,15 +4,7 @@ import { ChevronRight } from 'lucide-react'
|
|||||||
import { CardHeader } from './Card'
|
import { CardHeader } from './Card'
|
||||||
import { timelineConsultations } from '@/data/timeline'
|
import { timelineConsultations } from '@/data/timeline'
|
||||||
import { useDetailPanel } from '@/contexts/DetailPanelContext'
|
import { useDetailPanel } from '@/contexts/DetailPanelContext'
|
||||||
|
import { hexToRgba, prefersReducedMotion } from '@/lib/utils'
|
||||||
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})`
|
|
||||||
}
|
|
||||||
|
|
||||||
interface RoleItemProps {
|
interface RoleItemProps {
|
||||||
consultation: typeof timelineConsultations[0]
|
consultation: typeof timelineConsultations[0]
|
||||||
|
|||||||
@@ -86,5 +86,5 @@ export const HIDDEN_ENTITY_IDS = new Set([
|
|||||||
])
|
])
|
||||||
|
|
||||||
// Media queries (evaluated once at module level)
|
// 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
|
export const supportsCoarsePointer = window.matchMedia('(pointer: coarse)').matches
|
||||||
|
|||||||
@@ -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 type { ConstellationLink, ConstellationNode, RoleSkillMapping } from '@/types/pmr'
|
||||||
import { buildConstellationData } from '@/data/timeline'
|
import { buildConstellationData } from '@/data/timeline'
|
||||||
|
|
||||||
|
|||||||
@@ -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 type { Tag } from '@/types/pmr'
|
||||||
import { getTopTimelineSkills } from '@/data/timeline'
|
import { getTopTimelineSkills } from '@/data/timeline'
|
||||||
|
|
||||||
|
|||||||
@@ -6,3 +6,12 @@ export function calculateSkillOffset(level: number, radius: number): number {
|
|||||||
export function formatBootLine(text: string): string {
|
export function formatBootLine(text: string): string {
|
||||||
return text
|
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
|
||||||
|
|||||||
Reference in New Issue
Block a user