74 lines
2.1 KiB
TypeScript
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-gray-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-neutral-900 shadow-lg ring-1 ring-black/5'}>
|
|
{props.children}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default forwardRef(Dropdown);
|
|
|
|
|