clinicpal / src /components /ui /glass-dialog.tsx
Vrda's picture
Deploy ClinIcPal frontend
9bc2f29 verified
'use client';
import { forwardRef, type ReactNode } from 'react';
import * as Dialog from '@radix-ui/react-dialog';
import { motion, AnimatePresence } from 'framer-motion';
import { cn } from '@/lib/utils';
import { GlassButton } from './glass-button';
export interface GlassDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
title: string;
description?: string;
children?: ReactNode;
confirmLabel?: string;
cancelLabel?: string;
onConfirm?: () => void;
onCancel?: () => void;
variant?: 'default' | 'danger';
}
export const GlassDialog = forwardRef<HTMLDivElement, GlassDialogProps>(
(
{
open,
onOpenChange,
title,
description,
children,
confirmLabel = 'Confirm',
cancelLabel = 'Cancel',
onConfirm,
onCancel,
variant = 'default',
},
ref
) => {
const handleCancel = () => {
onCancel?.();
onOpenChange(false);
};
const handleConfirm = () => {
onConfirm?.();
onOpenChange(false);
};
return (
<Dialog.Root open={open} onOpenChange={onOpenChange}>
<AnimatePresence>
{open && (
<Dialog.Portal forceMount>
<Dialog.Overlay asChild>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.15 }}
className="fixed inset-0 bg-black/40 backdrop-blur-sm z-50"
/>
</Dialog.Overlay>
<Dialog.Content asChild>
<motion.div
ref={ref}
initial={{ opacity: 0, scale: 0.95, y: 10 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.95, y: 10 }}
transition={{ duration: 0.15, ease: 'easeOut' }}
className={cn(
'fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2',
'w-full max-w-md p-6 rounded-2xl z-50',
'glass-elevated',
'border border-[var(--glass-border)]',
'shadow-2xl',
'focus:outline-none'
)}
>
<Dialog.Title className="text-lg font-semibold text-[var(--foreground)]">
{title}
</Dialog.Title>
{description && (
<Dialog.Description className="mt-2 text-sm text-[var(--foreground)]/70">
{description}
</Dialog.Description>
)}
{children && <div className="mt-4">{children}</div>}
<div className="flex justify-end gap-3 mt-6">
<GlassButton variant="ghost" onClick={handleCancel}>
{cancelLabel}
</GlassButton>
<GlassButton
variant={variant === 'danger' ? 'primary' : 'primary'}
onClick={handleConfirm}
className={
variant === 'danger'
? 'bg-gradient-to-r from-red-500 to-red-600 border-red-400/30 shadow-red-500/20 hover:shadow-red-500/30 hover:from-red-400 hover:to-red-500'
: ''
}
>
{confirmLabel}
</GlassButton>
</div>
</motion.div>
</Dialog.Content>
</Dialog.Portal>
)}
</AnimatePresence>
</Dialog.Root>
);
}
);
GlassDialog.displayName = 'GlassDialog';