@@ -18,6 +18,49 @@ const NewsletterTimeline = () => {
1818 const [ filterYear , setFilterYear ] = useState < string > ( "all" ) ;
1919 const [ sortType , setSortType ] = useState < "newest" | "oldest" > ( "newest" ) ;
2020
21+ const parseDate = ( dateStr : string ) : Date => {
22+ const months : Record < string , number > = {
23+ Jan : 0 ,
24+ Feb : 1 ,
25+ Mar : 2 ,
26+ Apr : 3 ,
27+ May : 4 ,
28+ Jun : 5 ,
29+ Jul : 6 ,
30+ Aug : 7 ,
31+ Sep : 8 ,
32+ Oct : 9 ,
33+ Nov : 10 ,
34+ Dec : 11 ,
35+ } ;
36+
37+ const [ month , dayWithComma , year ] = dateStr . split ( " " ) ;
38+
39+ return new Date (
40+ Number ( year ) ,
41+ months [ month ] ,
42+ Number ( dayWithComma . replace ( "," , "" ) )
43+ ) ;
44+ } ;
45+
46+ const filteredNewsletters = newsletters
47+ . filter ( ( n ) => {
48+ const d = parseDate ( n . date ) ;
49+ const month = d . getMonth ( ) + 1 ;
50+ const year = d . getFullYear ( ) ;
51+
52+ const monthMatches =
53+ filterMonth === "all" || Number ( filterMonth ) === month ;
54+ const yearMatches = filterYear === "all" || Number ( filterYear ) === year ;
55+
56+ return monthMatches && yearMatches ;
57+ } )
58+ . sort ( ( a , b ) => {
59+ const da = parseDate ( a . date ) . getTime ( ) ;
60+ const db = parseDate ( b . date ) . getTime ( ) ;
61+ return sortType === "newest" ? db - da : da - db ;
62+ } ) ;
63+
2164 useEffect ( ( ) => {
2265 const timer = setTimeout ( ( ) => {
2366 setLineHeight ( 100 ) ;
@@ -39,12 +82,17 @@ const NewsletterTimeline = () => {
3982 ) ;
4083
4184 if ( entry . isIntersecting ) {
42- if ( ! visibleCards . has ( index ) ) {
43- setVisibleCards ( ( prev ) => new Set ( [ ...prev , index ] ) ) ;
44- }
45- setActiveDot ( index ) ;
46- setGlowingDot ( index ) ;
47- setTimeout ( ( ) => setGlowingDot ( null ) , 300 ) ;
85+ setVisibleCards ( ( prev ) => {
86+ if ( prev . has ( index ) ) return prev ;
87+ return new Set ( [ ...prev , index ] ) ;
88+ } ) ;
89+ setActiveDot ( ( prev ) => {
90+ if ( prev !== index ) {
91+ setGlowingDot ( index ) ;
92+ setTimeout ( ( ) => setGlowingDot ( null ) , 300 ) ;
93+ }
94+ return index ;
95+ } ) ;
4896 }
4997 } ) ;
5098 } ,
@@ -56,7 +104,7 @@ const NewsletterTimeline = () => {
56104 } ) ;
57105
58106 return ( ) => observer . disconnect ( ) ;
59- } , [ visibleCards ] ) ;
107+ } , [ ] ) ;
60108
61109 useEffect ( ( ) => {
62110 const handleScroll = ( ) => {
@@ -82,24 +130,19 @@ const NewsletterTimeline = () => {
82130 handleScroll ( ) ;
83131 return ( ) => window . removeEventListener ( "scroll" , handleScroll ) ;
84132 } , [ ] ) ;
133+ useEffect ( ( ) => {
134+ if ( ! selectedNewsletter ) return ;
85135
86- const filteredNewsletters = newsletters
87- . filter ( ( n ) => {
88- const d = new Date ( n . date ) ;
89- const month = d . getMonth ( ) + 1 ;
90- const year = d . getFullYear ( ) ;
91-
92- const monthMatches =
93- filterMonth === "all" || Number ( filterMonth ) === month ;
94- const yearMatches = filterYear === "all" || Number ( filterYear ) === year ;
136+ const handleEscape = ( e : KeyboardEvent ) => {
137+ if ( e . key === "Escape" ) {
138+ setSelectedNewsletter ( null ) ;
139+ setExpandingCard ( null ) ;
140+ }
141+ } ;
95142
96- return monthMatches && yearMatches ;
97- } )
98- . sort ( ( a , b ) => {
99- const da = new Date ( a . date ) . getTime ( ) ;
100- const db = new Date ( b . date ) . getTime ( ) ;
101- return sortType === "newest" ? db - da : da - db ;
102- } ) ;
143+ document . addEventListener ( "keydown" , handleEscape ) ;
144+ return ( ) => document . removeEventListener ( "keydown" , handleEscape ) ;
145+ } , [ selectedNewsletter ] ) ;
103146
104147 return (
105148 < div className = "min-h-screen bg-gradient-to-br from-[#0E0E10] via-[#15161A] to-[#0E0E10] text-white" >
@@ -117,7 +160,7 @@ const NewsletterTimeline = () => {
117160 < div className = "flex flex-wrap gap-4 justify-center mb-10" >
118161 < select
119162 value = { sortType }
120- onChange = { ( e ) => setSortType ( e . target . value as any ) }
163+ onChange = { ( e ) => setSortType ( e . target . value as "newest" | "oldest" ) }
121164 className = "bg-[#15161A] border border-[#27272A] rounded-lg px-3 py-2"
122165 >
123166 < option value = "newest" > Newest First</ option >
@@ -229,11 +272,7 @@ const NewsletterTimeline = () => {
229272 />
230273
231274 { /* Card */ }
232- < div
233- className = { `md:grid md:grid-cols-2 md:gap-4 items-center ${
234- isLeft ? "" : "direction-rtl"
235- } `}
236- >
275+ < div className = "md:grid md:grid-cols-2 md:gap-4 items-center" >
237276 < div
238277 className = {
239278 isLeft
@@ -334,6 +373,9 @@ const NewsletterTimeline = () => {
334373 { selectedNewsletter && (
335374 < div
336375 className = "fixed inset-0 z-50 bg-[#0E0E10]/95 backdrop-blur-md animate-in fade-in duration-300"
376+ role = "dialog"
377+ aria-modal = "true"
378+ aria-labelledby = "modal-title"
337379 onClick = { ( ) => {
338380 setSelectedNewsletter ( null ) ;
339381 setExpandingCard ( null ) ;
@@ -372,7 +414,10 @@ const NewsletterTimeline = () => {
372414
373415 < div className = "p-8" >
374416 { /* Title */ }
375- < h1 className = "text-4xl font-bold mb-4 text-white" >
417+ < h1
418+ id = "modal-title"
419+ className = "text-4xl font-bold mb-4 text-white"
420+ >
376421 { selectedNewsletter . title }
377422 </ h1 >
378423
0 commit comments