Files
support_frontend/src/components/ui/popover.tsx
T
2026-08-21 10:41:09 +05:30

95 lines
2.6 KiB
TypeScript

import * as React from 'react';
import { cn } from '@/lib/utils';
interface PopoverContextValue {
open: boolean;
setOpen: (open: boolean) => void;
}
const PopoverContext = React.createContext<PopoverContextValue | null>(null);
export interface PopoverProps {
children: React.ReactNode;
}
const Popover: React.FC<PopoverProps> = ({ children }) => {
const [open, setOpen] = React.useState(false);
const popoverRef = React.useRef<HTMLDivElement>(null);
React.useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (popoverRef.current && !popoverRef.current.contains(event.target as Node)) {
setOpen(false);
}
};
if (open) {
document.addEventListener('mousedown', handleClickOutside);
}
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [open]);
return (
<PopoverContext.Provider value={{ open, setOpen }}>
<div ref={popoverRef} className="relative inline-block text-left">
{children}
</div>
</PopoverContext.Provider>
);
};
export interface PopoverTriggerProps {
children: React.ReactElement;
}
const PopoverTrigger: React.FC<PopoverTriggerProps> = ({ children }) => {
const context = React.useContext(PopoverContext);
if (!context) throw new Error('PopoverTrigger must be used within Popover');
return React.cloneElement(children, {
onClick: (e: React.MouseEvent) => {
children.props.onClick?.(e);
context.setOpen(!context.open);
},
'aria-expanded': context.open,
});
};
export interface PopoverContentProps extends React.HTMLAttributes<HTMLDivElement> {
align?: 'left' | 'right' | 'center';
}
const PopoverContent = React.forwardRef<HTMLDivElement, PopoverContentProps>(
({ className, align = 'center', children, ...props }, ref) => {
const context = React.useContext(PopoverContext);
if (!context) throw new Error('PopoverContent must be used within Popover');
if (!context.open) return null;
const alignClasses = {
left: 'left-0 origin-top-left',
right: 'right-0 origin-top-right',
center: 'left-1/2 -translate-x-1/2 origin-top',
};
return (
<div
ref={ref}
className={cn(
'absolute z-50 mt-2 w-72 rounded-xl border border-border bg-popover p-4 text-popover-foreground shadow-dropdown outline-none animate-scale-in',
alignClasses[align],
className
)}
{...props}
>
{children}
</div>
);
}
);
PopoverContent.displayName = 'PopoverContent';
export { Popover, PopoverTrigger, PopoverContent };