Files
portfolio/src/contexts/DetailPanelContext.tsx
T
admin cf5399a767 US-003: Create DetailPanelContext, DetailPanel component, and useFocusTrap hook
Implements core detail panel infrastructure for slide-in content panels:

- DetailPanelContext: Manages panel state (content, open/close, isOpen)
- DetailPanel: Slide-in panel component with backdrop, header, and scrollable body
- useFocusTrap: Keyboard focus trap hook for modal accessibility
- Width mapping: narrow (400px) for kpi/skill/education, wide (60vw) for consultation/project/career-role
- Title mapping derives from content data (kpi.label, skill.name, etc.)
- Close triggers: backdrop click, Escape key, X button
- ARIA: aria-modal, role=dialog, aria-labelledby
- Mobile responsive: both widths become 100vw on <768px
- prefers-reduced-motion: instant appear, no animations
- Placeholder content (real renderers in later stories)
- Export CardHeaderProps interface from Card.tsx
- Add responsive panel width CSS rules

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-13 23:02:59 +00:00

53 lines
1.2 KiB
TypeScript

import { createContext, useContext, useState, ReactNode } from 'react'
import { DetailPanelContent } from '@/types/pmr'
interface DetailPanelContextValue {
content: DetailPanelContent | null
openPanel: (content: DetailPanelContent) => void
closePanel: () => void
isOpen: boolean
}
const DetailPanelContext = createContext<DetailPanelContextValue | undefined>(
undefined
)
interface DetailPanelProviderProps {
children: ReactNode
}
export function DetailPanelProvider({ children }: DetailPanelProviderProps) {
const [content, setContent] = useState<DetailPanelContent | null>(null)
const openPanel = (newContent: DetailPanelContent) => {
setContent(newContent)
}
const closePanel = () => {
setContent(null)
}
const isOpen = content !== null
const value: DetailPanelContextValue = {
content,
openPanel,
closePanel,
isOpen,
}
return (
<DetailPanelContext.Provider value={value}>
{children}
</DetailPanelContext.Provider>
)
}
export function useDetailPanel(): DetailPanelContextValue {
const context = useContext(DetailPanelContext)
if (!context) {
throw new Error('useDetailPanel must be used within DetailPanelProvider')
}
return context
}