RocketChat/Rocket.Chat

View on GitHub
packages/fuselage-ui-kit/src/elements/OverflowElement.tsx

Summary

Maintainability
A
1 hr
Test Coverage
import type { OptionType } from '@rocket.chat/fuselage';
import {
  IconButton,
  PositionAnimated,
  Options,
  useCursor,
} from '@rocket.chat/fuselage';
import type * as UiKit from '@rocket.chat/ui-kit';
import type { ReactElement } from 'react';
import { useRef, useCallback, useMemo } from 'react';

import { useStringFromTextObject } from '../hooks/useStringFromTextObject';
import { useUiKitState } from '../hooks/useUiKitState';
import type { BlockProps } from '../utils/BlockProps';

type OverflowElementProps = BlockProps<UiKit.OverflowElement>;

const OverflowElement = ({
  block,
  context,
}: OverflowElementProps): ReactElement => {
  const [{ loading }, action] = useUiKitState(block, context);
  const fromTextObjectToString = useStringFromTextObject();

  const fireChange = useCallback(
    ([value]: [UiKit.ActionOf<UiKit.OverflowElement>, string]) =>
      action({ target: { value } }),
    [action]
  );

  const options = useMemo<OptionType[]>(
    () =>
      block.options.map(({ value, text, url }) => [
        value,
        fromTextObjectToString(text) ?? '',
        undefined,
        undefined,
        undefined,
        url,
      ]),
    [block.options, fromTextObjectToString]
  );

  const [cursor, handleKeyDown, handleKeyUp, reset, [visible, hide, show]] =
    useCursor(-1, options, (selectedOption, [, hide]) => {
      fireChange([selectedOption[0] as string, selectedOption[1] as string]);
      reset();
      hide();
    });

  const ref = useRef<HTMLElement>(null);
  const onClick = useCallback(() => {
    ref.current?.focus();
    show();
  }, [show]);

  const handleSelection = useCallback(
    ([value, _label, _selected, _type, url]: OptionType) => {
      if (url) {
        window.open(url);
      }
      action({ target: { value: String(value) } });
      reset();
      hide();
    },
    [action, hide, reset]
  );

  return (
    <>
      <IconButton
        ref={ref}
        small
        onClick={onClick}
        onBlur={hide}
        onKeyUp={handleKeyUp}
        onKeyDown={handleKeyDown}
        disabled={loading}
        icon='kebab'
      />
      <PositionAnimated
        width='auto'
        visible={visible}
        anchor={ref}
        placement='bottom-start'
      >
        <Options onSelect={handleSelection} options={options} cursor={cursor} />
      </PositionAnimated>
    </>
  );
};

export default OverflowElement;