chore: update Dropdown component

This commit is contained in:
ValdiANS
2025-12-18 16:06:48 +07:00
parent e49c247f02
commit b9b7e45bc7
+77 -79
View File
@@ -1,111 +1,109 @@
'use client'; import React, { ReactNode, useState, useRef } from 'react';
import { ReactNode, useRef, useEffect, useState } from 'react';
import { cn } from '@/lib/helper'; import { cn } from '@/lib/helper';
interface DropdownProps { export interface DropdownProps {
trigger: ReactNode; trigger: ReactNode;
children: ReactNode; children: ReactNode;
position?: className?: {
| 'top' wrapper?: string;
| 'bottom' trigger?: string;
| 'left' content?: string;
| 'right' };
| 'top-start'
| 'top-end'
| 'bottom-start'
| 'bottom-end'
| 'left-start'
| 'left-end'
| 'right-start'
| 'right-end';
align?: 'start' | 'center' | 'end'; align?: 'start' | 'center' | 'end';
direction?: 'top' | 'bottom' | 'left' | 'right';
hover?: boolean; hover?: boolean;
className?: string; defaultOpen?: boolean;
contentClassName?: string; open?: boolean;
close?: boolean;
controlled?: boolean;
} }
const Dropdown = ({ const Dropdown = ({
trigger, trigger,
children, children,
position = 'bottom',
align = 'start',
hover = false,
className, className,
contentClassName, align,
direction,
hover,
defaultOpen = false,
open,
close,
controlled = false,
}: DropdownProps) => { }: DropdownProps) => {
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(defaultOpen);
const dropdownRef = useRef<HTMLDivElement>(null); const dropdownRef = useRef<HTMLDivElement>(null);
// Handle click outside to close dropdown const toggleDropdown = () => {
useEffect(() => { if (!controlled) {
const handleClickOutside = (event: MouseEvent) => { const newState = !isOpen;
if ( setIsOpen(newState);
dropdownRef.current &&
!dropdownRef.current.contains(event.target as Node)
) {
setIsOpen(false);
} }
}; };
if (isOpen) { const getWrapperClasses = () => {
document.addEventListener('mousedown', handleClickOutside); const openState = controlled ? open : isOpen;
}
return () => { return cn(
document.removeEventListener('mousedown', handleClickOutside); 'dropdown',
}; {
}, [isOpen]); 'dropdown-start': align === 'start',
'dropdown-center': align === 'center',
// Build position classes 'dropdown-end': align === 'end',
const getPositionClasses = () => { 'dropdown-top': direction === 'top',
const classes: string[] = []; 'dropdown-bottom': direction === 'bottom',
'dropdown-left': direction === 'left',
// Handle combined positions like 'top-start' 'dropdown-right': direction === 'right',
if (position.includes('-')) { 'dropdown-hover': hover,
const [pos, al] = position.split('-'); 'dropdown-open': openState && !close,
classes.push(`dropdown-${pos}`); 'dropdown-close': close,
classes.push(`dropdown-${al}`); },
} else { className?.wrapper
classes.push(`dropdown-${position}`); );
if (align !== 'start') {
classes.push(`dropdown-${align}`);
}
}
return classes.join(' ');
}; };
const handleToggle = (e: React.MouseEvent) => { const getTriggerClasses = () => {
e.preventDefault(); return cn(className?.trigger);
e.stopPropagation();
// alert('clicked');
setIsOpen(!isOpen);
}; };
const getContentClasses = () => {
return cn(
'dropdown-content z-[9999] shadow-sm bg-base-100 rounded-box',
className?.content
);
};
if (controlled) {
return (
<div className={getWrapperClasses()}>
{trigger}
{open && !close && (
<div tabIndex={-1} className={getContentClasses()}>
{children}
</div>
)}
</div>
);
}
return ( return (
<div ref={dropdownRef} className={getWrapperClasses()}>
<div <div
ref={dropdownRef} tabIndex={0}
className={cn( role='button'
'dropdown', className={getTriggerClasses()}
getPositionClasses(), onClick={toggleDropdown}
hover && 'dropdown-hover', onKeyDown={(e) => {
isOpen && 'dropdown-open', if (e.key === 'Enter' || e.key === ' ') {
className e.preventDefault();
)} toggleDropdown();
}
}}
> >
{/* Trigger Button */}
<div onClick={handleToggle} className='cursor-pointer'>
{trigger} {trigger}
</div> </div>
{!close && (
{/* Dropdown Content - Only render when open */} <div tabIndex={-1} className={getContentClasses()}>
{isOpen && (
<div
tabIndex={-1}
className={cn('dropdown-content z-[10]', contentClassName)}
onClick={() => setIsOpen(false)} // Close on item click
>
{children} {children}
</div> </div>
)} )}