Top level Reaction/Raise Hand buttons and remove More Options

This commit is contained in:
ayumi-signal 2024-01-17 12:29:44 -08:00 committed by GitHub
parent a6e744dcbc
commit 670da5722a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 141 additions and 151 deletions

View file

@ -1748,6 +1748,10 @@
"messageformat": "Stop presenting", "messageformat": "Stop presenting",
"description": "Button tooltip label for stopping screen sharing" "description": "Button tooltip label for stopping screen sharing"
}, },
"icu:calling__button--react": {
"messageformat": "React",
"description": "Button tooltip label to send a reaction during a call"
},
"icu:calling__button--ring__disabled-because-group-is-too-large": { "icu:calling__button--ring__disabled-because-group-is-too-large": {
"messageformat": "Group is too large to ring the participants.", "messageformat": "Group is too large to ring the participants.",
"description": "Button tooltip label when you can't ring because the group is too large" "description": "Button tooltip label when you can't ring because the group is too large"

View file

@ -4114,6 +4114,7 @@ button.module-image__border-overlay:focus {
&__footer { &__footer {
bottom: 0; bottom: 0;
display: flex; display: flex;
flex-direction: row-reverse;
justify-content: space-between; justify-content: space-between;
width: 100%; width: 100%;
z-index: $z-index-above-base; z-index: $z-index-above-base;

View file

@ -293,5 +293,5 @@ $NavTabs__Item__blockPadding: 2px;
$NavTabs__Toggle__blockPadding: 8px; $NavTabs__Toggle__blockPadding: 8px;
$NavTabs__ItemButton__blockPadding: 10px; $NavTabs__ItemButton__blockPadding: 10px;
$CallControls__height: 80px; $CallControls__height: 80px;
$CallControls__max-width: 600px; $CallControls__max-width: 640px;
$CallControls__initial-width: 440px; $CallControls__initial-width: 480px;

View file

@ -110,7 +110,7 @@
margin-block: -5px; margin-block: -5px;
} }
.CallControls__MoreOptionsButtonContainer--menu-shown .module-tooltip { .CallControls__ReactButtonContainer--menu-shown .module-tooltip {
opacity: 0; opacity: 0;
} }
@ -120,14 +120,12 @@
flex-basis: calc($local-preview-width + 16px); flex-basis: calc($local-preview-width + 16px);
} }
.CallControls__MoreOptionsContainer { .CallControls__ReactionPickerContainer {
position: absolute; position: absolute;
inset-inline-start: min(48%, 40vw); inset-inline-start: min(44%, 32vw);
inset-block-end: 70px; inset-block-end: 70px;
z-index: $z-index-toast; z-index: $z-index-toast;
}
.CallControls__MoreOptionsMenu {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
max-height: calc(100vh - 155px); max-height: calc(100vh - 155px);
@ -136,48 +134,13 @@
pointer-events: auto; pointer-events: auto;
} }
.CallControls__MoreOptionsMenu .module-emoji-picker { .CallControls__ReactionPickerContainer .module-emoji-picker {
margin-bottom: auto; margin-bottom: auto;
max-width: calc(100vw / 2 + 20px); max-width: calc(100vw / 2 + 20px);
} }
.CallControls__MoreOptionsMenu .CallControls__ReactionPickerContainer .module-ReactionPickerPicker {
.module-emoji-picker
+ .CallControls__MenuItemRaiseHand {
display: none;
}
.CallControls__MoreOptionsMenu .module-ReactionPickerPicker {
@media (prefers-reduced-motion: no-preference) { @media (prefers-reduced-motion: no-preference) {
animation-duration: 200ms; animation-duration: 200ms;
} }
} }
.CallControls__MenuItemRaiseHand {
@include button-reset;
display: flex;
min-width: 290px;
padding-block: 12px;
padding-inline: 12px;
margin-block-start: 8px;
border-radius: 10px;
align-items: center;
text-align: start;
background-color: $color-gray-75;
color: $color-white;
filter: drop-shadow(0px 4px 3px $color-black-alpha-20);
}
.CallControls__MenuItemRaiseHand:hover {
background-color: $color-gray-65;
}
.CallControls__MenuItemRaiseHandIcon {
@include color-svg(
'../images/icons/v3/raise_hand/raise_hand-light.svg',
$color-gray-15
);
height: 16px;
width: 16px;
margin-inline: 2px 12px;
}

View file

@ -31,8 +31,8 @@
div { div {
@include color-svg($icon, $icon-color); @include color-svg($icon, $icon-color);
height: 22px; height: 20px;
width: 22px; width: 20px;
} }
} }
@ -88,6 +88,26 @@
); );
} }
&--raise-hand {
$icon: '../images/icons/v3/raise_hand/raise_hand-light.svg';
&--on {
@include calling-button-icon-highlighted($icon);
}
&--off {
@include calling-button-icon-regular($icon);
}
}
&--react {
$icon: '../images/icons/v3/heart/heart-plus.svg';
&--on {
@include calling-button-icon-highlighted($icon);
}
&--off {
@include calling-button-icon-regular($icon);
}
}
&--ring { &--ring {
$icon: '../images/icons/v3/bell/bell-slash-fill.svg'; $icon: '../images/icons/v3/bell/bell-slash-fill.svg';
&--on { &--on {

View file

@ -72,7 +72,6 @@ import {
useCallingToasts, useCallingToasts,
} from './CallingToast'; } from './CallingToast';
import { Spinner } from './Spinner'; import { Spinner } from './Spinner';
import { handleOutsideClick } from '../util/handleOutsideClick';
import type { Props as ReactionPickerProps } from './conversation/ReactionPicker'; import type { Props as ReactionPickerProps } from './conversation/ReactionPicker';
import type { SmartReactionPicker } from '../state/smart/ReactionPicker'; import type { SmartReactionPicker } from '../state/smart/ReactionPicker';
import { Emoji } from './emoji/Emoji'; import { Emoji } from './emoji/Emoji';
@ -250,12 +249,12 @@ export function CallScreen({
hangUpActiveCall('button click'); hangUpActiveCall('button click');
}, [hangUpActiveCall]); }, [hangUpActiveCall]);
const moreOptionsMenuRef = React.useRef<null | HTMLDivElement>(null); const reactButtonRef = React.useRef<null | HTMLDivElement>(null);
const moreOptionsButtonRef = React.useRef<null | HTMLDivElement>(null);
const reactionPickerRef = React.useRef<null | HTMLDivElement>(null); const reactionPickerRef = React.useRef<null | HTMLDivElement>(null);
const [showMoreOptions, setShowMoreOptions] = useState(false); const reactionPickerContainerRef = React.useRef<null | HTMLDivElement>(null);
const toggleMoreOptions = useCallback(() => { const [showReactionPicker, setShowReactionPicker] = useState(false);
setShowMoreOptions(prevValue => !prevValue); const toggleReactionPicker = useCallback(() => {
setShowReactionPicker(prevValue => !prevValue);
}, []); }, []);
const [showRaisedHandsList, setShowRaisedHandsList] = useState(false); const [showRaisedHandsList, setShowRaisedHandsList] = useState(false);
@ -285,14 +284,19 @@ export function CallScreen({
}, [setLocalPreview, setRendererCanvas]); }, [setLocalPreview, setRendererCanvas]);
useEffect(() => { useEffect(() => {
if (!showControls || showMoreOptions || stickyControls || controlsHover) { if (
!showControls ||
showReactionPicker ||
stickyControls ||
controlsHover
) {
return noop; return noop;
} }
const timer = setTimeout(() => { const timer = setTimeout(() => {
setShowControls(false); setShowControls(false);
}, 5000); }, 5000);
return clearTimeout.bind(null, timer); return clearTimeout.bind(null, timer);
}, [showControls, showMoreOptions, stickyControls, controlsHover]); }, [showControls, showReactionPicker, stickyControls, controlsHover]);
useEffect(() => { useEffect(() => {
const handleKeyDown = (event: KeyboardEvent): void => { const handleKeyDown = (event: KeyboardEvent): void => {
@ -321,22 +325,6 @@ export function CallScreen({
}; };
}, [toggleAudio, toggleVideo]); }, [toggleAudio, toggleVideo]);
useEffect(() => {
if (!showMoreOptions) {
return noop;
}
return handleOutsideClick(
() => {
setShowMoreOptions(false);
return true;
},
{
containerElements: [moreOptionsButtonRef, moreOptionsMenuRef],
name: 'CallScreen.moreOptions',
}
);
}, [showMoreOptions]);
useScreenSharingStoppedToast({ activeCall, i18n }); useScreenSharingStoppedToast({ activeCall, i18n });
useViewModeChangedToast({ activeCall, i18n }); useViewModeChangedToast({ activeCall, i18n });
@ -468,8 +456,6 @@ export function CallScreen({
}); });
const isGroupCall = activeCall.callMode === CallMode.Group; const isGroupCall = activeCall.callMode === CallMode.Group;
const isMoreOptionsButtonEnabled =
isGroupCall && (isGroupCallRaiseHandEnabled || isGroupCallReactionsEnabled);
let presentingButtonType: CallingButtonType; let presentingButtonType: CallingButtonType;
if (presentingSource) { if (presentingSource) {
@ -518,6 +504,17 @@ export function CallScreen({
] ]
); );
let raiseHandButtonType: CallingButtonType | undefined;
let reactButtonType: CallingButtonType | undefined;
if (isGroupCall) {
raiseHandButtonType = localHandRaised
? CallingButtonType.RAISE_HAND_ON
: CallingButtonType.RAISE_HAND_OFF;
reactButtonType = showReactionPicker
? CallingButtonType.REACT_ON
: CallingButtonType.REACT_OFF;
}
const renderRaisedHandsToast = React.useCallback( const renderRaisedHandsToast = React.useCallback(
(hands: Array<number>) => { (hands: Array<number>) => {
// Sort "You" to the front. // Sort "You" to the front.
@ -793,8 +790,27 @@ export function CallScreen({
renderRaisedHandsToast={renderRaisedHandsToast} renderRaisedHandsToast={renderRaisedHandsToast}
i18n={i18n} i18n={i18n}
/> />
{/* We render the local preview first and set the footer flex direction to row-reverse
to ensure the preview is visible at low viewport widths. */}
<div className="module-ongoing-call__footer"> <div className="module-ongoing-call__footer">
<div className="module-calling__spacer CallControls__OuterSpacer" /> {localPreviewNode ? (
<div className="module-ongoing-call__footer__local-preview module-ongoing-call__footer__local-preview--active">
{localPreviewNode}
{!isSendingVideo && (
<div className="CallingStatusIndicator CallingStatusIndicator--Video" />
)}
<CallingAudioIndicator
hasAudio={hasLocalAudio}
audioLevel={localAudioLevel}
shouldShowSpeaking={isSpeaking}
/>
{syncedLocalHandRaised && (
<div className="CallingStatusIndicator CallingStatusIndicator--HandRaised" />
)}
</div>
) : (
<div className="module-ongoing-call__footer__local-preview" />
)}
<div <div
className={classNames( className={classNames(
'CallControls', 'CallControls',
@ -807,53 +823,28 @@ export function CallScreen({
<div className="CallControls__Status">{callStatus}</div> <div className="CallControls__Status">{callStatus}</div>
</div> </div>
{showMoreOptions && ( {showReactionPicker && (
<div className="CallControls__MoreOptionsContainer"> <div
<div className="CallControls__ReactionPickerContainer"
className="CallControls__MoreOptionsMenu" ref={reactionPickerContainerRef}
ref={moreOptionsMenuRef} >
> {isGroupCallReactionsEnabled &&
{isGroupCallReactionsEnabled && renderReactionPicker({
renderReactionPicker({ ref: reactionPickerRef,
ref: reactionPickerRef, onClose: () => setShowReactionPicker(false),
onClose: () => setShowMoreOptions(false), onPick: emoji => {
onPick: emoji => { setShowReactionPicker(false);
setShowMoreOptions(false); sendGroupCallReaction({
sendGroupCallReaction({ conversationId: conversation.id,
conversationId: conversation.id, value: emoji,
value: emoji, });
}); },
}, renderEmojiPicker,
renderEmojiPicker, })}
})}
{isGroupCallRaiseHandEnabled && (
<button
className="CallControls__MenuItemRaiseHand"
onClick={() => {
setShowMoreOptions(false);
toggleRaiseHand();
}}
type="button"
>
<span className="CallControls__MenuItemRaiseHandIcon" />
{localHandRaised
? i18n('icu:CallControls__MenuItemRaiseHand--lower')
: i18n('icu:CallControls__MenuItemRaiseHand')}
</button>
)}
</div>
</div> </div>
)} )}
<div className="CallControls__ButtonContainer"> <div className="CallControls__ButtonContainer">
<CallingButton
buttonType={presentingButtonType}
i18n={i18n}
onMouseEnter={onControlsMouseEnter}
onMouseLeave={onControlsMouseLeave}
onClick={togglePresenting}
tooltipDirection={TooltipPlacement.Top}
/>
<CallingButton <CallingButton
buttonType={videoButtonType} buttonType={videoButtonType}
i18n={i18n} i18n={i18n}
@ -870,23 +861,38 @@ export function CallScreen({
onClick={toggleAudio} onClick={toggleAudio}
tooltipDirection={TooltipPlacement.Top} tooltipDirection={TooltipPlacement.Top}
/> />
{isMoreOptionsButtonEnabled && ( <CallingButton
buttonType={presentingButtonType}
i18n={i18n}
onMouseEnter={onControlsMouseEnter}
onMouseLeave={onControlsMouseLeave}
onClick={togglePresenting}
tooltipDirection={TooltipPlacement.Top}
/>
{isGroupCallRaiseHandEnabled && raiseHandButtonType && (
<CallingButton
buttonType={raiseHandButtonType}
i18n={i18n}
onMouseEnter={onControlsMouseEnter}
onMouseLeave={onControlsMouseLeave}
onClick={() => toggleRaiseHand()}
tooltipDirection={TooltipPlacement.Top}
/>
)}
{isGroupCallReactionsEnabled && reactButtonType && (
<div <div
className={classNames( className={classNames('CallControls__ReactButtonContainer', {
'CallControls__MoreOptionsButtonContainer', 'CallControls__ReactButtonContainer--menu-shown':
{ showReactionPicker,
'CallControls__MoreOptionsButtonContainer--menu-shown': })}
showMoreOptions, ref={reactButtonRef}
}
)}
ref={moreOptionsButtonRef}
> >
<CallingButton <CallingButton
buttonType={CallingButtonType.MORE_OPTIONS} buttonType={reactButtonType}
i18n={i18n} i18n={i18n}
onMouseEnter={onControlsMouseEnter} onMouseEnter={onControlsMouseEnter}
onMouseLeave={onControlsMouseLeave} onMouseLeave={onControlsMouseLeave}
onClick={toggleMoreOptions} onClick={toggleReactionPicker}
tooltipDirection={TooltipPlacement.Top} tooltipDirection={TooltipPlacement.Top}
/> />
</div> </div>
@ -908,24 +914,7 @@ export function CallScreen({
</Button> </Button>
</div> </div>
</div> </div>
{localPreviewNode ? ( <div className="module-calling__spacer CallControls__OuterSpacer" />
<div className="module-ongoing-call__footer__local-preview module-ongoing-call__footer__local-preview--active">
{localPreviewNode}
{!isSendingVideo && (
<div className="CallingStatusIndicator CallingStatusIndicator--Video" />
)}
<CallingAudioIndicator
hasAudio={hasLocalAudio}
audioLevel={localAudioLevel}
shouldShowSpeaking={isSpeaking}
/>
{syncedLocalHandRaised && (
<div className="CallingStatusIndicator CallingStatusIndicator--HandRaised" />
)}
</div>
) : (
<div className="module-ongoing-call__footer__local-preview" />
)}
</div> </div>
</div> </div>
); );

View file

@ -16,6 +16,10 @@ export enum CallingButtonType {
PRESENTING_DISABLED = 'PRESENTING_DISABLED', PRESENTING_DISABLED = 'PRESENTING_DISABLED',
PRESENTING_OFF = 'PRESENTING_OFF', PRESENTING_OFF = 'PRESENTING_OFF',
PRESENTING_ON = 'PRESENTING_ON', PRESENTING_ON = 'PRESENTING_ON',
RAISE_HAND_OFF = 'RAISE_HAND_OFF',
RAISE_HAND_ON = 'RAISE_HAND_ON',
REACT_OFF = 'REACT_OFF',
REACT_ON = 'REACT_ON',
RING_DISABLED = 'RING_DISABLED', RING_DISABLED = 'RING_DISABLED',
RING_OFF = 'RING_OFF', RING_OFF = 'RING_OFF',
RING_ON = 'RING_ON', RING_ON = 'RING_ON',
@ -75,6 +79,17 @@ export function CallingButton({
tooltipContent = i18n( tooltipContent = i18n(
'icu:calling__button--ring__disabled-because-group-is-too-large' 'icu:calling__button--ring__disabled-because-group-is-too-large'
); );
} else if (buttonType === CallingButtonType.REACT_OFF) {
classNameSuffix = 'react--off';
tooltipContent = i18n('icu:calling__button--react');
} else if (buttonType === CallingButtonType.REACT_ON) {
classNameSuffix = 'react--on';
} else if (buttonType === CallingButtonType.RAISE_HAND_OFF) {
classNameSuffix = 'raise-hand--off';
tooltipContent = i18n('icu:CallControls__MenuItemRaiseHand');
} else if (buttonType === CallingButtonType.RAISE_HAND_ON) {
classNameSuffix = 'raise-hand--on';
tooltipContent = i18n('icu:CallControls__MenuItemRaiseHand--lower');
} else if (buttonType === CallingButtonType.RING_OFF) { } else if (buttonType === CallingButtonType.RING_OFF) {
classNameSuffix = 'ring--off'; classNameSuffix = 'ring--off';
tooltipContent = i18n('icu:CallingButton--ring-on'); tooltipContent = i18n('icu:CallingButton--ring-on');

View file

@ -2827,18 +2827,16 @@
{ {
"rule": "React-useRef", "rule": "React-useRef",
"path": "ts/components/CallScreen.tsx", "path": "ts/components/CallScreen.tsx",
"line": " const moreOptionsMenuRef = React.useRef<null | HTMLDivElement>(null);", "line": " const reactButtonRef = React.useRef<null | HTMLDivElement>(null);",
"reasonCategory": "usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2023-11-14T23:29:51.425Z", "updated": "2024-01-16T22:59:06.336Z"
"reasonDetail": "Used to detect clicks outside of the Calling More Options button menu"
}, },
{ {
"rule": "React-useRef", "rule": "React-useRef",
"path": "ts/components/CallScreen.tsx", "path": "ts/components/CallScreen.tsx",
"line": " const moreOptionsButtonRef = React.useRef<null | HTMLDivElement>(null);", "line": " const reactionPickerContainerRef = React.useRef<null | HTMLDivElement>(null);",
"reasonCategory": "usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2023-11-14T23:29:51.425Z", "updated": "2024-01-16T22:59:06.336Z"
"reasonDetail": "Used to detect clicks outside of the Calling More Options button menu and ensures clicking the button does not re-open the menu."
}, },
{ {
"rule": "React-useRef", "rule": "React-useRef",