github.com/freiheit-com/kuberpult@v1.24.2-0.20240328135542-315d5630abe6/services/frontend-service/src/ui/components/dialog/ConfirmationDialog.tsx (about)

     1  /*This file is part of kuberpult.
     2  
     3  Kuberpult is free software: you can redistribute it and/or modify
     4  it under the terms of the Expat(MIT) License as published by
     5  the Free Software Foundation.
     6  
     7  Kuberpult is distributed in the hope that it will be useful,
     8  but WITHOUT ANY WARRANTY; without even the implied warranty of
     9  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    10  MIT License for more details.
    11  
    12  You should have received a copy of the MIT License
    13  along with kuberpult. If not, see <https://directory.fsf.org/wiki/License:Expat>.
    14  
    15  Copyright 2023 freiheit.com*/
    16  
    17  import React from 'react';
    18  import { Button } from '../button';
    19  
    20  export type ConfirmationDialogProps = {
    21      onConfirm: () => void;
    22      onCancel: () => void;
    23      open: boolean;
    24      children: JSX.Element;
    25      headerLabel: string;
    26      confirmLabel: string;
    27      classNames: string;
    28      testIdRootRefParent?: string;
    29  };
    30  
    31  /**
    32   * A dialog that is used to confirm a question with either yes or no.
    33   */
    34  export const ConfirmationDialog: React.FC<ConfirmationDialogProps> = (props) => (
    35      <PlainDialog
    36          open={props.open}
    37          onClose={props.onCancel}
    38          classNames={props.classNames}
    39          disableBackground={true}
    40          center={true}
    41          testIdRootRefParent={props.testIdRootRefParent}>
    42          <>
    43              <div className={'confirmation-dialog-header'}>{props.headerLabel}</div>
    44              <hr />
    45              <div className={'confirmation-dialog-content'}>{props.children}</div>
    46              <hr />
    47              <div className={'confirmation-dialog-footer'}>
    48                  <div className={'item'} key={'button-menu-cancel'} title={'ESC also closes the dialog'}>
    49                      <Button
    50                          className="mdc-button--ripple button-cancel"
    51                          testId="test-confirm-button-cancel"
    52                          label={'Cancel'}
    53                          onClick={props.onCancel}
    54                      />
    55                  </div>
    56                  <div className={'item'} key={'button-menu-confirm'}>
    57                      <Button
    58                          className="mdc-button--unelevated button-confirm test-confirm-button-confirm"
    59                          testId="test-confirm-button-confirm"
    60                          label={props.confirmLabel}
    61                          onClick={props.onConfirm}
    62                      />
    63                  </div>
    64              </div>
    65          </>
    66      </PlainDialog>
    67  );
    68  
    69  export type PlainDialogProps = {
    70      open: boolean;
    71      onClose: () => void;
    72      children: JSX.Element;
    73      classNames: string;
    74      // NOTE: disableBackground only works for ConfirmationDialog for now, there is no plain-dialog-container-open CSS specification
    75      disableBackground: boolean;
    76      center: boolean;
    77      testIdRootRefParent?: string;
    78  };
    79  
    80  /**
    81   * A dialog that just renders its children. Invoker must take care of all buttons.
    82   */
    83  
    84  export const PlainDialog: React.FC<PlainDialogProps> = (props) => {
    85      const { onClose, open, children, center, disableBackground, classNames, testIdRootRefParent } = props;
    86      const classPrefix = center ? 'confirmation' : 'plain';
    87      const initialRef: HTMLElement | null = null;
    88      const rootRef = React.useRef(initialRef);
    89  
    90      React.useEffect(() => {
    91          if (!open) {
    92              return () => {};
    93          }
    94          const winListener = (event: KeyboardEvent): void => {
    95              if (event.key === 'Escape') {
    96                  onClose();
    97              }
    98          };
    99          const docListener = (event: MouseEvent): void => {
   100              if (!(event.target instanceof HTMLElement)) {
   101                  return;
   102              }
   103              const eventTarget: HTMLElement = event.target;
   104  
   105              if (rootRef.current === null) {
   106                  return;
   107              }
   108              const rootRefCurrent: HTMLElement = rootRef.current;
   109  
   110              const isInside = rootRefCurrent.contains(eventTarget);
   111              if (!isInside) {
   112                  onClose();
   113              }
   114          };
   115          window.addEventListener('keyup', winListener);
   116          document.addEventListener('pointerup', docListener);
   117          return () => {
   118              document.removeEventListener('keyup', winListener);
   119              document.removeEventListener('pointerup', docListener);
   120          };
   121      }, [onClose, open, classPrefix, center]);
   122  
   123      if (!open) {
   124          return <div className={''}></div>;
   125      }
   126      const clas = open && disableBackground ? classPrefix + '-dialog-container-open' : '';
   127      return (
   128          <div className={classPrefix + '-dialog-container ' + clas} data-testid={testIdRootRefParent}>
   129              <div ref={rootRef} className={classPrefix + '-dialog-open ' + (classNames ?? '')}>
   130                  {children}
   131              </div>
   132          </div>
   133      );
   134  };