blob: 268cab6cb5a0144bd3c371c309b50b1e8eb7fe7b [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 & {
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500109 hidden?: 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 = ({
116 hidden,
117 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 (
128 <Box>
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500129 {expandMenuOptions && (
130 <Menu
131 anchorEl={anchorEl}
132 open={!!anchorEl}
133 onClose={handleClose}
134 anchorOrigin={{
135 vertical: !hidden ? 'top' : 'center',
136 horizontal: !hidden ? 'center' : 'left',
137 }}
138 transformOrigin={{
139 vertical: !hidden ? 'bottom' : 'center',
140 horizontal: !hidden ? 'center' : 'right',
141 }}
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 )}
171 <Box
172 position="relative"
173 display="flex"
174 justifyContent="center"
175 alignItems="center"
176 onClick={(e) => setAnchorEl(e.currentTarget)}
177 >
178 {expandMenuOptions && (
simon33c06182022-11-02 17:39:31 -0400179 <IconButton
180 {...props}
simon33c06182022-11-02 17:39:31 -0400181 aria-label="expand options"
182 size="small"
183 sx={{
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500184 transform: !hidden ? 'scale(0.5)' : 'scale(0.5) rotate(-90deg)',
simon33c06182022-11-02 17:39:31 -0400185 position: 'absolute',
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500186 top: !hidden ? '-50%' : 0,
187 left: hidden ? '-50%' : 0,
188 width: '100%',
189 height: '100%',
simon33c06182022-11-02 17:39:31 -0400190 }}
191 >
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500192 <ExpandLessIcon
193 sx={{
194 backgroundColor: '#444444',
195 borderRadius: '5px',
196 }}
197 />
simon33c06182022-11-02 17:39:31 -0400198 </IconButton>
199 )}
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500200 <IconButtonComp {...props}>{Icon && <Icon />}</IconButtonComp>
simon33c06182022-11-02 17:39:31 -0400201 </Box>
202 </Box>
203 );
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500204};
simon33c06182022-11-02 17:39:31 -0400205
simon35378692022-10-02 23:25:57 -0400206export const CancelPictureButton = (props: IconButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400207 return <RoundButton {...props} aria-label="remove picture" Icon={CancelIcon} size="large" />;
208};
idillon-sfl44b05342022-08-24 15:46:42 -0400209
simon35378692022-10-02 23:25:57 -0400210export const EditPictureButton = (props: IconButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400211 return <RoundButton {...props} aria-label="edit picture" Icon={PenIcon} size="large" />;
212};
idillon-sfl44b05342022-08-24 15:46:42 -0400213
simon35378692022-10-02 23:25:57 -0400214export const UploadPictureButton = (props: IconButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400215 return <RoundButton {...props} aria-label="upload picture" Icon={FolderIcon} size="large" />;
216};
idillon-sfl44b05342022-08-24 15:46:42 -0400217
simon35378692022-10-02 23:25:57 -0400218export const TakePictureButton = (props: IconButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400219 return <RoundButton {...props} aria-label="take picture" Icon={CameraIcon} size="large" />;
220};
idillon-sfl37c18df2022-08-26 18:44:27 -0400221
Michelle Sepkap Simef5ebc2e2022-10-27 18:30:53 -0400222type InfoButtonProps = IconButtonProps & {
223 tooltipTitle: string;
224};
225export const InfoButton = ({ tooltipTitle, ...props }: InfoButtonProps) => {
226 return (
227 <CustomTooltip className="tooltip" title={tooltipTitle}>
228 <RoundButton {...props} aria-label="informations" Icon={InfoIcon} size="small" />
229 </CustomTooltip>
230 );
simond47ef9e2022-09-28 22:24:28 -0400231};
idillon-sfl37c18df2022-08-26 18:44:27 -0400232
simon35378692022-10-02 23:25:57 -0400233export const TipButton = (props: IconButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400234 return <RoundButton {...props} aria-label="tip" Icon={QuestionMark} size="medium" />;
235};
idillon-sfl37c18df2022-08-26 18:44:27 -0400236
simon35378692022-10-02 23:25:57 -0400237export const MoreButton = styled((props: IconButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400238 return (
239 <IconButton {...props} disableRipple={true} aria-label="more">
240 <CrossIcon fontSize="inherit" />
241 </IconButton>
242 );
243})(({ theme }) => ({
244 border: `1px solid ${theme.palette.primary.dark}`,
245 color: theme.palette.primary.dark,
246 fontSize: '10px',
247 height: '20px',
248 width: '20px',
249 '&:hover': {
250 background: theme.palette.primary.light,
251 },
252 '&:active': {
253 color: '#FFF',
254 background: theme.palette.primary.dark,
255 },
256}));
idillon927b7592022-09-15 12:56:45 -0400257
simon35378692022-10-02 23:25:57 -0400258export const BackButton = styled((props: IconButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400259 return (
260 <IconButton {...props} disableRipple={true} aria-label="back">
261 <ArrowIcon fontSize="inherit" />
262 </IconButton>
263 );
264})(({ theme }) => ({
265 color: theme.palette.primary.dark,
266 fontSize: '15px',
267 height: '30px',
268 width: '51px',
269 borderRadius: '5px',
270 '&:hover': {
271 background: theme.palette.primary.light,
272 },
273}));
idillonb3788bf2022-08-29 15:57:57 -0400274
simon1170c322022-10-31 14:51:31 -0400275export type ToggleIconButtonProps = IconButtonProps & {
276 selected: boolean;
277 toggle: () => void;
278 IconOn?: ComponentType<SvgIconProps>;
279 IconOff?: ComponentType<SvgIconProps>;
Gabriel Rochone3ec0d22022-10-08 14:27:03 -0400280};
simon33c06182022-11-02 17:39:31 -0400281
simon1170c322022-10-31 14:51:31 -0400282export const ToggleIconButton = ({
283 IconOn = RadioButtonChecked,
284 IconOff = RadioButtonUnchecked,
285 selected,
286 toggle,
287 ...props
288}: ToggleIconButtonProps) => {
Gabriel Rochone3ec0d22022-10-08 14:27:03 -0400289 return (
simon1170c322022-10-31 14:51:31 -0400290 <IconButton
291 {...props}
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500292 sx={{
293 color: selected ? 'white' : 'red',
294 ...props.sx,
295 }}
simon1170c322022-10-31 14:51:31 -0400296 onClick={() => {
297 toggle();
298 }}
299 >
300 {selected ? <IconOn /> : <IconOff />}
Gabriel Rochone3ec0d22022-10-08 14:27:03 -0400301 </IconButton>
302 );
303};
304
simon35378692022-10-02 23:25:57 -0400305export const CloseButton = styled((props: IconButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400306 return (
307 <IconButton {...props} disableRipple={true} aria-label="close">
308 <SaltireIcon fontSize="inherit" />
309 </IconButton>
310 );
311})(({ theme }) => ({
312 color: theme.palette.primary.dark,
313 fontSize: '15px',
314 height: '30px',
315 width: '30px',
316 borderRadius: '5px',
317 '&:hover': {
318 background: theme.palette.primary.light,
319 },
320}));
idillonb3788bf2022-08-29 15:57:57 -0400321
simon35378692022-10-02 23:25:57 -0400322type ToggleVisibilityButtonProps = IconButtonProps & {
323 visible: boolean;
324};
325export const ToggleVisibilityButton = styled(({ visible, ...props }: ToggleVisibilityButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400326 const Icon = visible ? CrossedEyeIcon : EyeIcon;
327 return (
328 <IconButton {...props} disableRipple={true}>
329 <Icon fontSize="inherit" />
330 </IconButton>
331 );
332})(({ theme }) => ({
333 color: theme.palette.primary.dark,
334 fontSize: '15px',
335 height: '15px',
336 width: '15px',
337 '&:hover': {
338 background: theme.palette.primary.light,
339 },
340}));
idillonaedab942022-09-01 14:29:43 -0400341
simon35378692022-10-02 23:25:57 -0400342const SquareButton = styled(({ Icon, ...props }: ShapedButtonProps) => (
simond47ef9e2022-09-28 22:24:28 -0400343 <IconButton {...props} disableRipple={true}>
344 <Icon fontSize="inherit" />
345 </IconButton>
simon416d0792022-11-03 02:46:18 -0400346))(() => ({
simond47ef9e2022-09-28 22:24:28 -0400347 color: '#7E7E7E',
348 fontSize: '25px',
349 height: '36px',
350 width: '36px',
351 borderRadius: '5px',
352 '&:hover': {
353 background: '#E5E5E5',
354 },
idillonaedab942022-09-01 14:29:43 -0400355}));
356
idillonae655dd2022-10-14 18:11:02 -0400357export const AddParticipantButton = (props: IconButtonProps) => {
358 return <SquareButton {...props} aria-label="add participant" Icon={PeopleWithPlusSignIcon} />;
359};
360
simon35378692022-10-02 23:25:57 -0400361export const RecordVideoMessageButton = (props: IconButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400362 return <SquareButton {...props} aria-label="record video message" Icon={CameraInBubbleIcon} />;
363};
idillonaedab942022-09-01 14:29:43 -0400364
simon35378692022-10-02 23:25:57 -0400365export const RecordVoiceMessageButton = (props: IconButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400366 return <SquareButton {...props} aria-label="record voice message" Icon={MicroInBubbleIcon} />;
367};
idillonaedab942022-09-01 14:29:43 -0400368
idillonae655dd2022-10-14 18:11:02 -0400369export const ShowOptionsMenuButton = (props: IconButtonProps) => {
370 return <SquareButton {...props} aria-label="show options menu" Icon={ListIcon} />;
371};
372
373export const StartVideoCallButton = (props: IconButtonProps) => {
simoncd698c52022-11-08 19:21:38 -0500374 return <SquareButton {...props} aria-label="start audio call" Icon={VideoCallIcon} />;
idillonae655dd2022-10-14 18:11:02 -0400375};
376
377export const StartAudioCallButton = (props: IconButtonProps) => {
simoncd698c52022-11-08 19:21:38 -0500378 return <SquareButton {...props} aria-label="start video call" Icon={AudioCallIcon} />;
idillonae655dd2022-10-14 18:11:02 -0400379};
380
simon35378692022-10-02 23:25:57 -0400381export const UploadFileButton = (props: IconButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400382 return <SquareButton {...props} aria-label="upload file" Icon={PaperClipIcon} />;
383};
idillonaedab942022-09-01 14:29:43 -0400384
simon35378692022-10-02 23:25:57 -0400385export const SendMessageButton = (props: IconButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400386 return <SquareButton {...props} aria-label="send message" Icon={Arrow2Icon} />;
387};
idillonaedab942022-09-01 14:29:43 -0400388
simon35378692022-10-02 23:25:57 -0400389export const ReplyMessageButton = styled((props: IconButtonProps) => (
simond47ef9e2022-09-28 22:24:28 -0400390 <IconButton {...props} disableRipple={true} aria-label="send message">
391 <Arrow3Icon fontSize="inherit" />
392 </IconButton>
393))(({ theme }) => ({
394 color: theme.palette.primary.dark,
395 fontSize: '20px',
396 height: '20px',
397 width: '20px',
398 borderRadius: '5px',
399 '&:hover': {
400 background: '#E5E5E5',
401 },
idillon927b7592022-09-15 12:56:45 -0400402}));
403
simon35378692022-10-02 23:25:57 -0400404type EmojiButtonProps = IconButtonProps & {
405 emoji: string;
406};
407export const EmojiButton = styled(({ emoji, ...props }: EmojiButtonProps) => (
simond47ef9e2022-09-28 22:24:28 -0400408 <IconButton {...props} disableRipple={true}>
409 {emoji}
410 </IconButton>
simon416d0792022-11-03 02:46:18 -0400411))(() => ({
simond47ef9e2022-09-28 22:24:28 -0400412 color: 'white',
413 fontSize: '20px',
414 height: '20px',
415 width: '20px',
idillon927b7592022-09-15 12:56:45 -0400416}));
417
simon35378692022-10-02 23:25:57 -0400418type SelectEmojiButtonProps = {
419 onEmojiSelected: (emoji: string) => void;
420};
simon416d0792022-11-03 02:46:18 -0400421export const SelectEmojiButton = ({ onEmojiSelected }: SelectEmojiButtonProps) => {
simon35378692022-10-02 23:25:57 -0400422 const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
idillon1664bb22022-09-14 17:18:15 -0400423
simon35378692022-10-02 23:25:57 -0400424 const handleOpenEmojiPicker = useCallback(
425 (e: MouseEvent<HTMLButtonElement>) => setAnchorEl(anchorEl ? null : e.currentTarget),
426 [anchorEl]
427 );
simond47ef9e2022-09-28 22:24:28 -0400428
429 const handleClose = useCallback(() => setAnchorEl(null), [setAnchorEl]);
430
431 const onEmojiClick = useCallback(
simon35378692022-10-02 23:25:57 -0400432 (e: MouseEvent, emojiObject: IEmojiData) => {
simon80b7b3b2022-09-28 17:50:10 -0400433 onEmojiSelected(emojiObject.emoji);
simond47ef9e2022-09-28 22:24:28 -0400434 handleClose();
435 },
simon80b7b3b2022-09-28 17:50:10 -0400436 [handleClose, onEmojiSelected]
simond47ef9e2022-09-28 22:24:28 -0400437 );
438
439 const open = Boolean(anchorEl);
440 const id = open ? 'simple-popover' : undefined;
441
442 return (
443 <ClickAwayListener onClickAway={handleClose}>
444 <Box>
idillonae655dd2022-10-14 18:11:02 -0400445 <SquareButton
446 aria-describedby={id}
447 aria-label="select emoji"
448 Icon={EmojiIcon}
449 onClick={handleOpenEmojiPicker}
450 />
simon35378692022-10-02 23:25:57 -0400451 <Popper id={id} open={open} anchorEl={anchorEl}>
452 <EmojiPicker onEmojiClick={onEmojiClick} disableAutoFocus={true} disableSkinTonePicker={true} native />
simond47ef9e2022-09-28 22:24:28 -0400453 </Popper>
454 </Box>
455 </ClickAwayListener>
456 );
457};