blob: 4937c0b8cabf9202994319a59589070d527b79ab [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 & {
109 Icon?: ComponentType<SvgIconProps>;
110 expandMenuOptions?: (ExpandMenuOption | ExpandMenuRadioOption)[];
111};
112
113export const ExpandableButton = styled(({ Icon, expandMenuOptions = [], ...props }: ExpandableButtonProps) => {
114 const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
115 const handleClose = () => {
116 setAnchorEl(null);
117 };
118
119 const hasExpandMenuOptions = expandMenuOptions.length > 0;
120 return (
121 <Box>
122 <Menu
123 anchorEl={anchorEl}
124 open={!!anchorEl}
125 onClose={handleClose}
126 anchorOrigin={{
127 vertical: 'top',
128 horizontal: 'center',
129 }}
130 transformOrigin={{
131 vertical: 'bottom',
132 horizontal: 'center',
133 }}
134 >
135 {expandMenuOptions.map((option, id) => {
136 if ('options' in option) {
137 const { options, defaultSelectedOption } = option;
138 return (
139 <RadioGroup key={id} defaultValue={defaultSelectedOption}>
140 {options.map(({ description, key }) => {
141 return (
142 <MenuItem key={key}>
143 <ListItemIcon>
144 <Radio value={key} />
145 </ListItemIcon>
146 <ListItemText>{description}</ListItemText>
147 </MenuItem>
148 );
149 })}
150 </RadioGroup>
151 );
152 }
153
154 return (
155 <MenuItem key={id} onClick={handleClose}>
156 <ListItemIcon>{option.icon}</ListItemIcon>
157 <ListItemText>{option.description}</ListItemText>
158 </MenuItem>
159 );
160 })}
161 </Menu>
162 <Box position="relative">
163 {hasExpandMenuOptions && (
164 <IconButton
165 {...props}
166 onClick={(e) => setAnchorEl(e.currentTarget)}
167 aria-label="expand options"
168 size="small"
169 sx={{
170 position: 'absolute',
171 left: 0,
172 right: 0,
173 top: '-50%',
174 color: 'white',
175 }}
176 >
177 <ExpandLessIcon sx={{ backgroundColor: '#555555', borderRadius: '5px' }} />
178 </IconButton>
179 )}
180 <IconButton {...props} disableRipple={true}>
181 {Icon && <Icon fontSize="inherit" />}
182 </IconButton>
183 </Box>
184 </Box>
185 );
186})(({ theme }) => ({
187 color: 'white',
188}));
189
simon35378692022-10-02 23:25:57 -0400190export const CancelPictureButton = (props: IconButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400191 return <RoundButton {...props} aria-label="remove picture" Icon={CancelIcon} size="large" />;
192};
idillon-sfl44b05342022-08-24 15:46:42 -0400193
simon35378692022-10-02 23:25:57 -0400194export const EditPictureButton = (props: IconButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400195 return <RoundButton {...props} aria-label="edit picture" Icon={PenIcon} size="large" />;
196};
idillon-sfl44b05342022-08-24 15:46:42 -0400197
simon35378692022-10-02 23:25:57 -0400198export const UploadPictureButton = (props: IconButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400199 return <RoundButton {...props} aria-label="upload picture" Icon={FolderIcon} size="large" />;
200};
idillon-sfl44b05342022-08-24 15:46:42 -0400201
simon35378692022-10-02 23:25:57 -0400202export const TakePictureButton = (props: IconButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400203 return <RoundButton {...props} aria-label="take picture" Icon={CameraIcon} size="large" />;
204};
idillon-sfl37c18df2022-08-26 18:44:27 -0400205
Michelle Sepkap Simef5ebc2e2022-10-27 18:30:53 -0400206type InfoButtonProps = IconButtonProps & {
207 tooltipTitle: string;
208};
209export const InfoButton = ({ tooltipTitle, ...props }: InfoButtonProps) => {
210 return (
211 <CustomTooltip className="tooltip" title={tooltipTitle}>
212 <RoundButton {...props} aria-label="informations" Icon={InfoIcon} size="small" />
213 </CustomTooltip>
214 );
simond47ef9e2022-09-28 22:24:28 -0400215};
idillon-sfl37c18df2022-08-26 18:44:27 -0400216
simon35378692022-10-02 23:25:57 -0400217export const TipButton = (props: IconButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400218 return <RoundButton {...props} aria-label="tip" Icon={QuestionMark} size="medium" />;
219};
idillon-sfl37c18df2022-08-26 18:44:27 -0400220
simon35378692022-10-02 23:25:57 -0400221export const MoreButton = styled((props: IconButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400222 return (
223 <IconButton {...props} disableRipple={true} aria-label="more">
224 <CrossIcon fontSize="inherit" />
225 </IconButton>
226 );
227})(({ theme }) => ({
228 border: `1px solid ${theme.palette.primary.dark}`,
229 color: theme.palette.primary.dark,
230 fontSize: '10px',
231 height: '20px',
232 width: '20px',
233 '&:hover': {
234 background: theme.palette.primary.light,
235 },
236 '&:active': {
237 color: '#FFF',
238 background: theme.palette.primary.dark,
239 },
240}));
idillon927b7592022-09-15 12:56:45 -0400241
simon35378692022-10-02 23:25:57 -0400242export const BackButton = styled((props: IconButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400243 return (
244 <IconButton {...props} disableRipple={true} aria-label="back">
245 <ArrowIcon fontSize="inherit" />
246 </IconButton>
247 );
248})(({ theme }) => ({
249 color: theme.palette.primary.dark,
250 fontSize: '15px',
251 height: '30px',
252 width: '51px',
253 borderRadius: '5px',
254 '&:hover': {
255 background: theme.palette.primary.light,
256 },
257}));
idillonb3788bf2022-08-29 15:57:57 -0400258
simon1170c322022-10-31 14:51:31 -0400259export type ToggleIconButtonProps = IconButtonProps & {
260 selected: boolean;
261 toggle: () => void;
262 IconOn?: ComponentType<SvgIconProps>;
263 IconOff?: ComponentType<SvgIconProps>;
Gabriel Rochone3ec0d22022-10-08 14:27:03 -0400264};
simon33c06182022-11-02 17:39:31 -0400265
simon1170c322022-10-31 14:51:31 -0400266export const ToggleIconButton = ({
267 IconOn = RadioButtonChecked,
268 IconOff = RadioButtonUnchecked,
269 selected,
270 toggle,
271 ...props
272}: ToggleIconButtonProps) => {
Gabriel Rochone3ec0d22022-10-08 14:27:03 -0400273 return (
simon1170c322022-10-31 14:51:31 -0400274 <IconButton
275 {...props}
276 onClick={() => {
277 toggle();
278 }}
279 >
280 {selected ? <IconOn /> : <IconOff />}
Gabriel Rochone3ec0d22022-10-08 14:27:03 -0400281 </IconButton>
282 );
283};
284
simon35378692022-10-02 23:25:57 -0400285export const CloseButton = styled((props: IconButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400286 return (
287 <IconButton {...props} disableRipple={true} aria-label="close">
288 <SaltireIcon fontSize="inherit" />
289 </IconButton>
290 );
291})(({ theme }) => ({
292 color: theme.palette.primary.dark,
293 fontSize: '15px',
294 height: '30px',
295 width: '30px',
296 borderRadius: '5px',
297 '&:hover': {
298 background: theme.palette.primary.light,
299 },
300}));
idillonb3788bf2022-08-29 15:57:57 -0400301
simon35378692022-10-02 23:25:57 -0400302type ToggleVisibilityButtonProps = IconButtonProps & {
303 visible: boolean;
304};
305export const ToggleVisibilityButton = styled(({ visible, ...props }: ToggleVisibilityButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400306 const Icon = visible ? CrossedEyeIcon : EyeIcon;
307 return (
308 <IconButton {...props} disableRipple={true}>
309 <Icon fontSize="inherit" />
310 </IconButton>
311 );
312})(({ theme }) => ({
313 color: theme.palette.primary.dark,
314 fontSize: '15px',
315 height: '15px',
316 width: '15px',
317 '&:hover': {
318 background: theme.palette.primary.light,
319 },
320}));
idillonaedab942022-09-01 14:29:43 -0400321
simon35378692022-10-02 23:25:57 -0400322const SquareButton = styled(({ Icon, ...props }: ShapedButtonProps) => (
simond47ef9e2022-09-28 22:24:28 -0400323 <IconButton {...props} disableRipple={true}>
324 <Icon fontSize="inherit" />
325 </IconButton>
simon416d0792022-11-03 02:46:18 -0400326))(() => ({
simond47ef9e2022-09-28 22:24:28 -0400327 color: '#7E7E7E',
328 fontSize: '25px',
329 height: '36px',
330 width: '36px',
331 borderRadius: '5px',
332 '&:hover': {
333 background: '#E5E5E5',
334 },
idillonaedab942022-09-01 14:29:43 -0400335}));
336
idillonae655dd2022-10-14 18:11:02 -0400337export const AddParticipantButton = (props: IconButtonProps) => {
338 return <SquareButton {...props} aria-label="add participant" Icon={PeopleWithPlusSignIcon} />;
339};
340
simon35378692022-10-02 23:25:57 -0400341export const RecordVideoMessageButton = (props: IconButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400342 return <SquareButton {...props} aria-label="record video message" Icon={CameraInBubbleIcon} />;
343};
idillonaedab942022-09-01 14:29:43 -0400344
simon35378692022-10-02 23:25:57 -0400345export const RecordVoiceMessageButton = (props: IconButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400346 return <SquareButton {...props} aria-label="record voice message" Icon={MicroInBubbleIcon} />;
347};
idillonaedab942022-09-01 14:29:43 -0400348
idillonae655dd2022-10-14 18:11:02 -0400349export const ShowOptionsMenuButton = (props: IconButtonProps) => {
350 return <SquareButton {...props} aria-label="show options menu" Icon={ListIcon} />;
351};
352
353export const StartVideoCallButton = (props: IconButtonProps) => {
354 return <SquareButton {...props} aria-label="start audio call" Icon={AudioCallIcon} />;
355};
356
357export const StartAudioCallButton = (props: IconButtonProps) => {
358 return <SquareButton {...props} aria-label="start video call" Icon={VideoCallIcon} />;
359};
360
simon35378692022-10-02 23:25:57 -0400361export const UploadFileButton = (props: IconButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400362 return <SquareButton {...props} aria-label="upload file" Icon={PaperClipIcon} />;
363};
idillonaedab942022-09-01 14:29:43 -0400364
simon35378692022-10-02 23:25:57 -0400365export const SendMessageButton = (props: IconButtonProps) => {
simond47ef9e2022-09-28 22:24:28 -0400366 return <SquareButton {...props} aria-label="send message" Icon={Arrow2Icon} />;
367};
idillonaedab942022-09-01 14:29:43 -0400368
simon35378692022-10-02 23:25:57 -0400369export const ReplyMessageButton = styled((props: IconButtonProps) => (
simond47ef9e2022-09-28 22:24:28 -0400370 <IconButton {...props} disableRipple={true} aria-label="send message">
371 <Arrow3Icon fontSize="inherit" />
372 </IconButton>
373))(({ theme }) => ({
374 color: theme.palette.primary.dark,
375 fontSize: '20px',
376 height: '20px',
377 width: '20px',
378 borderRadius: '5px',
379 '&:hover': {
380 background: '#E5E5E5',
381 },
idillon927b7592022-09-15 12:56:45 -0400382}));
383
simon35378692022-10-02 23:25:57 -0400384type EmojiButtonProps = IconButtonProps & {
385 emoji: string;
386};
387export const EmojiButton = styled(({ emoji, ...props }: EmojiButtonProps) => (
simond47ef9e2022-09-28 22:24:28 -0400388 <IconButton {...props} disableRipple={true}>
389 {emoji}
390 </IconButton>
simon416d0792022-11-03 02:46:18 -0400391))(() => ({
simond47ef9e2022-09-28 22:24:28 -0400392 color: 'white',
393 fontSize: '20px',
394 height: '20px',
395 width: '20px',
idillon927b7592022-09-15 12:56:45 -0400396}));
397
simon35378692022-10-02 23:25:57 -0400398type SelectEmojiButtonProps = {
399 onEmojiSelected: (emoji: string) => void;
400};
simon416d0792022-11-03 02:46:18 -0400401export const SelectEmojiButton = ({ onEmojiSelected }: SelectEmojiButtonProps) => {
simon35378692022-10-02 23:25:57 -0400402 const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
idillon1664bb22022-09-14 17:18:15 -0400403
simon35378692022-10-02 23:25:57 -0400404 const handleOpenEmojiPicker = useCallback(
405 (e: MouseEvent<HTMLButtonElement>) => setAnchorEl(anchorEl ? null : e.currentTarget),
406 [anchorEl]
407 );
simond47ef9e2022-09-28 22:24:28 -0400408
409 const handleClose = useCallback(() => setAnchorEl(null), [setAnchorEl]);
410
411 const onEmojiClick = useCallback(
simon35378692022-10-02 23:25:57 -0400412 (e: MouseEvent, emojiObject: IEmojiData) => {
simon80b7b3b2022-09-28 17:50:10 -0400413 onEmojiSelected(emojiObject.emoji);
simond47ef9e2022-09-28 22:24:28 -0400414 handleClose();
415 },
simon80b7b3b2022-09-28 17:50:10 -0400416 [handleClose, onEmojiSelected]
simond47ef9e2022-09-28 22:24:28 -0400417 );
418
419 const open = Boolean(anchorEl);
420 const id = open ? 'simple-popover' : undefined;
421
422 return (
423 <ClickAwayListener onClickAway={handleClose}>
424 <Box>
idillonae655dd2022-10-14 18:11:02 -0400425 <SquareButton
426 aria-describedby={id}
427 aria-label="select emoji"
428 Icon={EmojiIcon}
429 onClick={handleOpenEmojiPicker}
430 />
simon35378692022-10-02 23:25:57 -0400431 <Popper id={id} open={open} anchorEl={anchorEl}>
432 <EmojiPicker onEmojiClick={onEmojiClick} disableAutoFocus={true} disableSkinTonePicker={true} native />
simond47ef9e2022-09-28 22:24:28 -0400433 </Popper>
434 </Box>
435 </ClickAwayListener>
436 );
437};