mirror of https://github.com/jitsi/jitsi-meet
Created Reusable components for: - ListItem - used by participants list and lobby participants list - ContextMenu - used by participant context menu and advanced moderation context menu - Quick action button - used by quick action buttons on participant list items Moved participants custom theme to base/components/themes Created reusable button component for all participants pane buttons (Invite, Mute All, More) Moved web components to web folder Moved all styles from Styled Components to JSS Fixed accessibility labels for some buttons Removed unused code Updated all styles to use theme tokenspull/10279/head
parent
78e825de36
commit
7aca5e71b9
@ -0,0 +1,67 @@ |
||||
// @flow
|
||||
|
||||
import { makeStyles } from '@material-ui/styles'; |
||||
import React from 'react'; |
||||
|
||||
type Props = { |
||||
|
||||
/** |
||||
* Label used for accessibility. |
||||
*/ |
||||
accessibilityLabel: string, |
||||
|
||||
/** |
||||
* Additional class name for custom styles. |
||||
*/ |
||||
className: string, |
||||
|
||||
/** |
||||
* Children of the component. |
||||
*/ |
||||
children: string | React$Node, |
||||
|
||||
/** |
||||
* Click handler |
||||
*/ |
||||
onClick: Function, |
||||
|
||||
/** |
||||
* Data test id. |
||||
*/ |
||||
testId?: string |
||||
} |
||||
|
||||
const useStyles = makeStyles(theme => { |
||||
return { |
||||
button: { |
||||
backgroundColor: theme.palette.action01, |
||||
color: theme.palette.text01, |
||||
borderRadius: `${theme.shape.borderRadius}px`, |
||||
...theme.typography.labelBold, |
||||
lineHeight: `${theme.typography.labelBold.lineHeight}px`, |
||||
padding: '8px 12px', |
||||
display: 'flex', |
||||
justifyContent: 'center', |
||||
alignItems: 'center', |
||||
border: 0, |
||||
|
||||
'&:hover': { |
||||
backgroundColor: theme.palette.action01Hover |
||||
} |
||||
} |
||||
}; |
||||
}); |
||||
|
||||
const QuickActionButton = ({ accessibilityLabel, className, children, onClick, testId }: Props) => { |
||||
const styles = useStyles(); |
||||
|
||||
return (<button |
||||
aria-label = { accessibilityLabel } |
||||
className = { `${styles.button} ${className}` } |
||||
data-testid = { testId } |
||||
onClick = { onClick }> |
||||
{children} |
||||
</button>); |
||||
}; |
||||
|
||||
export default QuickActionButton; |
@ -0,0 +1,175 @@ |
||||
// @flow
|
||||
import { makeStyles } from '@material-ui/core'; |
||||
import clsx from 'clsx'; |
||||
import React, { useEffect, useLayoutEffect, useRef, useState } from 'react'; |
||||
import { useSelector } from 'react-redux'; |
||||
|
||||
import { getComputedOuterHeight } from '../../../participants-pane/functions'; |
||||
import { Drawer, JitsiPortal } from '../../../toolbox/components/web'; |
||||
import { showOverflowDrawer } from '../../../toolbox/functions.web'; |
||||
import participantsPaneTheme from '../themes/participantsPaneTheme.json'; |
||||
|
||||
type Props = { |
||||
|
||||
/** |
||||
* Children of the context menu. |
||||
*/ |
||||
children: React$Node, |
||||
|
||||
/** |
||||
* Class name for context menu. Used to overwrite default styles. |
||||
*/ |
||||
className?: string, |
||||
|
||||
/** |
||||
* The entity for which the context menu is displayed. |
||||
*/ |
||||
entity?: Object, |
||||
|
||||
/** |
||||
* Whether or not the menu is hidden. Used to overwrite the internal isHidden. |
||||
*/ |
||||
hidden?: boolean, |
||||
|
||||
/** |
||||
* Whether or not drawer should be open. |
||||
*/ |
||||
isDrawerOpen: boolean, |
||||
|
||||
/** |
||||
* Target elements against which positioning calculations are made |
||||
*/ |
||||
offsetTarget?: HTMLElement, |
||||
|
||||
/** |
||||
* Callback for click on an item in the menu |
||||
*/ |
||||
onClick?: Function, |
||||
|
||||
/** |
||||
* Callback for drawer close. |
||||
*/ |
||||
onDrawerClose: Function, |
||||
|
||||
/** |
||||
* Callback for the mouse entering the component |
||||
*/ |
||||
onMouseEnter?: Function, |
||||
|
||||
/** |
||||
* Callback for the mouse leaving the component |
||||
*/ |
||||
onMouseLeave: Function |
||||
}; |
||||
|
||||
const useStyles = makeStyles(theme => { |
||||
return { |
||||
contextMenu: { |
||||
backgroundColor: theme.palette.ui02, |
||||
borderRadius: `${theme.shape.borderRadius / 2}px`, |
||||
boxShadow: '0px 3px 16px rgba(0, 0, 0, 0.6), 0px 0px 4px 1px rgba(0, 0, 0, 0.25)', |
||||
color: theme.palette.text01, |
||||
...theme.typography.bodyShortRegular, |
||||
lineHeight: `${theme.typography.bodyShortRegular.lineHeight}px`, |
||||
marginTop: `${(participantsPaneTheme.panePadding * 2) + theme.typography.bodyShortRegular.fontSize}px`, |
||||
position: 'absolute', |
||||
right: `${participantsPaneTheme.panePadding}px`, |
||||
top: 0, |
||||
zIndex: 2 |
||||
}, |
||||
|
||||
contextMenuHidden: { |
||||
pointerEvents: 'none', |
||||
visibility: 'hidden' |
||||
}, |
||||
|
||||
drawer: { |
||||
|
||||
'& > div': { |
||||
...theme.typography.bodyShortRegularLarge, |
||||
lineHeight: `${theme.typography.bodyShortRegularLarge.lineHeight}px`, |
||||
|
||||
'& svg': { |
||||
fill: theme.palette.icon01 |
||||
} |
||||
}, |
||||
|
||||
'& > *:first-child': { |
||||
paddingTop: '15px!important' |
||||
} |
||||
} |
||||
}; |
||||
}); |
||||
|
||||
const ContextMenu = ({ |
||||
children, |
||||
className, |
||||
entity, |
||||
hidden, |
||||
isDrawerOpen, |
||||
offsetTarget, |
||||
onClick, |
||||
onDrawerClose, |
||||
onMouseEnter, |
||||
onMouseLeave |
||||
}: Props) => { |
||||
const [ isHidden, setIsHidden ] = useState(true); |
||||
const containerRef = useRef<HTMLDivElement | null>(null); |
||||
const styles = useStyles(); |
||||
const _overflowDrawer = useSelector(showOverflowDrawer); |
||||
|
||||
useLayoutEffect(() => { |
||||
if (_overflowDrawer) { |
||||
return; |
||||
} |
||||
if (entity && offsetTarget |
||||
&& containerRef.current |
||||
&& offsetTarget?.offsetParent |
||||
&& offsetTarget.offsetParent instanceof HTMLElement |
||||
) { |
||||
const { current: container } = containerRef; |
||||
const { offsetTop, offsetParent: { offsetHeight, scrollTop } } = offsetTarget; |
||||
const outerHeight = getComputedOuterHeight(container); |
||||
|
||||
container.style.top = offsetTop + outerHeight > offsetHeight + scrollTop |
||||
? `${offsetTop - outerHeight}` |
||||
: `${offsetTop}`; |
||||
|
||||
setIsHidden(false); |
||||
} else { |
||||
setIsHidden(true); |
||||
} |
||||
}, [ entity, offsetTarget, _overflowDrawer ]); |
||||
|
||||
useEffect(() => { |
||||
if (hidden !== undefined) { |
||||
setIsHidden(hidden); |
||||
} |
||||
}, [ hidden ]); |
||||
|
||||
return _overflowDrawer |
||||
? <JitsiPortal> |
||||
<Drawer |
||||
isOpen = { isDrawerOpen && _overflowDrawer } |
||||
onClose = { onDrawerClose }> |
||||
<div className = { styles.drawer }> |
||||
{children} |
||||
</div> |
||||
</Drawer> |
||||
</JitsiPortal> |
||||
: <div |
||||
className = { clsx(participantsPaneTheme.ignoredChildClassName, |
||||
styles.contextMenu, |
||||
isHidden && styles.contextMenuHidden, |
||||
className |
||||
) } |
||||
onClick = { onClick } |
||||
onMouseEnter = { onMouseEnter } |
||||
onMouseLeave = { onMouseLeave } |
||||
ref = { containerRef }> |
||||
{children} |
||||
</div> |
||||
; |
||||
}; |
||||
|
||||
export default ContextMenu; |
@ -0,0 +1,136 @@ |
||||
// @flow
|
||||
import { makeStyles } from '@material-ui/core'; |
||||
import clsx from 'clsx'; |
||||
import React from 'react'; |
||||
import { useSelector } from 'react-redux'; |
||||
|
||||
import { showOverflowDrawer } from '../../../toolbox/functions.web'; |
||||
import { Icon } from '../../icons'; |
||||
|
||||
export type Action = { |
||||
|
||||
/** |
||||
* Label used for accessibility. |
||||
*/ |
||||
accessibilityLabel: string, |
||||
|
||||
/** |
||||
* CSS class name used for custom styles. |
||||
*/ |
||||
className?: string, |
||||
|
||||
/** |
||||
* Custom icon. If used, the icon prop is ignored. |
||||
* Used to allow custom children instead of just the default icons. |
||||
*/ |
||||
customIcon?: React$Node, |
||||
|
||||
/** |
||||
* Id of the action container. |
||||
*/ |
||||
id?: string, |
||||
|
||||
/** |
||||
* Default icon for action. |
||||
*/ |
||||
icon?: Function, |
||||
|
||||
/** |
||||
* Click handler. |
||||
*/ |
||||
onClick?: Function, |
||||
|
||||
/** |
||||
* Action text. |
||||
*/ |
||||
text: string |
||||
} |
||||
|
||||
type Props = { |
||||
|
||||
/** |
||||
* List of actions in this group. |
||||
*/ |
||||
actions?: Array<Action>, |
||||
|
||||
/** |
||||
* The children of the component |
||||
*/ |
||||
children?: React$Node, |
||||
}; |
||||
|
||||
|
||||
const useStyles = makeStyles(theme => { |
||||
return { |
||||
contextMenuItemGroup: { |
||||
'&:not(:empty)': { |
||||
padding: `${theme.spacing(2)}px 0` |
||||
}, |
||||
|
||||
'& + &:not(:empty)': { |
||||
borderTop: `1px solid ${theme.palette.ui04}` |
||||
} |
||||
}, |
||||
|
||||
contextMenuItem: { |
||||
alignItems: 'center', |
||||
cursor: 'pointer', |
||||
display: 'flex', |
||||
minHeight: '40px', |
||||
padding: '10px 16px', |
||||
boxSizing: 'border-box', |
||||
|
||||
'& > *:not(:last-child)': { |
||||
marginRight: `${theme.spacing(3)}px` |
||||
}, |
||||
|
||||
'&:hover': { |
||||
backgroundColor: theme.palette.ui04 |
||||
} |
||||
}, |
||||
|
||||
contextMenuItemDrawer: { |
||||
padding: '12px 16px' |
||||
}, |
||||
|
||||
contextMenuItemIcon: { |
||||
'& svg': { |
||||
fill: theme.palette.icon01 |
||||
} |
||||
} |
||||
}; |
||||
}); |
||||
|
||||
const ContextMenuItemGroup = ({ |
||||
actions, |
||||
children |
||||
}: Props) => { |
||||
const styles = useStyles(); |
||||
const _overflowDrawer = useSelector(showOverflowDrawer); |
||||
|
||||
return ( |
||||
<div className = { styles.contextMenuItemGroup }> |
||||
{children} |
||||
{actions && actions.map(({ accessibilityLabel, className, customIcon, id, icon, onClick, text }) => ( |
||||
<div |
||||
aria-label = { accessibilityLabel } |
||||
className = { clsx(styles.contextMenuItem, |
||||
_overflowDrawer && styles.contextMenuItemDrawer, |
||||
className |
||||
) } |
||||
id = { id } |
||||
key = { text } |
||||
onClick = { onClick }> |
||||
{customIcon ? customIcon |
||||
: icon && <Icon |
||||
className = { styles.contextMenuItemIcon } |
||||
size = { 20 } |
||||
src = { icon } />} |
||||
<span>{text}</span> |
||||
</div> |
||||
))} |
||||
</div> |
||||
); |
||||
}; |
||||
|
||||
export default ContextMenuItemGroup; |
@ -0,0 +1,208 @@ |
||||
// @flow
|
||||
|
||||
import { makeStyles } from '@material-ui/styles'; |
||||
import React from 'react'; |
||||
|
||||
import { ACTION_TRIGGER } from '../../../participants-pane/constants'; |
||||
import participantsPaneTheme from '../themes/participantsPaneTheme.json'; |
||||
|
||||
type Props = { |
||||
|
||||
/** |
||||
* List item actions. |
||||
*/ |
||||
actions: React$Node, |
||||
|
||||
/** |
||||
* Icon to be displayed on the list item. (Avatar for participants) |
||||
*/ |
||||
icon: React$Node, |
||||
|
||||
/** |
||||
* Id of the container. |
||||
*/ |
||||
id: string, |
||||
|
||||
/** |
||||
* Whether or not the actions should be hidden. |
||||
*/ |
||||
hideActions?: Boolean, |
||||
|
||||
/** |
||||
* Indicators to be displayed on the list item. |
||||
*/ |
||||
indicators?: React$Node, |
||||
|
||||
/** |
||||
* Whether or not the item is highlighted. |
||||
*/ |
||||
isHighlighted?: boolean, |
||||
|
||||
/** |
||||
* Click handler. |
||||
*/ |
||||
onClick: Function, |
||||
|
||||
/** |
||||
* Mouse leave handler. |
||||
*/ |
||||
onMouseLeave: Function, |
||||
|
||||
/** |
||||
* Text children to be displayed on the list item. |
||||
*/ |
||||
textChildren: React$Node | string, |
||||
|
||||
/** |
||||
* The actions trigger. Can be Hover or Permanent. |
||||
*/ |
||||
trigger: string |
||||
|
||||
} |
||||
|
||||
const useStyles = makeStyles(theme => { |
||||
return { |
||||
container: { |
||||
alignItems: 'center', |
||||
color: theme.palette.text01, |
||||
display: 'flex', |
||||
...theme.typography.bodyShortRegular, |
||||
lineHeight: `${theme.typography.bodyShortRegular.lineHeight}px`, |
||||
margin: `0 -${participantsPaneTheme.panePadding}px`, |
||||
padding: `0 ${participantsPaneTheme.panePadding}px`, |
||||
position: 'relative', |
||||
boxShadow: 'inset 0px -1px 0px rgba(255, 255, 255, 0.15)', |
||||
|
||||
'&:hover': { |
||||
backgroundColor: theme.palette.action02Active, |
||||
|
||||
'& .indicators': { |
||||
display: 'none' |
||||
}, |
||||
|
||||
'& .actions': { |
||||
display: 'flex', |
||||
boxShadow: `-15px 0px 10px -5px ${theme.palette.action02Active}`, |
||||
backgroundColor: theme.palette.action02Active |
||||
} |
||||
}, |
||||
|
||||
[`@media(max-width: ${participantsPaneTheme.MD_BREAKPOINT})`]: { |
||||
...theme.typography.bodyShortRegularLarge, |
||||
lineHeight: `${theme.typography.bodyShortRegularLarge.lineHeight}px`, |
||||
padding: `${theme.spacing(2)}px ${participantsPaneTheme.panePadding}px` |
||||
} |
||||
}, |
||||
|
||||
highlighted: { |
||||
backgroundColor: theme.palette.action02Active |
||||
}, |
||||
|
||||
detailsContainer: { |
||||
display: 'flex', |
||||
alignItems: 'center', |
||||
flex: 1, |
||||
height: '100%', |
||||
overflow: 'hidden', |
||||
position: 'relative' |
||||
}, |
||||
|
||||
name: { |
||||
display: 'flex', |
||||
flex: 1, |
||||
marginRight: `${theme.spacing(2)}px`, |
||||
overflow: 'hidden', |
||||
flexDirection: 'column', |
||||
justifyContent: 'flex-start' |
||||
}, |
||||
|
||||
indicators: { |
||||
display: 'flex', |
||||
justifyContent: 'flex-end', |
||||
|
||||
'& > *': { |
||||
alignItems: 'center', |
||||
display: 'flex', |
||||
justifyContent: 'center' |
||||
}, |
||||
|
||||
'& > *:not(:last-child)': { |
||||
marginRight: `${theme.spacing(2)}px` |
||||
}, |
||||
|
||||
'& .jitsi-icon': { |
||||
padding: '3px' |
||||
} |
||||
}, |
||||
|
||||
indicatorsHidden: { |
||||
display: 'none' |
||||
}, |
||||
|
||||
actionsContainer: { |
||||
display: 'none', |
||||
boxShadow: `-15px 0px 10px -5px ${theme.palette.action02Active}`, |
||||
backgroundColor: theme.palette.action02Active |
||||
}, |
||||
|
||||
actionsPermanent: { |
||||
display: 'flex', |
||||
boxShadow: `-15px 0px 10px -5px ${theme.palette.ui01}`, |
||||
backgroundColor: theme.palette.ui01 |
||||
}, |
||||
|
||||
actionsVisible: { |
||||
display: 'flex', |
||||
boxShadow: `-15px 0px 10px -5px ${theme.palette.action02Active}`, |
||||
backgroundColor: theme.palette.action02Active |
||||
} |
||||
}; |
||||
}); |
||||
|
||||
const ListItem = ({ |
||||
actions, |
||||
icon, |
||||
id, |
||||
hideActions = false, |
||||
indicators, |
||||
isHighlighted, |
||||
onClick, |
||||
onMouseLeave, |
||||
textChildren, |
||||
trigger |
||||
}: Props) => { |
||||
const styles = useStyles(); |
||||
|
||||
return ( |
||||
<div |
||||
className = { `list-item-container ${styles.container} ${isHighlighted ? styles.highlighted : ''}` } |
||||
id = { id } |
||||
onClick = { onClick } |
||||
onMouseLeave = { onMouseLeave }> |
||||
<div> {icon} </div> |
||||
<div className = { styles.detailsContainer }> |
||||
<div className = { styles.name }> |
||||
{textChildren} |
||||
</div> |
||||
{indicators && ( |
||||
<div |
||||
className = { `indicators ${styles.indicators} ${ |
||||
isHighlighted || trigger === ACTION_TRIGGER.PERMANENT |
||||
? styles.indicatorsHidden : ''}` }>
|
||||
{indicators} |
||||
</div> |
||||
)} |
||||
{!hideActions && ( |
||||
<div |
||||
className = { `actions ${styles.actionsContainer} ${ |
||||
trigger === ACTION_TRIGGER.PERMANENT ? styles.actionsPermanent : ''} ${ |
||||
isHighlighted ? styles.actionsVisible : ''}` }>
|
||||
{actions} |
||||
</div> |
||||
)} |
||||
</div> |
||||
</div> |
||||
); |
||||
}; |
||||
|
||||
export default ListItem; |
@ -0,0 +1,10 @@ |
||||
{ |
||||
"colors": { |
||||
"moderationDisabled": "#E54B4B" |
||||
}, |
||||
"headerSize": 60, |
||||
"ignoredChildClassName": "ignore-child", |
||||
"panePadding": 16, |
||||
"participantsPaneWidth": 315, |
||||
"MD_BREAKPOINT": "580px" |
||||
} |
@ -1,46 +0,0 @@ |
||||
// @flow
|
||||
|
||||
import React, { useCallback } from 'react'; |
||||
import { useDispatch } from 'react-redux'; |
||||
|
||||
import { approveParticipant } from '../../av-moderation/actions'; |
||||
|
||||
import { QuickActionButton } from './web/styled'; |
||||
|
||||
type Props = { |
||||
|
||||
/** |
||||
* The translated ask unmute text. |
||||
*/ |
||||
askUnmuteText: string, |
||||
|
||||
/** |
||||
* Participant participantID. |
||||
*/ |
||||
participantID: string, |
||||
} |
||||
|
||||
/** |
||||
* Component used to display the `ask to unmute` button. |
||||
* |
||||
* @param {Object} participant - Participant reference. |
||||
* @returns {React$Element<'button'>} |
||||
*/ |
||||
export default function AskToUnmuteButton({ askUnmuteText, participantID }: Props) { |
||||
const dispatch = useDispatch(); |
||||
const askToUnmute = useCallback(() => { |
||||
dispatch(approveParticipant(participantID)); |
||||
}, [ dispatch, participantID ]); |
||||
|
||||
return ( |
||||
<QuickActionButton |
||||
aria-label = { `unmute-${participantID}` } |
||||
onClick = { askToUnmute } |
||||
primary = { true } |
||||
theme = {{ |
||||
panePadding: 16 |
||||
}}> |
||||
{ askUnmuteText } |
||||
</QuickActionButton> |
||||
); |
||||
} |
@ -1,162 +0,0 @@ |
||||
// @flow
|
||||
|
||||
import { makeStyles } from '@material-ui/core/styles'; |
||||
import clsx from 'clsx'; |
||||
import React, { useCallback } from 'react'; |
||||
import { useTranslation } from 'react-i18next'; |
||||
import { useDispatch, useSelector } from 'react-redux'; |
||||
|
||||
import { |
||||
requestDisableAudioModeration, |
||||
requestDisableVideoModeration, |
||||
requestEnableAudioModeration, |
||||
requestEnableVideoModeration |
||||
} from '../../av-moderation/actions'; |
||||
import { |
||||
isEnabled as isAvModerationEnabled, |
||||
isSupported as isAvModerationSupported |
||||
} from '../../av-moderation/functions'; |
||||
import { openDialog } from '../../base/dialog'; |
||||
import { Icon, IconCheck, IconVideoOff } from '../../base/icons'; |
||||
import { MEDIA_TYPE } from '../../base/media'; |
||||
import { |
||||
getParticipantCount, |
||||
isEveryoneModerator |
||||
} from '../../base/participants'; |
||||
import { MuteEveryonesVideoDialog } from '../../video-menu/components'; |
||||
|
||||
import { |
||||
ContextMenu, |
||||
ContextMenuItem, |
||||
ContextMenuItemGroup |
||||
} from './web/styled'; |
||||
|
||||
const useStyles = makeStyles(() => { |
||||
return { |
||||
contextMenu: { |
||||
bottom: 'auto', |
||||
margin: '0', |
||||
padding: '8px 0', |
||||
right: 0, |
||||
top: '-8px', |
||||
transform: 'translateY(-100%)', |
||||
width: '283px' |
||||
}, |
||||
drawer: { |
||||
width: '100%', |
||||
top: 'auto', |
||||
bottom: 0, |
||||
transform: 'none', |
||||
position: 'relative', |
||||
|
||||
'& > div': { |
||||
lineHeight: '32px' |
||||
} |
||||
}, |
||||
text: { |
||||
color: '#C2C2C2', |
||||
padding: '10px 16px' |
||||
}, |
||||
paddedAction: { |
||||
marginLeft: '36px' |
||||
} |
||||
}; |
||||
}); |
||||
|
||||
type Props = { |
||||
|
||||
/** |
||||
* Whether the menu is displayed inside a drawer. |
||||
*/ |
||||
inDrawer?: boolean, |
||||
|
||||
/** |
||||
* Callback for the mouse leaving this item. |
||||
*/ |
||||
onMouseLeave?: Function |
||||
}; |
||||
|
||||
export const FooterContextMenu = ({ inDrawer, onMouseLeave }: Props) => { |
||||
const dispatch = useDispatch(); |
||||
const isModerationSupported = useSelector(isAvModerationSupported()); |
||||
const allModerators = useSelector(isEveryoneModerator); |
||||
const participantCount = useSelector(getParticipantCount); |
||||
const isAudioModerationEnabled = useSelector(isAvModerationEnabled(MEDIA_TYPE.AUDIO)); |
||||
const isVideoModerationEnabled = useSelector(isAvModerationEnabled(MEDIA_TYPE.VIDEO)); |
||||
|
||||
const { t } = useTranslation(); |
||||
|
||||
const disableAudioModeration = useCallback(() => dispatch(requestDisableAudioModeration()), [ dispatch ]); |
||||
|
||||
const disableVideoModeration = useCallback(() => dispatch(requestDisableVideoModeration()), [ dispatch ]); |
||||
|
||||
const enableAudioModeration = useCallback(() => dispatch(requestEnableAudioModeration()), [ dispatch ]); |
||||
|
||||
const enableVideoModeration = useCallback(() => dispatch(requestEnableVideoModeration()), [ dispatch ]); |
||||
|
||||
const classes = useStyles(); |
||||
|
||||
const muteAllVideo = useCallback( |
||||
() => dispatch(openDialog(MuteEveryonesVideoDialog)), [ dispatch ]); |
||||
|
||||
return ( |
||||
<ContextMenu |
||||
className = { clsx(classes.contextMenu, inDrawer && clsx(classes.drawer)) } |
||||
onMouseLeave = { onMouseLeave }> |
||||
<ContextMenuItemGroup> |
||||
<ContextMenuItem |
||||
id = 'participants-pane-context-menu-stop-video' |
||||
onClick = { muteAllVideo }> |
||||
<Icon |
||||
size = { 20 } |
||||
src = { IconVideoOff } /> |
||||
<span>{ t('participantsPane.actions.stopEveryonesVideo') }</span> |
||||
</ContextMenuItem> |
||||
</ContextMenuItemGroup> |
||||
{isModerationSupported && (participantCount === 1 || !allModerators) ? ( |
||||
<ContextMenuItemGroup> |
||||
<div className = { classes.text }> |
||||
{t('participantsPane.actions.allow')} |
||||
</div> |
||||
{ isAudioModerationEnabled ? ( |
||||
<ContextMenuItem |
||||
id = 'participants-pane-context-menu-stop-audio-moderation' |
||||
onClick = { disableAudioModeration }> |
||||
<span className = { classes.paddedAction }> |
||||
{t('participantsPane.actions.audioModeration') } |
||||
</span> |
||||
</ContextMenuItem> |
||||
) : ( |
||||
<ContextMenuItem |
||||
id = 'participants-pane-context-menu-start-audio-moderation' |
||||
onClick = { enableAudioModeration }> |
||||
<Icon |
||||
size = { 20 } |
||||
src = { IconCheck } /> |
||||
<span>{t('participantsPane.actions.audioModeration') }</span> |
||||
</ContextMenuItem> |
||||
)} |
||||
{ isVideoModerationEnabled ? ( |
||||
<ContextMenuItem |
||||
id = 'participants-pane-context-menu-stop-video-moderation' |
||||
onClick = { disableVideoModeration }> |
||||
<span className = { classes.paddedAction }> |
||||
{t('participantsPane.actions.videoModeration')} |
||||
</span> |
||||
</ContextMenuItem> |
||||
) : ( |
||||
<ContextMenuItem |
||||
id = 'participants-pane-context-menu-start-video-moderation' |
||||
onClick = { enableVideoModeration }> |
||||
<Icon |
||||
size = { 20 } |
||||
src = { IconCheck } /> |
||||
<span>{t('participantsPane.actions.videoModeration')}</span> |
||||
</ContextMenuItem> |
||||
)} |
||||
</ContextMenuItemGroup> |
||||
) : undefined |
||||
} |
||||
</ContextMenu> |
||||
); |
||||
}; |
@ -1,78 +0,0 @@ |
||||
// @flow
|
||||
|
||||
import React from 'react'; |
||||
|
||||
import { QUICK_ACTION_BUTTON } from '../constants'; |
||||
|
||||
import AskToUnmuteButton from './AskToUnmuteButton'; |
||||
import { QuickActionButton } from './web/styled'; |
||||
|
||||
type Props = { |
||||
|
||||
/** |
||||
* The translated ask unmute aria label. |
||||
*/ |
||||
ariaLabel?: boolean, |
||||
|
||||
/** |
||||
* The translated "ask unmute" text. |
||||
*/ |
||||
askUnmuteText: string, |
||||
|
||||
/** |
||||
* The type of button to be displayed. |
||||
*/ |
||||
buttonType: string, |
||||
|
||||
/** |
||||
* Callback used to open a confirmation dialog for audio muting. |
||||
*/ |
||||
muteAudio: Function, |
||||
|
||||
/** |
||||
* Label for mute participant button. |
||||
*/ |
||||
muteParticipantButtonText: string, |
||||
|
||||
/** |
||||
* The ID of the participant. |
||||
*/ |
||||
participantID: string, |
||||
} |
||||
|
||||
/** |
||||
* Component used to display mute/ask to unmute button. |
||||
* |
||||
* @param {Props} props - The props of the component. |
||||
* @returns {React$Element<'button'>} |
||||
*/ |
||||
export default function ParticipantQuickAction({ |
||||
askUnmuteText, |
||||
buttonType, |
||||
muteAudio, |
||||
muteParticipantButtonText, |
||||
participantID |
||||
}: Props) { |
||||
switch (buttonType) { |
||||
case QUICK_ACTION_BUTTON.MUTE: { |
||||
return ( |
||||
<QuickActionButton |
||||
aria-label = { `mute-${participantID}` } |
||||
onClick = { muteAudio(participantID) } |
||||
primary = { true }> |
||||
{ muteParticipantButtonText } |
||||
</QuickActionButton> |
||||
); |
||||
} |
||||
case QUICK_ACTION_BUTTON.ASK_TO_UNMUTE: { |
||||
return ( |
||||
<AskToUnmuteButton |
||||
askUnmuteText = { askUnmuteText } |
||||
participantID = { participantID } /> |
||||
); |
||||
} |
||||
default: { |
||||
return null; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,57 @@ |
||||
// @flow
|
||||
|
||||
import { makeStyles } from '@material-ui/styles'; |
||||
import React from 'react'; |
||||
|
||||
import ParticipantPaneBaseButton from './ParticipantPaneBaseButton'; |
||||
|
||||
type Props = { |
||||
|
||||
/** |
||||
* Label used for accessibility. |
||||
*/ |
||||
accessibilityLabel: String, |
||||
|
||||
/** |
||||
* Children of the component. |
||||
*/ |
||||
children: string | React$Node, |
||||
|
||||
/** |
||||
* button id. |
||||
*/ |
||||
id?: string, |
||||
|
||||
/** |
||||
* Whether or not the button is icon button (no text). |
||||
*/ |
||||
isIconButton?: boolean, |
||||
|
||||
/** |
||||
* Click handler |
||||
*/ |
||||
onClick: Function |
||||
} |
||||
|
||||
const useStyles = makeStyles(theme => { |
||||
return { |
||||
button: { |
||||
padding: `${theme.spacing(2)}px` |
||||
} |
||||
}; |
||||
}); |
||||
|
||||
const FooterButton = ({ accessibilityLabel, children, id, isIconButton = false, onClick }: Props) => { |
||||
const styles = useStyles(); |
||||
|
||||
return (<ParticipantPaneBaseButton |
||||
accessibilityLabel = { accessibilityLabel } |
||||
className = { isIconButton ? styles.button : '' } |
||||
id = { id } |
||||
onClick = { onClick }> |
||||
{children} |
||||
</ParticipantPaneBaseButton> |
||||
); |
||||
}; |
||||
|
||||
export default FooterButton; |
@ -0,0 +1,145 @@ |
||||
// @flow
|
||||
|
||||
import { makeStyles } from '@material-ui/core/styles'; |
||||
import React, { useCallback } from 'react'; |
||||
import { useTranslation } from 'react-i18next'; |
||||
import { useDispatch, useSelector } from 'react-redux'; |
||||
|
||||
import { |
||||
requestDisableAudioModeration, |
||||
requestDisableVideoModeration, |
||||
requestEnableAudioModeration, |
||||
requestEnableVideoModeration |
||||
} from '../../../av-moderation/actions'; |
||||
import { |
||||
isEnabled as isAvModerationEnabled, |
||||
isSupported as isAvModerationSupported |
||||
} from '../../../av-moderation/functions'; |
||||
import ContextMenu from '../../../base/components/context-menu/ContextMenu'; |
||||
import ContextMenuItemGroup from '../../../base/components/context-menu/ContextMenuItemGroup'; |
||||
import { openDialog } from '../../../base/dialog'; |
||||
import { IconCheck, IconVideoOff } from '../../../base/icons'; |
||||
import { MEDIA_TYPE } from '../../../base/media'; |
||||
import { |
||||
getParticipantCount, |
||||
isEveryoneModerator |
||||
} from '../../../base/participants'; |
||||
import { MuteEveryonesVideoDialog } from '../../../video-menu/components'; |
||||
|
||||
const useStyles = makeStyles(theme => { |
||||
return { |
||||
contextMenu: { |
||||
bottom: 'auto', |
||||
margin: '0', |
||||
right: 0, |
||||
top: '-8px', |
||||
transform: 'translateY(-100%)', |
||||
width: '283px' |
||||
}, |
||||
|
||||
text: { |
||||
color: theme.palette.text02, |
||||
padding: '10px 16px', |
||||
height: '40px', |
||||
overflow: 'hidden', |
||||
display: 'flex', |
||||
alignItems: 'center', |
||||
boxSizing: 'border-box' |
||||
}, |
||||
|
||||
indentedLabel: { |
||||
'& > span': { |
||||
marginLeft: '36px' |
||||
} |
||||
} |
||||
}; |
||||
}); |
||||
|
||||
type Props = { |
||||
|
||||
/** |
||||
* Whether the menu is open. |
||||
*/ |
||||
isOpen: boolean, |
||||
|
||||
/** |
||||
* Drawer close callback. |
||||
*/ |
||||
onDrawerClose: Function, |
||||
|
||||
/** |
||||
* Callback for the mouse leaving this item. |
||||
*/ |
||||
onMouseLeave?: Function |
||||
}; |
||||
|
||||
export const FooterContextMenu = ({ isOpen, onDrawerClose, onMouseLeave }: Props) => { |
||||
const dispatch = useDispatch(); |
||||
const isModerationSupported = useSelector(isAvModerationSupported()); |
||||
const allModerators = useSelector(isEveryoneModerator); |
||||
const participantCount = useSelector(getParticipantCount); |
||||
const isAudioModerationEnabled = useSelector(isAvModerationEnabled(MEDIA_TYPE.AUDIO)); |
||||
const isVideoModerationEnabled = useSelector(isAvModerationEnabled(MEDIA_TYPE.VIDEO)); |
||||
|
||||
const { t } = useTranslation(); |
||||
|
||||
const disableAudioModeration = useCallback(() => dispatch(requestDisableAudioModeration()), [ dispatch ]); |
||||
|
||||
const disableVideoModeration = useCallback(() => dispatch(requestDisableVideoModeration()), [ dispatch ]); |
||||
|
||||
const enableAudioModeration = useCallback(() => dispatch(requestEnableAudioModeration()), [ dispatch ]); |
||||
|
||||
const enableVideoModeration = useCallback(() => dispatch(requestEnableVideoModeration()), [ dispatch ]); |
||||
|
||||
const classes = useStyles(); |
||||
|
||||
const muteAllVideo = useCallback( |
||||
() => dispatch(openDialog(MuteEveryonesVideoDialog)), [ dispatch ]); |
||||
|
||||
const actions = [ |
||||
{ |
||||
accessibilityLabel: t('participantsPane.actions.audioModeration'), |
||||
className: isAudioModerationEnabled ? classes.indentedLabel : '', |
||||
id: isAudioModerationEnabled |
||||
? 'participants-pane-context-menu-stop-audio-moderation' |
||||
: 'participants-pane-context-menu-start-audio-moderation', |
||||
icon: !isAudioModerationEnabled && IconCheck, |
||||
onClick: isAudioModerationEnabled ? disableAudioModeration : enableAudioModeration, |
||||
text: t('participantsPane.actions.audioModeration') |
||||
}, { |
||||
accessibilityLabel: t('participantsPane.actions.videoModeration'), |
||||
className: isVideoModerationEnabled ? classes.indentedLabel : '', |
||||
id: isVideoModerationEnabled |
||||
? 'participants-pane-context-menu-stop-video-moderation' |
||||
: 'participants-pane-context-menu-start-video-moderation', |
||||
icon: !isVideoModerationEnabled && IconCheck, |
||||
onClick: isVideoModerationEnabled ? disableVideoModeration : enableVideoModeration, |
||||
text: t('participantsPane.actions.videoModeration') |
||||
} |
||||
]; |
||||
|
||||
return ( |
||||
<ContextMenu |
||||
className = { classes.contextMenu } |
||||
hidden = { !isOpen } |
||||
isDrawerOpen = { isOpen } |
||||
onDrawerClose = { onDrawerClose } |
||||
onMouseLeave = { onMouseLeave }> |
||||
<ContextMenuItemGroup |
||||
actions = { [ { |
||||
accessibilityLabel: t('participantsPane.actions.stopEveryonesVideo'), |
||||
id: 'participants-pane-context-menu-stop-video', |
||||
icon: IconVideoOff, |
||||
onClick: muteAllVideo, |
||||
text: t('participantsPane.actions.stopEveryonesVideo') |
||||
} ] } /> |
||||
{isModerationSupported && (participantCount === 1 || !allModerators) && ( |
||||
<ContextMenuItemGroup actions = { actions }> |
||||
<div className = { classes.text }> |
||||
<span>{t('participantsPane.actions.allow')}</span> |
||||
</div> |
||||
</ContextMenuItemGroup> |
||||
)} |
||||
</ContextMenu> |
||||
); |
||||
}; |
@ -0,0 +1,70 @@ |
||||
// @flow
|
||||
|
||||
import { makeStyles } from '@material-ui/styles'; |
||||
import React from 'react'; |
||||
|
||||
import QuickActionButton from '../../../base/components/buttons/QuickActionButton'; |
||||
|
||||
type Props = { |
||||
|
||||
/** |
||||
* Label used for accessibility. |
||||
*/ |
||||
accessibilityLabel: string, |
||||
|
||||
/** |
||||
* Component children |
||||
*/ |
||||
children: string, |
||||
|
||||
/** |
||||
* Button class name. |
||||
*/ |
||||
className?: string, |
||||
|
||||
/** |
||||
* Click handler function. |
||||
*/ |
||||
onClick: Function, |
||||
|
||||
/** |
||||
* Whether or not the button is secondary. |
||||
*/ |
||||
secondary?: boolean, |
||||
|
||||
/** |
||||
* Data test id. |
||||
*/ |
||||
testId: string |
||||
} |
||||
|
||||
const useStyles = makeStyles(theme => { |
||||
return { |
||||
secondary: { |
||||
backgroundColor: theme.palette.ui04 |
||||
} |
||||
}; |
||||
}); |
||||
|
||||
const LobbyParticipantQuickAction = ({ |
||||
accessibilityLabel, |
||||
children, |
||||
className, |
||||
onClick, |
||||
secondary = false, |
||||
testId |
||||
}: Props) => { |
||||
const styles = useStyles(); |
||||
|
||||
return ( |
||||
<QuickActionButton |
||||
accessibilityLabel = { accessibilityLabel } |
||||
className = { `${secondary ? styles.secondary : ''} ${className ?? ''}` } |
||||
onClick = { onClick } |
||||
testId = { testId }> |
||||
{children} |
||||
</QuickActionButton> |
||||
); |
||||
}; |
||||
|
||||
export default LobbyParticipantQuickAction; |
@ -0,0 +1,43 @@ |
||||
// @flow
|
||||
|
||||
import { makeStyles } from '@material-ui/styles'; |
||||
import React from 'react'; |
||||
|
||||
import QuickActionButton from '../../../base/components/buttons/QuickActionButton'; |
||||
import { Icon, IconHorizontalPoints } from '../../../base/icons'; |
||||
|
||||
type Props = { |
||||
|
||||
/** |
||||
* Label used for accessibility. |
||||
*/ |
||||
accessibilityLabel: string, |
||||
|
||||
/** |
||||
* Click handler function. |
||||
*/ |
||||
onClick: Function |
||||
} |
||||
|
||||
const useStyles = makeStyles(() => { |
||||
return { |
||||
button: { |
||||
padding: '6px' |
||||
} |
||||
}; |
||||
}); |
||||
|
||||
const ParticipantActionEllipsis = ({ accessibilityLabel, onClick }: Props) => { |
||||
const styles = useStyles(); |
||||
|
||||
return ( |
||||
<QuickActionButton |
||||
accessibilityLabel = { accessibilityLabel } |
||||
className = { styles.button } |
||||
onClick = { onClick }> |
||||
<Icon src = { IconHorizontalPoints } /> |
||||
</QuickActionButton> |
||||
); |
||||
}; |
||||
|
||||
export default ParticipantActionEllipsis; |
@ -0,0 +1,98 @@ |
||||
// @flow
|
||||
|
||||
import { makeStyles } from '@material-ui/styles'; |
||||
import React from 'react'; |
||||
|
||||
import participantsPaneTheme from '../../../base/components/themes/participantsPaneTheme.json'; |
||||
|
||||
type Props = { |
||||
|
||||
/** |
||||
* Label used for accessibility. |
||||
*/ |
||||
accessibilityLabel: String, |
||||
|
||||
/** |
||||
* Additional class name for custom styles. |
||||
*/ |
||||
className?: string, |
||||
|
||||
/** |
||||
* Children of the component. |
||||
*/ |
||||
children: string | React$Node, |
||||
|
||||
/** |
||||
* Button id. |
||||
*/ |
||||
id?: string, |
||||
|
||||
/** |
||||
* Click handler |
||||
*/ |
||||
onClick: Function, |
||||
|
||||
/** |
||||
* Whether or not the button should have primary button style. |
||||
*/ |
||||
primary?: boolean |
||||
} |
||||
|
||||
const useStyles = makeStyles(theme => { |
||||
return { |
||||
button: { |
||||
alignItems: 'center', |
||||
backgroundColor: theme.palette.action02, |
||||
border: 0, |
||||
borderRadius: `${theme.shape.borderRadius}px`, |
||||
display: 'flex', |
||||
justifyContent: 'center', |
||||
minHeight: '40px', |
||||
padding: `${theme.spacing(2)}px ${theme.spacing(3)}px`, |
||||
...theme.typography.labelButton, |
||||
lineHeight: `${theme.typography.labelButton.lineHeight}px`, |
||||
|
||||
'&:hover': { |
||||
backgroundColor: theme.palette.action02Hover |
||||
}, |
||||
|
||||
[`@media (max-width: ${participantsPaneTheme.MD_BREAKPOINT})`]: { |
||||
...theme.typography.labelButtonLarge, |
||||
lineHeight: `${theme.typography.labelButtonLarge.lineHeight}px`, |
||||
minWidth: '48px', |
||||
minHeight: '48px' |
||||
} |
||||
}, |
||||
|
||||
buttonPrimary: { |
||||
backgroundColor: theme.palette.action01, |
||||
|
||||
'&:hover': { |
||||
backgroundColor: theme.palette.action01Hover |
||||
} |
||||
} |
||||
}; |
||||
}); |
||||
|
||||
const ParticipantPaneBaseButton = ({ |
||||
accessibilityLabel, |
||||
className, |
||||
children, |
||||
id, |
||||
onClick, |
||||
primary = false |
||||
}: Props) => { |
||||
const styles = useStyles(); |
||||
|
||||
return ( |
||||
<button |
||||
aria-label = { accessibilityLabel } |
||||
className = { `${styles.button} ${primary ? styles.buttonPrimary : ''} ${className ?? ''}` } |
||||
id = { id } |
||||
onClick = { onClick }> |
||||
{children} |
||||
</button> |
||||
); |
||||
}; |
||||
|
||||
export default ParticipantPaneBaseButton; |
@ -0,0 +1,103 @@ |
||||
// @flow
|
||||
|
||||
import { makeStyles } from '@material-ui/styles'; |
||||
import React, { useCallback } from 'react'; |
||||
import { useTranslation } from 'react-i18next'; |
||||
import { useDispatch } from 'react-redux'; |
||||
|
||||
import { approveParticipant } from '../../../av-moderation/actions'; |
||||
import QuickActionButton from '../../../base/components/buttons/QuickActionButton'; |
||||
import { QUICK_ACTION_BUTTON } from '../../constants'; |
||||
|
||||
type Props = { |
||||
|
||||
/** |
||||
* The translated ask unmute aria label. |
||||
*/ |
||||
ariaLabel?: boolean, |
||||
|
||||
/** |
||||
* The translated "ask unmute" text. |
||||
*/ |
||||
askUnmuteText: string, |
||||
|
||||
/** |
||||
* The type of button to be displayed. |
||||
*/ |
||||
buttonType: string, |
||||
|
||||
/** |
||||
* Callback used to open a confirmation dialog for audio muting. |
||||
*/ |
||||
muteAudio: Function, |
||||
|
||||
/** |
||||
* Label for mute participant button. |
||||
*/ |
||||
muteParticipantButtonText: string, |
||||
|
||||
/** |
||||
* The ID of the participant. |
||||
*/ |
||||
participantID: string, |
||||
|
||||
/** |
||||
* The name of the participant. |
||||
*/ |
||||
participantName: string |
||||
} |
||||
|
||||
const useStyles = makeStyles(theme => { |
||||
return { |
||||
button: { |
||||
marginRight: `${theme.spacing(2)}px` |
||||
} |
||||
}; |
||||
}); |
||||
|
||||
const ParticipantQuickAction = ({ |
||||
askUnmuteText, |
||||
buttonType, |
||||
muteAudio, |
||||
muteParticipantButtonText, |
||||
participantID, |
||||
participantName |
||||
}: Props) => { |
||||
const styles = useStyles(); |
||||
const dispatch = useDispatch(); |
||||
const { t } = useTranslation(); |
||||
|
||||
const askToUnmute = useCallback(() => { |
||||
dispatch(approveParticipant(participantID)); |
||||
}, [ dispatch, participantID ]); |
||||
|
||||
switch (buttonType) { |
||||
case QUICK_ACTION_BUTTON.MUTE: { |
||||
return ( |
||||
<QuickActionButton |
||||
accessibilityLabel = { `${t('participantsPane.actions.mute')} ${participantName}` } |
||||
className = { styles.button } |
||||
onClick = { muteAudio(participantID) } |
||||
testId = { `mute-${participantID}` }> |
||||
{muteParticipantButtonText} |
||||
</QuickActionButton> |
||||
); |
||||
} |
||||
case QUICK_ACTION_BUTTON.ASK_TO_UNMUTE: { |
||||
return ( |
||||
<QuickActionButton |
||||
accessibilityLabel = { `${t('participantsPane.actions.askUnmute')} ${participantName}` } |
||||
className = { styles.button } |
||||
onClick = { askToUnmute } |
||||
testId = { `unmute-${participantID}` }> |
||||
{ askUnmuteText } |
||||
</QuickActionButton> |
||||
); |
||||
} |
||||
default: { |
||||
return null; |
||||
} |
||||
} |
||||
}; |
||||
|
||||
export default ParticipantQuickAction; |
@ -1,9 +1,9 @@ |
||||
// @flow
|
||||
|
||||
import { translate } from '../../base/i18n'; |
||||
import { IconParticipants } from '../../base/icons'; |
||||
import { connect } from '../../base/redux'; |
||||
import { AbstractButton, type AbstractButtonProps } from '../../base/toolbox/components'; |
||||
import { translate } from '../../../base/i18n'; |
||||
import { IconParticipants } from '../../../base/icons'; |
||||
import { connect } from '../../../base/redux'; |
||||
import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components'; |
||||
|
||||
/** |
||||
* The type of the React {@code Component} props of {@link ParticipantsPaneButton}. |
@ -1,15 +1,29 @@ |
||||
// @flow
|
||||
|
||||
import { makeStyles } from '@material-ui/styles'; |
||||
import React from 'react'; |
||||
|
||||
import { Icon, IconRaisedHandHollow } from '../../../base/icons'; |
||||
|
||||
import { RaisedHandIndicatorBackground } from './styled'; |
||||
const useStyles = makeStyles(theme => { |
||||
return { |
||||
indicator: { |
||||
backgroundColor: theme.palette.warning02, |
||||
borderRadius: `${theme.shape.borderRadius / 2}px`, |
||||
height: '24px', |
||||
width: '24px' |
||||
} |
||||
}; |
||||
}); |
||||
|
||||
export const RaisedHandIndicator = () => ( |
||||
<RaisedHandIndicatorBackground> |
||||
<Icon |
||||
size = { 15 } |
||||
src = { IconRaisedHandHollow } /> |
||||
</RaisedHandIndicatorBackground> |
||||
); |
||||
export const RaisedHandIndicator = () => { |
||||
const styles = useStyles(); |
||||
|
||||
return ( |
||||
<div className = { styles.indicator }> |
||||
<Icon |
||||
size = { 15 } |
||||
src = { IconRaisedHandHollow } /> |
||||
</div> |
||||
); |
||||
}; |
||||
|
@ -1,5 +1,5 @@ |
||||
export * from './InviteButton'; |
||||
export * from './LobbyParticipantItem'; |
||||
export { default as ParticipantsPane } from './ParticipantsPane'; |
||||
export * from '../ParticipantsPaneButton'; |
||||
export { default as ParticipantsPaneButton } from './ParticipantsPaneButton'; |
||||
export * from './RaisedHandIndicator'; |
||||
|
@ -1,390 +0,0 @@ |
||||
import React from 'react'; |
||||
import styled from 'styled-components'; |
||||
|
||||
import { Icon, IconHorizontalPoints } from '../../../base/icons'; |
||||
import { ACTION_TRIGGER } from '../../constants'; |
||||
|
||||
const MD_BREAKPOINT = '580px'; |
||||
|
||||
export const ignoredChildClassName = 'ignore-child'; |
||||
|
||||
export const AntiCollapse = styled.br` |
||||
font-size: 0; |
||||
`;
|
||||
|
||||
export const Button = styled.button` |
||||
align-items: center; |
||||
background-color: ${ |
||||
// eslint-disable-next-line no-confusing-arrow
|
||||
props => props.primary ? '#0056E0' : '#3D3D3D' |
||||
}; |
||||
border: 0; |
||||
border-radius: 6px; |
||||
display: flex; |
||||
font-weight: unset; |
||||
justify-content: center; |
||||
min-height: 32px; |
||||
|
||||
&:hover { |
||||
background-color: ${ |
||||
// eslint-disable-next-line no-confusing-arrow
|
||||
props => props.primary ? '#246FE5' : '#525252' |
||||
}; |
||||
} |
||||
`;
|
||||
|
||||
export const QuickActionButton = styled(Button)` |
||||
padding: 0 12px; |
||||
`;
|
||||
|
||||
export const Container = styled.div` |
||||
box-sizing: border-box; |
||||
flex: 1; |
||||
overflow-y: auto; |
||||
position: relative; |
||||
padding: 0 ${props => props.theme.panePadding}px; |
||||
|
||||
& > * + *:not(.${ignoredChildClassName}) { |
||||
margin-top: 16px; |
||||
} |
||||
|
||||
&::-webkit-scrollbar { |
||||
display: none; |
||||
} |
||||
`;
|
||||
|
||||
export const ContextMenu = styled.div.attrs(props => { |
||||
return { |
||||
className: props.className |
||||
}; |
||||
})` |
||||
background-color: #292929; |
||||
border-radius: 3px; |
||||
box-shadow: 0px 3px 16px rgba(0, 0, 0, 0.6), 0px 0px 4px 1px rgba(0, 0, 0, 0.25); |
||||
color: white; |
||||
font-size: ${props => props.theme.contextFontSize}px; |
||||
font-weight: ${props => props.theme.contextFontWeight}; |
||||
margin-top: ${props => { |
||||
const { |
||||
participantActionButtonHeight, |
||||
participantItemHeight |
||||
} = props.theme; |
||||
|
||||
return ((3 * participantItemHeight) + participantActionButtonHeight) / 4; |
||||
}}px; |
||||
position: absolute; |
||||
right: ${props => props.theme.panePadding}px; |
||||
top: 0; |
||||
z-index: 2; |
||||
|
||||
& > li { |
||||
list-style: none; |
||||
} |
||||
|
||||
${props => props.isHidden && ` |
||||
pointer-events: none; |
||||
visibility: hidden; |
||||
`}
|
||||
`;
|
||||
|
||||
export const ContextMenuIcon = styled(Icon).attrs({ |
||||
size: 20 |
||||
})` |
||||
& > svg { |
||||
fill: #ffffff; |
||||
} |
||||
`;
|
||||
|
||||
export const ContextMenuItem = styled.div` |
||||
align-items: center; |
||||
box-sizing: border-box; |
||||
cursor: pointer; |
||||
display: flex; |
||||
min-height: 40px; |
||||
padding: 10px 16px; |
||||
|
||||
& > *:not(:last-child) { |
||||
margin-right: 16px; |
||||
} |
||||
|
||||
&:hover { |
||||
background-color: #525252; |
||||
} |
||||
`;
|
||||
|
||||
export const ContextMenuItemGroup = styled.div` |
||||
&:not(:empty) { |
||||
padding: 8px 0; |
||||
} |
||||
|
||||
& + &:not(:empty) { |
||||
border-top: 1px solid #4C4D50; |
||||
} |
||||
`;
|
||||
|
||||
export const Close = styled.div` |
||||
align-items: center; |
||||
cursor: pointer; |
||||
display: flex; |
||||
height: 20px; |
||||
justify-content: center; |
||||
width: 20px; |
||||
|
||||
&:before, &:after { |
||||
content: ''; |
||||
background-color: #a4b8d1; |
||||
border-radius: 2px; |
||||
height: 2px; |
||||
position: absolute; |
||||
transform-origin: center center; |
||||
width: 21px; |
||||
} |
||||
|
||||
&:before { |
||||
transform: rotate(45deg); |
||||
} |
||||
|
||||
&:after { |
||||
transform: rotate(-45deg); |
||||
} |
||||
`;
|
||||
|
||||
export const Footer = styled.div` |
||||
background-color: #141414; |
||||
display: flex; |
||||
justify-content: flex-end; |
||||
padding: 24px ${props => props.theme.panePadding}px; |
||||
|
||||
& > *:not(:last-child) { |
||||
margin-right: 16px; |
||||
} |
||||
`;
|
||||
|
||||
export const FooterButton = styled(Button)` |
||||
height: 40px; |
||||
font-size: 15px; |
||||
padding: 0 16px; |
||||
|
||||
@media (max-width: ${MD_BREAKPOINT}) { |
||||
font-size: 16px; |
||||
height: 48px; |
||||
min-width: 48px; |
||||
} |
||||
`;
|
||||
|
||||
export const FooterEllipsisButton = styled(FooterButton).attrs({ |
||||
children: <Icon src = { IconHorizontalPoints } /> |
||||
})` |
||||
padding: 8px; |
||||
`;
|
||||
|
||||
export const FooterEllipsisContainer = styled.div` |
||||
position: relative; |
||||
`;
|
||||
|
||||
export const Header = styled.div` |
||||
align-items: center; |
||||
box-sizing: border-box; |
||||
display: flex; |
||||
height: ${props => props.theme.headerSize}px; |
||||
padding: 0 20px; |
||||
`;
|
||||
|
||||
export const Heading = styled.div` |
||||
color: #d1dbe8; |
||||
font-style: normal; |
||||
font-size: 15px; |
||||
line-height: 24px; |
||||
margin: 8px 0 ${props => props.theme.panePadding}px; |
||||
|
||||
@media (max-width: ${MD_BREAKPOINT}) { |
||||
font-size: 16px; |
||||
} |
||||
`;
|
||||
|
||||
export const ParticipantActionButton = styled(Button)` |
||||
height: ${props => props.theme.participantActionButtonHeight}px; |
||||
padding: 6px 10px; |
||||
`;
|
||||
|
||||
export const ParticipantActionEllipsis = styled(ParticipantActionButton).attrs({ |
||||
children: <Icon src = { IconHorizontalPoints } />, |
||||
primary: true |
||||
})` |
||||
padding: 6px; |
||||
`;
|
||||
|
||||
export const ParticipantActions = styled.div` |
||||
align-items: center; |
||||
z-index: 1; |
||||
|
||||
& > *:not(:last-child) { |
||||
margin-right: 8px; |
||||
} |
||||
`;
|
||||
|
||||
export const ParticipantActionsHover = styled(ParticipantActions)` |
||||
background-color: #292929; |
||||
bottom: 1px; |
||||
display: none; |
||||
position: absolute; |
||||
right: ${props => props.theme.panePadding}; |
||||
top: 0; |
||||
|
||||
&:after { |
||||
content: ''; |
||||
background: linear-gradient(90deg, rgba(0, 0, 0, 0) 0%, #292929 100%); |
||||
bottom: 0; |
||||
display: block; |
||||
left: 0; |
||||
pointer-events: none; |
||||
position: absolute; |
||||
top: 0; |
||||
transform: translateX(-100%); |
||||
} |
||||
`;
|
||||
|
||||
export const ParticipantActionsPermanent = styled(ParticipantActions)` |
||||
display: flex; |
||||
`;
|
||||
|
||||
export const ParticipantContent = styled.div` |
||||
align-items: center; |
||||
box-shadow: inset 0px -1px 0px rgba(255, 255, 255, 0.15); |
||||
display: flex; |
||||
flex: 1; |
||||
height: 100%; |
||||
overflow: hidden; |
||||
padding-right: ${props => props.theme.panePadding}px; |
||||
`;
|
||||
|
||||
export const ParticipantStates = styled.div` |
||||
display: flex; |
||||
justify-content: flex-end; |
||||
|
||||
& > * { |
||||
align-items: center; |
||||
display: flex; |
||||
justify-content: center; |
||||
} |
||||
|
||||
& > *:not(:last-child) { |
||||
margin-right: 8px; |
||||
} |
||||
|
||||
.jitsi-icon { |
||||
padding: 3px; |
||||
} |
||||
`;
|
||||
|
||||
export const ParticipantContainer = styled.div` |
||||
align-items: center; |
||||
color: white; |
||||
display: flex; |
||||
font-size: 13px; |
||||
font-weight: normal; |
||||
height: ${props => props.theme.participantItemHeight}px; |
||||
margin: 0 -${props => props.theme.panePadding}px; |
||||
padding-left: ${props => props.theme.panePadding}px; |
||||
position: relative; |
||||
|
||||
@media (max-width: ${MD_BREAKPOINT}) { |
||||
font-size: 16px; |
||||
height: 64px; |
||||
} |
||||
|
||||
&:hover { |
||||
${ParticipantStates} { |
||||
${props => !props.local && 'display: none'}; |
||||
} |
||||
} |
||||
|
||||
${props => !props.isHighlighted && '&:hover {'} |
||||
background-color: #292929; |
||||
|
||||
& ${ParticipantActions} { |
||||
${props => props.trigger === ACTION_TRIGGER.HOVER && ` |
||||
display: flex; |
||||
`}
|
||||
} |
||||
|
||||
& ${ParticipantContent} { |
||||
box-shadow: none; |
||||
} |
||||
|
||||
& ${ParticipantStates} { |
||||
display: none; |
||||
} |
||||
${props => !props.isHighlighted && '}'} |
||||
`;
|
||||
|
||||
export const ParticipantInviteButton = styled(Button).attrs({ |
||||
primary: true |
||||
})` |
||||
font-size: 15px; |
||||
height: 40px; |
||||
width: 100%; |
||||
|
||||
& > *:not(:last-child) { |
||||
margin-right: 8px; |
||||
} |
||||
|
||||
@media (max-width: ${MD_BREAKPOINT}) { |
||||
font-size: 16px; |
||||
height: 48px; |
||||
} |
||||
`;
|
||||
|
||||
export const ParticipantName = styled.div` |
||||
overflow: hidden; |
||||
text-overflow: ellipsis; |
||||
white-space: nowrap; |
||||
`;
|
||||
|
||||
export const ParticipantNameContainer = styled.div` |
||||
display: flex; |
||||
flex: 1; |
||||
overflow: hidden; |
||||
`;
|
||||
|
||||
export const ModeratorLabel = styled.div` |
||||
font-size: 12px; |
||||
line-height: 16px; |
||||
color: #858585; |
||||
`;
|
||||
|
||||
export const ParticipantDetailsContainer = styled.div` |
||||
display: flex; |
||||
flex: 1; |
||||
margin-right: 8px; |
||||
overflow: hidden; |
||||
flex-direction: column; |
||||
justify-content: flex-start; |
||||
`;
|
||||
|
||||
export const RaisedHandIndicatorBackground = styled.div` |
||||
background-color: #ed9e1b; |
||||
border-radius: 3px; |
||||
height: 24px; |
||||
width: 24px; |
||||
`;
|
||||
|
||||
export const VolumeInput = styled.input.attrs({ |
||||
type: 'range' |
||||
})` |
||||
width: 100%; |
||||
`;
|
||||
|
||||
export const VolumeInputContainer = styled.div` |
||||
position: relative; |
||||
width: 100%; |
||||
`;
|
||||
|
||||
export const VolumeOverlay = styled.div` |
||||
background-color: #0376da; |
||||
border-radius: 1px 0 0 1px; |
||||
height: 100%; |
||||
left: 0; |
||||
pointer-events: none; |
||||
position: absolute; |
||||
`;
|
@ -1,13 +0,0 @@ |
||||
{ |
||||
"colors": { |
||||
"moderationDisabled": "#E54B4B" |
||||
}, |
||||
"contextFontSize": 14, |
||||
"contextFontWeight": 400, |
||||
"headerSize": 60, |
||||
"panePadding": 16, |
||||
"participantActionButtonHeight": 32, |
||||
"participantItemHeight": 48, |
||||
"participantsPaneWidth": 315, |
||||
"rangeInputThumbSize": 14 |
||||
} |
Loading…
Reference in new issue