1+ /* eslint-disable linebreak-style */
12import React , { useState , useEffect } from 'react' ;
23import { useRouter } from 'next/router' ;
4+ import {
5+ Collapsible ,
6+ CollapsibleContent ,
7+ CollapsibleTrigger ,
8+ } from '@/components/ui/collapsible' ;
9+ import { cn } from '@/lib/utils' ;
310
411interface AccordionItem {
512 question : string ;
@@ -12,84 +19,122 @@ interface AccordionProps {
1219}
1320
1421const Accordion : React . FC < AccordionProps > = ( { items } ) => {
15- const [ activeIndex , setActiveIndex ] = useState < number | null > ( null ) ;
22+ const [ openItems , setOpenItems ] = useState < Set < number > > ( new Set ( ) ) ;
1623 const router = useRouter ( ) ;
1724
18- const handleToggle = ( index : number ) => {
19- setActiveIndex ( ( prevIndex ) => ( prevIndex === index ? null : index ) ) ;
25+ const handleToggle = ( id : number ) => {
26+ setOpenItems ( ( prev ) => {
27+ const newSet = new Set ( prev ) ;
28+ if ( newSet . has ( id ) ) {
29+ newSet . delete ( id ) ;
30+ } else {
31+ newSet . add ( id ) ;
32+ }
33+ return newSet ;
34+ } ) ;
2035 } ;
2136
2237 useEffect ( ( ) => {
2338 const hash = router . asPath . split ( '#' ) [ 1 ] ;
2439 if ( hash ) {
2540 const id = parseInt ( hash , 10 ) ;
26- const index = items . findIndex ( ( item ) => item . id === id ) ;
27- if ( index !== - 1 ) {
28- setActiveIndex ( index ) ;
29-
30- setTimeout ( ( ) => {
31- const element = document . getElementById ( hash ) ;
32- if ( element ) {
33- const navbarHeight = 150 ;
34- const offset = element . offsetTop - navbarHeight ;
35- window . scrollTo ( { top : offset , behavior : 'smooth' } ) ;
36- }
37- } , 0 ) ;
41+ const item = items . find ( ( item ) => item . id === id ) ;
42+ if ( item ) {
43+ setOpenItems ( new Set ( [ id ] ) ) ;
3844 }
3945 }
4046 } , [ items , router . asPath ] ) ;
4147
42- const handleLinkClick = ( id : number ) => {
43- const index = items . findIndex ( ( item ) => item . id === id ) ;
44- setActiveIndex ( index ) ;
45-
46- const newUrl = `#${ id } ` ;
47- router . push ( newUrl , undefined , { shallow : true } ) ;
48- } ;
49-
5048 return (
51- < div >
52- { items . map ( ( item , index ) => (
53- < div
49+ < div className = 'w-full space-y-1' >
50+ { items . map ( ( item ) => (
51+ < Collapsible
5452 key = { item . id }
55- className = { `overflow-hidden transition-max-height border-t-2 ${
56- activeIndex === index ? 'max-h-96' : 'max-h-20'
57- } ${ index === items . length - 1 ? 'border-b-2' : '' } ` }
53+ open = { openItems . has ( item . id ) }
54+ onOpenChange = { ( ) => handleToggle ( item . id ) }
55+ className = 'w-full'
5856 data-test = { `accordion-item-${ item . id } ` }
5957 >
60- < div className = 'flex justify-between p-4 pl-2 cursor-pointer' >
61- < div className = 'text-[20px]' >
62- < a
63- href = { `#${ item . id } ` }
64- onClick = { ( e ) => {
65- e . preventDefault ( ) ;
66- handleLinkClick ( item . id ) ;
67- } }
68- data-test = { `accordion-question-${ item . id } ` }
69- >
70- { item . question }
71- </ a >
72- </ div >
73- < div
74- className = { `transform transition-transform duration-200 max-h-7 text-[20px] ${
75- activeIndex === index ? 'rotate-45' : ''
76- } `}
77- onClick = { ( ) => handleToggle ( index ) }
58+ < div
59+ className = { cn (
60+ 'border border-border dark:border-[#bfdbfe] rounded-lg transition-colors' ,
61+ openItems . has ( item . id ) &&
62+ 'border-primary/50 bg-[#e2e8f0] dark:bg-[#0f172a]' ,
63+ ) }
64+ >
65+ < CollapsibleTrigger
66+ asChild
67+ className = 'w-full'
7868 data-test = { `accordion-toggle-${ item . id } ` }
7969 >
80- +
81- </ div >
82- </ div >
83- { activeIndex === index && (
84- < div
85- id = { `${ item . id } ` }
86- className = 'p-2 text-gray-500 dark:text-slate-200 pb-4'
70+ < div className = 'flex items-center justify-between w-full p-4 text-left hover:bg-muted/50 transition-colors rounded-lg' >
71+ < div className = 'flex-1' >
72+ < a
73+ href = { `#${ item . id } ` }
74+ onClick = { ( e ) => {
75+ const isCurrentlyOpen = openItems . has ( item . id ) ;
76+ if ( isCurrentlyOpen ) {
77+ // If open, just close it without navigation
78+ e . preventDefault ( ) ;
79+ handleToggle ( item . id ) ;
80+ } else {
81+ // If closed, open it and scroll to a position a few pixels higher
82+ e . preventDefault ( ) ;
83+ handleToggle ( item . id ) ;
84+ setTimeout ( ( ) => {
85+ const element = document . getElementById ( `${ item . id } ` ) ;
86+ if ( element ) {
87+ const navbarHeight = 150 ;
88+ const offset =
89+ element . offsetTop - navbarHeight - 20 ; // 20px higher
90+ window . scrollTo ( {
91+ top : offset ,
92+ behavior : 'smooth' ,
93+ } ) ;
94+ }
95+ } , 100 ) ;
96+ }
97+ } }
98+ className = { cn (
99+ 'text-lg font-medium text-foreground transition-all duration-200 dark:hover:text-[#bfdbfe] hover:text-lg hover:text-blue-600' ,
100+ openItems . has ( item . id ) &&
101+ 'text-primary dark:text-[#bfdbfe]' ,
102+ ) }
103+ data-test = { `accordion-question-${ item . id } ` }
104+ >
105+ { item . question }
106+ </ a >
107+ </ div >
108+ < div className = 'ml-4 flex-shrink-0' >
109+ < div
110+ className = { cn (
111+ 'w-6 h-6 rounded-full border-2 border-border flex items-center justify-center transition-all duration-200 cursor-pointer' ,
112+ openItems . has ( item . id )
113+ ? 'border-primary bg-primary text-white rotate-45 dark:bg-[#bfdbfe] dark:text-black dark:border-[#bfdbfe]'
114+ : 'hover:border-primary/50' ,
115+ ) }
116+ >
117+ < span className = 'text-sm font-bold leading-none' >
118+ { openItems . has ( item . id ) ? '×' : '+' }
119+ </ span >
120+ </ div >
121+ </ div >
122+ </ div >
123+ </ CollapsibleTrigger >
124+
125+ < CollapsibleContent
126+ className = 'overflow-hidden data-[state=closed]:animate-collapsible-up data-[state=open]:animate-collapsible-down'
87127 data-test = { `accordion-answer-${ item . id } ` }
88128 >
89- { item . answer }
90- </ div >
91- ) }
92- </ div >
129+ < div
130+ id = { `${ item . id } ` }
131+ className = 'px-4 pb-4 text-muted-foreground leading-relaxed'
132+ >
133+ { item . answer }
134+ </ div >
135+ </ CollapsibleContent >
136+ </ div >
137+ </ Collapsible >
93138 ) ) }
94139 </ div >
95140 ) ;
0 commit comments