Files
Fizer Khan 818fe0728c add support for dashboards and widget.
Add ip allowlist support
2025-12-05 17:16:53 +05:30

204 lines
5.1 KiB
TypeScript

'use client';
import * as React from 'react';
import { cn } from '@/lib/utils';
interface DropdownMenuProps {
children: React.ReactNode;
}
interface DropdownMenuContextValue {
open: boolean;
setOpen: React.Dispatch<React.SetStateAction<boolean>>;
}
const DropdownMenuContext = React.createContext<DropdownMenuContextValue | null>(null);
function useDropdownMenu() {
const context = React.useContext(DropdownMenuContext);
if (!context) {
throw new Error('DropdownMenu components must be used within a DropdownMenu');
}
return context;
}
export function DropdownMenu({ children }: DropdownMenuProps) {
const [open, setOpen] = React.useState(false);
return (
<DropdownMenuContext.Provider value={{ open, setOpen }}>
<div className="relative inline-block">{children}</div>
</DropdownMenuContext.Provider>
);
}
interface DropdownMenuTriggerProps {
children: React.ReactNode;
asChild?: boolean;
}
export function DropdownMenuTrigger({ children, asChild }: DropdownMenuTriggerProps) {
const { open, setOpen } = useDropdownMenu();
const handleClick = (e: React.MouseEvent) => {
e.stopPropagation();
setOpen(!open);
};
if (asChild && React.isValidElement(children)) {
return React.cloneElement(children as React.ReactElement<any>, {
onClick: (e: React.MouseEvent) => {
handleClick(e);
const originalOnClick = (children as React.ReactElement<any>).props.onClick;
if (originalOnClick) originalOnClick(e);
},
});
}
return (
<button type="button" onClick={handleClick}>
{children}
</button>
);
}
interface DropdownMenuContentProps extends React.HTMLAttributes<HTMLDivElement> {
align?: 'start' | 'center' | 'end';
sideOffset?: number;
}
export function DropdownMenuContent({
className,
align = 'end',
children,
...props
}: DropdownMenuContentProps) {
const { open, setOpen } = useDropdownMenu();
const ref = React.useRef<HTMLDivElement>(null);
React.useEffect(() => {
if (!open) return;
const handleClickOutside = (e: MouseEvent) => {
if (ref.current && !ref.current.contains(e.target as Node)) {
setOpen(false);
}
};
const handleEscape = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
setOpen(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
document.addEventListener('keydown', handleEscape);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
document.removeEventListener('keydown', handleEscape);
};
}, [open, setOpen]);
if (!open) return null;
const alignClasses = {
start: 'left-0',
center: 'left-1/2 -translate-x-1/2',
end: 'right-0',
};
return (
<div
ref={ref}
className={cn(
'absolute top-full mt-1 z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md',
'animate-in fade-in-0 zoom-in-95',
alignClasses[align],
className
)}
{...props}
>
{children}
</div>
);
}
interface DropdownMenuItemProps extends React.HTMLAttributes<HTMLDivElement> {
disabled?: boolean;
asChild?: boolean;
}
export function DropdownMenuItem({
className,
disabled,
children,
asChild,
onClick,
...props
}: DropdownMenuItemProps) {
const { setOpen } = useDropdownMenu();
const handleClick = (e: React.MouseEvent<HTMLDivElement>) => {
if (disabled) return;
onClick?.(e);
setOpen(false);
};
if (asChild && React.isValidElement(children)) {
return React.cloneElement(children as React.ReactElement<any>, {
className: cn(
'relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors',
'hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground',
disabled && 'pointer-events-none opacity-50',
className,
(children as React.ReactElement<any>).props.className
),
onClick: (e: React.MouseEvent) => {
if (disabled) return;
const originalOnClick = (children as React.ReactElement<any>).props.onClick;
if (originalOnClick) originalOnClick(e);
setOpen(false);
},
});
}
return (
<div
className={cn(
'relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors',
'hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground',
disabled && 'pointer-events-none opacity-50',
className
)}
onClick={handleClick}
{...props}
>
{children}
</div>
);
}
export function DropdownMenuSeparator({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) {
return (
<div
className={cn('-mx-1 my-1 h-px bg-muted', className)}
{...props}
/>
);
}
export function DropdownMenuLabel({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) {
return (
<div
className={cn('px-2 py-1.5 text-sm font-semibold', className)}
{...props}
/>
);
}