| import * as RadixDialog from '@radix-ui/react-dialog'; |
| import { motion, type Variants } from 'framer-motion'; |
| import React, { memo, type ReactNode } from 'react'; |
| import { classNames } from '~/utils/classNames'; |
| import { cubicEasingFn } from '~/utils/easings'; |
| import { IconButton } from './IconButton'; |
|
|
| export { Close as DialogClose, Root as DialogRoot } from '@radix-ui/react-dialog'; |
|
|
| interface DialogButtonProps { |
| type: 'primary' | 'secondary' | 'danger'; |
| children: ReactNode; |
| onClick?: (event: React.MouseEvent) => void; |
| disabled?: boolean; |
| } |
|
|
| export const DialogButton = memo(({ type, children, onClick, disabled }: DialogButtonProps) => { |
| return ( |
| <button |
| className={classNames('inline-flex items-center gap-2 px-4 py-2 rounded-lg text-sm transition-colors', { |
| 'bg-purple-500 text-white hover:bg-purple-600 dark:bg-purple-500 dark:hover:bg-purple-600': type === 'primary', |
| 'bg-transparent text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-gray-900 dark:hover:text-gray-100': |
| type === 'secondary', |
| 'bg-transparent text-red-500 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-500/10': type === 'danger', |
| })} |
| onClick={onClick} |
| disabled={disabled} |
| > |
| {children} |
| </button> |
| ); |
| }); |
|
|
| export const DialogTitle = memo(({ className, children, ...props }: RadixDialog.DialogTitleProps) => { |
| return ( |
| <RadixDialog.Title |
| className={classNames('text-lg font-medium text-bolt-elements-textPrimary', 'flex items-center gap-2', className)} |
| {...props} |
| > |
| {children} |
| </RadixDialog.Title> |
| ); |
| }); |
|
|
| export const DialogDescription = memo(({ className, children, ...props }: RadixDialog.DialogDescriptionProps) => { |
| return ( |
| <RadixDialog.Description |
| className={classNames('text-sm text-bolt-elements-textSecondary', 'mt-1', className)} |
| {...props} |
| > |
| {children} |
| </RadixDialog.Description> |
| ); |
| }); |
|
|
| const transition = { |
| duration: 0.15, |
| ease: cubicEasingFn, |
| }; |
|
|
| export const dialogBackdropVariants = { |
| closed: { |
| opacity: 0, |
| transition, |
| }, |
| open: { |
| opacity: 1, |
| transition, |
| }, |
| } satisfies Variants; |
|
|
| export const dialogVariants = { |
| closed: { |
| x: '-50%', |
| y: '-40%', |
| scale: 0.96, |
| opacity: 0, |
| transition, |
| }, |
| open: { |
| x: '-50%', |
| y: '-50%', |
| scale: 1, |
| opacity: 1, |
| transition, |
| }, |
| } satisfies Variants; |
|
|
| interface DialogProps { |
| children: ReactNode; |
| className?: string; |
| showCloseButton?: boolean; |
| onClose?: () => void; |
| onBackdrop?: () => void; |
| } |
|
|
| export const Dialog = memo(({ children, className, showCloseButton = true, onClose, onBackdrop }: DialogProps) => { |
| return ( |
| <RadixDialog.Portal> |
| <RadixDialog.Overlay asChild> |
| <motion.div |
| className={classNames( |
| 'fixed inset-0 z-[9999]', |
| 'bg-[#FAFAFA]/80 dark:bg-[#0A0A0A]/80', |
| 'backdrop-blur-[2px]', |
| )} |
| initial="closed" |
| animate="open" |
| exit="closed" |
| variants={dialogBackdropVariants} |
| onClick={onBackdrop} |
| /> |
| </RadixDialog.Overlay> |
| <RadixDialog.Content asChild> |
| <motion.div |
| className={classNames( |
| 'fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2', |
| 'bg-[#FAFAFA] dark:bg-[#0A0A0A]', |
| 'rounded-lg shadow-lg', |
| 'border border-[#E5E5E5] dark:border-[#1A1A1A]', |
| 'z-[9999] w-[520px]', |
| className, |
| )} |
| initial="closed" |
| animate="open" |
| exit="closed" |
| variants={dialogVariants} |
| > |
| <div className="flex flex-col"> |
| {children} |
| {showCloseButton && ( |
| <RadixDialog.Close asChild onClick={onClose}> |
| <IconButton |
| icon="i-ph:x" |
| className="absolute top-3 right-3 text-bolt-elements-textSecondary hover:text-bolt-elements-textPrimary" |
| /> |
| </RadixDialog.Close> |
| )} |
| </div> |
| </motion.div> |
| </RadixDialog.Content> |
| </RadixDialog.Portal> |
| ); |
| }); |
|
|