blob: a89726c5b2581764493745b2757cc66b186eb3ff [file] [log] [blame]
simon26e79f72022-10-05 22:16:08 -04001/*
2 * Copyright (C) 2022 Savoir-faire Linux Inc.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Affero General Public License as
6 * published by the Free Software Foundation; either version 3 of the
7 * License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU Affero General Public License for more details.
13 *
14 * You should have received a copy of the GNU Affero General Public
15 * License along with this program. If not, see
16 * <https://www.gnu.org/licenses/>.
17 */
simon1170c322022-10-31 14:51:31 -040018import { QuestionMark, RadioButtonChecked, RadioButtonUnchecked } from '@mui/icons-material';
simon33c06182022-11-02 17:39:31 -040019import {
20 Box,
21 ClickAwayListener,
simon492e8402022-11-29 16:48:37 -050022 FormControlLabel,
simon33c06182022-11-02 17:39:31 -040023 IconButton,
24 IconButtonProps,
25 ListItemIcon,
26 ListItemText,
27 Menu,
28 MenuItem,
29 Popper,
30 Radio,
31 RadioGroup,
simon492e8402022-11-29 16:48:37 -050032 RadioGroupProps,
simon33c06182022-11-02 17:39:31 -040033 SvgIconProps,
34} from '@mui/material';
simond47ef9e2022-09-28 22:24:28 -040035import { styled } from '@mui/material/styles';
simon35378692022-10-02 23:25:57 -040036import EmojiPicker, { IEmojiData } from 'emoji-picker-react';
simon33c06182022-11-02 17:39:31 -040037import React, { ComponentType, MouseEvent, ReactNode, useCallback, useState } from 'react';
simon07b4eb02022-09-29 17:50:26 -040038
simond47ef9e2022-09-28 22:24:28 -040039import {
40 Arrow2Icon,
41 Arrow3Icon,
42 ArrowIcon,
idillonae655dd2022-10-14 18:11:02 -040043 AudioCallIcon,
simond47ef9e2022-09-28 22:24:28 -040044 CameraIcon,
45 CameraInBubbleIcon,
46 CancelIcon,
47 CrossedEyeIcon,
48 CrossIcon,
49 EmojiIcon,
simon33c06182022-11-02 17:39:31 -040050 ExpandLessIcon,
simond47ef9e2022-09-28 22:24:28 -040051 EyeIcon,
52 FolderIcon,
53 InfoIcon,
idillonae655dd2022-10-14 18:11:02 -040054 ListIcon,
simond47ef9e2022-09-28 22:24:28 -040055 MicroInBubbleIcon,
56 PaperClipIcon,
57 PenIcon,
idillonae655dd2022-10-14 18:11:02 -040058 PeopleWithPlusSignIcon,
simond47ef9e2022-09-28 22:24:28 -040059 SaltireIcon,
idillonae655dd2022-10-14 18:11:02 -040060 VideoCallIcon,
simon35378692022-10-02 23:25:57 -040061} from './SvgIcon';
Michelle Sepkap Simef5ebc2e2022-10-27 18:30:53 -040062import CustomTooltip from './Tooltip';
idillon-sfl44b05342022-08-24 15:46:42 -040063
simonf929a362022-11-18 16:53:45 -050064export type ShapedButtonProps = IconButtonProps & {
simon35378692022-10-02 23:25:57 -040065 Icon: ComponentType<SvgIconProps>;
66};
67
simonf929a362022-11-18 16:53:45 -050068export const RoundButton = styled(({ Icon, ...props }: ShapedButtonProps) => (
simond47ef9e2022-09-28 22:24:28 -040069 <IconButton {...props} disableRipple={true}>
70 <Icon fontSize="inherit" />
71 </IconButton>
72))(({ theme }) => ({
73 border: `1px solid ${theme.palette.primary.dark}`,
74 color: theme.palette.primary.dark,
75 fontSize: '15px',
76 '&:hover': {
77 background: theme.palette.primary.light,
78 },
79 '&:active': {
80 color: '#FFF',
81 background: theme.palette.primary.dark,
82 },
83 '&.MuiIconButton-sizeSmall': {
84 height: '15px',
85 width: '15px',
86 },
87 '&.MuiIconButton-sizeMedium': {
88 height: '30px',
89 width: '30px',
90 },
91 '&.MuiIconButton-sizeLarge': {
92 height: '53px',
93 width: '53px',
94 },
idillon-sfld5cc7862022-08-25 11:11:34 -040095}));
idillon-sfl44b05342022-08-24 15:46:42 -040096
simon33c06182022-11-02 17:39:31 -040097type ExpandMenuOption = {
98 description: ReactNode;
99 icon?: ReactNode;
100};
101
simon492e8402022-11-29 16:48:37 -0500102export type ExpandMenuRadioOption = RadioGroupProps & {
simon33c06182022-11-02 17:39:31 -0400103 options: {
104 key: string;
105 description: ReactNode;
106 }[];
simon33c06182022-11-02 17:39:31 -0400107};
108
109export type ExpandableButtonProps = IconButtonProps & {
simon9a8fe202022-11-15 18:25:49 -0500110 isVertical?: boolean;
simon33c06182022-11-02 17:39:31 -0400111 Icon?: ComponentType<SvgIconProps>;
112 expandMenuOptions?: (ExpandMenuOption | ExpandMenuRadioOption)[];
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500113 IconButtonComp?: ComponentType<IconButtonProps>;
simon33c06182022-11-02 17:39:31 -0400114};
115
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500116export const ExpandableButton = ({
simon9a8fe202022-11-15 18:25:49 -0500117 isVertical,
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500118 Icon,
119 expandMenuOptions = undefined,
120 IconButtonComp = IconButton,
121 ...props
122}: ExpandableButtonProps) => {
simon33c06182022-11-02 17:39:31 -0400123 const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
124 const handleClose = () => {
125 setAnchorEl(null);
126 };
127
simon33c06182022-11-02 17:39:31 -0400128 return (
simon9a8fe202022-11-15 18:25:49 -0500129 <>
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500130 {expandMenuOptions && (
131 <Menu
132 anchorEl={anchorEl}
133 open={!!anchorEl}
134 onClose={handleClose}
135 anchorOrigin={{
simon9a8fe202022-11-15 18:25:49 -0500136 vertical: !isVertical ? 'top' : 'center',
137 horizontal: !isVertical ? 'center' : 'left',
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500138 }}
139 transformOrigin={{
simon9a8fe202022-11-15 18:25:49 -0500140 vertical: !isVertical ? 'bottom' : 'center',
141 horizontal: !isVertical ? 'center' : 'right',
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500142 }}
143 >
144 {expandMenuOptions?.map((option, id) => {
145 if ('options' in option) {
simon492e8402022-11-29 16:48:37 -0500146 const { options, ...radioGroupProps } = option;
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500147 return (
simon492e8402022-11-29 16:48:37 -0500148 <RadioGroup key={id} {...radioGroupProps}>
149 {options.map(({ description, key }, i) => (
150 <MenuItem key={i}>
151 <FormControlLabel
152 value={key}
153 control={<Radio value={key} />}
154 label={<ListItemText>{description}</ListItemText>}
155 sx={{
156 width: '100%',
157 }}
158 />
159 </MenuItem>
160 ))}
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500161 </RadioGroup>
162 );
163 }
simon33c06182022-11-02 17:39:31 -0400164
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500165 return (
166 <MenuItem key={id} onClick={handleClose}>
167 <ListItemIcon>{option.icon}</ListItemIcon>
168 <ListItemText>{option.description}</ListItemText>
169 </MenuItem>
170 );
171 })}
172 </Menu>
173 )}
simon9a8fe202022-11-15 18:25:49 -0500174 <Box position="relative" display="flex" justifyContent="center" alignItems="center">
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500175 {expandMenuOptions && (
simon33c06182022-11-02 17:39:31 -0400176 <IconButton
simon33c06182022-11-02 17:39:31 -0400177 aria-label="expand options"
simon9a8fe202022-11-15 18:25:49 -0500178 onClick={(e) => setAnchorEl(e.currentTarget)}
simon33c06182022-11-02 17:39:31 -0400179 sx={{
simon9a8fe202022-11-15 18:25:49 -0500180 rotate: !isVertical ? '' : '-90deg',
simon33c06182022-11-02 17:39:31 -0400181 position: 'absolute',
simon9a8fe202022-11-15 18:25:49 -0500182 top: !isVertical ? '-55%' : 'auto',
183 left: !isVertical ? 'auto' : '-55%',
184 zIndex: 1,
simon33c06182022-11-02 17:39:31 -0400185 }}
simon9a8fe202022-11-15 18:25:49 -0500186 className={props.className}
simon33c06182022-11-02 17:39:31 -0400187 >
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500188 <ExpandLessIcon
simon9a8fe202022-11-15 18:25:49 -0500189 fontSize="small"
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500190 sx={{
191 backgroundColor: '#444444',
192 borderRadius: '5px',
193 }}
194 />
simon33c06182022-11-02 17:39:31 -0400195 </IconButton>
196 )}
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500197 <IconButtonComp {...props}>{Icon && <Icon />}</IconButtonComp>
simon33c06182022-11-02 17:39:31 -0400198 </Box>
simon9a8fe202022-11-15 18:25:49 -0500199 </>
simon33c06182022-11-02 17:39:31 -0400200 );
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500201};
simon33c06182022-11-02 17:39:31 -0400202
simon35378692022-10-02 23:25:57 -0400203export const CancelPictureButton = (props: IconButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400204 return <RoundButton {...props} aria-label="remove picture" Icon={CancelIcon} size="large" />;
205};
idillon-sfl44b05342022-08-24 15:46:42 -0400206
simon35378692022-10-02 23:25:57 -0400207export const EditPictureButton = (props: IconButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400208 return <RoundButton {...props} aria-label="edit picture" Icon={PenIcon} size="large" />;
209};
idillon-sfl44b05342022-08-24 15:46:42 -0400210
simon35378692022-10-02 23:25:57 -0400211export const UploadPictureButton = (props: IconButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400212 return <RoundButton {...props} aria-label="upload picture" Icon={FolderIcon} size="large" />;
213};
idillon-sfl44b05342022-08-24 15:46:42 -0400214
simon35378692022-10-02 23:25:57 -0400215export const TakePictureButton = (props: IconButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400216 return <RoundButton {...props} aria-label="take picture" Icon={CameraIcon} size="large" />;
217};
idillon-sfl37c18df2022-08-26 18:44:27 -0400218
Michelle Sepkap Simef5ebc2e2022-10-27 18:30:53 -0400219type InfoButtonProps = IconButtonProps & {
220 tooltipTitle: string;
221};
222export const InfoButton = ({ tooltipTitle, ...props }: InfoButtonProps) => {
223 return (
224 <CustomTooltip className="tooltip" title={tooltipTitle}>
225 <RoundButton {...props} aria-label="informations" Icon={InfoIcon} size="small" />
226 </CustomTooltip>
227 );
simond47ef9e2022-09-28 22:24:28 -0400228};
idillon-sfl37c18df2022-08-26 18:44:27 -0400229
simon35378692022-10-02 23:25:57 -0400230export const TipButton = (props: IconButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400231 return <RoundButton {...props} aria-label="tip" Icon={QuestionMark} size="medium" />;
232};
idillon-sfl37c18df2022-08-26 18:44:27 -0400233
simon35378692022-10-02 23:25:57 -0400234export const MoreButton = styled((props: IconButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400235 return (
236 <IconButton {...props} disableRipple={true} aria-label="more">
237 <CrossIcon fontSize="inherit" />
238 </IconButton>
239 );
240})(({ theme }) => ({
241 border: `1px solid ${theme.palette.primary.dark}`,
242 color: theme.palette.primary.dark,
243 fontSize: '10px',
244 height: '20px',
245 width: '20px',
246 '&:hover': {
247 background: theme.palette.primary.light,
248 },
249 '&:active': {
250 color: '#FFF',
251 background: theme.palette.primary.dark,
252 },
253}));
idillon927b7592022-09-15 12:56:45 -0400254
simon35378692022-10-02 23:25:57 -0400255export const BackButton = styled((props: IconButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400256 return (
257 <IconButton {...props} disableRipple={true} aria-label="back">
258 <ArrowIcon fontSize="inherit" />
259 </IconButton>
260 );
261})(({ theme }) => ({
262 color: theme.palette.primary.dark,
263 fontSize: '15px',
264 height: '30px',
265 width: '51px',
266 borderRadius: '5px',
267 '&:hover': {
268 background: theme.palette.primary.light,
269 },
270}));
idillonb3788bf2022-08-29 15:57:57 -0400271
simon1170c322022-10-31 14:51:31 -0400272export type ToggleIconButtonProps = IconButtonProps & {
273 selected: boolean;
274 toggle: () => void;
275 IconOn?: ComponentType<SvgIconProps>;
276 IconOff?: ComponentType<SvgIconProps>;
Gabriel Rochone3ec0d22022-10-08 14:27:03 -0400277};
simon33c06182022-11-02 17:39:31 -0400278
simon1170c322022-10-31 14:51:31 -0400279export const ToggleIconButton = ({
280 IconOn = RadioButtonChecked,
281 IconOff = RadioButtonUnchecked,
282 selected,
283 toggle,
284 ...props
285}: ToggleIconButtonProps) => {
Gabriel Rochone3ec0d22022-10-08 14:27:03 -0400286 return (
simon1170c322022-10-31 14:51:31 -0400287 <IconButton
288 {...props}
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500289 sx={{
290 color: selected ? 'white' : 'red',
291 ...props.sx,
292 }}
simon1170c322022-10-31 14:51:31 -0400293 onClick={() => {
294 toggle();
295 }}
296 >
297 {selected ? <IconOn /> : <IconOff />}
Gabriel Rochone3ec0d22022-10-08 14:27:03 -0400298 </IconButton>
299 );
300};
301
simon35378692022-10-02 23:25:57 -0400302export const CloseButton = styled((props: IconButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400303 return (
304 <IconButton {...props} disableRipple={true} aria-label="close">
305 <SaltireIcon fontSize="inherit" />
306 </IconButton>
307 );
308})(({ theme }) => ({
309 color: theme.palette.primary.dark,
310 fontSize: '15px',
311 height: '30px',
312 width: '30px',
313 borderRadius: '5px',
314 '&:hover': {
315 background: theme.palette.primary.light,
316 },
317}));
idillonb3788bf2022-08-29 15:57:57 -0400318
simon35378692022-10-02 23:25:57 -0400319type ToggleVisibilityButtonProps = IconButtonProps & {
320 visible: boolean;
321};
322export const ToggleVisibilityButton = styled(({ visible, ...props }: ToggleVisibilityButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400323 const Icon = visible ? CrossedEyeIcon : EyeIcon;
324 return (
325 <IconButton {...props} disableRipple={true}>
326 <Icon fontSize="inherit" />
327 </IconButton>
328 );
329})(({ theme }) => ({
330 color: theme.palette.primary.dark,
331 fontSize: '15px',
332 height: '15px',
333 width: '15px',
334 '&:hover': {
335 background: theme.palette.primary.light,
336 },
337}));
idillonaedab942022-09-01 14:29:43 -0400338
simon35378692022-10-02 23:25:57 -0400339const SquareButton = styled(({ Icon, ...props }: ShapedButtonProps) => (
simond47ef9e2022-09-28 22:24:28 -0400340 <IconButton {...props} disableRipple={true}>
341 <Icon fontSize="inherit" />
342 </IconButton>
simon416d0792022-11-03 02:46:18 -0400343))(() => ({
simond47ef9e2022-09-28 22:24:28 -0400344 color: '#7E7E7E',
345 fontSize: '25px',
346 height: '36px',
347 width: '36px',
348 borderRadius: '5px',
349 '&:hover': {
350 background: '#E5E5E5',
351 },
idillonaedab942022-09-01 14:29:43 -0400352}));
353
idillonae655dd2022-10-14 18:11:02 -0400354export const AddParticipantButton = (props: IconButtonProps) => {
355 return <SquareButton {...props} aria-label="add participant" Icon={PeopleWithPlusSignIcon} />;
356};
357
simon35378692022-10-02 23:25:57 -0400358export const RecordVideoMessageButton = (props: IconButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400359 return <SquareButton {...props} aria-label="record video message" Icon={CameraInBubbleIcon} />;
360};
idillonaedab942022-09-01 14:29:43 -0400361
simon35378692022-10-02 23:25:57 -0400362export const RecordVoiceMessageButton = (props: IconButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400363 return <SquareButton {...props} aria-label="record voice message" Icon={MicroInBubbleIcon} />;
364};
idillonaedab942022-09-01 14:29:43 -0400365
idillonae655dd2022-10-14 18:11:02 -0400366export const ShowOptionsMenuButton = (props: IconButtonProps) => {
367 return <SquareButton {...props} aria-label="show options menu" Icon={ListIcon} />;
368};
369
370export const StartVideoCallButton = (props: IconButtonProps) => {
simoncd698c52022-11-08 19:21:38 -0500371 return <SquareButton {...props} aria-label="start audio call" Icon={VideoCallIcon} />;
idillonae655dd2022-10-14 18:11:02 -0400372};
373
374export const StartAudioCallButton = (props: IconButtonProps) => {
simoncd698c52022-11-08 19:21:38 -0500375 return <SquareButton {...props} aria-label="start video call" Icon={AudioCallIcon} />;
idillonae655dd2022-10-14 18:11:02 -0400376};
377
simon35378692022-10-02 23:25:57 -0400378export const UploadFileButton = (props: IconButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400379 return <SquareButton {...props} aria-label="upload file" Icon={PaperClipIcon} />;
380};
idillonaedab942022-09-01 14:29:43 -0400381
simon35378692022-10-02 23:25:57 -0400382export const SendMessageButton = (props: IconButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400383 return <SquareButton {...props} aria-label="send message" Icon={Arrow2Icon} />;
384};
idillonaedab942022-09-01 14:29:43 -0400385
simon35378692022-10-02 23:25:57 -0400386export const ReplyMessageButton = styled((props: IconButtonProps) => (
simond47ef9e2022-09-28 22:24:28 -0400387 <IconButton {...props} disableRipple={true} aria-label="send message">
388 <Arrow3Icon fontSize="inherit" />
389 </IconButton>
390))(({ theme }) => ({
391 color: theme.palette.primary.dark,
392 fontSize: '20px',
393 height: '20px',
394 width: '20px',
395 borderRadius: '5px',
396 '&:hover': {
397 background: '#E5E5E5',
398 },
idillon927b7592022-09-15 12:56:45 -0400399}));
400
simon35378692022-10-02 23:25:57 -0400401type EmojiButtonProps = IconButtonProps & {
402 emoji: string;
403};
404export const EmojiButton = styled(({ emoji, ...props }: EmojiButtonProps) => (
simond47ef9e2022-09-28 22:24:28 -0400405 <IconButton {...props} disableRipple={true}>
406 {emoji}
407 </IconButton>
simon416d0792022-11-03 02:46:18 -0400408))(() => ({
simond47ef9e2022-09-28 22:24:28 -0400409 color: 'white',
410 fontSize: '20px',
411 height: '20px',
412 width: '20px',
idillon927b7592022-09-15 12:56:45 -0400413}));
414
simon35378692022-10-02 23:25:57 -0400415type SelectEmojiButtonProps = {
416 onEmojiSelected: (emoji: string) => void;
417};
simon416d0792022-11-03 02:46:18 -0400418export const SelectEmojiButton = ({ onEmojiSelected }: SelectEmojiButtonProps) => {
simon35378692022-10-02 23:25:57 -0400419 const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
idillon1664bb22022-09-14 17:18:15 -0400420
simon35378692022-10-02 23:25:57 -0400421 const handleOpenEmojiPicker = useCallback(
422 (e: MouseEvent<HTMLButtonElement>) => setAnchorEl(anchorEl ? null : e.currentTarget),
423 [anchorEl]
424 );
simond47ef9e2022-09-28 22:24:28 -0400425
426 const handleClose = useCallback(() => setAnchorEl(null), [setAnchorEl]);
427
428 const onEmojiClick = useCallback(
simon35378692022-10-02 23:25:57 -0400429 (e: MouseEvent, emojiObject: IEmojiData) => {
simon80b7b3b2022-09-28 17:50:10 -0400430 onEmojiSelected(emojiObject.emoji);
simond47ef9e2022-09-28 22:24:28 -0400431 handleClose();
432 },
simon80b7b3b2022-09-28 17:50:10 -0400433 [handleClose, onEmojiSelected]
simond47ef9e2022-09-28 22:24:28 -0400434 );
435
436 const open = Boolean(anchorEl);
437 const id = open ? 'simple-popover' : undefined;
438
439 return (
440 <ClickAwayListener onClickAway={handleClose}>
441 <Box>
idillonae655dd2022-10-14 18:11:02 -0400442 <SquareButton
443 aria-describedby={id}
444 aria-label="select emoji"
445 Icon={EmojiIcon}
446 onClick={handleOpenEmojiPicker}
447 />
simon35378692022-10-02 23:25:57 -0400448 <Popper id={id} open={open} anchorEl={anchorEl}>
449 <EmojiPicker onEmojiClick={onEmojiClick} disableAutoFocus={true} disableSkinTonePicker={true} native />
simond47ef9e2022-09-28 22:24:28 -0400450 </Popper>
451 </Box>
452 </ClickAwayListener>
453 );
454};