Feat/new confirm (#6984)

This commit is contained in:
Yi Xiao
2024-08-06 14:31:13 +08:00
committed by GitHub
parent bd3ed89516
commit 0c22e4e3d1
33 changed files with 192 additions and 461 deletions

View File

@@ -1,7 +0,0 @@
.wrapper-danger {
background: linear-gradient(180deg, rgba(217, 45, 32, 0.05) 0%, rgba(217, 45, 32, 0.00) 24.02%), #F9FAFB;
}
.wrapper-success {
background: linear-gradient(180deg, rgba(3, 152, 85, 0.05) 0%, rgba(3, 152, 85, 0.00) 22.44%), #F9FAFB;
}

View File

@@ -1,97 +0,0 @@
import type { FC, ReactElement } from 'react'
import { useTranslation } from 'react-i18next'
import {
RiCloseLine,
RiErrorWarningFill,
} from '@remixicon/react'
import s from './common.module.css'
import cn from '@/utils/classnames'
import Modal from '@/app/components/base/modal'
import { CheckCircle } from '@/app/components/base/icons/src/vender/solid/general'
import Button from '@/app/components/base/button'
export type ConfirmCommonProps = {
type?: string
isShow: boolean
onCancel: () => void
title: string
desc?: string
onConfirm?: () => void
showOperate?: boolean
showOperateCancel?: boolean
confirmBtnClassName?: string
confirmText?: string
confirmWrapperClassName?: string
confirmDisabled?: boolean
}
const ConfirmCommon: FC<ConfirmCommonProps> = ({
type = 'danger',
isShow,
onCancel,
title,
desc,
onConfirm,
showOperate = true,
showOperateCancel = true,
confirmBtnClassName,
confirmText,
confirmWrapperClassName,
confirmDisabled,
}) => {
const { t } = useTranslation()
const CONFIRM_MAP: Record<string, { icon: ReactElement; confirmText: string }> = {
danger: {
icon: <RiErrorWarningFill className='w-6 h-6 text-[#D92D20]' />,
confirmText: t('common.operation.remove'),
},
success: {
icon: <CheckCircle className='w-6 h-6 text-[#039855]' />,
confirmText: t('common.operation.ok'),
},
}
return (
<Modal isShow={isShow} onClose={() => { }} className='!w-[480px] !max-w-[480px] !p-0 !rounded-2xl' wrapperClassName={confirmWrapperClassName}>
<div className={cn(s[`wrapper-${type}`], 'relative p-8')}>
<div className='flex items-center justify-center absolute top-4 right-4 w-8 h-8 cursor-pointer' onClick={onCancel}>
<RiCloseLine className='w-4 h-4 text-gray-500' />
</div>
<div className='flex items-center justify-center mb-3 w-12 h-12 bg-white shadow-xl rounded-xl'>
{CONFIRM_MAP[type].icon}
</div>
<div className='text-xl font-semibold text-gray-900'>{title}</div>
{
desc && <div className='mt-1 text-sm text-gray-500'>{desc}</div>
}
{
showOperate && (
<div className='flex items-center justify-end mt-10'>
{
showOperateCancel && (
<Button
className='mr-2'
onClick={onCancel}
>
{t('common.operation.cancel')}
</Button>
)
}
<Button
variant='primary'
className={confirmBtnClassName || ''}
onClick={onConfirm}
disabled={confirmDisabled}
>
{confirmText || CONFIRM_MAP[type].confirmText}
</Button>
</div>
)
}
</div>
</Modal>
)
}
export default ConfirmCommon

View File

@@ -1,26 +1,27 @@
import { Dialog, Transition } from '@headlessui/react'
import { Fragment } from 'react'
import React, { useEffect, useRef, useState } from 'react'
import { createPortal } from 'react-dom'
import { useTranslation } from 'react-i18next'
import ConfirmUI from '../confirm-ui'
import Button from '../button'
// https://headlessui.com/react/dialog
type IConfirm = {
export type IConfirm = {
className?: string
isShow: boolean
onClose: () => void
type?: 'info' | 'warning'
title: string
content: string
confirmText?: string
content?: React.ReactNode
confirmText?: string | null
onConfirm: () => void
cancelText?: string
onCancel: () => void
isLoading?: boolean
isDisabled?: boolean
showConfirm?: boolean
showCancel?: boolean
maskClosable?: boolean
}
export default function Confirm({
function Confirm({
isShow,
onClose,
type = 'warning',
title,
content,
@@ -28,52 +29,76 @@ export default function Confirm({
cancelText,
onConfirm,
onCancel,
showConfirm = true,
showCancel = true,
isLoading = false,
isDisabled = false,
maskClosable = true,
}: IConfirm) {
const { t } = useTranslation()
const dialogRef = useRef<HTMLDivElement>(null)
const [isVisible, setIsVisible] = useState(isShow)
const confirmTxt = confirmText || `${t('common.operation.confirm')}`
const cancelTxt = cancelText || `${t('common.operation.cancel')}`
return (
<Transition appear show={isShow} as={Fragment}>
<Dialog as="div" className="relative z-[100]" onClose={onClose} onClick={e => e.preventDefault()}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-black bg-opacity-25" />
</Transition.Child>
<div className="fixed inset-0 overflow-y-auto">
<div className="flex items-center justify-center min-h-full p-4 text-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className={'w-full max-w-md transform overflow-hidden rounded-2xl bg-white text-left align-middle shadow-xl transition-all'}>
<ConfirmUI
type={type}
title={title}
content={content}
confirmText={confirmTxt}
cancelText={cancelTxt}
onConfirm={onConfirm}
onCancel={onCancel}
/>
</Dialog.Panel>
</Transition.Child>
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === 'Escape')
onCancel()
}
document.addEventListener('keydown', handleKeyDown)
return () => {
document.removeEventListener('keydown', handleKeyDown)
}
}, [onCancel])
const handleClickOutside = (event: MouseEvent) => {
if (maskClosable && dialogRef.current && !dialogRef.current.contains(event.target as Node))
onCancel()
}
useEffect(() => {
document.addEventListener('mousedown', handleClickOutside)
return () => {
document.removeEventListener('mousedown', handleClickOutside)
}
}, [maskClosable])
useEffect(() => {
if (isShow) {
setIsVisible(true)
}
else {
const timer = setTimeout(() => setIsVisible(false), 200)
return () => clearTimeout(timer)
}
}, [isShow])
if (!isVisible)
return null
return createPortal(
<div className={'fixed inset-0 flex items-center justify-center z-[10000000] bg-background-overlay'}
onClick={(e) => {
e.preventDefault()
e.stopPropagation()
}}>
<div ref={dialogRef} className={'relative w-full max-w-[480px] overflow-hidden'}>
<div className='flex flex-col items-start max-w-full rounded-2xl border-[0.5px] border-solid border-components-panel-border shadows-shadow-lg bg-components-panel-bg'>
<div className='flex pt-6 pl-6 pr-6 pb-4 flex-col items-start gap-2 self-stretch'>
<div className='title-2xl-semi-bold text-text-primary'>{title}</div>
<div className='system-md-regular text-text-tertiary'>{content}</div>
</div>
<div className='flex p-6 gap-2 justify-end items-start self-stretch'>
{showCancel && <Button onClick={onCancel}>{cancelTxt}</Button>}
{showConfirm && <Button variant={'primary'} destructive={type !== 'info'} loading={isLoading} disabled={isDisabled} onClick={onConfirm}>{confirmTxt}</Button>}
</div>
</div>
</Dialog>
</Transition>
</div>
</div>, document.body,
)
}
export default React.memo(Confirm)