blob: 379dc8fc651569dbfa033ea17c1ceef7704223e3 [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,
22 IconButton,
23 IconButtonProps,
24 ListItemIcon,
25 ListItemText,
26 Menu,
27 MenuItem,
28 Popper,
29 Radio,
30 RadioGroup,
31 SvgIconProps,
32} from '@mui/material';
simond47ef9e2022-09-28 22:24:28 -040033import { styled } from '@mui/material/styles';
simon35378692022-10-02 23:25:57 -040034import EmojiPicker, { IEmojiData } from 'emoji-picker-react';
simon33c06182022-11-02 17:39:31 -040035import React, { ComponentType, MouseEvent, ReactNode, useCallback, useState } from 'react';
simon07b4eb02022-09-29 17:50:26 -040036
simond47ef9e2022-09-28 22:24:28 -040037import {
38 Arrow2Icon,
39 Arrow3Icon,
40 ArrowIcon,
idillonae655dd2022-10-14 18:11:02 -040041 AudioCallIcon,
simond47ef9e2022-09-28 22:24:28 -040042 CameraIcon,
43 CameraInBubbleIcon,
44 CancelIcon,
45 CrossedEyeIcon,
46 CrossIcon,
47 EmojiIcon,
simon33c06182022-11-02 17:39:31 -040048 ExpandLessIcon,
simond47ef9e2022-09-28 22:24:28 -040049 EyeIcon,
50 FolderIcon,
51 InfoIcon,
idillonae655dd2022-10-14 18:11:02 -040052 ListIcon,
simond47ef9e2022-09-28 22:24:28 -040053 MicroInBubbleIcon,
54 PaperClipIcon,
55 PenIcon,
idillonae655dd2022-10-14 18:11:02 -040056 PeopleWithPlusSignIcon,
simond47ef9e2022-09-28 22:24:28 -040057 SaltireIcon,
idillonae655dd2022-10-14 18:11:02 -040058 VideoCallIcon,
simon35378692022-10-02 23:25:57 -040059} from './SvgIcon';
Michelle Sepkap Simef5ebc2e2022-10-27 18:30:53 -040060import CustomTooltip from './Tooltip';
idillon-sfl44b05342022-08-24 15:46:42 -040061
simon35378692022-10-02 23:25:57 -040062type ShapedButtonProps = IconButtonProps & {
63 Icon: ComponentType<SvgIconProps>;
64};
65
66const RoundButton = styled(({ Icon, ...props }: ShapedButtonProps) => (
simond47ef9e2022-09-28 22:24:28 -040067 <IconButton {...props} disableRipple={true}>
68 <Icon fontSize="inherit" />
69 </IconButton>
70))(({ theme }) => ({
71 border: `1px solid ${theme.palette.primary.dark}`,
72 color: theme.palette.primary.dark,
73 fontSize: '15px',
74 '&:hover': {
75 background: theme.palette.primary.light,
76 },
77 '&:active': {
78 color: '#FFF',
79 background: theme.palette.primary.dark,
80 },
81 '&.MuiIconButton-sizeSmall': {
82 height: '15px',
83 width: '15px',
84 },
85 '&.MuiIconButton-sizeMedium': {
86 height: '30px',
87 width: '30px',
88 },
89 '&.MuiIconButton-sizeLarge': {
90 height: '53px',
91 width: '53px',
92 },
idillon-sfld5cc7862022-08-25 11:11:34 -040093}));
idillon-sfl44b05342022-08-24 15:46:42 -040094
simon33c06182022-11-02 17:39:31 -040095type ExpandMenuOption = {
96 description: ReactNode;
97 icon?: ReactNode;
98};
99
100type ExpandMenuRadioOption = {
101 options: {
102 key: string;
103 description: ReactNode;
104 }[];
105 defaultSelectedOption?: string;
106};
107
108export type ExpandableButtonProps = IconButtonProps & {
simon9a8fe202022-11-15 18:25:49 -0500109 isVertical?: boolean;
simon33c06182022-11-02 17:39:31 -0400110 Icon?: ComponentType<SvgIconProps>;
111 expandMenuOptions?: (ExpandMenuOption | ExpandMenuRadioOption)[];
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500112 IconButtonComp?: ComponentType<IconButtonProps>;
simon33c06182022-11-02 17:39:31 -0400113};
114
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500115export const ExpandableButton = ({
simon9a8fe202022-11-15 18:25:49 -0500116 isVertical,
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500117 Icon,
118 expandMenuOptions = undefined,
119 IconButtonComp = IconButton,
120 ...props
121}: ExpandableButtonProps) => {
simon33c06182022-11-02 17:39:31 -0400122 const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
123 const handleClose = () => {
124 setAnchorEl(null);
125 };
126
simon33c06182022-11-02 17:39:31 -0400127 return (
simon9a8fe202022-11-15 18:25:49 -0500128 <>
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500129 {expandMenuOptions && (
130 <Menu
131 anchorEl={anchorEl}
132 open={!!anchorEl}
133 onClose={handleClose}
134 anchorOrigin={{
simon9a8fe202022-11-15 18:25:49 -0500135 vertical: !isVertical ? 'top' : 'center',
136 horizontal: !isVertical ? 'center' : 'left',
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500137 }}
138 transformOrigin={{
simon9a8fe202022-11-15 18:25:49 -0500139 vertical: !isVertical ? 'bottom' : 'center',
140 horizontal: !isVertical ? 'center' : 'right',
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500141 }}
142 >
143 {expandMenuOptions?.map((option, id) => {
144 if ('options' in option) {
145 const { options, defaultSelectedOption } = option;
146 return (
147 <RadioGroup key={id} defaultValue={defaultSelectedOption}>
148 {options.map(({ description, key }) => {
149 return (
150 <MenuItem key={key}>
151 <ListItemIcon>
152 <Radio value={key} />
153 </ListItemIcon>
154 <ListItemText>{description}</ListItemText>
155 </MenuItem>
156 );
157 })}
158 </RadioGroup>
159 );
160 }
simon33c06182022-11-02 17:39:31 -0400161
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500162 return (
163 <MenuItem key={id} onClick={handleClose}>
164 <ListItemIcon>{option.icon}</ListItemIcon>
165 <ListItemText>{option.description}</ListItemText>
166 </MenuItem>
167 );
168 })}
169 </Menu>
170 )}
simon9a8fe202022-11-15 18:25:49 -0500171 <Box position="relative" display="flex" justifyContent="center" alignItems="center">
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500172 {expandMenuOptions && (
simon33c06182022-11-02 17:39:31 -0400173 <IconButton
simon33c06182022-11-02 17:39:31 -0400174 aria-label="expand options"
simon9a8fe202022-11-15 18:25:49 -0500175 onClick={(e) => setAnchorEl(e.currentTarget)}
simon33c06182022-11-02 17:39:31 -0400176 sx={{
simon9a8fe202022-11-15 18:25:49 -0500177 rotate: !isVertical ? '' : '-90deg',
simon33c06182022-11-02 17:39:31 -0400178 position: 'absolute',
simon9a8fe202022-11-15 18:25:49 -0500179 top: !isVertical ? '-55%' : 'auto',
180 left: !isVertical ? 'auto' : '-55%',
181 zIndex: 1,
simon33c06182022-11-02 17:39:31 -0400182 }}
simon9a8fe202022-11-15 18:25:49 -0500183 className={props.className}
simon33c06182022-11-02 17:39:31 -0400184 >
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500185 <ExpandLessIcon
simon9a8fe202022-11-15 18:25:49 -0500186 fontSize="small"
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500187 sx={{
188 backgroundColor: '#444444',
189 borderRadius: '5px',
190 }}
191 />
simon33c06182022-11-02 17:39:31 -0400192 </IconButton>
193 )}
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500194 <IconButtonComp {...props}>{Icon && <Icon />}</IconButtonComp>
simon33c06182022-11-02 17:39:31 -0400195 </Box>
simon9a8fe202022-11-15 18:25:49 -0500196 </>
simon33c06182022-11-02 17:39:31 -0400197 );
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500198};
simon33c06182022-11-02 17:39:31 -0400199
simon35378692022-10-02 23:25:57 -0400200export const CancelPictureButton = (props: IconButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400201 return <RoundButton {...props} aria-label="remove picture" Icon={CancelIcon} size="large" />;
202};
idillon-sfl44b05342022-08-24 15:46:42 -0400203
simon35378692022-10-02 23:25:57 -0400204export const EditPictureButton = (props: IconButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400205 return <RoundButton {...props} aria-label="edit picture" Icon={PenIcon} size="large" />;
206};
idillon-sfl44b05342022-08-24 15:46:42 -0400207
simon35378692022-10-02 23:25:57 -0400208export const UploadPictureButton = (props: IconButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400209 return <RoundButton {...props} aria-label="upload picture" Icon={FolderIcon} size="large" />;
210};
idillon-sfl44b05342022-08-24 15:46:42 -0400211
simon35378692022-10-02 23:25:57 -0400212export const TakePictureButton = (props: IconButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400213 return <RoundButton {...props} aria-label="take picture" Icon={CameraIcon} size="large" />;
214};
idillon-sfl37c18df2022-08-26 18:44:27 -0400215
Michelle Sepkap Simef5ebc2e2022-10-27 18:30:53 -0400216type InfoButtonProps = IconButtonProps & {
217 tooltipTitle: string;
218};
219export const InfoButton = ({ tooltipTitle, ...props }: InfoButtonProps) => {
220 return (
221 <CustomTooltip className="tooltip" title={tooltipTitle}>
222 <RoundButton {...props} aria-label="informations" Icon={InfoIcon} size="small" />
223 </CustomTooltip>
224 );
simond47ef9e2022-09-28 22:24:28 -0400225};
idillon-sfl37c18df2022-08-26 18:44:27 -0400226
simon35378692022-10-02 23:25:57 -0400227export const TipButton = (props: IconButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400228 return <RoundButton {...props} aria-label="tip" Icon={QuestionMark} size="medium" />;
229};
idillon-sfl37c18df2022-08-26 18:44:27 -0400230
simon35378692022-10-02 23:25:57 -0400231export const MoreButton = styled((props: IconButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400232 return (
233 <IconButton {...props} disableRipple={true} aria-label="more">
234 <CrossIcon fontSize="inherit" />
235 </IconButton>
236 );
237})(({ theme }) => ({
238 border: `1px solid ${theme.palette.primary.dark}`,
239 color: theme.palette.primary.dark,
240 fontSize: '10px',
241 height: '20px',
242 width: '20px',
243 '&:hover': {
244 background: theme.palette.primary.light,
245 },
246 '&:active': {
247 color: '#FFF',
248 background: theme.palette.primary.dark,
249 },
250}));
idillon927b7592022-09-15 12:56:45 -0400251
simon35378692022-10-02 23:25:57 -0400252export const BackButton = styled((props: IconButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400253 return (
254 <IconButton {...props} disableRipple={true} aria-label="back">
255 <ArrowIcon fontSize="inherit" />
256 </IconButton>
257 );
258})(({ theme }) => ({
259 color: theme.palette.primary.dark,
260 fontSize: '15px',
261 height: '30px',
262 width: '51px',
263 borderRadius: '5px',
264 '&:hover': {
265 background: theme.palette.primary.light,
266 },
267}));
idillonb3788bf2022-08-29 15:57:57 -0400268
simon1170c322022-10-31 14:51:31 -0400269export type ToggleIconButtonProps = IconButtonProps & {
270 selected: boolean;
271 toggle: () => void;
272 IconOn?: ComponentType<SvgIconProps>;
273 IconOff?: ComponentType<SvgIconProps>;
Gabriel Rochone3ec0d22022-10-08 14:27:03 -0400274};
simon33c06182022-11-02 17:39:31 -0400275
simon1170c322022-10-31 14:51:31 -0400276export const ToggleIconButton = ({
277 IconOn = RadioButtonChecked,
278 IconOff = RadioButtonUnchecked,
279 selected,
280 toggle,
281 ...props
282}: ToggleIconButtonProps) => {
Gabriel Rochone3ec0d22022-10-08 14:27:03 -0400283 return (
simon1170c322022-10-31 14:51:31 -0400284 <IconButton
285 {...props}
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500286 sx={{
287 color: selected ? 'white' : 'red',
288 ...props.sx,
289 }}
simon1170c322022-10-31 14:51:31 -0400290 onClick={() => {
291 toggle();
292 }}
293 >
294 {selected ? <IconOn /> : <IconOff />}
Gabriel Rochone3ec0d22022-10-08 14:27:03 -0400295 </IconButton>
296 );
297};
298
simon35378692022-10-02 23:25:57 -0400299export const CloseButton = styled((props: IconButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400300 return (
301 <IconButton {...props} disableRipple={true} aria-label="close">
302 <SaltireIcon fontSize="inherit" />
303 </IconButton>
304 );
305})(({ theme }) => ({
306 color: theme.palette.primary.dark,
307 fontSize: '15px',
308 height: '30px',
309 width: '30px',
310 borderRadius: '5px',
311 '&:hover': {
312 background: theme.palette.primary.light,
313 },
314}));
idillonb3788bf2022-08-29 15:57:57 -0400315
simon35378692022-10-02 23:25:57 -0400316type ToggleVisibilityButtonProps = IconButtonProps & {
317 visible: boolean;
318};
319export const ToggleVisibilityButton = styled(({ visible, ...props }: ToggleVisibilityButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400320 const Icon = visible ? CrossedEyeIcon : EyeIcon;
321 return (
322 <IconButton {...props} disableRipple={true}>
323 <Icon fontSize="inherit" />
324 </IconButton>
325 );
326})(({ theme }) => ({
327 color: theme.palette.primary.dark,
328 fontSize: '15px',
329 height: '15px',
330 width: '15px',
331 '&:hover': {
332 background: theme.palette.primary.light,
333 },
334}));
idillonaedab942022-09-01 14:29:43 -0400335
simon35378692022-10-02 23:25:57 -0400336const SquareButton = styled(({ Icon, ...props }: ShapedButtonProps) => (
simond47ef9e2022-09-28 22:24:28 -0400337 <IconButton {...props} disableRipple={true}>
338 <Icon fontSize="inherit" />
339 </IconButton>
simon416d0792022-11-03 02:46:18 -0400340))(() => ({
simond47ef9e2022-09-28 22:24:28 -0400341 color: '#7E7E7E',
342 fontSize: '25px',
343 height: '36px',
344 width: '36px',
345 borderRadius: '5px',
346 '&:hover': {
347 background: '#E5E5E5',
348 },
idillonaedab942022-09-01 14:29:43 -0400349}));
350
idillonae655dd2022-10-14 18:11:02 -0400351export const AddParticipantButton = (props: IconButtonProps) => {
352 return <SquareButton {...props} aria-label="add participant" Icon={PeopleWithPlusSignIcon} />;
353};
354
simon35378692022-10-02 23:25:57 -0400355export const RecordVideoMessageButton = (props: IconButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400356 return <SquareButton {...props} aria-label="record video message" Icon={CameraInBubbleIcon} />;
357};
idillonaedab942022-09-01 14:29:43 -0400358
simon35378692022-10-02 23:25:57 -0400359export const RecordVoiceMessageButton = (props: IconButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400360 return <SquareButton {...props} aria-label="record voice message" Icon={MicroInBubbleIcon} />;
361};
idillonaedab942022-09-01 14:29:43 -0400362
idillonae655dd2022-10-14 18:11:02 -0400363export const ShowOptionsMenuButton = (props: IconButtonProps) => {
364 return <SquareButton {...props} aria-label="show options menu" Icon={ListIcon} />;
365};
366
367export const StartVideoCallButton = (props: IconButtonProps) => {
simoncd698c52022-11-08 19:21:38 -0500368 return <SquareButton {...props} aria-label="start audio call" Icon={VideoCallIcon} />;
idillonae655dd2022-10-14 18:11:02 -0400369};
370
371export const StartAudioCallButton = (props: IconButtonProps) => {
simoncd698c52022-11-08 19:21:38 -0500372 return <SquareButton {...props} aria-label="start video call" Icon={AudioCallIcon} />;
idillonae655dd2022-10-14 18:11:02 -0400373};
374
simon35378692022-10-02 23:25:57 -0400375export const UploadFileButton = (props: IconButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400376 return <SquareButton {...props} aria-label="upload file" Icon={PaperClipIcon} />;
377};
idillonaedab942022-09-01 14:29:43 -0400378
simon35378692022-10-02 23:25:57 -0400379export const SendMessageButton = (props: IconButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400380 return <SquareButton {...props} aria-label="send message" Icon={Arrow2Icon} />;
381};
idillonaedab942022-09-01 14:29:43 -0400382
simon35378692022-10-02 23:25:57 -0400383export const ReplyMessageButton = styled((props: IconButtonProps) => (
simond47ef9e2022-09-28 22:24:28 -0400384 <IconButton {...props} disableRipple={true} aria-label="send message">
385 <Arrow3Icon fontSize="inherit" />
386 </IconButton>
387))(({ theme }) => ({
388 color: theme.palette.primary.dark,
389 fontSize: '20px',
390 height: '20px',
391 width: '20px',
392 borderRadius: '5px',
393 '&:hover': {
394 background: '#E5E5E5',
395 },
idillon927b7592022-09-15 12:56:45 -0400396}));
397
simon35378692022-10-02 23:25:57 -0400398type EmojiButtonProps = IconButtonProps & {
399 emoji: string;
400};
401export const EmojiButton = styled(({ emoji, ...props }: EmojiButtonProps) => (
simond47ef9e2022-09-28 22:24:28 -0400402 <IconButton {...props} disableRipple={true}>
403 {emoji}
404 </IconButton>
simon416d0792022-11-03 02:46:18 -0400405))(() => ({
simond47ef9e2022-09-28 22:24:28 -0400406 color: 'white',
407 fontSize: '20px',
408 height: '20px',
409 width: '20px',
idillon927b7592022-09-15 12:56:45 -0400410}));
411
simon35378692022-10-02 23:25:57 -0400412type SelectEmojiButtonProps = {
413 onEmojiSelected: (emoji: string) => void;
414};
simon416d0792022-11-03 02:46:18 -0400415export const SelectEmojiButton = ({ onEmojiSelected }: SelectEmojiButtonProps) => {
simon35378692022-10-02 23:25:57 -0400416 const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
idillon1664bb22022-09-14 17:18:15 -0400417
simon35378692022-10-02 23:25:57 -0400418 const handleOpenEmojiPicker = useCallback(
419 (e: MouseEvent<HTMLButtonElement>) => setAnchorEl(anchorEl ? null : e.currentTarget),
420 [anchorEl]
421 );
simond47ef9e2022-09-28 22:24:28 -0400422
423 const handleClose = useCallback(() => setAnchorEl(null), [setAnchorEl]);
424
425 const onEmojiClick = useCallback(
simon35378692022-10-02 23:25:57 -0400426 (e: MouseEvent, emojiObject: IEmojiData) => {
simon80b7b3b2022-09-28 17:50:10 -0400427 onEmojiSelected(emojiObject.emoji);
simond47ef9e2022-09-28 22:24:28 -0400428 handleClose();
429 },
simon80b7b3b2022-09-28 17:50:10 -0400430 [handleClose, onEmojiSelected]
simond47ef9e2022-09-28 22:24:28 -0400431 );
432
433 const open = Boolean(anchorEl);
434 const id = open ? 'simple-popover' : undefined;
435
436 return (
437 <ClickAwayListener onClickAway={handleClose}>
438 <Box>
idillonae655dd2022-10-14 18:11:02 -0400439 <SquareButton
440 aria-describedby={id}
441 aria-label="select emoji"
442 Icon={EmojiIcon}
443 onClick={handleOpenEmojiPicker}
444 />
simon35378692022-10-02 23:25:57 -0400445 <Popper id={id} open={open} anchorEl={anchorEl}>
446 <EmojiPicker onEmojiClick={onEmojiClick} disableAutoFocus={true} disableSkinTonePicker={true} native />
simond47ef9e2022-09-28 22:24:28 -0400447 </Popper>
448 </Box>
449 </ClickAwayListener>
450 );
451};