1
0
mirror of https://github.com/matrix-org/matrix-authentication-service.git synced 2025-11-20 12:02:22 +03:00

Confirm destructive actions (#1759)

Co-authored-by: Quentin Gliech <quenting@element.io>
This commit is contained in:
Kerry
2023-09-15 00:35:09 +12:00
committed by GitHub
parent aa5b91f3de
commit e19002a0ca
19 changed files with 3316 additions and 6172 deletions

View File

@@ -0,0 +1,106 @@
// Copyright 2023 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import {
Root,
Portal,
Overlay,
Content,
Trigger,
Title,
Description,
Action,
Cancel,
} from "@radix-ui/react-alert-dialog";
import { Button } from "@vector-im/compound-web";
import classNames from "classnames";
import { ReactNode, useState } from "react";
import styles from "./ConfirmationModal.module.css";
type Props = {
onConfirm: () => void;
onDeny?: () => void;
title?: ReactNode | string;
// element used to trigger opening of modal
trigger: ReactNode;
className?: string;
};
/**
* Generic confirmation modal
* controls its own open state
* calls onDeny on cancel, esc, or overlay click
* calls onConfirm on confirm click
*/
const ConfirmationModal: React.FC<React.PropsWithChildren<Props>> = ({
onConfirm,
onDeny,
className,
children,
trigger,
title,
}) => {
const [isOpen, setIsOpen] = useState(false);
const onClose = (callback?: () => void) => (): void => {
setIsOpen(false);
callback?.();
};
// radix's autofocus doesn't work for some reason
// maybe https://www.radix-ui.com/primitives/docs/guides/composition#your-component-must-forward-ref
// when this is replaced with compound's own/wrapped dialog this should be fixed
// until then, focus the cancel button for a deniable modal
// and continue button otherwise
const onOpenAutoFocus = (e: Event): void => {
const focusButtonKind = onDeny ? "tertiary" : "destructive";
(e.target as Element)
?.querySelector<HTMLButtonElement>(
`button[data-kind="${focusButtonKind}"]`,
)
?.focus();
};
return (
<Root open={isOpen} onOpenChange={setIsOpen}>
<Trigger asChild>{trigger}</Trigger>
<Portal>
<Overlay className={styles.overlay} onClick={onClose(onDeny)} />
<Content
className={classNames(styles.content, className)}
onEscapeKeyDown={onClose(onDeny)}
onOpenAutoFocus={onOpenAutoFocus}
>
<Title>{title}</Title>
<Description>{children}</Description>
<div className={styles.buttons}>
{onDeny && (
<Cancel asChild>
<Button kind="tertiary" size="sm" onClick={onClose(onDeny)}>
Cancel
</Button>
</Cancel>
)}
<Action asChild>
<Button kind="destructive" size="sm" onClick={onClose(onConfirm)}>
Continue
</Button>
</Action>
</div>
</Content>
</Portal>
</Root>
);
};
export default ConfirmationModal;