| 
									
										
										
										
											2021-09-09 11:29:01 -05:00
										 |  |  | // Copyright 2020-2021 Signal Messenger, LLC
 | 
					
						
							| 
									
										
										
										
											2020-10-30 15:34:04 -05:00
										 |  |  | // SPDX-License-Identifier: AGPL-3.0-only
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-23 18:57:37 -05:00
										 |  |  | import * as React from 'react'; | 
					
						
							|  |  |  | import classNames from 'classnames'; | 
					
						
							| 
									
										
										
										
											2021-09-09 11:29:01 -05:00
										 |  |  | import * as log from '../../logging/log'; | 
					
						
							| 
									
										
										
										
											2020-01-23 18:57:37 -05:00
										 |  |  | import { Emoji } from '../emoji/Emoji'; | 
					
						
							| 
									
										
										
										
											2020-05-05 15:49:34 -04:00
										 |  |  | import { convertShortName } from '../emoji/lib'; | 
					
						
							|  |  |  | import { Props as EmojiPickerProps } from '../emoji/EmojiPicker'; | 
					
						
							| 
									
										
										
										
											2021-09-09 11:29:01 -05:00
										 |  |  | import { missingCaseError } from '../../util/missingCaseError'; | 
					
						
							| 
									
										
										
										
											2021-08-02 14:19:18 -07:00
										 |  |  | import { useRestoreFocus } from '../../util/hooks/useRestoreFocus'; | 
					
						
							| 
									
										
										
										
											2020-05-05 15:49:34 -04:00
										 |  |  | import { LocalizerType } from '../../types/Util'; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-09 11:29:01 -05:00
										 |  |  | export enum ReactionPickerSelectionStyle { | 
					
						
							|  |  |  |   Picker, | 
					
						
							|  |  |  |   Menu, | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-05 15:49:34 -04:00
										 |  |  | export type RenderEmojiPickerProps = Pick<Props, 'onClose' | 'style'> & | 
					
						
							| 
									
										
										
										
											2021-09-09 11:29:01 -05:00
										 |  |  |   Pick<EmojiPickerProps, 'onClickSettings' | 'onPickEmoji'> & { | 
					
						
							| 
									
										
										
										
											2020-05-05 15:49:34 -04:00
										 |  |  |     ref: React.Ref<HTMLDivElement>; | 
					
						
							|  |  |  |   }; | 
					
						
							| 
									
										
										
										
											2020-01-23 18:57:37 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  | export type OwnProps = { | 
					
						
							| 
									
										
										
										
											2021-09-09 11:29:01 -05:00
										 |  |  |   hasMoreButton?: boolean; | 
					
						
							| 
									
										
										
										
											2020-05-05 15:49:34 -04:00
										 |  |  |   i18n: LocalizerType; | 
					
						
							| 
									
										
										
										
											2020-01-23 18:57:37 -05:00
										 |  |  |   selected?: string; | 
					
						
							| 
									
										
										
										
											2021-09-09 11:29:01 -05:00
										 |  |  |   selectionStyle: ReactionPickerSelectionStyle; | 
					
						
							| 
									
										
										
										
											2020-01-23 18:57:37 -05:00
										 |  |  |   onClose?: () => unknown; | 
					
						
							|  |  |  |   onPick: (emoji: string) => unknown; | 
					
						
							| 
									
										
										
										
											2021-09-09 11:29:01 -05:00
										 |  |  |   openCustomizePreferredReactionsModal?: () => unknown; | 
					
						
							|  |  |  |   preferredReactionEmoji: Array<string>; | 
					
						
							| 
									
										
										
										
											2020-05-05 15:49:34 -04:00
										 |  |  |   renderEmojiPicker: (props: RenderEmojiPickerProps) => React.ReactElement; | 
					
						
							| 
									
										
										
										
											2020-10-02 13:05:09 -07:00
										 |  |  |   skinTone: number; | 
					
						
							| 
									
										
										
										
											2020-01-23 18:57:37 -05:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export type Props = OwnProps & Pick<React.HTMLProps<HTMLDivElement>, 'style'>; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-07 16:30:58 -05:00
										 |  |  | const EmojiButton = React.forwardRef< | 
					
						
							|  |  |  |   HTMLButtonElement, | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     emoji: string; | 
					
						
							|  |  |  |     onSelect: () => unknown; | 
					
						
							|  |  |  |     selected: boolean; | 
					
						
							|  |  |  |     title?: string; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | >(({ emoji, onSelect, selected, title }, ref) => ( | 
					
						
							|  |  |  |   <button | 
					
						
							|  |  |  |     type="button" | 
					
						
							|  |  |  |     key={emoji} | 
					
						
							|  |  |  |     ref={ref} | 
					
						
							|  |  |  |     tabIndex={0} | 
					
						
							|  |  |  |     className={classNames( | 
					
						
							|  |  |  |       'module-ReactionPicker__button', | 
					
						
							|  |  |  |       'module-ReactionPicker__button--emoji', | 
					
						
							|  |  |  |       selected && 'module-ReactionPicker__button--selected' | 
					
						
							|  |  |  |     )} | 
					
						
							|  |  |  |     onClick={e => { | 
					
						
							|  |  |  |       e.stopPropagation(); | 
					
						
							|  |  |  |       onSelect(); | 
					
						
							|  |  |  |     }} | 
					
						
							|  |  |  |   > | 
					
						
							|  |  |  |     <Emoji size={48} emoji={emoji} title={title} /> | 
					
						
							|  |  |  |   </button> | 
					
						
							|  |  |  | )); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-23 18:57:37 -05:00
										 |  |  | export const ReactionPicker = React.forwardRef<HTMLDivElement, Props>( | 
					
						
							| 
									
										
										
										
											2020-10-02 13:05:09 -07:00
										 |  |  |   ( | 
					
						
							| 
									
										
										
										
											2021-09-09 11:29:01 -05:00
										 |  |  |     { | 
					
						
							|  |  |  |       hasMoreButton = true, | 
					
						
							|  |  |  |       i18n, | 
					
						
							|  |  |  |       onClose, | 
					
						
							|  |  |  |       onPick, | 
					
						
							|  |  |  |       openCustomizePreferredReactionsModal, | 
					
						
							|  |  |  |       preferredReactionEmoji, | 
					
						
							|  |  |  |       renderEmojiPicker, | 
					
						
							|  |  |  |       selected, | 
					
						
							|  |  |  |       selectionStyle, | 
					
						
							|  |  |  |       skinTone, | 
					
						
							|  |  |  |       style, | 
					
						
							|  |  |  |     }, | 
					
						
							| 
									
										
										
										
											2020-10-02 13:05:09 -07:00
										 |  |  |     ref | 
					
						
							|  |  |  |   ) => { | 
					
						
							| 
									
										
										
										
											2020-05-05 15:49:34 -04:00
										 |  |  |     const [pickingOther, setPickingOther] = React.useState(false); | 
					
						
							| 
									
										
										
										
											2020-01-23 18:57:37 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |     // Handle escape key
 | 
					
						
							|  |  |  |     React.useEffect(() => { | 
					
						
							|  |  |  |       const handler = (e: KeyboardEvent) => { | 
					
						
							|  |  |  |         if (onClose && e.key === 'Escape') { | 
					
						
							|  |  |  |           onClose(); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       document.addEventListener('keydown', handler); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       return () => { | 
					
						
							|  |  |  |         document.removeEventListener('keydown', handler); | 
					
						
							|  |  |  |       }; | 
					
						
							|  |  |  |     }, [onClose]); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-05 15:49:34 -04:00
										 |  |  |     // Handle EmojiPicker::onPickEmoji
 | 
					
						
							|  |  |  |     const onPickEmoji: EmojiPickerProps['onPickEmoji'] = React.useCallback( | 
					
						
							| 
									
										
										
										
											2020-10-02 13:05:09 -07:00
										 |  |  |       ({ shortName, skinTone: pickedSkinTone }) => { | 
					
						
							|  |  |  |         onPick(convertShortName(shortName, pickedSkinTone)); | 
					
						
							| 
									
										
										
										
											2020-05-05 15:49:34 -04:00
										 |  |  |       }, | 
					
						
							|  |  |  |       [onPick] | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-07 16:30:58 -05:00
										 |  |  |     // Focus first button and restore focus on unmount
 | 
					
						
							|  |  |  |     const [focusRef] = useRestoreFocus(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (pickingOther) { | 
					
						
							| 
									
										
										
										
											2021-09-09 11:29:01 -05:00
										 |  |  |       return renderEmojiPicker({ | 
					
						
							|  |  |  |         onClickSettings: openCustomizePreferredReactionsModal, | 
					
						
							|  |  |  |         onClose, | 
					
						
							|  |  |  |         onPickEmoji, | 
					
						
							|  |  |  |         ref, | 
					
						
							|  |  |  |         style, | 
					
						
							|  |  |  |       }); | 
					
						
							| 
									
										
										
										
											2021-09-07 16:30:58 -05:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-09 11:29:01 -05:00
										 |  |  |     const emojis = preferredReactionEmoji.map(shortName => | 
					
						
							| 
									
										
										
										
											2020-10-02 13:05:09 -07:00
										 |  |  |       convertShortName(shortName, skinTone) | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-03 14:36:17 -04:00
										 |  |  |     const otherSelected = selected && !emojis.includes(selected); | 
					
						
							| 
									
										
										
										
											2020-05-05 15:49:34 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-07 16:30:58 -05:00
										 |  |  |     let moreButton: React.ReactNode; | 
					
						
							| 
									
										
										
										
											2021-09-09 11:29:01 -05:00
										 |  |  |     if (!hasMoreButton) { | 
					
						
							|  |  |  |       moreButton = undefined; | 
					
						
							|  |  |  |     } else if (otherSelected) { | 
					
						
							| 
									
										
										
										
											2021-09-07 16:30:58 -05:00
										 |  |  |       moreButton = ( | 
					
						
							|  |  |  |         <EmojiButton | 
					
						
							|  |  |  |           emoji={selected} | 
					
						
							|  |  |  |           onSelect={() => { | 
					
						
							|  |  |  |             onPick(selected); | 
					
						
							|  |  |  |           }} | 
					
						
							|  |  |  |           selected | 
					
						
							|  |  |  |           title={i18n('Reactions--remove')} | 
					
						
							|  |  |  |         /> | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       moreButton = ( | 
					
						
							|  |  |  |         <button | 
					
						
							| 
									
										
										
										
											2021-09-08 11:25:16 -05:00
										 |  |  |           aria-label={i18n('Reactions--more')} | 
					
						
							| 
									
										
										
										
											2021-09-07 16:30:58 -05:00
										 |  |  |           className="module-ReactionPicker__button module-ReactionPicker__button--more" | 
					
						
							|  |  |  |           onClick={event => { | 
					
						
							|  |  |  |             event.stopPropagation(); | 
					
						
							|  |  |  |             setPickingOther(true); | 
					
						
							|  |  |  |           }} | 
					
						
							|  |  |  |           tabIndex={0} | 
					
						
							| 
									
										
										
										
											2021-09-08 11:25:16 -05:00
										 |  |  |           title={i18n('Reactions--more')} | 
					
						
							| 
									
										
										
										
											2021-09-07 16:30:58 -05:00
										 |  |  |           type="button" | 
					
						
							|  |  |  |         > | 
					
						
							|  |  |  |           <div className="module-ReactionPicker__button--more__dot" /> | 
					
						
							|  |  |  |           <div className="module-ReactionPicker__button--more__dot" /> | 
					
						
							|  |  |  |           <div className="module-ReactionPicker__button--more__dot" /> | 
					
						
							|  |  |  |         </button> | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-09 11:29:01 -05:00
										 |  |  |     let selectionStyleClassName: string; | 
					
						
							|  |  |  |     switch (selectionStyle) { | 
					
						
							|  |  |  |       case ReactionPickerSelectionStyle.Picker: | 
					
						
							|  |  |  |         selectionStyleClassName = 'module-ReactionPicker--picker-style'; | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |       case ReactionPickerSelectionStyle.Menu: | 
					
						
							|  |  |  |         selectionStyleClassName = 'module-ReactionPicker--menu-style'; | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |       default: | 
					
						
							|  |  |  |         log.error(missingCaseError(selectionStyle)); | 
					
						
							|  |  |  |         selectionStyleClassName = 'module-ReactionPicker--picker-style'; | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-07 16:30:58 -05:00
										 |  |  |     return ( | 
					
						
							| 
									
										
										
										
											2021-09-09 11:29:01 -05:00
										 |  |  |       <div | 
					
						
							|  |  |  |         ref={ref} | 
					
						
							|  |  |  |         style={style} | 
					
						
							|  |  |  |         className={classNames( | 
					
						
							|  |  |  |           'module-ReactionPicker', | 
					
						
							|  |  |  |           selectionStyleClassName, | 
					
						
							|  |  |  |           selected ? 'module-ReactionPicker--something-selected' : undefined | 
					
						
							|  |  |  |         )} | 
					
						
							|  |  |  |       > | 
					
						
							| 
									
										
										
										
											2020-06-03 14:36:17 -04:00
										 |  |  |         {emojis.map((emoji, index) => { | 
					
						
							| 
									
										
										
										
											2020-01-23 18:57:37 -05:00
										 |  |  |           const maybeFocusRef = index === 0 ? focusRef : undefined; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           return ( | 
					
						
							| 
									
										
										
										
											2021-09-07 16:30:58 -05:00
										 |  |  |             <EmojiButton | 
					
						
							|  |  |  |               emoji={emoji} | 
					
						
							| 
									
										
										
										
											2020-01-23 18:57:37 -05:00
										 |  |  |               key={emoji} | 
					
						
							| 
									
										
										
										
											2021-09-07 16:30:58 -05:00
										 |  |  |               onSelect={() => { | 
					
						
							| 
									
										
										
										
											2020-01-23 18:57:37 -05:00
										 |  |  |                 onPick(emoji); | 
					
						
							|  |  |  |               }} | 
					
						
							| 
									
										
										
										
											2021-09-07 16:30:58 -05:00
										 |  |  |               ref={maybeFocusRef} | 
					
						
							|  |  |  |               selected={emoji === selected} | 
					
						
							|  |  |  |             /> | 
					
						
							| 
									
										
										
										
											2020-01-23 18:57:37 -05:00
										 |  |  |           ); | 
					
						
							|  |  |  |         })} | 
					
						
							| 
									
										
										
										
											2021-09-07 16:30:58 -05:00
										 |  |  |         {moreButton} | 
					
						
							| 
									
										
										
										
											2020-01-23 18:57:37 -05:00
										 |  |  |       </div> | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | ); |