// Copyright 2018-2021 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import type { KeyboardEvent } from 'react'; import React, { useCallback, useEffect, useState } from 'react'; import { noop } from 'lodash'; import { createPortal } from 'react-dom'; import classNames from 'classnames'; import { Manager, Popper, Reference } from 'react-popper'; import type { LocalizerType } from '../types/Util'; import { useRefMerger } from '../hooks/useRefMerger'; import { handleOutsideClick } from '../util/handleOutsideClick'; export type PropsType = { i18n: LocalizerType; isHighQuality: boolean; onSelectQuality: (isHQ: boolean) => unknown; }; export const MediaQualitySelector = ({ i18n, isHighQuality, onSelectQuality, }: PropsType): JSX.Element => { const [menuShowing, setMenuShowing] = useState(false); const [popperRoot, setPopperRoot] = useState<HTMLElement | null>(null); const [focusedOption, setFocusedOption] = useState<0 | 1 | undefined>( undefined ); const buttonRef = React.useRef<HTMLButtonElement | null>(null); const refMerger = useRefMerger(); const handleClick = () => { setMenuShowing(true); }; const handleKeyDown = (ev: KeyboardEvent) => { if (!popperRoot) { if (ev.key === 'Enter') { setFocusedOption(isHighQuality ? 1 : 0); } return; } if (ev.key === 'ArrowDown' || ev.key === 'ArrowUp') { setFocusedOption(oldFocusedOption => (oldFocusedOption === 1 ? 0 : 1)); ev.stopPropagation(); ev.preventDefault(); } if (ev.key === 'Enter') { onSelectQuality(Boolean(focusedOption)); setMenuShowing(false); ev.stopPropagation(); ev.preventDefault(); } }; const handleClose = useCallback(() => { setMenuShowing(false); setFocusedOption(undefined); }, [setMenuShowing]); useEffect(() => { if (menuShowing) { const root = document.createElement('div'); setPopperRoot(root); document.body.appendChild(root); return () => { document.body.removeChild(root); setPopperRoot(null); }; } return noop; }, [menuShowing, setPopperRoot, handleClose]); useEffect(() => { if (!menuShowing) { return noop; } return handleOutsideClick( () => { handleClose(); return true; }, { containerElements: [popperRoot, buttonRef], name: 'MediaQualitySelector', } ); }, [menuShowing, popperRoot, handleClose]); return ( <Manager> <Reference> {({ ref }) => ( <button aria-label={i18n('MediaQualitySelector--button')} className={classNames({ MediaQualitySelector__button: true, 'MediaQualitySelector__button--hq': isHighQuality, 'MediaQualitySelector__button--active': menuShowing, })} onClick={handleClick} onKeyDown={handleKeyDown} ref={refMerger(buttonRef, ref)} type="button" /> )} </Reference> {menuShowing && popperRoot ? createPortal( <Popper placement="top-start" strategy="fixed"> {({ ref, style, placement }) => ( <div className="MediaQualitySelector__popper" data-placement={placement} ref={ref} style={style} > <div className="MediaQualitySelector__title"> {i18n('MediaQualitySelector--title')} </div> <button aria-label={i18n( 'MediaQualitySelector--standard-quality-title' )} className={classNames({ MediaQualitySelector__option: true, 'MediaQualitySelector__option--focused': focusedOption === 0, })} type="button" onClick={() => { onSelectQuality(false); setMenuShowing(false); }} > <div className={classNames({ 'MediaQualitySelector__option--checkmark': true, 'MediaQualitySelector__option--selected': !isHighQuality, })} /> <div> <div className="MediaQualitySelector__option--title"> {i18n('MediaQualitySelector--standard-quality-title')} </div> <div className="MediaQualitySelector__option--description"> {i18n( 'MediaQualitySelector--standard-quality-description' )} </div> </div> </button> <button aria-label={i18n( 'MediaQualitySelector--high-quality-title' )} className={classNames({ MediaQualitySelector__option: true, 'MediaQualitySelector__option--focused': focusedOption === 1, })} type="button" onClick={() => { onSelectQuality(true); setMenuShowing(false); }} > <div className={classNames({ 'MediaQualitySelector__option--checkmark': true, 'MediaQualitySelector__option--selected': isHighQuality, })} /> <div> <div className="MediaQualitySelector__option--title"> {i18n('MediaQualitySelector--high-quality-title')} </div> <div className="MediaQualitySelector__option--description"> {i18n('MediaQualitySelector--high-quality-description')} </div> </div> </button> </div> )} </Popper>, popperRoot ) : null} </Manager> ); };