snapshot of https://github.com/jitsi/jitsi-meet.git @ fd1749dd28f4138aa80234fc7f311b19aaffa0e7 (2026-09-15)
This commit is contained in:
194
react/features/toolbox/components/web/Drawer.tsx
Normal file
194
react/features/toolbox/components/web/Drawer.tsx
Normal file
@@ -0,0 +1,194 @@
|
||||
import React, { KeyboardEvent, ReactNode, useCallback } from 'react';
|
||||
import { FocusOn } from 'react-focus-on';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import { isElementInTheViewport } from '../../../base/ui/functions.web';
|
||||
import { DRAWER_MAX_HEIGHT } from '../../constants';
|
||||
|
||||
|
||||
interface IProps {
|
||||
|
||||
/**
|
||||
* The component(s) to be displayed within the drawer menu.
|
||||
*/
|
||||
children: ReactNode;
|
||||
|
||||
/**
|
||||
* Class name for custom styles.
|
||||
*/
|
||||
className?: string;
|
||||
|
||||
/**
|
||||
* The id of the dom element acting as the Drawer label.
|
||||
*/
|
||||
headingId?: string;
|
||||
|
||||
/**
|
||||
* Whether the drawer should be shown or not.
|
||||
*/
|
||||
isOpen: boolean;
|
||||
|
||||
/**
|
||||
* Function that hides the drawer.
|
||||
*/
|
||||
onClose?: Function;
|
||||
}
|
||||
|
||||
const useStyles = makeStyles()(theme => {
|
||||
return {
|
||||
drawerMenuContainer: {
|
||||
backgroundColor: 'rgba(0,0,0,0.6)',
|
||||
height: '100dvh',
|
||||
display: 'flex',
|
||||
alignItems: 'flex-end'
|
||||
},
|
||||
|
||||
drawer: {
|
||||
backgroundColor: theme.palette.drawerBackground,
|
||||
maxHeight: `calc(${DRAWER_MAX_HEIGHT})`,
|
||||
borderRadius: '24px 24px 0 0',
|
||||
overflowY: 'auto',
|
||||
marginBottom: 'env(safe-area-inset-bottom, 0)',
|
||||
width: '100%',
|
||||
|
||||
'& .overflow-menu': {
|
||||
margin: 'auto',
|
||||
fontSize: '1.2em',
|
||||
listStyleType: 'none',
|
||||
padding: 0,
|
||||
height: 'calc(80vh - 144px - 64px)',
|
||||
overflowY: 'auto',
|
||||
|
||||
'& .overflow-menu-item': {
|
||||
boxSizing: 'border-box',
|
||||
height: '48px',
|
||||
padding: '12px 16px',
|
||||
alignItems: 'center',
|
||||
color: theme.palette.overflowMenuItemText,
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
fontSize: '1rem',
|
||||
|
||||
'& div': {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
|
||||
'& svg': {
|
||||
fill: theme.palette.overflowMenuItemIcon
|
||||
}
|
||||
},
|
||||
|
||||
'& > svg': {
|
||||
fill: theme.palette.overflowMenuItemIcon
|
||||
},
|
||||
|
||||
'@media (hover: hover) and (pointer: fine)': {
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.overflowMenuItemHover,
|
||||
|
||||
'& svg': {
|
||||
fill: theme.palette.overflowMenuItemIcon
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
'&.disabled': {
|
||||
cursor: 'initial',
|
||||
color: theme.palette.overflowMenuItemDisabled,
|
||||
|
||||
'& svg': {
|
||||
fill: theme.palette.overflowMenuItemDisabled
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Component that displays the mobile friendly drawer on web.
|
||||
*
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
function Drawer({
|
||||
children,
|
||||
className = '',
|
||||
headingId,
|
||||
isOpen,
|
||||
onClose
|
||||
}: IProps) {
|
||||
const { classes, cx } = useStyles();
|
||||
|
||||
/**
|
||||
* Handles clicks within the menu, preventing the propagation of the click event.
|
||||
*
|
||||
* @param {Object} event - The click event.
|
||||
* @returns {void}
|
||||
*/
|
||||
const handleInsideClick = useCallback(event => {
|
||||
event.stopPropagation();
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Handles clicks outside of the menu, closing it, and also stopping further propagation.
|
||||
*
|
||||
* @param {Object} event - The click event.
|
||||
* @returns {void}
|
||||
*/
|
||||
const handleOutsideClick = useCallback(event => {
|
||||
event.stopPropagation();
|
||||
onClose?.();
|
||||
}, [ onClose ]);
|
||||
|
||||
/**
|
||||
* Handles pressing the escape key, closing the drawer.
|
||||
*
|
||||
* @param {KeyboardEvent<HTMLDivElement>} event - The keydown event.
|
||||
* @returns {void}
|
||||
*/
|
||||
const handleEscKey = useCallback((event: KeyboardEvent<HTMLDivElement>) => {
|
||||
if (event.key === 'Escape') {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
onClose?.();
|
||||
}
|
||||
}, [ onClose ]);
|
||||
|
||||
return (
|
||||
isOpen ? (
|
||||
<div
|
||||
className = { classes.drawerMenuContainer }
|
||||
onClick = { handleOutsideClick }
|
||||
onKeyDown = { handleEscKey }>
|
||||
<div
|
||||
className = { cx(classes.drawer, className) }
|
||||
onClick = { handleInsideClick }>
|
||||
<FocusOn
|
||||
returnFocus = {
|
||||
|
||||
// If we return the focus to an element outside the viewport the page will scroll to
|
||||
// this element which in our case is undesirable and the element is outside of the
|
||||
// viewport on purpose (to be hidden). For example if we return the focus to the toolbox
|
||||
// when it is hidden the whole page will move up in order to show the toolbox. This is
|
||||
// usually followed up with displaying the toolbox (because now it is on focus) but
|
||||
// because of the animation the whole scenario looks like jumping large video.
|
||||
isElementInTheViewport
|
||||
}>
|
||||
<div
|
||||
aria-labelledby = { headingId ? `#${headingId}` : undefined }
|
||||
aria-modal = { true }
|
||||
data-autofocus = { true }
|
||||
role = 'dialog'
|
||||
tabIndex = { -1 }>
|
||||
{children}
|
||||
</div>
|
||||
</FocusOn>
|
||||
</div>
|
||||
</div>
|
||||
) : null
|
||||
);
|
||||
}
|
||||
|
||||
export default Drawer;
|
||||
Reference in New Issue
Block a user