From ed8020ec85ec0c53637c06803bf555020bc23425 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Sl=C3=A1ma?= Date: Thu, 29 Jun 2023 16:04:15 +0200 Subject: [PATCH] feat: add emoji plugin (#3653) --- .../src/LexicalEditor/LexicalEditor.test.tsx | 31 ++++ .../src/LexicalEditor/LexicalEditor.tsx | 31 ++-- .../src/LexicalEditor/styles.ts | 7 + .../src/LexicalEditor/types.ts | 32 ++++ .../LexicalEditor/utils/createLexicalTheme.ts | 1 + .../LexicalEditorToolbarPlugin.tsx | 35 ++++- .../LexicalEmojiPlugin.test.ts | 77 ++++++++++ .../LexicalEmojiPlugin/LexicalEmojiPlugin.tsx | 41 +++++ .../src/LexicalEmojiPlugin/commands/index.ts | 11 ++ .../src/LexicalEmojiPlugin/index.ts | 3 + .../nodes/CustomEmojiNode.tsx | 141 ++++++++++++++++++ .../src/LexicalEmojiPlugin/nodes/index.ts | 1 + .../src/LexicalEmojiPlugin/types.ts | 1 + .../src/RichTextEditor/RichTextEditor.tsx | 16 +- .../src/RichTextEditor/styles.ts | 6 + .../RichTextEditorEmojiPicker.tsx | 9 +- .../RichTextEditorToolbar.tsx | 5 +- 17 files changed, 426 insertions(+), 22 deletions(-) create mode 100644 packages/picasso-rich-text-editor/src/LexicalEmojiPlugin/LexicalEmojiPlugin.test.ts create mode 100644 packages/picasso-rich-text-editor/src/LexicalEmojiPlugin/LexicalEmojiPlugin.tsx create mode 100644 packages/picasso-rich-text-editor/src/LexicalEmojiPlugin/commands/index.ts create mode 100644 packages/picasso-rich-text-editor/src/LexicalEmojiPlugin/index.ts create mode 100644 packages/picasso-rich-text-editor/src/LexicalEmojiPlugin/nodes/CustomEmojiNode.tsx create mode 100644 packages/picasso-rich-text-editor/src/LexicalEmojiPlugin/nodes/index.ts create mode 100644 packages/picasso-rich-text-editor/src/LexicalEmojiPlugin/types.ts diff --git a/packages/picasso-rich-text-editor/src/LexicalEditor/LexicalEditor.test.tsx b/packages/picasso-rich-text-editor/src/LexicalEditor/LexicalEditor.test.tsx index eee8d45be5..01adaa7a9d 100644 --- a/packages/picasso-rich-text-editor/src/LexicalEditor/LexicalEditor.test.tsx +++ b/packages/picasso-rich-text-editor/src/LexicalEditor/LexicalEditor.test.tsx @@ -9,6 +9,7 @@ import LexicalTextLengthPlugin from '../LexicalTextLengthPlugin' import LexicalHeadingsReplacementPlugin from '../LexicalHeadingsReplacementPlugin' import ToolbarPlugin from '../LexicalEditorToolbarPlugin' import LexicalListPlugin from '../LexicalListPlugin' +import type { CustomEmojiGroup } from './types' jest.mock('../LexicalEditorToolbarPlugin', () => ({ __esModule: true, @@ -18,6 +19,10 @@ jest.mock('../LexicalListPlugin', () => ({ __esModule: true, default: jest.fn(() =>
LexicalListPlugin
), })) +jest.mock('../LexicalEmojiPlugin', () => ({ + __esModule: true, + default: jest.fn(() =>
LexicalEmojiPlugin
), +})) jest.mock('@lexical/react/LexicalComposerContext', () => ({ __esModule: true, @@ -136,6 +141,8 @@ describe('LexicalEditor', () => { expect(mockedToolbarPlugin).toHaveBeenCalledWith( { disabled: true, + customEmojis: undefined, + plugins: [], toolbarRef: { current: null, }, @@ -152,6 +159,30 @@ describe('LexicalEditor', () => { expect(mockedToolbarPlugin).toHaveBeenCalledWith( { disabled: true, + customEmojis: undefined, + plugins: [], + toolbarRef: { + current: null, + }, + }, + {} + ) + }) + }) + + describe('when customEmojis and plugins prop is passed', () => { + it('renders ToolbarPlugin with correct props', () => { + renderLexicalEditor({ + disabled: true, + customEmojis: ['foo' as unknown as CustomEmojiGroup], + plugins: ['link'], + }) + + expect(mockedToolbarPlugin).toHaveBeenCalledWith( + { + disabled: true, + customEmojis: ['foo'], + plugins: ['link'], toolbarRef: { current: null, }, diff --git a/packages/picasso-rich-text-editor/src/LexicalEditor/LexicalEditor.tsx b/packages/picasso-rich-text-editor/src/LexicalEditor/LexicalEditor.tsx index d489c9a752..bd3903d817 100644 --- a/packages/picasso-rich-text-editor/src/LexicalEditor/LexicalEditor.tsx +++ b/packages/picasso-rich-text-editor/src/LexicalEditor/LexicalEditor.tsx @@ -8,25 +8,32 @@ import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin' import { AutoFocusPlugin } from '@lexical/react/LexicalAutoFocusPlugin' import { ContentEditable } from '@lexical/react/LexicalContentEditable' import LexicalErrorBoundary from '@lexical/react/LexicalErrorBoundary' -import { HeadingNode } from '@lexical/rich-text' import { $generateHtmlFromNodes } from '@lexical/html' import { noop } from '@toptal/picasso/utils' import { Container, Typography } from '@toptal/picasso' -import { ListItemNode, ListNode } from '@lexical/list' import { $isRootTextContentEmpty } from '@lexical/text' import type { LexicalEditor as LexicalEditorType } from 'lexical' import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin' +import { ListItemNode, ListNode } from '@lexical/list' +import { HeadingNode } from '@lexical/rich-text' import { TriggerInitialOnChangePlugin } from './plugins' import { createLexicalTheme, setEditorValue } from './utils' import { useTypographyClasses, useOnFocus } from './hooks' import styles from './styles' -import type { ChangeHandler, TextLengthChangeHandler } from './types' +import type { + ChangeHandler, + EditorPlugin, + TextLengthChangeHandler, + CustomEmojiGroup, +} from './types' import ToolbarPlugin from '../LexicalEditorToolbarPlugin' import LexicalTextLengthPlugin from '../LexicalTextLengthPlugin' import LexicalListPlugin from '../LexicalListPlugin' import LexicalHeadingsReplacementPlugin from '../LexicalHeadingsReplacementPlugin' import type { ASTType } from '../RichText' +import { CustomEmojiNode } from '../LexicalEmojiPlugin/nodes/CustomEmojiNode' +import LexicalEmojiPlugin from '../LexicalEmojiPlugin' const useStyles = makeStyles(styles, { name: 'LexicalEditor', @@ -67,8 +74,7 @@ export type Props = BaseProps & { onTextLengthChange: TextLengthChangeHandler /** The placeholder attribute specifies a short hint that describes the expected value of a text editor. */ placeholder?: string - /** List of plugins to enable on the editor */ - // plugins?: EditorPlugin[] + testIds?: { editor?: string // headerSelect?: string @@ -77,7 +83,9 @@ export type Props = BaseProps & { // unorderedListButton?: string // orderedListButton?: string } - // customEmojis?: CustomEmojiGroup[] + customEmojis?: CustomEmojiGroup[] + /** List of plugins to enable on the editor */ + plugins?: EditorPlugin[] } const LexicalEditor = forwardRef(function LexicalEditor( @@ -85,7 +93,7 @@ const LexicalEditor = forwardRef(function LexicalEditor( ref ) { const { - // plugins, + plugins = [], autoFocus = false, defaultValue, disabled = false, @@ -107,7 +115,7 @@ const LexicalEditor = forwardRef(function LexicalEditor( // @todo don't know what to do with NAME prop // name, // highlight, - // customEmojis, + customEmojis, } = props const classes = useStyles() @@ -127,7 +135,7 @@ const LexicalEditor = forwardRef(function LexicalEditor( typographyClassNames, classes, }), - [typographyClassNames, classes.paragraph] + [typographyClassNames, classes] ) const editorConfig: InitialConfigType = useMemo( @@ -142,7 +150,7 @@ const LexicalEditor = forwardRef(function LexicalEditor( throw error }, namespace: 'editor', - nodes: [ListNode, ListItemNode, HeadingNode], + nodes: [CustomEmojiNode, ListNode, ListItemNode, HeadingNode], editable: !disabled, }), [defaultValue, theme, disabled] @@ -177,6 +185,8 @@ const LexicalEditor = forwardRef(function LexicalEditor( toolbarRef={toolbarRef} // remount Toolbar when disabled key={`${disabled || !isFocused}`} + customEmojis={customEmojis} + plugins={plugins} /> {defaultValue ? ( @@ -187,6 +197,7 @@ const LexicalEditor = forwardRef(function LexicalEditor( +
{ italic: { fontStyle: 'italic', }, + customEmoji: { + '& > img': { + verticalAlign: 'bottom', + width: '22px', + height: '22px', + }, + }, }) } diff --git a/packages/picasso-rich-text-editor/src/LexicalEditor/types.ts b/packages/picasso-rich-text-editor/src/LexicalEditor/types.ts index 935bb66cc3..9014525a86 100644 --- a/packages/picasso-rich-text-editor/src/LexicalEditor/types.ts +++ b/packages/picasso-rich-text-editor/src/LexicalEditor/types.ts @@ -1,3 +1,35 @@ +export type SettingName = 'link' | 'emoji' + export type ChangeHandler = (html: string) => void export type { TextLengthChangeHandler } from '../LexicalTextLengthPlugin' + +export type EditorPlugin = SettingName + +export type CustomEmoji = { + id: string + name: string + keywords: string[] + skins: [ + { + src: string + } + ] +} + +export type CustomEmojiGroup = { + id: string + name: string + emojis: CustomEmoji[] +} + +export type Emoji = { + id: string + name: string + native?: string + unified?: string + keywords: string[] + shortcodes: string + emoticons?: string[] + src?: string +} diff --git a/packages/picasso-rich-text-editor/src/LexicalEditor/utils/createLexicalTheme.ts b/packages/picasso-rich-text-editor/src/LexicalEditor/utils/createLexicalTheme.ts index 2087fddc4d..2c88f25695 100644 --- a/packages/picasso-rich-text-editor/src/LexicalEditor/utils/createLexicalTheme.ts +++ b/packages/picasso-rich-text-editor/src/LexicalEditor/utils/createLexicalTheme.ts @@ -34,6 +34,7 @@ export const createLexicalTheme = ({ ul: classes.ul, ol: classes.ol, }, + customEmoji: classes.customEmoji, } return theme diff --git a/packages/picasso-rich-text-editor/src/LexicalEditorToolbarPlugin/LexicalEditorToolbarPlugin.tsx b/packages/picasso-rich-text-editor/src/LexicalEditorToolbarPlugin/LexicalEditorToolbarPlugin.tsx index 99573e900b..18924765db 100644 --- a/packages/picasso-rich-text-editor/src/LexicalEditorToolbarPlugin/LexicalEditorToolbarPlugin.tsx +++ b/packages/picasso-rich-text-editor/src/LexicalEditorToolbarPlugin/LexicalEditorToolbarPlugin.tsx @@ -21,19 +21,32 @@ import { synchronizeToolbarState, toolbarStateReducer, } from '../LexicalEditor/utils' +import type { + CustomEmojiGroup, + EditorPlugin, + Emoji, +} from '../LexicalEditor/types' +import { + INSERT_CUSTOM_EMOJI_COMMAND, + INSERT_EMOJI_COMMAND, +} from '../LexicalEmojiPlugin/commands' import type { HeaderValue } from '../RichTextEditorToolbar' import RichTextEditorToolbar, { ALLOWED_HEADER_TYPE, } from '../RichTextEditorToolbar' type Props = { + customEmojis?: CustomEmojiGroup[] disabled?: boolean toolbarRef: React.RefObject + plugins?: EditorPlugin[] } const LexicalEditorToolbarPlugin = ({ disabled = false, toolbarRef, + customEmojis, + plugins, }: Props) => { const [editor] = useLexicalComposerContext() const [{ bold, italic, list, header }, dispatch] = useReducer( @@ -73,6 +86,24 @@ const LexicalEditorToolbarPlugin = ({ ) } + const handleInsertEmoji = (emoji: Emoji) => { + const isNativeEmoji = emoji.native + const isCustomEmoji = emoji.src + + if (isNativeEmoji) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + editor.dispatchCommand(INSERT_EMOJI_COMMAND, emoji.native!) + } + + if (isCustomEmoji) { + editor.dispatchCommand(INSERT_CUSTOM_EMOJI_COMMAND, { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + src: emoji.src!, + id: emoji.id, + }) + } + } + const handleHeaderClick = ({ target: { value }, }: ChangeEvent<{ @@ -108,8 +139,10 @@ const LexicalEditorToolbarPlugin = ({ onLinkClick={noop} onHeaderChange={handleHeaderClick} disabled={disabled} - onInsertEmoji={noop} + onInsertEmoji={handleInsertEmoji} ref={toolbarRef} + customEmojis={customEmojis} + plugins={plugins} /> ) } diff --git a/packages/picasso-rich-text-editor/src/LexicalEmojiPlugin/LexicalEmojiPlugin.test.ts b/packages/picasso-rich-text-editor/src/LexicalEmojiPlugin/LexicalEmojiPlugin.test.ts new file mode 100644 index 0000000000..4ef4d2658e --- /dev/null +++ b/packages/picasso-rich-text-editor/src/LexicalEmojiPlugin/LexicalEmojiPlugin.test.ts @@ -0,0 +1,77 @@ +import { renderHook } from '@testing-library/react-hooks' +import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' +import { $createTextNode, $insertNodes, COMMAND_PRIORITY_EDITOR } from 'lexical' + +import LexicalEmojiPlugin, { + INSERT_CUSTOM_EMOJI_COMMAND, + INSERT_EMOJI_COMMAND, +} from './index' +import { $createCustomEmojiNode } from './nodes/CustomEmojiNode' + +jest.mock('@lexical/react/LexicalComposerContext', () => ({ + useLexicalComposerContext: jest.fn(() => [{}]), +})) + +jest.mock('@lexical/utils', () => ({ + mergeRegister: jest.fn(), +})) + +jest.mock('lexical', () => ({ + $createTextNode: jest.fn(), + $insertNodes: jest.fn(), + COMMAND_PRIORITY_EDITOR: jest.fn(), + createCommand: jest.fn(), +})) + +jest.mock('./nodes/CustomEmojiNode', () => ({ + $createCustomEmojiNode: jest.fn(), +})) + +describe('LexicalEmojiPlugin', () => { + const mockEditor = { + registerCommand: jest.fn(), + } + + beforeEach(() => { + jest.resetAllMocks() + ;(useLexicalComposerContext as jest.Mock).mockReturnValue([mockEditor]) + }) + + it('registers commands on mount', () => { + renderHook(() => LexicalEmojiPlugin()) + + expect(mockEditor.registerCommand).toHaveBeenCalledTimes(2) + expect(mockEditor.registerCommand).toHaveBeenCalledWith( + INSERT_EMOJI_COMMAND, + expect.any(Function), + COMMAND_PRIORITY_EDITOR + ) + expect(mockEditor.registerCommand).toHaveBeenCalledWith( + INSERT_CUSTOM_EMOJI_COMMAND, + expect.any(Function), + COMMAND_PRIORITY_EDITOR + ) + }) + + it('inserts a text node when the native emoji command is called', () => { + renderHook(() => LexicalEmojiPlugin()) + const nativeEmojiCommand = mockEditor.registerCommand.mock.calls[0][1] + + nativeEmojiCommand('😃') + + expect($createTextNode).toHaveBeenCalledWith('😃') + expect($insertNodes).toHaveBeenCalledWith([$createTextNode()]) + }) + + it('inserts a custom emoji node when the custom emoji command is called', () => { + const payload = { id: 'custom emoji', src: 'https://example.com/emoji.png' } + + renderHook(() => LexicalEmojiPlugin()) + const customEmojiCommand = mockEditor.registerCommand.mock.calls[1][1] + + customEmojiCommand(payload) + + expect($createCustomEmojiNode).toHaveBeenCalledWith(payload) + expect($insertNodes).toHaveBeenCalledWith([$createCustomEmojiNode(payload)]) + }) +}) diff --git a/packages/picasso-rich-text-editor/src/LexicalEmojiPlugin/LexicalEmojiPlugin.tsx b/packages/picasso-rich-text-editor/src/LexicalEmojiPlugin/LexicalEmojiPlugin.tsx new file mode 100644 index 0000000000..f7c4392a84 --- /dev/null +++ b/packages/picasso-rich-text-editor/src/LexicalEmojiPlugin/LexicalEmojiPlugin.tsx @@ -0,0 +1,41 @@ +import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' +import { mergeRegister } from '@lexical/utils' +import { useEffect } from 'react' +import { $createTextNode, $insertNodes, COMMAND_PRIORITY_EDITOR } from 'lexical' + +import { INSERT_CUSTOM_EMOJI_COMMAND, INSERT_EMOJI_COMMAND } from './commands' +import { $createCustomEmojiNode } from './nodes/CustomEmojiNode' +import type { CustomEmojiPayload } from './types' + +const LexicalEmojiPlugin = () => { + const [editor] = useLexicalComposerContext() + + useEffect(() => { + return mergeRegister( + editor.registerCommand( + INSERT_EMOJI_COMMAND, + (nativeEmoji: string) => { + $insertNodes([$createTextNode(nativeEmoji)]) + + return true + }, + COMMAND_PRIORITY_EDITOR + ), + editor.registerCommand( + INSERT_CUSTOM_EMOJI_COMMAND, + (customEmojiPayload: CustomEmojiPayload) => { + const emojiNode = $createCustomEmojiNode(customEmojiPayload) + + $insertNodes([emojiNode]) + + return true + }, + COMMAND_PRIORITY_EDITOR + ) + ) + }, [editor]) + + return null +} + +export default LexicalEmojiPlugin diff --git a/packages/picasso-rich-text-editor/src/LexicalEmojiPlugin/commands/index.ts b/packages/picasso-rich-text-editor/src/LexicalEmojiPlugin/commands/index.ts new file mode 100644 index 0000000000..1bb6fa4277 --- /dev/null +++ b/packages/picasso-rich-text-editor/src/LexicalEmojiPlugin/commands/index.ts @@ -0,0 +1,11 @@ +import { createCommand } from 'lexical' +import type { LexicalCommand } from 'lexical' + +import type { CustomEmojiPayload } from '../nodes/CustomEmojiNode' + +export const INSERT_EMOJI_COMMAND: LexicalCommand = createCommand( + 'INSERT_EMOJI_COMMAND' +) + +export const INSERT_CUSTOM_EMOJI_COMMAND: LexicalCommand = + createCommand('INSERT_CUSTOM_EMOJI_COMMAND') diff --git a/packages/picasso-rich-text-editor/src/LexicalEmojiPlugin/index.ts b/packages/picasso-rich-text-editor/src/LexicalEmojiPlugin/index.ts new file mode 100644 index 0000000000..4fe8385887 --- /dev/null +++ b/packages/picasso-rich-text-editor/src/LexicalEmojiPlugin/index.ts @@ -0,0 +1,3 @@ +export { default } from './LexicalEmojiPlugin' +export * from './commands' +export * from './nodes' diff --git a/packages/picasso-rich-text-editor/src/LexicalEmojiPlugin/nodes/CustomEmojiNode.tsx b/packages/picasso-rich-text-editor/src/LexicalEmojiPlugin/nodes/CustomEmojiNode.tsx new file mode 100644 index 0000000000..4976c8d620 --- /dev/null +++ b/packages/picasso-rich-text-editor/src/LexicalEmojiPlugin/nodes/CustomEmojiNode.tsx @@ -0,0 +1,141 @@ +import React from 'react' +import { $applyNodeReplacement, DecoratorNode } from 'lexical' +import type { + DOMConversionMap, + DOMConversionOutput, + DOMExportOutput, + EditorConfig, + LexicalNode, + NodeKey, + SerializedElementNode, + Spread, +} from 'lexical' + +export interface CustomEmojiPayload { + src: string + id: string +} + +type SerializedCustomEmojiNode = Spread< + { + src: string + id: string + }, + SerializedElementNode +> + +const convertImageElement = (domNode: Node): null | DOMConversionOutput => { + if (domNode instanceof HTMLImageElement) { + const src = domNode.getAttribute('src') + const id = domNode.getAttribute('data-emoji-name') + + if (src && id) { + return { + node: $createCustomEmojiNode({ + src, + id, + }), + } + } + + return null + } + + return null +} + +export class CustomEmojiNode extends DecoratorNode { + src: string + id: string + + static getType() { + return 'custom-emoji' + } + + static clone(node: CustomEmojiNode): CustomEmojiNode { + return new CustomEmojiNode(node.src, node.id) + } + + constructor(src: string, id: string, key?: NodeKey) { + super(key) + this.src = src + this.id = id + } + + static importJSON( + serializedNode: SerializedCustomEmojiNode + ): CustomEmojiNode { + const { src, id } = serializedNode + + const node = $createCustomEmojiNode({ src, id }) + + return node + } + + createDOM(config: EditorConfig): HTMLElement { + const span = document.createElement('span') + + const theme = config.theme + const className = theme.customEmoji + + if (className !== undefined) { + span.className = className + } + + return span + } + + updateDOM() { + return false + } + + exportDOM(): DOMExportOutput { + const element = document.createElement('img') + + element.setAttribute('src', this.src) + element.setAttribute('data-src', this.src) + element.setAttribute('data-emoji-name', this.id) + + return { element } + } + + static importDOM(): DOMConversionMap | null { + return { + img: () => ({ + conversion: convertImageElement, + priority: 0, + }), + } + } + + exportJSON(): SerializedCustomEmojiNode { + return { + version: 1, + type: 'custom-emoji', + src: this.src, + id: this.id, + children: [], + direction: this.__direction, + format: '', + indent: this.__indent, + } + } + + decorate() { + return + } +} + +export const $isCustomEmojiNode = ( + node: LexicalNode | null | undefined +): node is CustomEmojiNode => { + return node instanceof CustomEmojiNode +} + +export const $createCustomEmojiNode = ({ + src, + id, +}: CustomEmojiPayload): CustomEmojiNode => { + // return new CustomEmojiNode(src, id) + return $applyNodeReplacement(new CustomEmojiNode(src, id)) +} diff --git a/packages/picasso-rich-text-editor/src/LexicalEmojiPlugin/nodes/index.ts b/packages/picasso-rich-text-editor/src/LexicalEmojiPlugin/nodes/index.ts new file mode 100644 index 0000000000..bd2ba9c89d --- /dev/null +++ b/packages/picasso-rich-text-editor/src/LexicalEmojiPlugin/nodes/index.ts @@ -0,0 +1 @@ +export { CustomEmojiNode } from './CustomEmojiNode' diff --git a/packages/picasso-rich-text-editor/src/LexicalEmojiPlugin/types.ts b/packages/picasso-rich-text-editor/src/LexicalEmojiPlugin/types.ts new file mode 100644 index 0000000000..8f3ff95a4f --- /dev/null +++ b/packages/picasso-rich-text-editor/src/LexicalEmojiPlugin/types.ts @@ -0,0 +1 @@ +export type { CustomEmojiPayload } from './nodes/CustomEmojiNode' diff --git a/packages/picasso-rich-text-editor/src/RichTextEditor/RichTextEditor.tsx b/packages/picasso-rich-text-editor/src/RichTextEditor/RichTextEditor.tsx index 099d0e8cbf..73f41bccc1 100644 --- a/packages/picasso-rich-text-editor/src/RichTextEditor/RichTextEditor.tsx +++ b/packages/picasso-rich-text-editor/src/RichTextEditor/RichTextEditor.tsx @@ -8,14 +8,16 @@ import { noop, usePropDeprecationWarning } from '@toptal/picasso/utils' import { InputMultilineAdornment } from '@toptal/picasso' import type { OutlinedInputStatus } from '@toptal/picasso' -// @todo: remove this import once we remove the old QuillEditor -import type { CustomEmojiGroup, EditorPlugin } from '../QuillEditor' import styles from './styles' import { useCounter } from './hooks' import type { ASTType } from '../RichText' import type { CounterMessageSetter } from './types' import LexicalEditor from '../LexicalEditor' -import type { ChangeHandler } from '../LexicalEditor' +import type { + ChangeHandler, + CustomEmojiGroup, + EditorPlugin, +} from '../LexicalEditor' export interface Props extends BaseProps { /** Indicates that an element is to be focused on page load */ @@ -95,7 +97,7 @@ export const RichTextEditor = forwardRef( function RichTextEditor(props, ref) { const { 'data-testid': dataTestId, - // plugins, + plugins, autoFocus = false, className, defaultValue, @@ -116,7 +118,7 @@ export const RichTextEditor = forwardRef( setHasMultilineCounter, name, highlight, - // customEmojis, + customEmojis, } = props const classes = useStyles() @@ -188,13 +190,15 @@ export const RichTextEditor = forwardRef( disabled={disabled} autoFocus={autoFocus} defaultValue={defaultValue} + plugins={plugins} + customEmojis={customEmojis} /> {hiddenInputId && ( // Native `for` attribute on label does not work for div target )}
diff --git a/packages/picasso-rich-text-editor/src/RichTextEditor/styles.ts b/packages/picasso-rich-text-editor/src/RichTextEditor/styles.ts index 591d9106b0..8f2877a388 100644 --- a/packages/picasso-rich-text-editor/src/RichTextEditor/styles.ts +++ b/packages/picasso-rich-text-editor/src/RichTextEditor/styles.ts @@ -37,6 +37,12 @@ export default (theme: Theme) => { ...outline(palette.primary.main), }, + hiddenInput: { + position: 'absolute', + opacity: 0, + zIndex: -1, + }, + ...highlightAutofillStyles(theme), }) } diff --git a/packages/picasso-rich-text-editor/src/RichTextEditorEmojiPicker/RichTextEditorEmojiPicker.tsx b/packages/picasso-rich-text-editor/src/RichTextEditorEmojiPicker/RichTextEditorEmojiPicker.tsx index 4bdd7052ef..c0bb0e476f 100644 --- a/packages/picasso-rich-text-editor/src/RichTextEditorEmojiPicker/RichTextEditorEmojiPicker.tsx +++ b/packages/picasso-rich-text-editor/src/RichTextEditorEmojiPicker/RichTextEditorEmojiPicker.tsx @@ -8,12 +8,13 @@ import cx from 'classnames' import { Container } from '@toptal/picasso' import TextEditorButton from '../RichTextEditorButton' -import type { CustomEmojiGroup } from '../QuillEditor' +import type { CustomEmojiGroup, Emoji } from '../LexicalEditor' interface Props { richEditorId: string customEmojis?: CustomEmojiGroup[] - onInsertEmoji: (emoji: string) => void + onInsertEmoji: (emoji: Emoji) => void + disabled?: boolean } const TRIGGER_EMOJI_PICKER_ID = 'trigger-emoji-picker' @@ -48,6 +49,7 @@ export const RichtTextEditorEmojiPicker = ({ richEditorId, customEmojis, onInsertEmoji, + disabled, }: Props) => { const [showEmojiPicker, setShowEmojiPicker] = React.useState(false) @@ -61,7 +63,7 @@ export const RichtTextEditorEmojiPicker = ({ setShowEmojiPicker(false) } - const handleEmojiInsert = (emoji: string) => { + const handleEmojiInsert = (emoji: Emoji) => { onInsertEmoji(emoji) setShowEmojiPicker(false) } @@ -88,6 +90,7 @@ export const RichtTextEditorEmojiPicker = ({ onClick={handleEmojiPickerClick} icon={🙂} id={TRIGGER_EMOJI_PICKER_ID} + disabled={disabled} /> void + onInsertEmoji: (emoji: Emoji) => void onHeaderChange: SelectOnChangeHandler onUnorderedClick: ButtonHandlerType onOrderedClick: ButtonHandlerType @@ -146,6 +146,7 @@ export const RichTextEditorToolbar = forwardRef( richEditorId={id} customEmojis={customEmojis} onInsertEmoji={onInsertEmoji} + disabled={disabled} /> )}