Local modifications to ClusterLabs/Anvil by Alteeve
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

288 lines
7.2 KiB

import { Box, Dialog as MUIDialog, SxProps, Theme } from '@mui/material';
import {
ButtonHTMLAttributes,
ElementType,
FormEventHandler,
forwardRef,
MouseEventHandler,
useImperativeHandle,
useMemo,
useState,
} from 'react';
import { BLUE, RED, TEXT } from '../lib/consts/DEFAULT_THEME';
import ContainedButton from './ContainedButton';
import FlexBox from './FlexBox';
import { Panel, PanelHeader } from './Panels';
import Spinner from './Spinner';
import { BodyText, HeaderText } from './Text';
const MAP_TO_COLOUR: Record<
Exclude<ConfirmDialogProps['proceedColour'], undefined>,
string
> = {
blue: BLUE,
red: RED,
};
const ConfirmDialog = forwardRef<
ConfirmDialogForwardedRefContent,
ConfirmDialogProps
>(
(
{
actionCancelText = 'Cancel',
actionProceedText,
contentContainerProps = {},
closeOnProceed: isCloseOnProceed = false,
content,
dialogProps: {
open: baseOpen = false,
PaperProps: paperProps = {},
...restDialogProps
} = {},
disableProceed: isDisableProceed,
formContent: isFormContent,
loading: isLoading = false,
loadingAction: isLoadingAction = false,
onActionAppend,
onCancelAppend,
onProceedAppend,
onSubmitAppend,
openInitially = false,
preActionArea,
proceedButtonProps = {},
proceedColour: proceedColourKey = 'blue',
scrollContent: isScrollContent = false,
scrollBoxProps: { sx: scrollBoxSx, ...restScrollBoxProps } = {},
titleText,
},
ref,
) => {
const { sx: paperSx, ...restPaperProps } = paperProps;
const {
disabled: proceedButtonDisabled = isDisableProceed,
sx: proceedButtonSx,
...restProceedButtonProps
} = proceedButtonProps;
const [isOpen, setIsOpen] = useState<boolean>(openInitially);
// TODO: using base open is depreciated; use internal state once all
// dependent components finish the migrate.
const open = useMemo(
() => (ref ? isOpen : baseOpen),
[baseOpen, isOpen, ref],
);
const proceedColour = useMemo(
() => MAP_TO_COLOUR[proceedColourKey],
[proceedColourKey],
);
const {
contentContainerComponent,
contentContainerSubmitEventHandler,
proceedButtonClickEventHandler,
proceedButtonType,
} = useMemo(() => {
let ccComponent: ElementType | undefined;
let ccSubmitEventHandler: FormEventHandler<HTMLDivElement> | undefined;
let pbClickEventHandler:
| MouseEventHandler<HTMLButtonElement>
| undefined = (...args) => {
if (isCloseOnProceed) {
setIsOpen(false);
}
onActionAppend?.call(null, ...args);
onProceedAppend?.call(null, ...args);
};
let pbType: ButtonHTMLAttributes<HTMLButtonElement>['type'] | undefined;
if (isFormContent) {
ccComponent = 'form';
ccSubmitEventHandler = (event, ...restArgs) => {
event.preventDefault();
if (isCloseOnProceed) {
setIsOpen(false);
}
onSubmitAppend?.call(null, event, ...restArgs);
};
pbClickEventHandler = undefined;
pbType = 'submit';
}
return {
contentContainerComponent: ccComponent,
contentContainerSubmitEventHandler: ccSubmitEventHandler,
proceedButtonClickEventHandler: pbClickEventHandler,
proceedButtonType: pbType,
};
}, [
isCloseOnProceed,
isFormContent,
onActionAppend,
onProceedAppend,
onSubmitAppend,
]);
const cancelButtonElement = useMemo(
() => (
<ContainedButton
onClick={(...args) => {
setIsOpen(false);
onActionAppend?.call(null, ...args);
onCancelAppend?.call(null, ...args);
}}
>
{actionCancelText}
</ContainedButton>
),
[actionCancelText, onActionAppend, onCancelAppend],
);
const proceedButtonElement = useMemo(
() => (
<ContainedButton
disabled={proceedButtonDisabled}
onClick={proceedButtonClickEventHandler}
type={proceedButtonType}
{...restProceedButtonProps}
sx={{
backgroundColor: proceedColour,
color: TEXT,
'&:hover': { backgroundColor: `${proceedColour}F0` },
...proceedButtonSx,
}}
>
{actionProceedText}
</ContainedButton>
),
[
actionProceedText,
proceedButtonClickEventHandler,
proceedButtonDisabled,
proceedButtonSx,
proceedButtonType,
proceedColour,
restProceedButtonProps,
],
);
const actionAreaElement = useMemo(
() =>
isLoadingAction ? (
<Spinner mt={0} />
) : (
<FlexBox
row
spacing=".5em"
sx={{ justifyContent: 'flex-end', width: '100%' }}
>
{cancelButtonElement}
{proceedButtonElement}
</FlexBox>
),
[cancelButtonElement, isLoadingAction, proceedButtonElement],
);
const contentElement = useMemo(
() =>
typeof content === 'string' ? <BodyText text={content} /> : content,
[content],
);
const headerElement = useMemo(
() =>
typeof titleText === 'string' ? (
<HeaderText>{titleText}</HeaderText>
) : (
titleText
),
[titleText],
);
const combinedScrollBoxSx = useMemo(() => {
let result: SxProps<Theme> | undefined;
if (isScrollContent) {
let overflowX: 'hidden' | undefined;
let paddingTop: string | undefined;
if (isFormContent) {
overflowX = 'hidden';
paddingTop = '.6em';
}
result = {
maxHeight: '60vh',
overflowX,
overflowY: 'scroll',
paddingRight: '.4em',
paddingTop,
...scrollBoxSx,
};
}
return result;
}, [isFormContent, isScrollContent, scrollBoxSx]);
const contentAreaElement = useMemo(
() =>
isLoading ? (
<Spinner />
) : (
<>
<Box {...restScrollBoxProps} sx={combinedScrollBoxSx}>
{contentElement}
</Box>
{preActionArea}
{actionAreaElement}
</>
),
[
actionAreaElement,
combinedScrollBoxSx,
contentElement,
isLoading,
preActionArea,
restScrollBoxProps,
],
);
useImperativeHandle(
ref,
() => ({
setOpen: (value) => setIsOpen(value),
}),
[],
);
return (
<MUIDialog
open={open}
PaperComponent={Panel}
PaperProps={{
...restPaperProps,
sx: { overflow: 'visible', ...paperSx },
}}
{...restDialogProps}
>
<PanelHeader>{headerElement}</PanelHeader>
<FlexBox
component={contentContainerComponent}
onSubmit={contentContainerSubmitEventHandler}
{...contentContainerProps}
>
{contentAreaElement}
</FlexBox>
</MUIDialog>
);
},
);
ConfirmDialog.displayName = 'ConfirmDialog';
export default ConfirmDialog;