120 lines
		
	
	
	
		
			3.7 KiB
			
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			120 lines
		
	
	
	
		
			3.7 KiB
			
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
// Copyright 2021 Signal Messenger, LLC
 | 
						|
// SPDX-License-Identifier: AGPL-3.0-only
 | 
						|
 | 
						|
import type { ReactChild } from 'react';
 | 
						|
import React, { useState } from 'react';
 | 
						|
import lodash from 'lodash';
 | 
						|
 | 
						|
import type { LocalizerType } from '../types/Util.js';
 | 
						|
import { Button, ButtonVariant } from './Button.js';
 | 
						|
import { Spinner } from './Spinner.js';
 | 
						|
 | 
						|
const { noop } = lodash;
 | 
						|
 | 
						|
export enum CallingLobbyJoinButtonVariant {
 | 
						|
  CallIsFull = 'CallIsFull',
 | 
						|
  Join = 'Join',
 | 
						|
  Loading = 'Loading',
 | 
						|
  Start = 'Start',
 | 
						|
  AskToJoin = 'AskToJoin',
 | 
						|
}
 | 
						|
 | 
						|
type PropsType = {
 | 
						|
  disabled?: boolean;
 | 
						|
  i18n: LocalizerType;
 | 
						|
  onClick: () => void;
 | 
						|
  variant: CallingLobbyJoinButtonVariant;
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * This component is a little weird. Why not just render a button with some children?
 | 
						|
 *
 | 
						|
 * The contents of this component can change but we don't want its size to change, so we
 | 
						|
 * render all the variants invisibly, compute the maximum size, and then render the
 | 
						|
 * "final" button with those dimensions.
 | 
						|
 *
 | 
						|
 * For example, we might initially render "Join call" and then render a spinner when you
 | 
						|
 * click the button. The button shouldn't resize in that situation.
 | 
						|
 */
 | 
						|
export function CallingLobbyJoinButton({
 | 
						|
  disabled,
 | 
						|
  i18n,
 | 
						|
  onClick,
 | 
						|
  variant,
 | 
						|
}: PropsType): JSX.Element {
 | 
						|
  const [width, setWidth] = useState<undefined | number>();
 | 
						|
  const [height, setHeight] = useState<undefined | number>();
 | 
						|
 | 
						|
  const childrenByVariant: Record<CallingLobbyJoinButtonVariant, ReactChild> = {
 | 
						|
    [CallingLobbyJoinButtonVariant.CallIsFull]: i18n(
 | 
						|
      'icu:CallingLobbyJoinButton--call-full'
 | 
						|
    ),
 | 
						|
    [CallingLobbyJoinButtonVariant.Loading]: (
 | 
						|
      <Spinner size="18px" svgSize="small" />
 | 
						|
    ),
 | 
						|
    [CallingLobbyJoinButtonVariant.Join]: i18n(
 | 
						|
      'icu:CallingLobbyJoinButton--join'
 | 
						|
    ),
 | 
						|
    [CallingLobbyJoinButtonVariant.Start]: i18n(
 | 
						|
      'icu:CallingLobbyJoinButton--start'
 | 
						|
    ),
 | 
						|
    [CallingLobbyJoinButtonVariant.AskToJoin]: i18n(
 | 
						|
      'icu:CallingLobbyJoinButton--ask-to-join'
 | 
						|
    ),
 | 
						|
  };
 | 
						|
 | 
						|
  return (
 | 
						|
    <>
 | 
						|
      {Boolean(width && height) && (
 | 
						|
        <Button
 | 
						|
          className="CallingLobbyJoinButton CallControls__JoinLeaveButton"
 | 
						|
          disabled={disabled}
 | 
						|
          onClick={onClick}
 | 
						|
          style={{ width, height }}
 | 
						|
          tabIndex={0}
 | 
						|
          variant={ButtonVariant.Calling}
 | 
						|
        >
 | 
						|
          {childrenByVariant[variant]}
 | 
						|
        </Button>
 | 
						|
      )}
 | 
						|
      <div
 | 
						|
        style={{
 | 
						|
          visibility: 'hidden',
 | 
						|
          position: 'fixed',
 | 
						|
          left: -9999,
 | 
						|
          top: -9999,
 | 
						|
        }}
 | 
						|
      >
 | 
						|
        {Object.values(CallingLobbyJoinButtonVariant).map(candidateVariant => (
 | 
						|
          <Button
 | 
						|
            key={candidateVariant}
 | 
						|
            className="CallingLobbyJoinButton CallControls__JoinLeaveButton"
 | 
						|
            variant={ButtonVariant.Calling}
 | 
						|
            onClick={noop}
 | 
						|
            ref={(button: HTMLButtonElement | null) => {
 | 
						|
              if (!button) {
 | 
						|
                return;
 | 
						|
              }
 | 
						|
              const { width: variantWidth, height: variantHeight } =
 | 
						|
                button.getBoundingClientRect();
 | 
						|
 | 
						|
              // We could set the padding in CSS, but we don't do that in case some other
 | 
						|
              //   styling causes a re-render of the button but not of the component. This
 | 
						|
              //   is easiest to reproduce in Storybook, where the font hasn't loaded yet;
 | 
						|
              //   we compute the size, then the font makes the text a bit larger, and
 | 
						|
              //   there's a layout issue.
 | 
						|
              setWidth((previousWidth = 0) =>
 | 
						|
                Math.ceil(Math.max(previousWidth, variantWidth))
 | 
						|
              );
 | 
						|
              setHeight((previousHeight = 0) =>
 | 
						|
                Math.ceil(Math.max(previousHeight, variantHeight))
 | 
						|
              );
 | 
						|
            }}
 | 
						|
          >
 | 
						|
            {childrenByVariant[candidateVariant]}
 | 
						|
          </Button>
 | 
						|
        ))}
 | 
						|
      </div>
 | 
						|
    </>
 | 
						|
  );
 | 
						|
}
 |