signal-desktop/ts/components/ConfirmationDialog.tsx

173 lines
4 KiB
TypeScript
Raw Normal View History

2023-01-03 19:55:46 +00:00
// Copyright 2019 Signal Messenger, LLC
2020-10-30 20:34:04 +00:00
// SPDX-License-Identifier: AGPL-3.0-only
import type { MouseEvent } from 'react';
import React, { useCallback } from 'react';
2021-10-14 16:52:42 +00:00
import { animated } from '@react-spring/web';
import { Button, ButtonVariant } from './Button';
import type { LocalizerType } from '../types/Util';
import { ModalHost } from './ModalHost';
import { ModalPage } from './Modal';
import type { Theme } from '../util/theme';
import { useAnimated } from '../hooks/useAnimated';
2023-02-27 22:34:43 +00:00
import { Spinner } from './Spinner';
2020-05-27 21:37:06 +00:00
export type ActionSpec = {
text: string;
action: () => unknown;
style?: 'affirmative' | 'negative';
2023-02-27 22:34:43 +00:00
autoClose?: boolean;
2020-05-27 21:37:06 +00:00
};
export type OwnProps = Readonly<{
actions?: Array<ActionSpec>;
2022-09-27 20:24:21 +00:00
dialogName: string;
cancelButtonVariant?: ButtonVariant;
cancelText?: string;
2023-02-27 22:34:43 +00:00
isSpinning?: boolean;
children?: React.ReactNode;
hasXButton?: boolean;
i18n: LocalizerType;
moduleClassName?: string;
noMouseClose?: boolean;
noDefaultCancelButton?: boolean;
onCancel?: () => unknown;
onClose: () => unknown;
onTopOfEverything?: boolean;
theme?: Theme;
2022-11-04 13:22:07 +00:00
title?: React.ReactNode;
}>;
export type Props = OwnProps;
function focusRef(el: HTMLElement | null) {
if (el) {
el.focus();
}
}
function getButtonVariant(
buttonStyle?: 'affirmative' | 'negative'
): ButtonVariant {
if (buttonStyle === 'affirmative') {
return ButtonVariant.Primary;
}
if (buttonStyle === 'negative') {
return ButtonVariant.Destructive;
}
return ButtonVariant.Secondary;
}
2022-11-18 00:45:19 +00:00
export const ConfirmationDialog = React.memo(function ConfirmationDialogInner({
actions = [],
dialogName,
cancelButtonVariant,
cancelText,
children,
hasXButton,
i18n,
2023-02-27 22:34:43 +00:00
isSpinning,
2022-11-18 00:45:19 +00:00
moduleClassName,
noMouseClose,
noDefaultCancelButton,
onCancel,
onClose,
onTopOfEverything,
theme,
title,
}: Props) {
const { close, overlayStyles, modalStyles } = useAnimated(onClose, {
getFrom: () => ({ opacity: 0, transform: 'scale(0.25)' }),
getTo: isOpen => ({ opacity: isOpen ? 1 : 0, transform: 'scale(1)' }),
});
2022-11-18 00:45:19 +00:00
const cancelAndClose = useCallback(() => {
if (onCancel) {
onCancel();
}
close();
}, [close, onCancel]);
2022-11-18 00:45:19 +00:00
const handleCancel = useCallback(
(e: MouseEvent) => {
if (e.target === e.currentTarget) {
cancelAndClose();
}
},
[cancelAndClose]
);
2022-11-18 00:45:19 +00:00
const hasActions = Boolean(actions.length);
2022-11-18 00:45:19 +00:00
const footer = (
<>
2023-02-27 22:34:43 +00:00
{!isSpinning && !noDefaultCancelButton ? (
2022-11-18 00:45:19 +00:00
<Button
onClick={handleCancel}
ref={focusRef}
variant={
cancelButtonVariant ||
(hasActions ? ButtonVariant.Secondary : ButtonVariant.Primary)
}
>
{cancelText || i18n('confirmation-dialog--Cancel')}
</Button>
) : null}
{actions.map((action, i) => (
<Button
key={action.text}
2023-02-27 22:34:43 +00:00
disabled={isSpinning}
2022-11-18 00:45:19 +00:00
onClick={() => {
action.action();
2023-02-27 22:34:43 +00:00
if (action.autoClose !== false) {
close();
}
2022-11-18 00:45:19 +00:00
}}
data-action={i}
variant={getButtonVariant(action.style)}
>
2023-02-27 22:34:43 +00:00
{isSpinning ? (
<Spinner
size="22px"
svgSize="small"
direction="on-primary-button"
/>
) : (
action.text
)}
2022-11-18 00:45:19 +00:00
</Button>
))}
</>
);
2022-11-18 00:45:19 +00:00
const modalName = `ConfirmationDialog.${dialogName}`;
2022-09-27 20:24:21 +00:00
2022-11-18 00:45:19 +00:00
return (
<ModalHost
modalName={modalName}
noMouseClose={noMouseClose}
onClose={close}
onEscape={cancelAndClose}
onTopOfEverything={onTopOfEverything}
overlayStyles={overlayStyles}
theme={theme}
>
<animated.div style={modalStyles}>
<ModalPage
modalName={modalName}
hasXButton={hasXButton}
i18n={i18n}
moduleClassName={moduleClassName}
onClose={cancelAndClose}
title={title}
modalFooter={footer}
>
{children}
</ModalPage>
</animated.div>
</ModalHost>
);
});