@@ -17,6 +17,14 @@ const NewsletterTimeline = () => {
1717 const [ filterMonth , setFilterMonth ] = useState < string > ( "all" ) ;
1818 const [ filterYear , setFilterYear ] = useState < string > ( "all" ) ;
1919 const [ sortType , setSortType ] = useState < "newest" | "oldest" > ( "newest" ) ;
20+ const previousActiveElement = useRef < HTMLElement | null > ( null ) ;
21+
22+ const closeModal = ( ) => {
23+ setSelectedNewsletter ( null ) ;
24+ setExpandingCard ( null ) ;
25+ previousActiveElement . current ?. focus ( ) ;
26+ } ;
27+
2028
2129 const parseDate = ( dateStr : string ) : Date => {
2230 const months : Record < string , number > = {
@@ -42,7 +50,7 @@ const NewsletterTimeline = () => {
4250 Number ( dayWithComma . replace ( "," , "" ) )
4351 ) ;
4452 } ;
45-
53+
4654 const filteredNewsletters = newsletters
4755 . filter ( ( n ) => {
4856 const d = parseDate ( n . date ) ;
@@ -72,6 +80,45 @@ const NewsletterTimeline = () => {
7280 return ( ) => clearTimeout ( timer ) ;
7381 } , [ ] ) ;
7482
83+ useEffect ( ( ) => {
84+ if ( ! selectedNewsletter ) return ;
85+
86+ const modal = document . getElementById ( "newsletter-modal" ) ;
87+ const firstFocusable = document . getElementById ( "modal-back-button" ) ;
88+
89+ // store old focus
90+ previousActiveElement . current = document . activeElement as HTMLElement ;
91+
92+ // move focus into modal
93+ firstFocusable ?. focus ( ) ;
94+
95+ const handleTab = ( e : KeyboardEvent ) => {
96+ if ( ! modal ) return ;
97+
98+ const focusable = modal . querySelectorAll < HTMLElement > (
99+ 'button, a, input, textarea, select, [tabindex]:not([tabindex="-1"])'
100+ ) ;
101+ const first = focusable [ 0 ] ;
102+ const last = focusable [ focusable . length - 1 ] ;
103+
104+ if ( e . key === "Tab" ) {
105+ if ( e . shiftKey && document . activeElement === first ) {
106+ e . preventDefault ( ) ;
107+ last . focus ( ) ;
108+ } else if ( ! e . shiftKey && document . activeElement === last ) {
109+ e . preventDefault ( ) ;
110+ first . focus ( ) ;
111+ }
112+ }
113+ } ;
114+
115+ document . addEventListener ( "keydown" , handleTab ) ;
116+ return ( ) => {
117+ document . removeEventListener ( "keydown" , handleTab ) ;
118+ } ;
119+ } , [ selectedNewsletter ] ) ;
120+
121+
75122 useEffect ( ( ) => {
76123 const observer = new IntersectionObserver (
77124 ( entries ) => {
@@ -372,6 +419,7 @@ const NewsletterTimeline = () => {
372419 { /* Full Article View */ }
373420 { selectedNewsletter && (
374421 < div
422+ id = "newsletter-modal"
375423 className = "fixed inset-0 z-50 bg-[#0E0E10]/95 backdrop-blur-md animate-in fade-in duration-300"
376424 role = "dialog"
377425 aria-modal = "true"
@@ -388,6 +436,7 @@ const NewsletterTimeline = () => {
388436 < div className = "max-w-4xl mx-auto px-4 py-16" >
389437 { /* Back Button */ }
390438 < button
439+ id = "modal-back-button"
391440 onClick = { ( ) => {
392441 setSelectedNewsletter ( null ) ;
393442 setExpandingCard ( null ) ;
0 commit comments