add: accordion component
This commit is contained in:
@@ -0,0 +1,94 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import useToggle from '@/hooks/helpers/useToggle';
|
||||||
|
import clsx from 'clsx'
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import { Icon } from 'iconsax-react'
|
||||||
|
import { ChevronDown } from 'lucide-react';
|
||||||
|
import React, { useEffect } from 'react'
|
||||||
|
|
||||||
|
interface DropdownProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||||
|
initial?: boolean,
|
||||||
|
children?: React.ReactNode
|
||||||
|
title?: string
|
||||||
|
icon?: Icon
|
||||||
|
group?: string
|
||||||
|
};
|
||||||
|
|
||||||
|
function Accordion({ initial, group, title, icon: Icon, children, className, ...rest }: DropdownProps) {
|
||||||
|
const { state, toggle, set } = useToggle(initial);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleClickOutside = (event: MouseEvent) => {
|
||||||
|
if (event.target) {
|
||||||
|
const targetGroup = (event.target as HTMLElement).getAttribute('data-group');
|
||||||
|
if (targetGroup && targetGroup === group) {
|
||||||
|
set?.(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (group && state) {
|
||||||
|
document.addEventListener('click', handleClickOutside);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('click', handleClickOutside);
|
||||||
|
};
|
||||||
|
}, [group, set, state]);
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={clsx(
|
||||||
|
'rounded-normal py-3',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
data-group={group}
|
||||||
|
onClick={toggle}
|
||||||
|
data-active={state}
|
||||||
|
className={clsx(
|
||||||
|
'hover:underline rounded-normal flex items-center justify-between w-full',
|
||||||
|
'active:brightness-97 hover:brightness-99',
|
||||||
|
'data-[active=true]:rounded-b-none data-[active=true]:border-b-0 **:pointer-events-none'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className='flex items-center gap-2'>
|
||||||
|
{Icon &&
|
||||||
|
<Icon size={16} className='stroke-foreground mb-0.5' />
|
||||||
|
}
|
||||||
|
<span className='text-sm2'>{title ?? ''}</span>
|
||||||
|
</div>
|
||||||
|
<ChevronDown
|
||||||
|
size={16}
|
||||||
|
data-active={state}
|
||||||
|
className={clsx(
|
||||||
|
'cursor-pointer stroke-foreground data-[active=false]:rotate-x-0',
|
||||||
|
'data-[active=true]:rotate-z-180 transition-transform duration-200'
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
initial={{ height: state ? 'auto' : 0 }}
|
||||||
|
animate={{ height: state ? 'auto' : 0 }}
|
||||||
|
transition={{ duration: 0.1 }}
|
||||||
|
data-active={state}
|
||||||
|
className={clsx(
|
||||||
|
'mt-0 relative overflow-hidden',
|
||||||
|
'rounded-b-normal z-50',
|
||||||
|
'w-full',
|
||||||
|
!state && 'pointer-events-none'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className='py-2'>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Accordion
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
|
|
||||||
const useToggle = () => {
|
const useToggle = (initial?: boolean) => {
|
||||||
const [state, setState] = useState<boolean>(false);
|
const [state, setState] = useState<boolean>(initial ?? false);
|
||||||
|
|
||||||
const toggle = (e?: React.MouseEvent | null) => {
|
const toggle = (e?: React.MouseEvent | null) => {
|
||||||
if (e) {
|
if (e) {
|
||||||
|
|||||||
Reference in New Issue
Block a user