UserDashboard/components/dropdown.tsx
2025-08-20 09:27:24 +08:00

74 lines
2.1 KiB
TypeScript

// Dropdown.tsx
'use client';
import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
import { usePopper } from 'react-popper';
import type { ReactNode } from 'react';
type DropdownProps = {
button?: ReactNode; // 👈 make optional
children: ReactNode;
btnClassName?: string;
placement?: any;
offset?: [number, number];
panelClassName?: string;
closeOnItemClick?: boolean;
};
const Dropdown = (props: DropdownProps, forwardedRef: any) => {
const [visible, setVisible] = useState(false);
const referenceRef = useRef<HTMLButtonElement | null>(null);
const popperRef = useRef<HTMLDivElement | null>(null);
const { styles, attributes } = usePopper(referenceRef.current, popperRef.current, {
placement: props.placement || 'bottom-end',
modifiers: [{ name: 'offset', options: { offset: props.offset ?? [0, 8] } }],
});
useEffect(() => {
const onDoc = (e: MouseEvent) => {
if (!referenceRef.current || !popperRef.current) return;
if (referenceRef.current.contains(e.target as Node)) return;
if (popperRef.current.contains(e.target as Node)) return;
setVisible(false);
};
document.addEventListener('mousedown', onDoc);
return () => document.removeEventListener('mousedown', onDoc);
}, []);
useImperativeHandle(forwardedRef, () => ({ close: () => setVisible(false) }));
const defaultButton = (
<span className="inline-flex h-9 w-9 items-center justify-center rounded-full bg-gray-200 dark:bg-rtgray-700" />
);
return (
<>
<button
ref={referenceRef}
type="button"
className={props.btnClassName}
onClick={() => setVisible((v) => !v)}
>
{props.button ?? defaultButton} {/* 👈 fallback */}
</button>
<div
ref={popperRef}
style={styles.popper}
{...attributes.popper}
className="z-50"
>
{visible && (
<div className={props.panelClassName ?? 'rounded-lg bg-white dark:bg-rtgray-700 shadow-lg ring-1 ring-black/5'}>
{props.children}
</div>
)}
</div>
</>
);
};
export default forwardRef(Dropdown);