github.com/freiheit-com/kuberpult@v1.24.2-0.20240328135542-315d5630abe6/services/frontend-service/src/ui/components/ServiceLane/DotsMenu.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  import * as React from 'react';
    17  import { Button } from '../button';
    18  import { useState } from 'react';
    19  
    20  export type DotsMenuButton = {
    21      label: string;
    22      onClick: () => void;
    23      icon?: JSX.Element;
    24  };
    25  
    26  export type DotsMenuProps = {
    27      buttons: DotsMenuButton[];
    28  };
    29  
    30  export const DotsMenu: React.FC<DotsMenuProps> = (props) => {
    31      const [open, setOpen] = useState(false);
    32  
    33      const initialRef: HTMLElement | null = null;
    34      const rootRef = React.useRef(initialRef);
    35  
    36      const openMenu = React.useCallback(() => {
    37          setOpen(true);
    38      }, []);
    39      const closeMenu = React.useCallback(() => {
    40          setOpen(false);
    41      }, []);
    42  
    43      const memoizedOnClick = React.useCallback(
    44          (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
    45              const index = e.currentTarget.id;
    46              props.buttons[Number(index)].onClick();
    47              setOpen(false);
    48          },
    49          [props.buttons]
    50      );
    51  
    52      React.useEffect(() => {
    53          if (!open) {
    54              return () => {};
    55          }
    56          const winListener = (event: KeyboardEvent): void => {
    57              if (event.key === 'Escape') {
    58                  closeMenu();
    59              }
    60          };
    61          const docListener = (event: MouseEvent): void => {
    62              if (!(event.target instanceof HTMLElement)) {
    63                  return;
    64              }
    65              const eventTarget: HTMLElement = event.target;
    66  
    67              if (rootRef.current === null) {
    68                  return;
    69              }
    70              const rootRefCurrent: HTMLElement = rootRef.current;
    71  
    72              const isOutside: boolean = !rootRefCurrent.contains(eventTarget);
    73              if (isOutside) {
    74                  closeMenu();
    75              }
    76          };
    77          window.addEventListener('keyup', winListener);
    78          document.addEventListener('pointerup', docListener);
    79          return () => {
    80              document.removeEventListener('keyup', winListener);
    81              document.removeEventListener('pointerup', docListener);
    82          };
    83      }, [closeMenu, open]);
    84  
    85      if (!open) {
    86          return (
    87              <div className={'dots-menu dots-menu-hidden'}>
    88                  <Button className="mdc-button--unelevated" label={'⋮'} onClick={openMenu} />
    89              </div>
    90          );
    91      }
    92  
    93      return (
    94          <div className={'dots-menu dots-menu-open'} ref={rootRef}>
    95              <ul className={'list'}>
    96                  {props.buttons.map((button, index) => (
    97                      <li className={'item'} key={'button-menu-' + String(index)}>
    98                          <Button
    99                              id={String(index)}
   100                              icon={button.icon}
   101                              className="mdc-button--unelevated"
   102                              label={button.label}
   103                              onClick={memoizedOnClick}
   104                          />
   105                      </li>
   106                  ))}
   107              </ul>
   108          </div>
   109      );
   110  };