Skip to content

Commit d4ead01

Browse files
committed
docs added for alert and accordion
1 parent 76b98da commit d4ead01

File tree

6 files changed

+1116
-0
lines changed

6 files changed

+1116
-0
lines changed
Lines changed: 370 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,370 @@
1+
'use client';
2+
3+
import React, { createContext, useContext, useRef, useState } from 'react';
4+
import { cn } from '../../../lib/utils';
5+
6+
// Import styles
7+
import {
8+
accordionClassNames,
9+
accordionItemClassNames,
10+
accordionTriggerClassNames,
11+
accordionContentClassNames,
12+
animationConfigs,
13+
} from './styles';
14+
15+
// Types
16+
type AccordionContextValue = {
17+
value: string[];
18+
onValueChange: (value: string[]) => void;
19+
type: 'single' | 'multiple';
20+
collapsible?: boolean;
21+
mode?: 'light' | 'dark';
22+
};
23+
24+
type AccordionItemContextValue = {
25+
id: string;
26+
isOpen: boolean;
27+
toggleItem: () => void;
28+
mode: 'light' | 'dark';
29+
};
30+
31+
// Create contexts with default values
32+
const AccordionContext = createContext<AccordionContextValue | null>(null);
33+
const AccordionItemContext = createContext<AccordionItemContextValue | null>(null);
34+
35+
// Hook to use Accordion context with safe fallbacks
36+
const useAccordion = () => {
37+
const context = useContext(AccordionContext);
38+
// Provide fallback values if context is not available
39+
if (!context) {
40+
return {
41+
value: [],
42+
onValueChange: () => {},
43+
type: 'single' as const,
44+
collapsible: false,
45+
mode: 'light' as const,
46+
};
47+
}
48+
return context;
49+
};
50+
51+
// Hook to use AccordionItem context with safe fallbacks
52+
const useAccordionItem = () => {
53+
const context = useContext(AccordionItemContext);
54+
// Provide fallback values if context is not available
55+
if (!context) {
56+
return {
57+
id: '',
58+
isOpen: false,
59+
toggleItem: () => {},
60+
mode: 'light' as const,
61+
};
62+
}
63+
return context;
64+
};
65+
66+
// Custom ChevronDown component instead of using Feather icons
67+
const ChevronDown: React.FC<{
68+
size?: number;
69+
color?: string;
70+
className?: string;
71+
}> = ({ size = 16, color = '#000000', className = '' }) => {
72+
// Create a proper chevron using two lines meeting at a point
73+
const thickness = Math.max(1.5, Math.floor((size || 16) / 14));
74+
const lineLength = (size || 16) * 0.35;
75+
const angle = 35; // degrees for proper chevron appearance
76+
77+
return (
78+
<div
79+
className={cn('flex items-center justify-center', className || '')}
80+
style={{ width: size || 16, height: size || 16 }}
81+
>
82+
<div
83+
style={{
84+
width: size || 16,
85+
height: (size || 16) * 0.5,
86+
position: 'relative',
87+
}}
88+
className="flex justify-center items-center"
89+
>
90+
{/* Left side of chevron */}
91+
<div
92+
style={{
93+
position: 'absolute',
94+
width: lineLength,
95+
height: thickness,
96+
backgroundColor: color || '#000000',
97+
borderRadius: thickness / 2,
98+
transform: `translateX(${-lineLength * 0.3}px) rotate(${angle}deg)`,
99+
}}
100+
/>
101+
{/* Right side of chevron */}
102+
<div
103+
style={{
104+
position: 'absolute',
105+
width: lineLength,
106+
height: thickness,
107+
backgroundColor: color || '#000000',
108+
borderRadius: thickness / 2,
109+
transform: `translateX(${lineLength * 0.3}px) rotate(-${angle}deg)`,
110+
}}
111+
/>
112+
</div>
113+
</div>
114+
);
115+
};
116+
117+
// Root Accordion component
118+
interface AccordionProps {
119+
type?: 'single' | 'multiple';
120+
collapsible?: boolean;
121+
defaultValue?: string[];
122+
value?: string[];
123+
onValueChange?: (value: string[]) => void;
124+
className?: string;
125+
mode?: 'light' | 'dark';
126+
children: React.ReactNode;
127+
}
128+
129+
export const Accordion: React.FC<AccordionProps> = ({
130+
type = 'single',
131+
collapsible = false,
132+
defaultValue = [],
133+
value,
134+
onValueChange,
135+
className = '',
136+
mode = 'light',
137+
children,
138+
}) => {
139+
const [stateValue, setStateValue] = useState<string[]>(defaultValue || []);
140+
141+
const handleValueChange = (newValue: string[]) => {
142+
if (onValueChange) {
143+
onValueChange(newValue);
144+
} else {
145+
setStateValue(newValue);
146+
}
147+
};
148+
149+
const accordionValue = value !== undefined ? value : stateValue;
150+
151+
return (
152+
<AccordionContext.Provider
153+
value={{
154+
value: accordionValue,
155+
onValueChange: handleValueChange,
156+
type: type || 'single',
157+
collapsible: collapsible || false,
158+
mode: mode || 'light',
159+
}}
160+
>
161+
<div className={cn(accordionClassNames?.base || '', className || '')}>{children}</div>
162+
</AccordionContext.Provider>
163+
);
164+
};
165+
166+
// AccordionItem component
167+
interface AccordionItemProps {
168+
id: string;
169+
className?: string;
170+
children: React.ReactNode;
171+
}
172+
173+
export const AccordionItem: React.FC<AccordionItemProps> = ({ id, className = '', children }) => {
174+
const {
175+
value = [],
176+
onValueChange = () => {},
177+
type = 'single',
178+
collapsible = false,
179+
mode = 'light',
180+
} = useAccordion();
181+
const isDark = mode === 'dark';
182+
183+
const isOpen = value.includes(id);
184+
185+
const toggleItem = () => {
186+
if (type === 'single') {
187+
if (isOpen) {
188+
if (collapsible) {
189+
onValueChange([]);
190+
}
191+
} else {
192+
onValueChange([id]);
193+
}
194+
} else {
195+
if (isOpen) {
196+
onValueChange(value.filter(v => v !== id));
197+
} else {
198+
onValueChange([...value, id]);
199+
}
200+
}
201+
};
202+
203+
return (
204+
<AccordionItemContext.Provider value={{ id, isOpen, toggleItem, mode }}>
205+
<div
206+
className={cn(
207+
accordionItemClassNames?.base || '',
208+
isDark
209+
? accordionItemClassNames?.theme?.dark || ''
210+
: accordionItemClassNames?.theme?.light || '',
211+
className || ''
212+
)}
213+
>
214+
{children}
215+
</div>
216+
</AccordionItemContext.Provider>
217+
);
218+
};
219+
220+
// AccordionTrigger component
221+
interface AccordionTriggerProps {
222+
className?: string;
223+
textClassName?: string;
224+
children: React.ReactNode;
225+
}
226+
227+
export const AccordionTrigger: React.FC<AccordionTriggerProps> = ({
228+
className = '',
229+
textClassName = '',
230+
children,
231+
}) => {
232+
const { isOpen = false, toggleItem = () => {}, mode = 'light' } = useAccordionItem();
233+
const isDark = mode === 'dark';
234+
235+
// Helper function to render trigger content
236+
const renderTriggerContent = (content: React.ReactNode): React.ReactNode => {
237+
if (typeof content === 'string' || typeof content === 'number') {
238+
return (
239+
<span
240+
className={cn(
241+
accordionTriggerClassNames?.text?.base || '',
242+
isDark
243+
? accordionTriggerClassNames?.text?.theme?.dark || ''
244+
: accordionTriggerClassNames?.text?.theme?.light || '',
245+
textClassName || ''
246+
)}
247+
>
248+
{content}
249+
</span>
250+
);
251+
}
252+
253+
if (React.isValidElement(content)) {
254+
return content;
255+
}
256+
257+
if (Array.isArray(content)) {
258+
return content.map((item, index) => (
259+
<React.Fragment key={index}>{renderTriggerContent(item)}</React.Fragment>
260+
));
261+
}
262+
263+
return content;
264+
};
265+
266+
return (
267+
<button
268+
className={cn(
269+
accordionTriggerClassNames?.base || '',
270+
'flex items-center justify-between w-full',
271+
className || ''
272+
)}
273+
onClick={toggleItem}
274+
type="button"
275+
>
276+
{renderTriggerContent(children)}
277+
<div
278+
className={cn(
279+
'transform transition-transform duration-200',
280+
isOpen ? 'rotate-180' : 'rotate-0'
281+
)}
282+
>
283+
<ChevronDown
284+
size={16}
285+
color={
286+
isDark && accordionTriggerClassNames?.icon?.color
287+
? accordionTriggerClassNames?.icon?.color?.dark || '#ffffff'
288+
: accordionTriggerClassNames?.icon?.color?.light || '#000000'
289+
}
290+
/>
291+
</div>
292+
</button>
293+
);
294+
};
295+
296+
// AccordionContent component
297+
interface AccordionContentProps {
298+
className?: string;
299+
contentClassName?: string;
300+
textClassName?: string;
301+
children: React.ReactNode;
302+
}
303+
304+
export const AccordionContent: React.FC<AccordionContentProps> = ({
305+
className = '',
306+
contentClassName = '',
307+
textClassName = '',
308+
children,
309+
}) => {
310+
const { isOpen = false, mode = 'light' } = useAccordionItem();
311+
const isDark = mode === 'dark';
312+
313+
// Helper function to ensure all text is wrapped in span elements
314+
const renderContent = (content: React.ReactNode): React.ReactNode => {
315+
if (typeof content === 'string' || typeof content === 'number') {
316+
return (
317+
<span
318+
className={cn(
319+
accordionContentClassNames?.text?.base || '',
320+
isDark
321+
? accordionContentClassNames?.text?.theme?.dark || ''
322+
: accordionContentClassNames?.text?.theme?.light || '',
323+
textClassName || ''
324+
)}
325+
>
326+
{content}
327+
</span>
328+
);
329+
}
330+
331+
if (React.isValidElement(content)) {
332+
const elementContent = content as React.ReactElement<any>;
333+
if (elementContent.props && elementContent.props.children) {
334+
return React.cloneElement(
335+
elementContent,
336+
{ ...elementContent.props },
337+
React.Children.map(
338+
elementContent.props.children,
339+
(child): React.ReactNode => renderContent(child)
340+
)
341+
);
342+
}
343+
return content;
344+
}
345+
346+
if (Array.isArray(content)) {
347+
return content.map((item, index) => (
348+
<React.Fragment key={index}>{renderContent(item)}</React.Fragment>
349+
));
350+
}
351+
352+
return content;
353+
};
354+
355+
return (
356+
<div
357+
className={cn(
358+
accordionContentClassNames?.base || '',
359+
'transition-all duration-200',
360+
isOpen ? 'h-auto opacity-100' : 'h-0 opacity-0',
361+
'overflow-hidden',
362+
className || ''
363+
)}
364+
>
365+
<div className={cn(accordionContentClassNames?.content?.base || '', contentClassName || '')}>
366+
{renderContent(children)}
367+
</div>
368+
</div>
369+
);
370+
};

0 commit comments

Comments
 (0)