Browse Source
* feat: add dialog abstraction system * removed unneeded file * fix formatting * linting fixespull/841/head
committed by
GitHub
6 changed files with 327 additions and 123 deletions
@ -0,0 +1,141 @@ |
|||
import { Button } from "@components/UI/Button.tsx"; |
|||
import { |
|||
Dialog, |
|||
DialogClose, |
|||
DialogContent, |
|||
DialogDescription, |
|||
DialogFooter, |
|||
DialogHeader, |
|||
DialogTitle, |
|||
} from "@components/UI/Dialog.tsx"; |
|||
import { Separator } from "@components/UI/Separator.tsx"; |
|||
import type { ReactNode } from "react"; |
|||
import { useTranslation } from "react-i18next"; |
|||
|
|||
export type DialogVariant = "default" | "destructive"; |
|||
export type DialogType = "confirm" | "alert" | "info" | "custom"; |
|||
|
|||
export interface DialogWrapperProps { |
|||
open: boolean; |
|||
onOpenChange: (open: boolean) => void; |
|||
title: string; |
|||
description?: string; |
|||
type?: DialogType; |
|||
icon?: ReactNode; |
|||
children?: ReactNode; |
|||
|
|||
showFooter?: boolean; |
|||
confirmText?: string; |
|||
cancelText?: string; |
|||
dismissText?: string; |
|||
variant?: DialogVariant; |
|||
confirmIcon?: ReactNode; |
|||
customFooter?: ReactNode; |
|||
|
|||
onConfirm?: () => void | Promise<void>; |
|||
onCancel?: () => void; |
|||
onDismiss?: () => void; |
|||
} |
|||
|
|||
export const DialogWrapper = ({ |
|||
open, |
|||
onOpenChange, |
|||
title, |
|||
description, |
|||
type = "custom", |
|||
icon, |
|||
children, |
|||
showFooter = true, |
|||
confirmText, |
|||
cancelText, |
|||
dismissText, |
|||
variant = "default", |
|||
confirmIcon, |
|||
customFooter, |
|||
onConfirm, |
|||
onCancel, |
|||
onDismiss, |
|||
}: DialogWrapperProps) => { |
|||
const { t } = useTranslation("dialog"); |
|||
|
|||
const handleClose = () => { |
|||
onOpenChange(false); |
|||
}; |
|||
|
|||
const handleConfirm = async () => { |
|||
if (onConfirm) { |
|||
await onConfirm(); |
|||
} |
|||
handleClose(); |
|||
}; |
|||
|
|||
const handleCancel = () => { |
|||
if (onCancel) { |
|||
onCancel(); |
|||
} |
|||
handleClose(); |
|||
}; |
|||
|
|||
const handleDismiss = () => { |
|||
if (onDismiss) { |
|||
onDismiss(); |
|||
} |
|||
handleClose(); |
|||
}; |
|||
|
|||
const renderFooter = () => { |
|||
if (!showFooter) { |
|||
return null; |
|||
} |
|||
|
|||
if (customFooter) { |
|||
return <DialogFooter className="mt-4">{customFooter}</DialogFooter>; |
|||
} |
|||
|
|||
switch (type) { |
|||
case "confirm": |
|||
return ( |
|||
<DialogFooter className="mt-4"> |
|||
<Button variant="outline" onClick={handleCancel} name="cancel"> |
|||
{cancelText || t("button.cancel")} |
|||
</Button> |
|||
<Button variant={variant} onClick={handleConfirm} name="confirm"> |
|||
{confirmIcon && <span className="mr-2">{confirmIcon}</span>} |
|||
{confirmText || t("button.confirm")} |
|||
</Button> |
|||
</DialogFooter> |
|||
); |
|||
|
|||
case "alert": |
|||
case "info": |
|||
return ( |
|||
<DialogFooter className="mt-4"> |
|||
<Button variant="outline" onClick={handleDismiss} name="dismiss"> |
|||
{dismissText || t("button.dismiss")} |
|||
</Button> |
|||
</DialogFooter> |
|||
); |
|||
|
|||
default: |
|||
return null; |
|||
} |
|||
}; |
|||
|
|||
return ( |
|||
<Dialog open={open} onOpenChange={onOpenChange}> |
|||
<DialogContent> |
|||
<DialogClose /> |
|||
<DialogHeader> |
|||
<DialogTitle className="flex items-center gap-2"> |
|||
{icon && icon} |
|||
{title} |
|||
</DialogTitle> |
|||
{type !== "custom" && <Separator />} |
|||
{description && <DialogDescription>{description}</DialogDescription>} |
|||
</DialogHeader> |
|||
{children} |
|||
{renderFooter()} |
|||
</DialogContent> |
|||
</Dialog> |
|||
); |
|||
}; |
|||
@ -0,0 +1,118 @@ |
|||
import { type ReactNode, useCallback, useState } from "react"; |
|||
import type { useTranslation } from "react-i18next"; |
|||
|
|||
export type DialogType = "confirm" | "alert" | "info" | "custom"; |
|||
|
|||
export interface BaseDialogConfig { |
|||
type: DialogType; |
|||
title: string; |
|||
description?: string; |
|||
onConfirm?: () => void | Promise<void>; |
|||
onCancel?: () => void; |
|||
onClose?: () => void; |
|||
} |
|||
|
|||
export interface ConfirmDialogConfig extends BaseDialogConfig { |
|||
type: "confirm"; |
|||
confirmText?: string; |
|||
cancelText?: string; |
|||
variant?: "default" | "destructive"; |
|||
icon?: ReactNode; |
|||
confirmIcon?: ReactNode; |
|||
onConfirm: () => void | Promise<void>; |
|||
} |
|||
|
|||
export interface AlertDialogConfig extends BaseDialogConfig { |
|||
type: "alert"; |
|||
dismissText?: string; |
|||
icon?: ReactNode; |
|||
} |
|||
|
|||
export interface InfoDialogConfig extends BaseDialogConfig { |
|||
type: "info"; |
|||
dismissText?: string; |
|||
icon?: ReactNode; |
|||
} |
|||
|
|||
export interface CustomDialogConfig extends BaseDialogConfig { |
|||
type: "custom"; |
|||
icon?: React.ReactNode; |
|||
content: ReactNode; |
|||
footerActions?: ReactNode; |
|||
} |
|||
|
|||
export type DialogConfig = |
|||
| ConfirmDialogConfig |
|||
| AlertDialogConfig |
|||
| InfoDialogConfig |
|||
| CustomDialogConfig; |
|||
|
|||
export interface DialogState { |
|||
isOpen: boolean; |
|||
config?: DialogConfig; |
|||
} |
|||
|
|||
export interface UseDialogReturn { |
|||
isOpen: boolean; |
|||
config?: DialogConfig; |
|||
openDialog: (config: DialogConfig) => void; |
|||
closeDialog: () => void; |
|||
handleConfirm: () => void; |
|||
handleCancel: () => void; |
|||
} |
|||
|
|||
export const useDialog = (initialState?: DialogState): UseDialogReturn => { |
|||
const [state, setState] = useState<DialogState>( |
|||
initialState ?? { isOpen: false }, |
|||
); |
|||
|
|||
const openDialog = useCallback((config: DialogConfig) => { |
|||
setState({ isOpen: true, config }); |
|||
}, []); |
|||
|
|||
const closeDialog = useCallback(() => { |
|||
state.config?.onClose?.(); |
|||
setState({ isOpen: false, config: undefined }); |
|||
}, [state.config]); |
|||
|
|||
const handleConfirm = useCallback(async () => { |
|||
if (state.config?.onConfirm) { |
|||
await state.config.onConfirm(); |
|||
} |
|||
closeDialog(); |
|||
}, [state.config, closeDialog]); |
|||
|
|||
const handleCancel = useCallback(() => { |
|||
state.config?.onCancel?.(); |
|||
closeDialog(); |
|||
}, [state.config, closeDialog]); |
|||
|
|||
return { |
|||
isOpen: state.isOpen, |
|||
config: state.config, |
|||
openDialog, |
|||
closeDialog, |
|||
handleConfirm, |
|||
handleCancel, |
|||
}; |
|||
}; |
|||
|
|||
export const getDefaultTexts = ( |
|||
type: DialogType, |
|||
t: ReturnType<typeof useTranslation>["t"], |
|||
) => { |
|||
switch (type) { |
|||
case "confirm": |
|||
return { |
|||
confirmText: t("button.confirm"), |
|||
cancelText: t("button.cancel"), |
|||
}; |
|||
case "alert": |
|||
case "info": |
|||
return { |
|||
dismissText: t("button.dismiss"), |
|||
}; |
|||
default: |
|||
return {}; |
|||
} |
|||
}; |
|||
Loading…
Reference in new issue