Spaces:
Running
Running
| 'use client'; | |
| import { useRef } from 'react'; | |
| import { useState, useEffect } from 'react'; | |
| import { createGlobalState } from 'react-global-hooks'; | |
| import { Dialog, DialogBackdrop, DialogPanel, DialogTitle } from '@headlessui/react'; | |
| import { FaExclamationTriangle, FaInfo } from 'react-icons/fa'; | |
| import { TextInput } from './formInputs'; | |
| import React from 'react'; | |
| import { useFromNull } from '@/hooks/useFromNull'; | |
| import classNames from 'classnames'; | |
| export interface ConfirmState { | |
| title: string; | |
| message?: string; | |
| confirmText?: string; | |
| type?: 'danger' | 'warning' | 'info'; | |
| inputTitle?: string; | |
| onConfirm?: (value?: string) => void | Promise<void>; | |
| onCancel?: () => void; | |
| } | |
| export const confirmstate = createGlobalState<ConfirmState | null>(null); | |
| export const openConfirm = (confirmProps: ConfirmState) => { | |
| confirmstate.set(confirmProps); | |
| }; | |
| export default function ConfirmModal() { | |
| const [confirm, setConfirm] = confirmstate.use(); | |
| const [isOpen, setIsOpen] = useState(false); | |
| const [inputValue, setInputValue] = useState<string>(''); | |
| const inputRef = useRef<HTMLInputElement>(null); | |
| useFromNull(() => { | |
| setTimeout(() => { | |
| if (inputRef.current) { | |
| inputRef.current.focus(); | |
| } | |
| }, 100); | |
| }, [confirm]); | |
| useEffect(() => { | |
| if (confirm) { | |
| setIsOpen(true); | |
| setInputValue(''); | |
| } | |
| }, [confirm]); | |
| useEffect(() => { | |
| if (!isOpen) { | |
| // use timeout to allow the dialog to close before resetting the state | |
| setTimeout(() => { | |
| setConfirm(null); | |
| }, 500); | |
| } | |
| }, [isOpen]); | |
| const onCancel = () => { | |
| if (confirm?.onCancel) { | |
| confirm.onCancel(); | |
| } | |
| setIsOpen(false); | |
| }; | |
| const onConfirm = () => { | |
| if (confirm?.onConfirm) { | |
| confirm.onConfirm(inputValue); | |
| } | |
| setIsOpen(false); | |
| }; | |
| let Icon = FaExclamationTriangle; | |
| let color = confirm?.type || 'danger'; | |
| // Use conditional rendering for icon | |
| if (color === 'info') { | |
| Icon = FaInfo; | |
| } | |
| // Color mapping for background colors | |
| const getBgColor = () => { | |
| switch (color) { | |
| case 'danger': | |
| return 'bg-red-500'; | |
| case 'warning': | |
| return 'bg-yellow-500'; | |
| case 'info': | |
| return 'bg-blue-500'; | |
| default: | |
| return 'bg-red-500'; | |
| } | |
| }; | |
| // Color mapping for text colors | |
| const getTextColor = () => { | |
| switch (color) { | |
| case 'danger': | |
| return 'text-red-950'; | |
| case 'warning': | |
| return 'text-yellow-950'; | |
| case 'info': | |
| return 'text-blue-950'; | |
| default: | |
| return 'text-red-950'; | |
| } | |
| }; | |
| // Color mapping for titles | |
| const getTitleColor = () => { | |
| switch (color) { | |
| case 'danger': | |
| return 'text-red-500'; | |
| case 'warning': | |
| return 'text-yellow-500'; | |
| case 'info': | |
| return 'text-blue-500'; | |
| default: | |
| return 'text-red-500'; | |
| } | |
| }; | |
| // Button background color mapping | |
| const getButtonBgColor = () => { | |
| switch (color) { | |
| case 'danger': | |
| return 'bg-red-700 hover:bg-red-500'; | |
| case 'warning': | |
| return 'bg-yellow-700 hover:bg-yellow-500'; | |
| case 'info': | |
| return 'bg-blue-700 hover:bg-blue-500'; | |
| default: | |
| return 'bg-red-700 hover:bg-red-500'; | |
| } | |
| }; | |
| return ( | |
| <Dialog open={isOpen} onClose={onCancel} className="relative z-10"> | |
| <DialogBackdrop | |
| transition | |
| className="fixed inset-0 bg-gray-900/75 transition-opacity data-closed:opacity-0 data-enter:duration-300 data-enter:ease-out data-leave:duration-200 data-leave:ease-in" | |
| /> | |
| <div className="fixed inset-0 z-10 w-screen overflow-y-auto"> | |
| <div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0"> | |
| <DialogPanel | |
| transition | |
| className="relative transform overflow-hidden rounded-lg bg-gray-800 text-left shadow-xl transition-all data-closed:translate-y-4 data-closed:opacity-0 data-enter:duration-300 data-enter:ease-out data-leave:duration-200 data-leave:ease-in sm:my-8 sm:w-full sm:max-w-lg data-closed:sm:translate-y-0 data-closed:sm:scale-95" | |
| > | |
| <div className="bg-gray-800 px-4 pt-5 pb-4 sm:p-6 sm:pb-4"> | |
| <div className="sm:flex sm:items-start"> | |
| <div | |
| className={`mx-auto flex size-12 shrink-0 items-center justify-center rounded-full ${getBgColor()} sm:mx-0 sm:size-10`} | |
| > | |
| <Icon aria-hidden="true" className={`size-6 ${getTextColor()}`} /> | |
| </div> | |
| <div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left flex-1"> | |
| <DialogTitle as="h3" className={`text-base font-semibold ${getTitleColor()}`}> | |
| {confirm?.title} | |
| </DialogTitle> | |
| <div className="mt-2"> | |
| <p className="text-sm text-gray-200">{confirm?.message}</p> | |
| <div className={classNames('mt-4 w-full', { hidden: !confirm?.inputTitle })}> | |
| <form onSubmit={(e) => { | |
| e.preventDefault() | |
| onConfirm() | |
| }}> | |
| <TextInput | |
| value={inputValue} | |
| ref={inputRef} | |
| onChange={setInputValue} | |
| placeholder={confirm?.inputTitle} | |
| /> | |
| </form> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div className="bg-gray-700 px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6"> | |
| <button | |
| type="button" | |
| onClick={onConfirm} | |
| className={`inline-flex w-full justify-center rounded-md ${getButtonBgColor()} px-3 py-2 text-sm font-semibold text-white shadow-xs sm:ml-3 sm:w-auto`} | |
| > | |
| {confirm?.confirmText || 'Confirm'} | |
| </button> | |
| <button | |
| type="button" | |
| data-autofocus | |
| onClick={onCancel} | |
| className="mt-3 inline-flex w-full justify-center rounded-md bg-gray-800 px-3 py-2 text-sm font-semibold text-gray-200 hover:bg-gray-800 sm:mt-0 sm:w-auto ring-0" | |
| > | |
| Cancel | |
| </button> | |
| </div> | |
| </DialogPanel> | |
| </div> | |
| </div> | |
| </Dialog> | |
| ); | |
| } | |