Skip to content

Commit 08ec496

Browse files
authored
Migrate Accordion and DocTable components to ShadCN +2 issues resolved (#1742)
* adapting DocTable to shadcn and adding tests * adapting accordion to shadcn and adding tests * adjusting carbon ads to dark mode * format adjustments * fromat adjustments * add tests for full coverage * adjusting the mouse pointer to show the hand icon
1 parent 2d7dea6 commit 08ec496

File tree

6 files changed

+1074
-312
lines changed

6 files changed

+1074
-312
lines changed

components/Accordion.tsx

Lines changed: 103 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
1+
/* eslint-disable linebreak-style */
12
import React, { useState, useEffect } from 'react';
23
import { useRouter } from 'next/router';
4+
import {
5+
Collapsible,
6+
CollapsibleContent,
7+
CollapsibleTrigger,
8+
} from '@/components/ui/collapsible';
9+
import { cn } from '@/lib/utils';
310

411
interface AccordionItem {
512
question: string;
@@ -12,84 +19,122 @@ interface AccordionProps {
1219
}
1320

1421
const 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-
&#43;
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
);

components/CarbonsAds.tsx

Lines changed: 70 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -75,46 +75,92 @@ CarbonAds.stylesheet = {
7575
#carbonads {
7676
padding: 0.5rem;
7777
margin: 2rem auto;
78-
background-color: #f9f9f9;
79-
border: 1px dashed;
80-
border-color: #cacaca;
78+
background-color: #ffffff;
79+
border: 1px dashed #e5e7eb;
8180
box-sizing: border-box;
8281
border-radius: 10px;
8382
max-width: 300px;
83+
transition: background-color 0.3s ease, color 0.3s ease;
8484
}
8585
86-
#carbonads > span {
87-
width: 100%;
88-
height: fit-content;
89-
display: flex;
90-
flex-direction: column;
86+
#carbonads .carbon-wrap {
87+
background-color: #f9f9f9 !important;
9188
}
9289
93-
.carbon-wrap {
94-
flex: 1;
95-
display: inline-flex;
96-
flex-direction: row;
90+
#carbonads .carbon-text,
91+
#carbonads .carbon-poweredby {
92+
background-color: inherit !important;
93+
color: #374151 !important;
9794
}
9895
99-
.carbon-img {
100-
margin: auto;
96+
#carbonads .carbon-text {
97+
font-size: 12px;
98+
font-family: Inter, ui-sans-serif, system-ui;
99+
line-height: 1.4;
101100
}
102101
103-
.carbon-img > img {
104-
align-self: stretch;
105-
margin-right: 0.75rem;
102+
#carbonads .carbon-img {
103+
text-align: center;
104+
margin: 0 auto 8px;
106105
}
107106
108-
.carbon-text {
109-
font-size: 12px;
110-
font-family: Inter, ui-sans-serif, system-ui;
111-
color: rgb(100 116 139);
107+
#carbonads .carbon-img img {
108+
display: inline-block;
109+
margin: 0 auto;
110+
filter: brightness(1);
111+
border: 1px solid #e5e7eb !important;
112+
background-color: #f9fafb !important;
113+
border-radius: 4px;
112114
}
113115
114-
.carbon-poweredby {
115-
font-size: 12px;
116+
#carbonads .carbon-poweredby {
117+
font-size: 11px !important;
118+
text-align: center !important;
119+
display: block;
116120
margin-top: 4px;
117-
color: rgb(100 116 139);
121+
color: #6b7280 !important;
122+
}
123+
124+
#carbonads a {
125+
color: #2563eb !important;
126+
text-decoration: none !important;
127+
}
128+
129+
#carbonads a:hover {
130+
color: #1d4ed8 !important;
131+
}
132+
133+
/* Dark mode overrides */
134+
.dark #carbonads {
135+
background-color: rgb(30 41 59) !important;
136+
border-color: rgb(51 65 85) !important;
137+
}
138+
139+
.dark #carbonads .carbon-text,
140+
.dark #carbonads .carbon-poweredby {
141+
color: #f1f5f9 !important;
142+
}
143+
144+
.dark #carbonads .carbon-poweredby {
145+
color: #94a3b8 !important;
146+
}
147+
148+
.dark #carbonads a {
149+
color: #7dd3fc !important;
150+
}
151+
152+
.dark #carbonads .carbon-wrap {
153+
background-color: #0f172a !important;
154+
}
155+
156+
.dark #carbonads a:hover {
157+
color: #e2e8f0 !important;
158+
}
159+
160+
.dark #carbonads .carbon-img img {
161+
filter: brightness(0.9) contrast(1.1);
162+
border-color: rgb(51 65 85) !important;
163+
background-color: rgb(15 23 42) !important;
118164
}
119165
120166
@media (max-width: 1023px) {

0 commit comments

Comments
 (0)