| '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'; |
|
|