Dialog Box
Overview
The Dialog Box component provides a flexible way to display modal dialogs with various animations and types. It's built on top of Framer Motion for smooth animations and supports different dialog types like alerts, confirmations, and custom content.
Preview
- Preview
- Code
import { DialogProvider, useDialog } from './components/ui';
import { Button } from './components/ui';
function Demo() {
const { openDialog } = useDialog();
return (
<Button
onClick={() => openDialog({
title: 'Alert',
content: 'This is an alert dialog.',
dialogType: 'alert'
})}
>
Show Alert Dialog
</Button>
);
}
// In your app:
<DialogProvider>
<Demo />
</DialogProvider>
Installation
- npm
- yarn
- pnpm
- manual
npx @mindfiredigital/ignix-ui add dialog-box
yarn @mindfiredigital/ignix-ui add dialog-box
pnpm @mindfiredigital/ignix-ui add dialog-box
import { motion, AnimatePresence } from 'framer-motion';
import { createContext, CSSProperties, useState } from 'react';
import { X, Check, AlertTriangle, Info, XCircle } from 'lucide-react';
// types/dialog.ts
export interface DialogRef {
open: (options?: DialogProps) => void;
close: () => void;
}
export type DialogAnimationTypes =
| 'popIn'
| 'springPop'
| 'backdropZoom'
| 'flip3D'
| 'skewSlide'
| 'glassBlur'
| 'skyDrop'
| 'morphSlide'
| 'quantumFold'
| 'prismBreak'
| 'liquidFloat'
| 'dimensionRip';
export type DialogTypes = 'success' | 'confirm' | 'error' | 'alert' | 'info' | 'warning';
export const animations = {
popIn: {
initial: {
scale: 0.95,
opacity: 0,
y: 20,
},
animate: {
scale: 1,
opacity: 1,
y: 0,
transition: {
type: 'spring',
stiffness: 300,
damping: 25,
duration: 0.3,
},
},
exit: {
scale: 0.95,
opacity: 0,
y: 20,
transition: {
duration: 0.2,
},
},
},
springPop: {
initial: {
y: 100,
opacity: 0,
scale: 0.2,
rotateX: 20,
filter: 'blur(8px)',
},
animate: {
y: 0,
opacity: 1,
scale: 1,
rotateX: 0,
filter: 'blur(0px)',
transition: {
type: 'spring',
stiffness: 200,
damping: 15,
mass: 0.5,
velocity: 2,
},
},
exit: {
y: 100,
scale: 0.2,
opacity: 0,
filter: 'blur(4px)',
transition: {
duration: 0.4,
ease: 'easeIn',
},
},
},
backdropZoom: {
initial: { scale: 1.3, opacity: 0, filter: 'blur(10px)' },
animate: {
scale: 1,
opacity: 1,
filter: 'blur(0px)',
transition: {
duration: 0.8,
type: 'spring',
stiffness: 200,
ease: 'easeInOut',
},
},
exit: {
scale: 0.95,
opacity: 0,
filter: 'blur(4px)',
transition: { duration: 0.3, ease: 'easeIn' },
},
},
flip3D: {
initial: {
rotateY: -180,
opacity: 0,
scale: 0.8,
filter: 'brightness(0.5)',
},
animate: {
rotateY: 0,
opacity: 1,
scale: 1,
filter: 'brightness(1)',
transition: {
duration: 0.8,
ease: [0.68, -0.55, 0.265, 1.55],
times: [0, 0.6, 1],
},
},
exit: {
rotateY: 180,
opacity: 0,
scale: 0.8,
filter: 'brightness(0.5)',
transition: { duration: 0.5, ease: 'easeIn' },
},
},
skewSlide: {
initial: {
skewY: -30,
skewX: -30,
scale: 0.2,
y: -400,
x: -300,
opacity: 0,
rotateZ: -15,
filter: 'blur(8px) brightness(0.3)',
},
animate: {
skewY: [30, -5, 0],
skewX: [30, -5, 0],
scale: [0.2, 1.1, 1],
y: [-400, 20, 0],
x: [-300, 15, 0],
opacity: [0, 0.7, 1],
rotateZ: [-15, 2, 0],
filter: 'blur(0px) brightness(1)',
transition: {
duration: 1.2,
ease: [0.68, -0.55, 0.265, 1.55],
times: [0, 0.7, 1],
},
},
exit: {
skewY: 30,
skewX: 30,
scale: 0.2,
y: -300,
x: 400,
opacity: 0,
rotateZ: 15,
filter: 'blur(6px)',
transition: { duration: 0.6, ease: 'easeIn' },
},
},
glassBlur: {
initial: {
opacity: 0,
scale: 0.95,
filter: 'blur(20px) saturate(1.5)',
backdropFilter: 'blur(0px)',
},
animate: {
opacity: 1,
scale: 1,
filter: 'blur(0px) saturate(1)',
backdropFilter: 'blur(20px)',
transition: {
duration: 0.8,
ease: 'easeOut',
filter: { duration: 1 },
},
},
exit: {
opacity: 0,
scale: 0.95,
filter: 'blur(10px)',
transition: { duration: 0.4, ease: 'easeIn' },
},
},
skyDrop: {
initial: {
y: -500,
opacity: 0,
scale: 0.7,
rotateX: -45,
filter: 'blur(10px)',
},
animate: {
y: [0, 15, 0],
opacity: [0, 0.8, 1],
scale: [0.7, 1.05, 1],
rotateX: [-45, 5, 0],
filter: 'blur(0px)',
transition: {
duration: 1,
ease: [0.34, 1.56, 0.64, 1],
times: [0, 0.6, 1],
},
},
exit: {
y: -400,
opacity: 0,
scale: 0.8,
rotateX: -30,
filter: 'blur(6px)',
transition: { duration: 0.5, ease: 'easeIn' },
},
},
// NEW PREMIUM ANIMATIONS
morphSlide: {
initial: {
scale: 0.3,
opacity: 0,
x: -200,
rotateZ: -45,
borderRadius: '50%',
filter: 'blur(15px) hue-rotate(180deg)',
},
animate: {
scale: [0.3, 1.2, 1],
opacity: [0, 0.6, 1],
x: [-200, 30, 0],
rotateZ: [-45, 10, 0],
borderRadius: ['50%', '20%', '12px'],
filter: 'blur(0px) hue-rotate(0deg)',
transition: {
duration: 1.5,
ease: [0.68, -0.55, 0.265, 1.55],
times: [0, 0.6, 1],
},
},
exit: {
scale: 0.4,
opacity: 0,
x: 200,
rotateZ: 45,
borderRadius: '50%',
filter: 'blur(10px)',
transition: { duration: 0.8, ease: 'easeIn' },
},
},
quantumFold: {
initial: {
scaleX: 0,
scaleY: 1.5,
opacity: 0,
rotateY: 90,
rotateX: 45,
filter: 'brightness(2) contrast(0.5) blur(10px)',
},
animate: {
scaleX: [0, 1.3, 1],
scaleY: [1.5, 0.8, 1],
opacity: [0, 0.4, 1],
rotateY: [90, -15, 0],
rotateX: [45, -10, 0],
filter: 'brightness(1) contrast(1) blur(0px)',
transition: {
duration: 1.3,
ease: [0.68, -0.55, 0.265, 1.55],
times: [0, 0.5, 1],
},
},
exit: {
scaleX: 0,
scaleY: 1.5,
opacity: 0,
rotateY: -90,
filter: 'blur(8px)',
transition: { duration: 0.6, ease: 'easeIn' },
},
},
prismBreak: {
initial: {
scale: 0.1,
opacity: 0,
rotateX: 180,
rotateY: 180,
rotateZ: 180,
filter: 'blur(20px) saturate(3) hue-rotate(90deg)',
},
animate: {
scale: [0.1, 1.4, 1],
opacity: [0, 0.3, 1],
rotateX: [180, -20, 0],
rotateY: [180, 20, 0],
rotateZ: [180, -10, 0],
filter: 'blur(0px) saturate(1) hue-rotate(0deg)',
transition: {
duration: 1.8,
ease: [0.68, -0.55, 0.265, 1.55],
times: [0, 0.4, 1],
},
},
exit: {
scale: 0.2,
opacity: 0,
rotateX: -90,
rotateY: 90,
rotateZ: 45,
filter: 'blur(15px)',
transition: { duration: 0.8, ease: 'easeIn' },
},
},
liquidFloat: {
initial: {
scale: 0.6,
opacity: 0,
y: 100,
borderRadius: '50%',
filter: 'blur(12px)',
},
animate: {
scale: [0.6, 1.1, 0.95, 1.02, 1],
opacity: [0, 0.7, 0.9, 1, 1],
y: [100, -20, 10, -5, 0],
borderRadius: ['50%', '30%', '20%', '15%', '12px'],
filter: 'blur(0px)',
transition: {
duration: 2,
ease: 'easeOut',
times: [0, 0.3, 0.6, 0.8, 1],
},
},
exit: {
scale: 0.7,
opacity: 0,
y: -50,
borderRadius: '50%',
filter: 'blur(8px)',
transition: { duration: 0.6, ease: 'easeIn' },
},
},
dimensionRip: {
initial: {
scaleX: 0.1,
scaleY: 2,
opacity: 0,
rotateZ: 45,
skewX: 30,
filter: 'blur(20px) brightness(3) contrast(2)',
},
animate: {
scaleX: [0.1, 2, 1],
scaleY: [2, 0.5, 1],
opacity: [0, 0.5, 1],
rotateZ: [45, -10, 0],
skewX: [30, -5, 0],
filter: 'blur(0px) brightness(1) contrast(1)',
transition: {
duration: 1.4,
ease: [0.68, -0.55, 0.265, 1.55],
times: [0, 0.4, 1],
},
},
exit: {
scaleX: 0.1,
scaleY: 2,
opacity: 0,
rotateZ: -45,
skewX: -30,
filter: 'blur(15px)',
transition: { duration: 0.7, ease: 'easeIn' },
},
},
};
interface DialogProps {
animationKey?: DialogAnimationTypes;
confirmationCallBack?: (confirm: boolean) => void;
dialogType?: DialogTypes;
title?: string;
confirmButtonText?: string;
cancelButtonText?: string;
headerStyles?: CSSProperties;
children?: React.ReactNode;
defaultButtons?: boolean;
content?: string;
showCloseButton?: boolean;
size?: 'sm' | 'md' | 'lg' | 'xl';
}
const typeConfig = {
success: {
gradient: 'from-success-light via-success to-success-dark',
icon: Check,
iconBg: 'bg-success/20',
iconColor: 'text-success-dark',
borderColor: 'border-success/30',
shadowColor: 'shadow-success/25',
},
confirm: {
gradient: 'from-primary-light via-primary to-primary-dark',
icon: Info,
iconBg: 'bg-primary/20',
iconColor: 'text-primary-dark',
borderColor: 'border-primary/30',
shadowColor: 'shadow-primary/25',
},
error: {
gradient: 'from-destructive-light via-destructive to-destructive-dark',
icon: XCircle,
iconBg: 'bg-destructive/20',
iconColor: 'text-destructive-dark',
borderColor: 'border-destructive/30',
shadowColor: 'shadow-destructive/25',
},
alert: {
gradient: 'from-warning-light via-warning to-warning-dark',
icon: AlertTriangle,
iconBg: 'bg-warning/20',
iconColor: 'text-warning-dark',
borderColor: 'border-warning/30',
shadowColor: 'shadow-warning/25',
},
info: {
gradient: 'from-info-light via-info to-info-dark',
icon: Info,
iconBg: 'bg-info/20',
iconColor: 'text-info-dark',
borderColor: 'border-info/30',
shadowColor: 'shadow-info/25',
},
warning: {
gradient: 'from-warning-light via-warning to-warning-dark',
icon: AlertTriangle,
iconBg: 'bg-warning/20',
iconColor: 'text-warning-dark',
borderColor: 'border-warning/30',
shadowColor: 'shadow-warning/25',
},
};
const sizeConfig = {
sm: 'max-w-sm',
md: 'max-w-md',
lg: 'max-w-lg',
xl: 'max-w-xl',
};
export interface DialogContextType {
openDialog: (opts: DialogProps) => void;
closeDialog: () => void;
}
export const DialogContext = createContext<DialogContextType | undefined>(
undefined
) as React.Context<DialogContextType>;
export const DialogProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [isOpen, setIsOpen] = useState(false);
const [options, setOptions] = useState<DialogProps>({
dialogType: 'alert',
title: 'Default Title',
});
const openDialog = (opts: DialogProps) => {
const finalOptions: DialogProps = {
animationKey: 'popIn',
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
defaultButtons: false,
showCloseButton: true,
size: 'md',
...opts,
};
setOptions(finalOptions);
setIsOpen(true);
};
const closeDialog = () => {
setIsOpen(false);
};
const handleClose = (confirm = false) => {
setIsOpen(false);
if (options?.dialogType === 'confirm' && options?.confirmationCallBack) {
options?.confirmationCallBack(confirm);
}
};
const anim = animations[options?.animationKey || 'popIn'];
const config = typeConfig[options?.dialogType || 'alert'];
const IconComponent = config.icon;
return (
<DialogContext.Provider value={{ openDialog, closeDialog }}>
{children}
<AnimatePresence>
{isOpen && (
<motion.div
className="fixed inset-0 z-50 flex items-center justify-center p-4"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.2 }}
>
{/* Backdrop */}
<motion.div
className="absolute inset-0 dark:bg-black/60 bg-white/60 backdrop-blur-sm"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.2 }}
onClick={() => handleClose(false)}
/>
{/* Subtle animated background pattern */}
<div className="absolute inset-0 opacity-10">
<div className="absolute inset-0 bg-gradient-to-br from-primary/20 via-secondary/20 to-accent/20 animate-pulse" />
</div>
<motion.div
className={`
relative bg-background/95 backdrop-blur-xl rounded-2xl shadow-2xl
${sizeConfig[options?.size || 'md']} w-full max-h-[85vh]
flex flex-col border border-border/40 overflow-hidden
${config.shadowColor}
before:absolute before:inset-0 before:bg-gradient-to-br before:from-white/10 before:via-white/5 before:to-transparent before:pointer-events-none
after:absolute after:inset-0 after:bg-gradient-to-t after:from-background/20 after:via-transparent after:to-white/10 after:pointer-events-none
`}
style={{ transformStyle: 'preserve-3d' }}
{...anim}
>
{/* Premium Header with enhanced gradients and effects */}
<div
className={`
relative flex items-center justify-between rounded-t-2xl p-6
bg-gradient-to-br ${config.gradient} text-white overflow-hidden
border-b ${config.borderColor}
`}
style={options?.headerStyles}
>
{/* Animated background pattern */}
<motion.div
className="absolute inset-0 bg-gradient-to-r from-white/10 via-transparent to-white/10"
animate={{
x: ['-100%', '100%'],
}}
transition={{
duration: 3,
repeat: Infinity,
ease: 'linear',
}}
/>
{/* Icon with enhanced styling */}
<motion.div
className={`
flex items-center justify-center w-12 h-12 rounded-xl mr-4
${config.iconBg} backdrop-blur-sm border border-white/20
shadow-lg
`}
initial={{ scale: 0, rotate: -180 }}
animate={{ scale: 1, rotate: 0 }}
transition={{
delay: 0.2,
type: 'spring',
stiffness: 300,
damping: 15,
}}
whileHover={{
scale: 1.1,
rotate: 5,
transition: { duration: 0.2 },
}}
>
<IconComponent className="h-6 w-6 text-white" />
</motion.div>
{/* Enhanced Title */}
<motion.h2
className="text-xl sm:text-2xl font-bold flex-1 relative z-10"
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: 0.3, duration: 0.5 }}
>
{options?.title}
</motion.h2>
{/* Premium Close Button */}
{options?.showCloseButton && (
<motion.button
onClick={() => handleClose(false)}
className="
relative w-10 h-10 rounded-xl bg-white/20 backdrop-blur-sm
flex items-center justify-center transition-all duration-300
hover:bg-white/30 hover:scale-110 active:scale-95
border border-white/30 group z-10
"
initial={{ opacity: 0, scale: 0, rotate: 90 }}
animate={{ opacity: 1, scale: 1, rotate: 0 }}
transition={{ delay: 0.4, type: 'spring', stiffness: 300 }}
whileHover={{ rotate: 90 }}
whileTap={{ scale: 0.9 }}
>
<X className="h-5 w-5 text-white group-hover:text-white/90" />
</motion.button>
)}
</div>
{/* Enhanced Content Area */}
<motion.div
className="p-6 flex-1 overflow-y-auto relative"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.3, duration: 0.5 }}
>
<div className="prose prose-sm max-w-none dark:prose-invert">
{options.children ?? (
<p className="text-muted-foreground leading-relaxed">
{options.content}
</p>
)}
</div>
</motion.div>
{/* Premium Button Area */}
{!options?.defaultButtons && (
<motion.div
className="flex items-center justify-center gap-3 p-6 pt-0"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.4, duration: 0.5 }}
>
{options?.dialogType === 'confirm' && (
<motion.button
onClick={() => handleClose(true)}
className="
relative px-6 py-3 font-semibold text-white rounded-xl overflow-hidden
bg-gradient-to-br from-primary via-primary-dark to-primary-darker
hover:from-primary-dark hover:via-primary-darker hover:to-primary-darkest
shadow-lg shadow-primary/25 hover:shadow-xl hover:shadow-primary/40
border border-primary/20 backdrop-blur-sm
transition-all duration-300 group transform-gpu
hover:scale-105 active:scale-95
before:absolute before:inset-0 before:bg-gradient-to-t
before:from-white/0 before:to-white/20 before:opacity-0
before:group-hover:opacity-100 before:transition-opacity
"
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
transition={{ type: 'spring', stiffness: 400, damping: 25 }}
>
<span className="relative z-10 flex items-center gap-2">
<Check className="h-4 w-4" />
{options?.confirmButtonText || 'Confirm'}
</span>
</motion.button>
)}
<motion.button
onClick={() => handleClose(false)}
className="
relative px-6 py-3 font-semibold rounded-xl overflow-hidden
bg-gradient-to-br from-muted via-muted-dark to-muted-darker text-muted-foreground
hover:from-muted-dark hover:via-muted-darker hover:to-muted-darkest
shadow-lg shadow-muted/20 hover:shadow-xl hover:shadow-muted/30
border border-muted/30 backdrop-blur-sm
transition-all duration-300 group transform-gpu
hover:scale-105 active:scale-95
"
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
transition={{ type: 'spring', stiffness: 400, damping: 25 }}
>
<span className="relative z-10 flex items-center gap-2">
<X className="h-4 w-4" />
{options?.cancelButtonText || 'Cancel'}
</span>
</motion.button>
</motion.div>
)}
{/* Subtle border glow effect */}
<div
className={`absolute inset-0 rounded-2xl border ${config.borderColor} opacity-50 pointer-events-none`}
/>
</motion.div>
</motion.div>
)}
</AnimatePresence>
</DialogContext.Provider>
export const useDialog = () => {
const context = useContext(DialogContext);
if (!context) throw new Error('useDialog must be used within a DialogProvider');
return context;
};
);
};
Basic Usage
1. Wrap your app with DialogProvider
import { DialogProvider } from './components/ui';
function App() {
return (
<DialogProvider>
{/* Your app components */}
</DialogProvider>
);
}
2. Use the useDialog hook in your components
import { useDialog } from './components/ui';
function MyComponent() {
const { openDialog } = useDialog();
const handleClick = () => {
openDialog({
title: 'Hello',
content: 'This is a dialog!',
dialogType: 'alert'
});
};
return <button onClick={handleClick}>Open Dialog</button>;
}
Variants
- Preview
- Code
<Button
onClick={() => openDialog({
title: 'Alert',
content: 'This is an alert dialog.',
dialogType: 'alert',
animationKey: 'popIn',
})}
>
Show Alert Dialog
</Button>