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>
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user