108 lines
3.6 KiB
TypeScript
108 lines
3.6 KiB
TypeScript
|
// Copyright 2021 Signal Messenger, LLC
|
||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||
|
|
||
|
import React, { FunctionComponent, ReactChild, useState } from 'react';
|
||
|
import { noop } from 'lodash';
|
||
|
|
||
|
import type { LocalizerType } from '../types/Util';
|
||
|
import { Button, ButtonVariant } from './Button';
|
||
|
import { Spinner } from './Spinner';
|
||
|
|
||
|
const PADDING_HORIZONTAL = 48;
|
||
|
const PADDING_VERTICAL = 12;
|
||
|
|
||
|
export enum CallingLobbyJoinButtonVariant {
|
||
|
CallIsFull = 'CallIsFull',
|
||
|
Join = 'Join',
|
||
|
Loading = 'Loading',
|
||
|
Start = 'Start',
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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 const CallingLobbyJoinButton: FunctionComponent<{
|
||
|
disabled?: boolean;
|
||
|
i18n: LocalizerType;
|
||
|
onClick: () => void;
|
||
|
variant: CallingLobbyJoinButtonVariant;
|
||
|
}> = ({ disabled, i18n, onClick, variant }) => {
|
||
|
const [width, setWidth] = useState<undefined | number>();
|
||
|
const [height, setHeight] = useState<undefined | number>();
|
||
|
|
||
|
const childrenByVariant: Record<CallingLobbyJoinButtonVariant, ReactChild> = {
|
||
|
[CallingLobbyJoinButtonVariant.CallIsFull]: i18n('calling__call-is-full'),
|
||
|
[CallingLobbyJoinButtonVariant.Loading]: <Spinner svgSize="small" />,
|
||
|
[CallingLobbyJoinButtonVariant.Join]: i18n('calling__join'),
|
||
|
[CallingLobbyJoinButtonVariant.Start]: i18n('calling__start'),
|
||
|
};
|
||
|
|
||
|
return (
|
||
|
<>
|
||
|
{Boolean(width && height) && (
|
||
|
<Button
|
||
|
className="module-CallingLobbyJoinButton"
|
||
|
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="module-CallingLobbyJoinButton"
|
||
|
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 + PADDING_HORIZONTAL)
|
||
|
)
|
||
|
);
|
||
|
setHeight((previousHeight = 0) =>
|
||
|
Math.ceil(
|
||
|
Math.max(previousHeight, variantHeight + PADDING_VERTICAL)
|
||
|
)
|
||
|
);
|
||
|
}}
|
||
|
>
|
||
|
{childrenByVariant[candidateVariant]}
|
||
|
</Button>
|
||
|
))}
|
||
|
</div>
|
||
|
</>
|
||
|
);
|
||
|
};
|