blob: ddd99e90ec25d060b91fe99265f0008893955bc8 [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';
simondaae9102022-12-02 16:51:31 -050037import { 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;
simon571240f2022-11-29 23:59:27 -0500111 expandMenuOnClick?: boolean;
simon33c06182022-11-02 17:39:31 -0400112 Icon?: ComponentType<SvgIconProps>;
113 expandMenuOptions?: (ExpandMenuOption | ExpandMenuRadioOption)[];
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500114 IconButtonComp?: ComponentType<IconButtonProps>;
simon33c06182022-11-02 17:39:31 -0400115};
116
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500117export const ExpandableButton = ({
simon9a8fe202022-11-15 18:25:49 -0500118 isVertical,
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500119 Icon,
simon571240f2022-11-29 23:59:27 -0500120 expandMenuOnClick,
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500121 expandMenuOptions = undefined,
122 IconButtonComp = IconButton,
123 ...props
124}: ExpandableButtonProps) => {
simon33c06182022-11-02 17:39:31 -0400125 const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
126 const handleClose = () => {
127 setAnchorEl(null);
128 };
129
simon33c06182022-11-02 17:39:31 -0400130 return (
simon9a8fe202022-11-15 18:25:49 -0500131 <>
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500132 {expandMenuOptions && (
133 <Menu
134 anchorEl={anchorEl}
135 open={!!anchorEl}
136 onClose={handleClose}
137 anchorOrigin={{
simon9a8fe202022-11-15 18:25:49 -0500138 vertical: !isVertical ? 'top' : 'center',
139 horizontal: !isVertical ? 'center' : 'left',
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500140 }}
141 transformOrigin={{
simon9a8fe202022-11-15 18:25:49 -0500142 vertical: !isVertical ? 'bottom' : 'center',
143 horizontal: !isVertical ? 'center' : 'right',
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500144 }}
145 >
146 {expandMenuOptions?.map((option, id) => {
147 if ('options' in option) {
simon492e8402022-11-29 16:48:37 -0500148 const { options, ...radioGroupProps } = option;
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500149 return (
simon492e8402022-11-29 16:48:37 -0500150 <RadioGroup key={id} {...radioGroupProps}>
151 {options.map(({ description, key }, i) => (
152 <MenuItem key={i}>
153 <FormControlLabel
154 value={key}
155 control={<Radio value={key} />}
156 label={<ListItemText>{description}</ListItemText>}
157 sx={{
158 width: '100%',
159 }}
160 />
161 </MenuItem>
162 ))}
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500163 </RadioGroup>
164 );
165 }
simon33c06182022-11-02 17:39:31 -0400166
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500167 return (
168 <MenuItem key={id} onClick={handleClose}>
169 <ListItemIcon>{option.icon}</ListItemIcon>
170 <ListItemText>{option.description}</ListItemText>
171 </MenuItem>
172 );
173 })}
174 </Menu>
175 )}
simon9a8fe202022-11-15 18:25:49 -0500176 <Box position="relative" display="flex" justifyContent="center" alignItems="center">
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500177 {expandMenuOptions && (
simon33c06182022-11-02 17:39:31 -0400178 <IconButton
simon33c06182022-11-02 17:39:31 -0400179 aria-label="expand options"
simon9a8fe202022-11-15 18:25:49 -0500180 onClick={(e) => setAnchorEl(e.currentTarget)}
simon33c06182022-11-02 17:39:31 -0400181 sx={{
simon9a8fe202022-11-15 18:25:49 -0500182 rotate: !isVertical ? '' : '-90deg',
simon33c06182022-11-02 17:39:31 -0400183 position: 'absolute',
simon9a8fe202022-11-15 18:25:49 -0500184 top: !isVertical ? '-55%' : 'auto',
185 left: !isVertical ? 'auto' : '-55%',
186 zIndex: 1,
simon33c06182022-11-02 17:39:31 -0400187 }}
simon9a8fe202022-11-15 18:25:49 -0500188 className={props.className}
simon33c06182022-11-02 17:39:31 -0400189 >
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500190 <ExpandLessIcon
simon9a8fe202022-11-15 18:25:49 -0500191 fontSize="small"
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500192 sx={{
193 backgroundColor: '#444444',
194 borderRadius: '5px',
195 }}
196 />
simon33c06182022-11-02 17:39:31 -0400197 </IconButton>
198 )}
simon571240f2022-11-29 23:59:27 -0500199 <IconButtonComp
200 onClick={
201 expandMenuOnClick
202 ? (event) => {
203 setAnchorEl(event.currentTarget);
204 }
205 : undefined
206 }
207 {...props}
208 >
209 {Icon && <Icon />}
210 </IconButtonComp>
simon33c06182022-11-02 17:39:31 -0400211 </Box>
simon9a8fe202022-11-15 18:25:49 -0500212 </>
simon33c06182022-11-02 17:39:31 -0400213 );
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500214};
simon33c06182022-11-02 17:39:31 -0400215
simon35378692022-10-02 23:25:57 -0400216export const CancelPictureButton = (props: IconButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400217 return <RoundButton {...props} aria-label="remove picture" Icon={CancelIcon} size="large" />;
218};
idillon-sfl44b05342022-08-24 15:46:42 -0400219
simon35378692022-10-02 23:25:57 -0400220export const EditPictureButton = (props: IconButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400221 return <RoundButton {...props} aria-label="edit picture" Icon={PenIcon} size="large" />;
222};
idillon-sfl44b05342022-08-24 15:46:42 -0400223
simon35378692022-10-02 23:25:57 -0400224export const UploadPictureButton = (props: IconButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400225 return <RoundButton {...props} aria-label="upload picture" Icon={FolderIcon} size="large" />;
226};
idillon-sfl44b05342022-08-24 15:46:42 -0400227
simon35378692022-10-02 23:25:57 -0400228export const TakePictureButton = (props: IconButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400229 return <RoundButton {...props} aria-label="take picture" Icon={CameraIcon} size="large" />;
230};
idillon-sfl37c18df2022-08-26 18:44:27 -0400231
Michelle Sepkap Simef5ebc2e2022-10-27 18:30:53 -0400232type InfoButtonProps = IconButtonProps & {
233 tooltipTitle: string;
234};
235export const InfoButton = ({ tooltipTitle, ...props }: InfoButtonProps) => {
236 return (
237 <CustomTooltip className="tooltip" title={tooltipTitle}>
238 <RoundButton {...props} aria-label="informations" Icon={InfoIcon} size="small" />
239 </CustomTooltip>
240 );
simond47ef9e2022-09-28 22:24:28 -0400241};
idillon-sfl37c18df2022-08-26 18:44:27 -0400242
simon35378692022-10-02 23:25:57 -0400243export const TipButton = (props: IconButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400244 return <RoundButton {...props} aria-label="tip" Icon={QuestionMark} size="medium" />;
245};
idillon-sfl37c18df2022-08-26 18:44:27 -0400246
simon35378692022-10-02 23:25:57 -0400247export const MoreButton = styled((props: IconButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400248 return (
249 <IconButton {...props} disableRipple={true} aria-label="more">
250 <CrossIcon fontSize="inherit" />
251 </IconButton>
252 );
253})(({ theme }) => ({
254 border: `1px solid ${theme.palette.primary.dark}`,
255 color: theme.palette.primary.dark,
256 fontSize: '10px',
257 height: '20px',
258 width: '20px',
259 '&:hover': {
260 background: theme.palette.primary.light,
261 },
262 '&:active': {
263 color: '#FFF',
264 background: theme.palette.primary.dark,
265 },
266}));
idillon927b7592022-09-15 12:56:45 -0400267
simon35378692022-10-02 23:25:57 -0400268export const BackButton = styled((props: IconButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400269 return (
270 <IconButton {...props} disableRipple={true} aria-label="back">
271 <ArrowIcon fontSize="inherit" />
272 </IconButton>
273 );
274})(({ theme }) => ({
275 color: theme.palette.primary.dark,
276 fontSize: '15px',
277 height: '30px',
278 width: '51px',
279 borderRadius: '5px',
280 '&:hover': {
281 background: theme.palette.primary.light,
282 },
283}));
idillonb3788bf2022-08-29 15:57:57 -0400284
simon1170c322022-10-31 14:51:31 -0400285export type ToggleIconButtonProps = IconButtonProps & {
286 selected: boolean;
287 toggle: () => void;
288 IconOn?: ComponentType<SvgIconProps>;
289 IconOff?: ComponentType<SvgIconProps>;
Gabriel Rochone3ec0d22022-10-08 14:27:03 -0400290};
simon33c06182022-11-02 17:39:31 -0400291
simon1170c322022-10-31 14:51:31 -0400292export const ToggleIconButton = ({
293 IconOn = RadioButtonChecked,
294 IconOff = RadioButtonUnchecked,
295 selected,
296 toggle,
297 ...props
298}: ToggleIconButtonProps) => {
Gabriel Rochone3ec0d22022-10-08 14:27:03 -0400299 return (
simon1170c322022-10-31 14:51:31 -0400300 <IconButton
301 {...props}
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500302 sx={{
303 color: selected ? 'white' : 'red',
304 ...props.sx,
305 }}
simon1170c322022-10-31 14:51:31 -0400306 onClick={() => {
307 toggle();
308 }}
309 >
310 {selected ? <IconOn /> : <IconOff />}
Gabriel Rochone3ec0d22022-10-08 14:27:03 -0400311 </IconButton>
312 );
313};
314
simon35378692022-10-02 23:25:57 -0400315export const CloseButton = styled((props: IconButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400316 return (
317 <IconButton {...props} disableRipple={true} aria-label="close">
318 <SaltireIcon fontSize="inherit" />
319 </IconButton>
320 );
321})(({ theme }) => ({
322 color: theme.palette.primary.dark,
323 fontSize: '15px',
324 height: '30px',
325 width: '30px',
326 borderRadius: '5px',
327 '&:hover': {
328 background: theme.palette.primary.light,
329 },
330}));
idillonb3788bf2022-08-29 15:57:57 -0400331
simon35378692022-10-02 23:25:57 -0400332type ToggleVisibilityButtonProps = IconButtonProps & {
333 visible: boolean;
334};
335export const ToggleVisibilityButton = styled(({ visible, ...props }: ToggleVisibilityButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400336 const Icon = visible ? CrossedEyeIcon : EyeIcon;
337 return (
338 <IconButton {...props} disableRipple={true}>
339 <Icon fontSize="inherit" />
340 </IconButton>
341 );
342})(({ theme }) => ({
343 color: theme.palette.primary.dark,
344 fontSize: '15px',
345 height: '15px',
346 width: '15px',
347 '&:hover': {
348 background: theme.palette.primary.light,
349 },
350}));
idillonaedab942022-09-01 14:29:43 -0400351
simon35378692022-10-02 23:25:57 -0400352const SquareButton = styled(({ Icon, ...props }: ShapedButtonProps) => (
simond47ef9e2022-09-28 22:24:28 -0400353 <IconButton {...props} disableRipple={true}>
354 <Icon fontSize="inherit" />
355 </IconButton>
simon416d0792022-11-03 02:46:18 -0400356))(() => ({
simond47ef9e2022-09-28 22:24:28 -0400357 color: '#7E7E7E',
358 fontSize: '25px',
359 height: '36px',
360 width: '36px',
361 borderRadius: '5px',
362 '&:hover': {
363 background: '#E5E5E5',
364 },
idillonaedab942022-09-01 14:29:43 -0400365}));
366
idillonae655dd2022-10-14 18:11:02 -0400367export const AddParticipantButton = (props: IconButtonProps) => {
368 return <SquareButton {...props} aria-label="add participant" Icon={PeopleWithPlusSignIcon} />;
369};
370
simon35378692022-10-02 23:25:57 -0400371export const RecordVideoMessageButton = (props: IconButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400372 return <SquareButton {...props} aria-label="record video message" Icon={CameraInBubbleIcon} />;
373};
idillonaedab942022-09-01 14:29:43 -0400374
simon35378692022-10-02 23:25:57 -0400375export const RecordVoiceMessageButton = (props: IconButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400376 return <SquareButton {...props} aria-label="record voice message" Icon={MicroInBubbleIcon} />;
377};
idillonaedab942022-09-01 14:29:43 -0400378
idillonae655dd2022-10-14 18:11:02 -0400379export const ShowOptionsMenuButton = (props: IconButtonProps) => {
380 return <SquareButton {...props} aria-label="show options menu" Icon={ListIcon} />;
381};
382
383export const StartVideoCallButton = (props: IconButtonProps) => {
simoncd698c52022-11-08 19:21:38 -0500384 return <SquareButton {...props} aria-label="start audio call" Icon={VideoCallIcon} />;
idillonae655dd2022-10-14 18:11:02 -0400385};
386
387export const StartAudioCallButton = (props: IconButtonProps) => {
simoncd698c52022-11-08 19:21:38 -0500388 return <SquareButton {...props} aria-label="start video call" Icon={AudioCallIcon} />;
idillonae655dd2022-10-14 18:11:02 -0400389};
390
simon35378692022-10-02 23:25:57 -0400391export const UploadFileButton = (props: IconButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400392 return <SquareButton {...props} aria-label="upload file" Icon={PaperClipIcon} />;
393};
idillonaedab942022-09-01 14:29:43 -0400394
simon35378692022-10-02 23:25:57 -0400395export const SendMessageButton = (props: IconButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400396 return <SquareButton {...props} aria-label="send message" Icon={Arrow2Icon} />;
397};
idillonaedab942022-09-01 14:29:43 -0400398
simon35378692022-10-02 23:25:57 -0400399export const ReplyMessageButton = styled((props: IconButtonProps) => (
simond47ef9e2022-09-28 22:24:28 -0400400 <IconButton {...props} disableRipple={true} aria-label="send message">
401 <Arrow3Icon fontSize="inherit" />
402 </IconButton>
403))(({ theme }) => ({
404 color: theme.palette.primary.dark,
405 fontSize: '20px',
406 height: '20px',
407 width: '20px',
408 borderRadius: '5px',
409 '&:hover': {
410 background: '#E5E5E5',
411 },
idillon927b7592022-09-15 12:56:45 -0400412}));
413
simon35378692022-10-02 23:25:57 -0400414type EmojiButtonProps = IconButtonProps & {
415 emoji: string;
416};
417export const EmojiButton = styled(({ emoji, ...props }: EmojiButtonProps) => (
simond47ef9e2022-09-28 22:24:28 -0400418 <IconButton {...props} disableRipple={true}>
419 {emoji}
420 </IconButton>
simon416d0792022-11-03 02:46:18 -0400421))(() => ({
simond47ef9e2022-09-28 22:24:28 -0400422 color: 'white',
423 fontSize: '20px',
424 height: '20px',
425 width: '20px',
idillon927b7592022-09-15 12:56:45 -0400426}));
427
simon35378692022-10-02 23:25:57 -0400428type SelectEmojiButtonProps = {
429 onEmojiSelected: (emoji: string) => void;
430};
simon416d0792022-11-03 02:46:18 -0400431export const SelectEmojiButton = ({ onEmojiSelected }: SelectEmojiButtonProps) => {
simon35378692022-10-02 23:25:57 -0400432 const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
idillon1664bb22022-09-14 17:18:15 -0400433
simon35378692022-10-02 23:25:57 -0400434 const handleOpenEmojiPicker = useCallback(
435 (e: MouseEvent<HTMLButtonElement>) => setAnchorEl(anchorEl ? null : e.currentTarget),
436 [anchorEl]
437 );
simond47ef9e2022-09-28 22:24:28 -0400438
439 const handleClose = useCallback(() => setAnchorEl(null), [setAnchorEl]);
440
441 const onEmojiClick = useCallback(
simon35378692022-10-02 23:25:57 -0400442 (e: MouseEvent, emojiObject: IEmojiData) => {
simon80b7b3b2022-09-28 17:50:10 -0400443 onEmojiSelected(emojiObject.emoji);
simond47ef9e2022-09-28 22:24:28 -0400444 handleClose();
445 },
simon80b7b3b2022-09-28 17:50:10 -0400446 [handleClose, onEmojiSelected]
simond47ef9e2022-09-28 22:24:28 -0400447 );
448
449 const open = Boolean(anchorEl);
450 const id = open ? 'simple-popover' : undefined;
451
452 return (
453 <ClickAwayListener onClickAway={handleClose}>
454 <Box>
idillonae655dd2022-10-14 18:11:02 -0400455 <SquareButton
456 aria-describedby={id}
457 aria-label="select emoji"
458 Icon={EmojiIcon}
459 onClick={handleOpenEmojiPicker}
460 />
simon35378692022-10-02 23:25:57 -0400461 <Popper id={id} open={open} anchorEl={anchorEl}>
462 <EmojiPicker onEmojiClick={onEmojiClick} disableAutoFocus={true} disableSkinTonePicker={true} native />
simond47ef9e2022-09-28 22:24:28 -0400463 </Popper>
464 </Box>
465 </ClickAwayListener>
466 );
467};