diff --git a/.changeset/cold-apes-leave.md b/.changeset/cold-apes-leave.md new file mode 100644 index 0000000000..688187388d --- /dev/null +++ b/.changeset/cold-apes-leave.md @@ -0,0 +1,6 @@ +--- +"@twilio-paste/chat-composer": patch +"@twilio-paste/core": patch +--- + +[ChatComposer] updated JS Doc used for tpye gen in docs website diff --git a/packages/paste-core/components/ai-chat-log/stories/composer.stories.tsx b/packages/paste-core/components/ai-chat-log/stories/composer.stories.tsx index 12b15ddb3e..5377f69d63 100644 --- a/packages/paste-core/components/ai-chat-log/stories/composer.stories.tsx +++ b/packages/paste-core/components/ai-chat-log/stories/composer.stories.tsx @@ -130,11 +130,7 @@ export const AIChatLogComposer = (): React.ReactNode => { content: ( Gibby Radki - - Lorem ipsum dolor, sit amet consectetur adipisicing elit. Deserunt delectus fuga, necessitatibus eligendi - iure adipisci facilis exercitationem officiis dolorem laborum, ex fugiat quisquam itaque, earum sit nesciunt - impedit repellat assumenda. - + Hi, I am getting errors codes when sending an SMS. ), }, @@ -144,9 +140,7 @@ export const AIChatLogComposer = (): React.ReactNode => { Good Bot - Lorem ipsum dolor, sit amet consectetur adipisicing elit. Deserunt delectus fuga, necessitatibus - eligendiiure adipisci facilis exercitationem officiis dolorem laborum, ex fugiat quisquam itaque, earum sit - nesciunt impedit repellat assumenda. + Error codes can be returned from various parts of the process. What error codes are you encountering? - - ); -}; - export const EnterKeySubmitPlugin = ({ onKeyDown }: { onKeyDown: () => void }): null => { const [editor] = useLexicalComposerContext(); @@ -134,7 +114,7 @@ export const EnterKeySubmitPlugin = ({ onKeyDown }: { onKeyDown: () => void }): }; export const ChatDialogExample = `const ChatDialog = () => { - const {chats, push} = useChatLogger( + const { chats, push } = useChatLogger( { content: ( @@ -146,7 +126,7 @@ export const ChatDialogExample = `const ChatDialog = () => { ), }, { - variant: 'inbound', + variant: "inbound", content: ( Quisque ullamcorper ipsum vitae lorem euismod sodales. @@ -170,7 +150,7 @@ export const ChatDialogExample = `const ChatDialog = () => { ), }, { - variant: 'inbound', + variant: "inbound", content: ( Lorem ipsum dolor sit amet, consectetur adipiscing elit. @@ -182,9 +162,9 @@ export const ChatDialogExample = `const ChatDialog = () => { ), - } + }, ); - const [message, setMessage] = React.useState(''); + const [message, setMessage] = React.useState(""); const [mounted, setMounted] = React.useState(false); const loggerRef = React.useRef(null); @@ -196,7 +176,7 @@ export const ChatDialogExample = `const ChatDialog = () => { React.useEffect(() => { if (!mounted || !loggerRef.current) return; - scrollerRef.current?.scrollTo({top: loggerRef.current.scrollHeight, behavior: 'smooth'}); + scrollerRef.current?.scrollTo({ top: loggerRef.current.scrollHeight, behavior: "smooth" }); }, [chats, mounted]); const handleComposerChange = (editorState) => { @@ -207,10 +187,12 @@ export const ChatDialogExample = `const ChatDialog = () => { }; const submitMessage = () => { - if (message === '') return; + if (message === "") return; push(createNewMessage(message)); }; + const editorInstanceRef = React.useRef(null); + return ( @@ -221,31 +203,148 @@ export const ChatDialogExample = `const ChatDialog = () => { borderWidth="borderWidth0" borderTopWidth="borderWidth10" borderColor="colorBorderWeak" - display="flex" - flexDirection="row" columnGap="space30" paddingX="space70" paddingTop="space50" > - { - throw error; - }, - }} - ariaLabel="Message" - placeholder="Type here..." - onChange={handleComposerChange} - > - - - - + + { + throw error; + }, + }} + ariaLabel="Message" + placeholder="Type here..." + onChange={handleComposerChange} + editorInstanceRef={editorInstanceRef} + > + + + + + + + + ); }; render()`.trim(); + +export const ResponsiveContainedAttachmentsExample = `const ResponsiveContainedAttachmentsExample = () => { + const ExampleAttachment = () => ( + {}} attachmentIcon={}> + Document-FINAL.doc + 123 MB + + ) + + return ( + + { + throw e; + }, + }} + /> + + + + + + {Array.from({ length: 6 }).map((_, index) => ( + + ))} + + + ) +} + +render()`.trim(); + +export const ContainedDisabledExample = `const ContainedDisabledExample = () => { + const [isDisabled, setIsDisabled] = React.useState(true); + return ( + <> + + setIsDisabled((disabled) => !disabled)}> + Disable Input + + + + { + throw e; + }, + }} + disabled={isDisabled} + /> + + + + + + + ); +} + +render()`.trim(); + +export const ContainedExample = `const ContainedExample = () => { + return ( + + { + throw e; + }, + }} + /> + + + + + + ); +} + +render()`.trim(); diff --git a/packages/paste-website/src/pages/components/ai-chat-log/api.mdx b/packages/paste-website/src/pages/components/ai-chat-log/api.mdx index 20adf134bd..7b193b8f71 100644 --- a/packages/paste-website/src/pages/components/ai-chat-log/api.mdx +++ b/packages/paste-website/src/pages/components/ai-chat-log/api.mdx @@ -24,6 +24,7 @@ export const getStaticProps = async () => { data: { ...packageJson, ...feature, + nameOverride: 'AI Chat Log', }, componentApi, mdxHeadings: [...mdxHeadings, ...componentApiTocData], @@ -31,7 +32,7 @@ export const getStaticProps = async () => { pageHeaderData: { categoryRoute: SidebarCategoryRoutes.COMPONENTS, githubUrl: "https://github.com/twilio-labs/paste/tree/main/packages/paste-core/components/ai-chat-log", - storybookUrl: "/?path=/story/components-chatlog--example-chat-log", + storybookUrl: "/?path=/story/components-ai-chat-log--example-ai-chat-log", }, }, }; diff --git a/packages/paste-website/src/pages/components/ai-chat-log/changelog.mdx b/packages/paste-website/src/pages/components/ai-chat-log/changelog.mdx index b78049f582..98cc2b70a7 100644 --- a/packages/paste-website/src/pages/components/ai-chat-log/changelog.mdx +++ b/packages/paste-website/src/pages/components/ai-chat-log/changelog.mdx @@ -23,13 +23,14 @@ export const getStaticProps = async () => { data: { ...packageJson, ...feature, + nameOverride: 'AI Chat Log', }, navigationData, mdxHeadings, pageHeaderData: { categoryRoute: SidebarCategoryRoutes.COMPONENTS, githubUrl: "https://github.com/twilio-labs/paste/tree/main/packages/paste-core/components/ai-chat-log", - storybookUrl: "/?path=/story/components-chatlog--example-chat-log", + storybookUrl: "/?path=/story/components-ai-chat-log--example-ai-chat-log", }, }, }; diff --git a/packages/paste-website/src/pages/components/ai-chat-log/index.mdx b/packages/paste-website/src/pages/components/ai-chat-log/index.mdx index b4a86406f7..deb44de867 100644 --- a/packages/paste-website/src/pages/components/ai-chat-log/index.mdx +++ b/packages/paste-website/src/pages/components/ai-chat-log/index.mdx @@ -58,13 +58,14 @@ export const getStaticProps = async () => { data: { ...packageJson, ...feature, + nameOverride: 'AI Chat Log', }, navigationData, mdxHeadings, pageHeaderData: { categoryRoute: SidebarCategoryRoutes.COMPONENTS, githubUrl: "https://github.com/twilio-labs/paste/tree/main/packages/paste-core/components/ai-chat-log", - storybookUrl: "/?path=/story/components-chatlog--example-chat-log", + storybookUrl: "/?path=/story/components-ai-chat-log--example-ai-chat-log", }, }, }; diff --git a/packages/paste-website/src/pages/components/chat-composer/index.mdx b/packages/paste-website/src/pages/components/chat-composer/index.mdx index 957a171fb2..c602ae4351 100644 --- a/packages/paste-website/src/pages/components/chat-composer/index.mdx +++ b/packages/paste-website/src/pages/components/chat-composer/index.mdx @@ -1,13 +1,13 @@ export const meta = { - title: 'Chat Composer', - package: '@twilio-paste/chat-composer', - description: 'A Chat Composer is an input made for users to type rich chat messages.', - slug: '/components/chat-composer/', + title: "Chat Composer", + package: "@twilio-paste/chat-composer", + description: "A Chat Composer is an input made for users to type rich chat messages.", + slug: "/components/chat-composer/", }; -import {Avatar} from '@twilio-paste/avatar'; -import {Anchor} from '@twilio-paste/anchor'; -import {Box} from '@twilio-paste/box'; +import { Avatar } from "@twilio-paste/avatar"; +import { Anchor } from "@twilio-paste/anchor"; +import { Box } from "@twilio-paste/box"; import { ChatAttachment, ChatAttachmentDescription, @@ -22,36 +22,56 @@ import { ChatMessageMetaItem, useChatLogger, ChatLogger, -} from '@twilio-paste/chat-log'; -import {ChatComposer} from '@twilio-paste/chat-composer'; -import Changelog from '@twilio-paste/chat-composer/CHANGELOG.md'; -import {SendIcon} from '@twilio-paste/icons/esm/SendIcon'; -import {DownloadIcon} from '@twilio-paste/icons/esm/DownloadIcon'; -import {HelpText} from '@twilio-paste/help-text'; -import {Callout, CalloutHeading, CalloutText} from '@twilio-paste/callout'; -import {Button} from '@twilio-paste/button'; -import {ButtonGroup} from '@twilio-paste/button-group'; -import {Stack} from '@twilio-paste/stack'; -import {$getRoot, $createParagraphNode, $createTextNode, ClearEditorPlugin} from '@twilio-paste/lexical-library'; - -import {SidebarCategoryRoutes} from '../../../constants'; +} from "@twilio-paste/chat-log"; +import { + ChatComposer, + ChatComposerContainer, + ChatComposerActionGroup, + ChatComposerAttachmentGroup, + ChatComposerAttachmentCard, + ChatComposerAttachmentLink, + ChatComposerAttachmentDescription, +} from "@twilio-paste/chat-composer"; +import Changelog from "@twilio-paste/chat-composer/CHANGELOG.md"; +import { SendIcon } from "@twilio-paste/icons/esm/SendIcon"; +import { AttachIcon } from "@twilio-paste/icons/esm/AttachIcon"; +import { DownloadIcon } from "@twilio-paste/icons/esm/DownloadIcon"; +import { HelpText } from "@twilio-paste/help-text"; +import { Callout, CalloutHeading, CalloutText } from "@twilio-paste/callout"; +import { Button } from "@twilio-paste/button"; +import { ButtonGroup } from "@twilio-paste/button-group"; +import { Stack } from "@twilio-paste/stack"; +import { Checkbox } from "@twilio-paste/checkbox"; +import { + $getRoot, + $createParagraphNode, + $createTextNode, + ClearEditorPlugin, + LexicalEditor, + CLEAR_EDITOR_COMMAND, +} from "@twilio-paste/lexical-library"; +import { useRef } from "react"; + +import { SidebarCategoryRoutes } from "../../../constants"; import { - ChatDialogExample, RichTextExample, MaxHeightExample, SendButtonPlugin, EnterKeySubmitPlugin, createNewMessage, -} from '../../../component-examples/ChatComposerExamples'; -import packageJson from '@twilio-paste/chat-composer/package.json'; -import ComponentPageLayout from '../../../layouts/ComponentPageLayout'; -import {getFeature, getNavigationData} from '../../../utils/api'; + ResponsiveContainedAttachmentsExample, + ContainedDisabledExample, + ContainedExample, +} from "../../../component-examples/ChatComposerExamples"; +import packageJson from "@twilio-paste/chat-composer/package.json"; +import ComponentPageLayout from "../../../layouts/ComponentPageLayout"; +import { getFeature, getNavigationData } from "../../../utils/api"; export default ComponentPageLayout; export const getStaticProps = async () => { const navigationData = await getNavigationData(); - const feature = await getFeature('Chat Composer'); + const feature = await getFeature("Chat Composer"); return { props: { data: { @@ -62,15 +82,28 @@ export const getStaticProps = async () => { mdxHeadings, pageHeaderData: { categoryRoute: SidebarCategoryRoutes.COMPONENTS, - githubUrl: 'https://github.com/twilio-labs/paste/tree/main/packages/paste-core/components/chat-composer', - storybookUrl: '/?path=/story/components-chat-composer--default', + githubUrl: "https://github.com/twilio-labs/paste/tree/main/packages/paste-core/components/chat-composer", + storybookUrl: "/?path=/story/components-chat-composer--default", }, }, }; }; - - {` { throw e } }} placeholder="Chat text" ariaLabel="A basic chat composer" />`} + + {` + { throw e } }} placeholder="Chat text" ariaLabel="A basic chat composer" /> + + + + +`} ## Guidelines @@ -78,106 +111,420 @@ export const getStaticProps = async () => { ### About Chat Composer A Chat Composer is an input made for users to type rich chat messages. Chat Composer is best used as one part of larger chat user interface to provide a seamless authoring experience. -Within the context of Paste, Chat Composer is most typically used alongside the [`Chat Log`](/components/chat-log) component. +Within the context of Paste, Chat Composer is most typically used alongside the [`Chat Log`](/components/chat-log) and [`AI Chat Log`](/components/ai-chat-log) components. + +### ChatComposer -### Accessibility +When referring to `ChatComposer` it is the rich text area only. You can use the `ChatComposer` component by itself, or use it within the `ChatComposerContainer` for consistent styling across chat features. -Chat Composer supports a variety of aria attributes which are passed into the content editable region of the component. +#### Accessibility -- If the surrounding UI includes a screen reader visible label reference the label element using `aria-labelledby`. +Chat Composer supports a variety of ARIA attributes which are passed into the content editable region of the component. + +- If the surrounding UI includes a screen reader visible label, reference the label element using `aria-labelledby`. - If the surrounding UI does not include a screen reader visible label, use `aria-label` to describe the input. -- If the surrounding UI includes additional help or error text use `aria-describedby` to reference the associated element. +- If the surrounding UI includes additional help or error text, use `aria-describedby` to reference the associated element. -### Lexical and plugins +#### Lexical and plugins Chat Composer is built on top of the [Lexical](https://lexical.dev) editor. Lexical is extensible and follows a declarative approach to configuration via JSX. Developers can leverage a wide variety of [existing plugins](https://github.com/twilio-labs/paste/blob/main/packages/paste-libraries/lexical/src/index.tsx) via the `@twilio-paste/lexical-library` package or other sources. Alternatively, developers can write their own custom plugin logic. Plugins are provided to the Chat Composer via the `children` prop. -#### Auto Link Plugin +##### Auto Link Plugin Chat Composer uses a custom [`AutoLinkPlugin`](https://github.com/twilio-labs/paste/blob/main/packages/paste-core/components/chat-composer/src/AutoLinkPlugin.tsx) internally which you can see being configured [here](https://github.com/twilio-labs/paste/blob/main/packages/paste-core/components/chat-composer/src/ChatComposer.tsx#L116) as a JSX child. +### ChatComposer component suite + +The Chat Composer component suite offers a variety of components designed to enhance and enrich the chat experience. Each element plays a crucial role in maintaining a consistent and cohesive styling, ensuring a seamless user interaction. The available components include: + +- **ChatComposerContainer**: The primary container that houses the entire chat composer interface. +- **ChatComposerActionGroup**: A collection of buttons and controls, allowing users to perform various actions. +- **ChatComposerAttachmentGroup**: Groups multiple attachments together in responsive columns. +- **ChatComposerAttachmentCard**: A card-like component for showcasing attachment previews, making it easy for users to view details at a glance with the option to set the icon for the attachment. +- **ChatComposerAttachmentDescription**: Provides a description or additional information about an attachment, adding context for the user. +- **ChatComposerAttachmentLink**: Creates clickable links for attachments, facilitating easy access and interaction. + ## Examples -### With placeholder +### ChatComposer with placeholder Set a placeholder value using a `placeholder` prop. - + {` { throw e } }} placeholder="Chat text" ariaLabel="A placeholder chat composer" />`} -### With initial value +### Chat Composer with initial value Set an initial value using an `initialValue` prop. This prop is limited to providing single line strings. For more complicated initial values interact with the Lexical API directly using the `config` prop and `editorState` callback. - + {` { throw e } }} initialValue="This is my initial value" ariaLabel="An initial value chat composer" />`} -### With max height +### Chat Composer with max height Restrict the height of the composer using a `maxHeight` prop. - + {MaxHeightExample} -### With rich text +### Chat Composer with rich text Set a rich text value using one of the Lexical formatting APIs such as [`toggleFormat`](https://lexical.dev/docs/api/classes/lexical.TextNode#toggleformat) - + {RichTextExample} -### With Chat Log +### Chat Composer with responsive attachments -Use Chat Composer alongside other Paste components such as [Chat Log](/components/chat-log) to build more complex chat UI. +For responsive attachment cards when using the Chat Composer component suite, use the `columns` prop. + {ResponsiveContainedAttachmentsExample} + + +### Chat Composer contained variant + +The `ChatComposerContainer` component has 2 variants, `default` and `contained`. + + + {ContainedExample} + + +### Chat Composer disabled contained variant + +When the container is disabled, styling is applied to the container component. The disabled state is managed at the implementation level. If action buttons are included, their disabled state must also be managed by the developer. + + - {ChatDialogExample} + {ContainedDisabledExample} -### Adding interactivity with plugins +### Chat Composer with Chat Log -In the above example, we're using 3 Lexical plugins: `ClearEditorPlugin` that is provided by Lexical, and 2 custom plugins, `SendButtonPlugin` and `EnterKeySubmitPlugin`. We also keep track of the content provided to the composer via the `onChange` handler. Together we can add custom interactivity such as: +Use Chat Composer alongside other Paste components such as [Chat Log](/components/chat-log) to build more complex chat UI. + + { + const { chats, push } = useChatLogger( + { + content: ( + + Today + + Chat Started・3:34 PM + + + ), + }, + { + variant: "inbound", + content: ( + + Quisque ullamcorper ipsum vitae lorem euismod sodales. + + }> + Document-FINAL.doc + 123 MB + + + + Gibby Radki ・ 5:04 PM + + + ), + }, + { + content: ( + + Lauren Gardner has joined the chat ・ 4:26 PM + + ), + }, + { + variant: "inbound", + content: ( + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. + + + + Lauren Gardner ・ 4:30 PM + + + + ), + }, + ); + const [message, setMessage] = React.useState(""); + const [mounted, setMounted] = React.useState(false); + const loggerRef = React.useRef(null); + const scrollerRef = React.useRef(null); + React.useEffect(() => { + setMounted(true); + }, []); + React.useEffect(() => { + if (!mounted || !loggerRef.current) return; + scrollerRef.current?.scrollTo({ top: loggerRef.current.scrollHeight, behavior: "smooth" }); + }, [chats, mounted]); + const handleComposerChange = (editorState): void => { + editorState.read(() => { + const text = $getRoot().getTextContent(); + setMessage(text); + }); + }; + const submitMessage = (): void => { + if (message === "") return; + push(createNewMessage(message)); + }; + const editorInstanceRef = React.useRef(null); + return ( + + + + + + + { + throw error; + }, + }} + ariaLabel="Message" + placeholder="Type here..." + onChange={handleComposerChange} + editorInstanceRef={editorInstanceRef} + > + + + + + + + + + + + ); +}:`} +/> + +### Chat Composer with AI Chat Log + +Use Chat Composer alongside other Paste components such as [AI Chat Log](/components/ai-chat-log) to build more complex chat UI. For the AI experience be sure to use the `contained` variant. + + { + const { aiChats, push } = useAIChatLogger( + { + variant: "user", + content: ( + + Gibby Radki + Hi, I am getting errors codes when sending an SMS. + + ), + }, + { + variant: "bot", + content: ( + + Good Bot + + Error codes can be returned from various parts of the process. What error codes are you encountering? + + + + + + + + + + + Is this helpful? + + + + + + ), + } + ); + const [message, setMessage] = React.useState(""); + const [mounted, setMounted] = React.useState(false); + const loggerRef = React.useRef(null); + const scrollerRef = React.useRef(null); + React.useEffect(() => { + setMounted(true); + }, []); + React.useEffect(() => { + if (!mounted || !loggerRef.current) return; + const scrollPosition: any = scrollerRef.current; + const scrollHeight: any = loggerRef.current; + scrollPosition?.scrollTo({ top: scrollHeight.scrollHeight, behavior: "smooth" }); + }, [aiChats, mounted]); + const handleComposerChange = (editorState): void => { + editorState.read(() => { + const text = $getRoot().getTextContent(); + setMessage(text); + }); + }; + const submitMessage = (): void => { + if (message === "") return; + push({ + variant: "user", + content: ( + + Gibby Radki + {message} + + ), + }); + }; + const editorInstanceRef = React.useRef(null); + return ( + + + + + + { + throw error; + }, + }} + ariaLabel="Message" + placeholder="Type here..." + onChange={handleComposerChange} + editorInstanceRef={editorInstanceRef} + > + + + + + + + + + + ); +};`} +/> + +### Chat Composer with AI Chat Logger + +Use Chat Composer alongside other Paste components such as [Chat Log](/components/ai-chat-log) to build more complex chat UI. + +### Adding interactivity to ChatComposer with plugins + +In the above example, we're using 2 Lexical plugins: `ClearEditorPlugin` that is provided by Lexical, and a custom plugin, `EnterKeySubmitPlugin`. We also keep track of the content provided to the composer via the `onChange` handler. Together we can add custom interactivity such as: - Clear the editor on button click using `ClearEditorPlugin` -- Submit on enter key press using `EnterKeySubmitPlugin` -- Submit on button click using `SendButtonPlugin` +- Submit on enter key press and submit button handler using `EnterKeySubmitPlugin` Plugins are functions that must be children of the `ChatComposer` component, so that they can access the Composer context. @@ -200,7 +547,7 @@ const handleComposerChange = (editorState: EditorState): void => { The `ClearEditorPlugin` supplied by Lexical allows you to build functionality into the composer that will clear the composer content when a certain action is performed. -When passed as a child to `ChatComposer`, it will automatically register a `CLEAR_EDITOR_COMMAND`. You can then dispatch this command from elsewhere to clear the composer content. In the example, we created 2 plugins: `SendButtonPlugin` and `EnterKeySubmitPlugin` which both dispatch the `CLEAR_EDITOR_COMMAND`, and clear the composer content as a result. +When passed as a child to `ChatComposer`, it will automatically register a `CLEAR_EDITOR_COMMAND`. You can then dispatch this command from elsewhere to clear the composer content. In the example, we created a plugin: `EnterKeySubmitPlugin` which dispatch the `CLEAR_EDITOR_COMMAND`, and clear the composer content as a result. ```jsx @@ -208,38 +555,63 @@ When passed as a child to `ChatComposer`, it will automatically register a `CLEA ``` +#### Accessing Lexical State + +To access the Lexical state out of the context we make use of the [``](https://lexical.dev/docs/react/plugins#lexicaleditorrefplugin) provided by the library. In order to use this you must create a ref to the LexicalEditor instance and pass it to the `ChatComposer` component. + +```jsx +export const ChatComposerImpl = () => { + const editorInstanceRef = React.useRef(null); + + return ( + + + + ); +}; +``` + #### Custom plugins -`SendButtonPlugin` and `EnterKeySubmitPlugin` are custom plugins that submit a user message and clear the composer content. They first must be passed to the `ChatComposer` as a child. +`EnterKeySubmitPlugin` is a custom plugin that submits a user message and clear the composer content when the enter key is pressed. They first must be passed to the `ChatComposer` as a child. ```jsx - ``` -Once "registered" as children of `ChatComposer`, the plugins gain access to the composer context and can dispatch commands. They can also return JSX to be rendered into the composer. Take the `SendButtonPlugin` as an example: +Once "registered" as children of `ChatComposer`, the plugins gain access to the composer context and can dispatch commands. They can also return JSX to be rendered into the composer. It is recommended to avoid putting buttons in the Composer, instead use the container with `ChatComposerActionGroup`: ```jsx -export const SendButtonPlugin = ({onClick}: {onClick: () => void}): JSX.Element => { +export const EnterKeySubmitPlugin = ({ onKeyDown }: { onKeyDown: () => void }): null => { // get the editor from the composer context const [editor] = useLexicalComposerContext(); - // an event handler called from custom UI can the interact with the editor to perform certain actions - const handleSend = (): void => { - onClick(); - editor.dispatchCommand(CLEAR_EDITOR_COMMAND, undefined); - }; - - return ( - - - + const handleEnterKey = React.useCallback( + (event: KeyboardEvent) => { + const { shiftKey, ctrlKey } = event; + if (shiftKey || ctrlKey) return false; + event.preventDefault(); + event.stopPropagation(); + onKeyDown(); + editor.dispatchCommand(CLEAR_EDITOR_COMMAND, undefined); + return true; + }, + [editor, onKeyDown] ); + + React.useEffect(() => { + // register the command to be dispatched when the enter key is pressed + return editor.registerCommand(KEY_ENTER_COMMAND, handleEnterKey, COMMAND_PRIORITY_HIGH); + }, [editor, handleEnterKey]); + return null; }; ```