From a6f0224c009f85d473bd7bb6edae73b87a504a8f Mon Sep 17 00:00:00 2001 From: Kristian Antrobus Date: Thu, 20 Jun 2024 12:31:54 -0500 Subject: [PATCH 01/29] feat(composer): wip base elements --- .../src/ChatComposerAttatchmentGroup.tsx | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 packages/paste-core/components/chat-composer/src/ChatComposerAttatchmentGroup.tsx diff --git a/packages/paste-core/components/chat-composer/src/ChatComposerAttatchmentGroup.tsx b/packages/paste-core/components/chat-composer/src/ChatComposerAttatchmentGroup.tsx new file mode 100644 index 0000000000..957244364c --- /dev/null +++ b/packages/paste-core/components/chat-composer/src/ChatComposerAttatchmentGroup.tsx @@ -0,0 +1,32 @@ +import type { BoxProps } from "@twilio-paste/box"; +import { Box, safelySpreadBoxProps } from "@twilio-paste/box"; +import * as React from "react"; + +export interface ChatComposerAttatchmentGroupProps { + children?: React.ReactNode; + /** + * Overrides the default element name to apply unique styles with the Customization Provider + * @default 'CHAT_COMPOSER_ATTATCHMENT_GROUP' + * @type {BoxProps['element']} + * @memberof PageHeaderProps + */ + element?: BoxProps["element"]; +} + +export const ChatComposerAttatchmentGroup = React.forwardRef( + ({ element = "CHAT_COMPOSER_ATTATCHMENT_GROUP", children, ...props }, ref) => ( + + {children} + + ), +); + +ChatComposerAttatchmentGroup.displayName = "ChatComposerAttatchmentGroup"; From 8b934dd10a4b82dcbcaa07e954348f2c0f59eaae Mon Sep 17 00:00:00 2001 From: Kristian Antrobus Date: Thu, 20 Jun 2024 15:28:35 -0500 Subject: [PATCH 02/29] feat(composer): addition of all attatchment components --- .../chat-composer/src/ChatComposer.tsx | 3 +- .../src/ChatComposerAttachment.tsx | 49 +++++++++++++++++++ .../src/ChatComposerAttatchmentGroup.tsx | 32 ------------ .../src/ChatComposerContainer.tsx | 1 + .../src/ToggleDisabledPlugin.tsx | 19 +++++++ 5 files changed, 71 insertions(+), 33 deletions(-) create mode 100644 packages/paste-core/components/chat-composer/src/ChatComposerAttachment.tsx delete mode 100644 packages/paste-core/components/chat-composer/src/ChatComposerAttatchmentGroup.tsx create mode 100644 packages/paste-core/components/chat-composer/src/ToggleDisabledPlugin.tsx diff --git a/packages/paste-core/components/chat-composer/src/ChatComposer.tsx b/packages/paste-core/components/chat-composer/src/ChatComposer.tsx index 331fec1ee9..082b2d6cf6 100644 --- a/packages/paste-core/components/chat-composer/src/ChatComposer.tsx +++ b/packages/paste-core/components/chat-composer/src/ChatComposer.tsx @@ -47,6 +47,7 @@ import { PlaceholderWrapper } from "./PlaceholderWrapper"; import { ToggleEditablePlugin } from "./ToggleEditablePlugin"; import { baseConfig, renderInitialText } from "./helpers"; import { chatComposerLexicalStyles } from "./styles"; +import { ThemeShape } from "@twilio-paste/theme"; export interface ChatComposerProps extends Omit { children?: LexicalComposerProps["children"]; @@ -133,7 +134,7 @@ export const ChatComposer = React.forwardRef( editorInstanceRef, ...props }, - ref, + ref ) => { const { setIsDisabled } = React.useContext(ChatComposerContext); diff --git a/packages/paste-core/components/chat-composer/src/ChatComposerAttachment.tsx b/packages/paste-core/components/chat-composer/src/ChatComposerAttachment.tsx new file mode 100644 index 0000000000..5f75a602c0 --- /dev/null +++ b/packages/paste-core/components/chat-composer/src/ChatComposerAttachment.tsx @@ -0,0 +1,49 @@ +import type { BoxElementProps } from "@twilio-paste/box"; +import { Box } from "@twilio-paste/box"; +import { MediaBody, MediaFigure, MediaObject } from "@twilio-paste/media-object"; +import { Stack } from "@twilio-paste/stack"; +import type { HTMLPasteProps } from "@twilio-paste/types"; +import * as React from "react"; + +export interface ChatComposerAttachmentProps extends HTMLPasteProps<"div"> { + children: NonNullable; + /** + * Overrides the default element name to apply unique styles with the Customization Provider + * + * @default "CHAT_COMPOSER_ATTACHMENT" + * @type {BoxProps["element"]} + * @memberof ChatComposerAttachmentProps + */ + element?: BoxElementProps["element"]; + /** + * Pass an icon to use for the attachment message. DownloadIcon recommended + * + * @default null + * @type {NonNullable} + * @memberof ChatComposerAttachmentProps + */ + attachmentIcon: NonNullable; +} + +const ChatComposerAttachment = React.forwardRef( + ({ children, element = "CHAT_COMPOSER_ATTACHMENT", attachmentIcon, ...props }, ref) => { + return ( + + + + {attachmentIcon} + + + + + {children} + + + + ); + }, +); + +ChatComposerAttachment.displayName = "ChatComposerAttachment"; + +export { ChatComposerAttachment }; diff --git a/packages/paste-core/components/chat-composer/src/ChatComposerAttatchmentGroup.tsx b/packages/paste-core/components/chat-composer/src/ChatComposerAttatchmentGroup.tsx deleted file mode 100644 index 957244364c..0000000000 --- a/packages/paste-core/components/chat-composer/src/ChatComposerAttatchmentGroup.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import type { BoxProps } from "@twilio-paste/box"; -import { Box, safelySpreadBoxProps } from "@twilio-paste/box"; -import * as React from "react"; - -export interface ChatComposerAttatchmentGroupProps { - children?: React.ReactNode; - /** - * Overrides the default element name to apply unique styles with the Customization Provider - * @default 'CHAT_COMPOSER_ATTATCHMENT_GROUP' - * @type {BoxProps['element']} - * @memberof PageHeaderProps - */ - element?: BoxProps["element"]; -} - -export const ChatComposerAttatchmentGroup = React.forwardRef( - ({ element = "CHAT_COMPOSER_ATTATCHMENT_GROUP", children, ...props }, ref) => ( - - {children} - - ), -); - -ChatComposerAttatchmentGroup.displayName = "ChatComposerAttatchmentGroup"; diff --git a/packages/paste-core/components/chat-composer/src/ChatComposerContainer.tsx b/packages/paste-core/components/chat-composer/src/ChatComposerContainer.tsx index dc31b1d14d..1e9974c6fc 100644 --- a/packages/paste-core/components/chat-composer/src/ChatComposerContainer.tsx +++ b/packages/paste-core/components/chat-composer/src/ChatComposerContainer.tsx @@ -1,5 +1,6 @@ import type { BoxProps, BoxStyleProps } from "@twilio-paste/box"; import { Box, safelySpreadBoxProps } from "@twilio-paste/box"; +import { ThemeShape } from "@twilio-paste/theme"; import * as React from "react"; import { ChatComposerContext } from "./ChatComposerContext"; diff --git a/packages/paste-core/components/chat-composer/src/ToggleDisabledPlugin.tsx b/packages/paste-core/components/chat-composer/src/ToggleDisabledPlugin.tsx new file mode 100644 index 0000000000..d996b3c118 --- /dev/null +++ b/packages/paste-core/components/chat-composer/src/ToggleDisabledPlugin.tsx @@ -0,0 +1,19 @@ +import { useLexicalComposerContext } from "@twilio-paste/lexical-library"; +import * as React from "react"; +import { ChatComposerContext } from "./ChatComposerContext"; + +export const ToggleEditablePlugin: React.FC<{ disabled: boolean | undefined }> = ({ disabled }): null => { + const [editor] = useLexicalComposerContext(); + const { setIsDisabled } = React.useContext(ChatComposerContext); + + React.useEffect(() => { + if (disabled !== undefined) { + !!setIsDisabled && setIsDisabled(disabled); + editor.setEditable(!disabled); + } + }, [disabled]); + + return null; +}; + +ToggleEditablePlugin.displayName = "ToggleEditablePlugin"; From 0829a26af5a4b7a042865f92831d9046f4cbf779 Mon Sep 17 00:00:00 2001 From: Kristian Antrobus Date: Thu, 20 Jun 2024 15:59:54 -0500 Subject: [PATCH 03/29] feat(composer): lint fix --- .../paste-core/components/chat-composer/src/ChatComposer.tsx | 2 +- .../components/chat-composer/src/ToggleDisabledPlugin.tsx | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/paste-core/components/chat-composer/src/ChatComposer.tsx b/packages/paste-core/components/chat-composer/src/ChatComposer.tsx index 082b2d6cf6..608170689a 100644 --- a/packages/paste-core/components/chat-composer/src/ChatComposer.tsx +++ b/packages/paste-core/components/chat-composer/src/ChatComposer.tsx @@ -38,6 +38,7 @@ import type { OnChangeFunction, } from "@twilio-paste/lexical-library"; import { StylingGlobals } from "@twilio-paste/styling-library"; +import { ThemeShape } from "@twilio-paste/theme"; import merge from "deepmerge"; import * as React from "react"; @@ -47,7 +48,6 @@ import { PlaceholderWrapper } from "./PlaceholderWrapper"; import { ToggleEditablePlugin } from "./ToggleEditablePlugin"; import { baseConfig, renderInitialText } from "./helpers"; import { chatComposerLexicalStyles } from "./styles"; -import { ThemeShape } from "@twilio-paste/theme"; export interface ChatComposerProps extends Omit { children?: LexicalComposerProps["children"]; diff --git a/packages/paste-core/components/chat-composer/src/ToggleDisabledPlugin.tsx b/packages/paste-core/components/chat-composer/src/ToggleDisabledPlugin.tsx index d996b3c118..7fdaaafe81 100644 --- a/packages/paste-core/components/chat-composer/src/ToggleDisabledPlugin.tsx +++ b/packages/paste-core/components/chat-composer/src/ToggleDisabledPlugin.tsx @@ -1,5 +1,6 @@ import { useLexicalComposerContext } from "@twilio-paste/lexical-library"; import * as React from "react"; + import { ChatComposerContext } from "./ChatComposerContext"; export const ToggleEditablePlugin: React.FC<{ disabled: boolean | undefined }> = ({ disabled }): null => { @@ -8,7 +9,9 @@ export const ToggleEditablePlugin: React.FC<{ disabled: boolean | undefined }> = React.useEffect(() => { if (disabled !== undefined) { - !!setIsDisabled && setIsDisabled(disabled); + if (setIsDisabled !== undefined) { + setIsDisabled(disabled); + } editor.setEditable(!disabled); } }, [disabled]); From ea4c801735546e15f7975b85823aa00e30ec2955 Mon Sep 17 00:00:00 2001 From: Kristian Antrobus Date: Fri, 21 Jun 2024 09:58:04 -0500 Subject: [PATCH 04/29] feat(composer): fix disabled styling --- .../paste-core/components/chat-composer/src/ChatComposer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/paste-core/components/chat-composer/src/ChatComposer.tsx b/packages/paste-core/components/chat-composer/src/ChatComposer.tsx index 608170689a..cbfa0a31b0 100644 --- a/packages/paste-core/components/chat-composer/src/ChatComposer.tsx +++ b/packages/paste-core/components/chat-composer/src/ChatComposer.tsx @@ -134,7 +134,7 @@ export const ChatComposer = React.forwardRef( editorInstanceRef, ...props }, - ref + ref, ) => { const { setIsDisabled } = React.useContext(ChatComposerContext); From 441ba425eada1b98c4c6e9017881d998a5f55bf7 Mon Sep 17 00:00:00 2001 From: Kristian Antrobus Date: Tue, 25 Jun 2024 13:40:56 -0500 Subject: [PATCH 05/29] feat(composer): responsive columns and JS doc --- .../stories/container.stories.tsx | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/packages/paste-core/components/chat-composer/stories/container.stories.tsx b/packages/paste-core/components/chat-composer/stories/container.stories.tsx index 399dbb7a46..dc75854fae 100644 --- a/packages/paste-core/components/chat-composer/stories/container.stories.tsx +++ b/packages/paste-core/components/chat-composer/stories/container.stories.tsx @@ -156,6 +156,62 @@ export const ResponsiveContainedVariantWithAttachments: StoryFn = () => { ResponsiveContainedVariantWithAttachments.storyName = "Responsive Contained Variant with Attachments"; +export const ResponsiveContainedVariantWithAttatchments: StoryFn = () => { + return ( + + + + + + + + {}}> + }> + Document-FINAL.doc + 123 MB + + + {}}> + }> + Document-FINAL.doc + 123 MB + + + {}}> + }> + Document-FINAL.doc + 123 MB + + + {}}> + }> + Document-FINAL.doc + 123 MB + + + {}}> + }> + Document-FINAL.doc + 123 MB + + + {}}> + }> + Document-FINAL.doc + 123 MB + + + + + ); +}; + +ResponsiveContainedVariantWithAttatchments.storyName = "Responsive Contained Variant with Attatchments"; + export const ContainedDisabledVariant: StoryFn = () => { const [isDisabled, setIsDisabled] = React.useState(true); return ( From 876088c585d85b3f0b743ec347c3f31b651fce15 Mon Sep 17 00:00:00 2001 From: Kristian Antrobus Date: Wed, 26 Jun 2024 10:46:53 -0500 Subject: [PATCH 06/29] feat(composer-docs): wip examples --- .../ChatComposerExamples.tsx | 158 +++++++++-- .../pages/components/chat-composer/index.mdx | 256 +++++++++++++----- 2 files changed, 326 insertions(+), 88 deletions(-) diff --git a/packages/paste-website/src/component-examples/ChatComposerExamples.tsx b/packages/paste-website/src/component-examples/ChatComposerExamples.tsx index 9a18c97a30..bfdebeac85 100644 --- a/packages/paste-website/src/component-examples/ChatComposerExamples.tsx +++ b/packages/paste-website/src/component-examples/ChatComposerExamples.tsx @@ -111,7 +111,7 @@ export const SendButtonPlugin = ({ onClick }: { onClick: () => void }): JSX.Elem ); }; -export const EnterKeySubmitPlugin = ({ onKeyDown }: { onKeyDown: () => void }): null => { +export const EnterKeyWithSubmitPlugin = React.forwardRef(({ onKeyDown }: { onKeyDown: () => void }, ref): null => { const [editor] = useLexicalComposerContext(); const handleEnterKey = React.useCallback( @@ -127,11 +127,18 @@ export const EnterKeySubmitPlugin = ({ onKeyDown }: { onKeyDown: () => void }): [editor, onKeyDown], ); + React.useImperativeHandle(ref, () => ({ + handleSubmit() { + onKeyDown(); + editor.dispatchCommand(CLEAR_EDITOR_COMMAND, undefined); + }, + })); + React.useEffect(() => { return editor.registerCommand(KEY_ENTER_COMMAND, handleEnterKey, COMMAND_PRIORITY_HIGH); }, [editor, handleEnterKey]); return null; -}; +}); export const ChatDialogExample = `const ChatDialog = () => { const {chats, push} = useChatLogger( @@ -211,6 +218,8 @@ export const ChatDialogExample = `const ChatDialog = () => { push(createNewMessage(message)); }; + const submitPluginRef = useRef(); + return ( @@ -221,31 +230,138 @@ 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} + > + + + + + + + + ); }; render()`.trim(); + +export const ResponsiveContainedAttachmentsExample = `const ResponsiveContainedAttachmentsExample = () => { + return ( + + { + throw e; + }, + }} + /> + + + + + + {}}> + }> + Document-FINAL.doc + 123 MB + + + {}}> + }> + Document-FINAL.doc + 123 MB + + + {}}> + }> + Document-FINAL.doc + 123 MB + + + {}}> + }> + Document-FINAL.doc + 123 MB + + + {}}> + }> + Document-FINAL.doc + 123 MB + + + {}}> + }> + Document-FINAL.doc + 123 MB + + + + + ) +} + +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(); 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..b0209b0562 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,50 @@ 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, + ChatComposerAttachment, + 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 } from "@twilio-paste/lexical-library"; +import { useRef } from "react"; + +import { SidebarCategoryRoutes } from "../../../constants"; import { ChatDialogExample, RichTextExample, MaxHeightExample, SendButtonPlugin, - EnterKeySubmitPlugin, + EnterKeyWithSubmitPlugin, 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 +} 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 +76,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 @@ -80,7 +107,11 @@ export const getStaticProps = async () => { 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. -### Accessibility +### ChatComposer + +When refering to `ChatComposer` it is the rich text area only. You can use the `ChatComposer` component only, or use it within the `ChatComposerContainer` for consistent styling across chat features. + +#### Accessibility Chat Composer supports a variety of aria attributes which are passed into the content editable region of the component. @@ -88,53 +119,118 @@ Chat Composer supports a variety of aria attributes which are passed into the co - 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. -### 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. +### Chat Composer 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 action buttons and controls, allowing users to perform various actions within the chat composer. +- **ChatComposerAttachmentGroup**: Groups multiple attachments together in responsive columns, ensuring a tidy and organized display within the chat composer. +- **ChatComposerAttachmentCard**: A card-like component for showcasing attachment previews, making it easy for users to view details at a glance. +- **ChatComposerAttachment**: Displays file attachments, witht eh 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. + +#### Variants + +ChatComposerContainer has a `default` and `contained` variant. + ## 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 +### ChatComposer 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 +### ChatCompoer with max height Restrict the height of the composer using a `maxHeight` prop. - + {MaxHeightExample} -### With rich text +### ChatComposer 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 + +Chat composer cmoponents suite demonstrating responsive attachments + + + {ResponsiveContainedAttachmentsExample} + + +### 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 individually. + + + {ContainedDisabledExample} + + +### Chat Composer with Chat Log Use Chat Composer alongside other Paste components such as [Chat Log](/components/chat-log) to build more complex chat UI. @@ -160,24 +256,35 @@ Use Chat Composer alongside other Paste components such as [Chat Log](/component Button, SendIcon, SendButtonPlugin, - EnterKeySubmitPlugin, + EnterKeyWithSubmitPlugin, createNewMessage, $getRoot, useChatLogger, DownloadIcon, ClearEditorPlugin, + ChatComposerContainer, + ChatComposerActionGroup, + SendIcon, + AttachIcon, + useRef, }} > {ChatDialogExample} -### Adding interactivity with plugins +### 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. + + -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: + +### 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, `EnterKeyWithSubmitPlugin`. 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 `EnterKeyWithSubmitPlugin` Plugins are functions that must be children of the `ChatComposer` component, so that they can access the Composer context. @@ -200,7 +307,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: `EnterKeyWithSubmitPlugin` which dispatch the `CLEAR_EDITOR_COMMAND`, and clear the composer content as a result. ```jsx @@ -210,37 +317,52 @@ When passed as a child to `ChatComposer`, it will automatically register a `CLEA #### 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. +`EnterKeyWithSubmitPlugin` 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 EnterKeyWithSubmitPlugin = React.forwardRef(({ onKeyDown }: { onKeyDown: () => void }, ref): 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(); + // uses the event to clear input provided by the `ClearEditorPlugin` from Lexical + editor.dispatchCommand(CLEAR_EDITOR_COMMAND, undefined); + return true; + }, + [editor, onKeyDown] ); -}; + + // In order to access the Lexical editor context you must use a function within the plugin. This is called by the submit button in the ChatComposerActionGroup + React.useImperativeHandle(ref, () => ({ + handleSubmit() { + onKeyDown(); + editor.dispatchCommand(CLEAR_EDITOR_COMMAND, undefined); + }, + })); + + 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; +}); ``` -Here we're rendering a button that when clicked can call a callback function, and dispatch the `CLEAR_EDITOR_COMMAND` for the `ClearEditorPlugin` respond to. We use it to add a new chat message in the chat log, and then clear the composer ready for the next message to be typed. +Here we're rendering a button that when clicked can call a callback function, and dispatch the `CLEAR_EDITOR_COMMAND` for the `ClearEditorPlugin` respond to. We use it to add a new chat message in the chat log, and then clear the composer ready for the next message to be typed., ChatComposer, ChatComposerContainerimport { Composer } from "../../../components/assistant/AssistantLayout";import { add } from "lodash"; +import { Logger } from "winston"; + From e76ef9b4f055122d964bb0138bae4d257b104828 Mon Sep 17 00:00:00 2001 From: Kristian Antrobus Date: Fri, 28 Jun 2024 12:58:02 -0500 Subject: [PATCH 07/29] feat(composer): add new plugin to access state --- .../components/ai-chat-log/stories/composer.stories.tsx | 1 + 1 file changed, 1 insertion(+) 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..5f39e24205 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 @@ -20,6 +20,7 @@ import { } from "@twilio-paste/lexical-library"; import * as React from "react"; +import { AttachIcon } from "@twilio-paste/icons/esm/AttachIcon"; import { AIChatLog, AIChatLogger, From a05ed0c30ab4af52a5883a0c46bb296438588977 Mon Sep 17 00:00:00 2001 From: Kristian Antrobus Date: Fri, 28 Jun 2024 13:05:43 -0500 Subject: [PATCH 08/29] feat(composer): fix lint issues --- .../components/ai-chat-log/stories/composer.stories.tsx | 1 - 1 file changed, 1 deletion(-) 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 5f39e24205..12b15ddb3e 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 @@ -20,7 +20,6 @@ import { } from "@twilio-paste/lexical-library"; import * as React from "react"; -import { AttachIcon } from "@twilio-paste/icons/esm/AttachIcon"; import { AIChatLog, AIChatLogger, From 4713bed4262c56536a8bc386266fff36d10f7a19 Mon Sep 17 00:00:00 2001 From: Kristian Antrobus Date: Fri, 28 Jun 2024 14:17:56 -0500 Subject: [PATCH 09/29] feat(docs-composer): added in stories for log types --- .../chat-composer/stories/logs.stories.tsx | 1 - .../ChatComposerExamples.tsx | 66 ++-- .../pages/components/chat-composer/index.mdx | 340 ++++++++++++++---- 3 files changed, 302 insertions(+), 105 deletions(-) diff --git a/packages/paste-core/components/chat-composer/stories/logs.stories.tsx b/packages/paste-core/components/chat-composer/stories/logs.stories.tsx index 30c13c006d..2298f691a2 100644 --- a/packages/paste-core/components/chat-composer/stories/logs.stories.tsx +++ b/packages/paste-core/components/chat-composer/stories/logs.stories.tsx @@ -372,7 +372,6 @@ export const AIChatLogComposer = (): React.ReactNode => { - => { }; }; -export const SendButtonPlugin = ({ onClick }: { onClick: () => void }): JSX.Element => { - const [editor] = useLexicalComposerContext(); - - const handleSend = (): void => { - onClick(); - editor.dispatchCommand(CLEAR_EDITOR_COMMAND, undefined); - }; - - return ( - - - - ); -}; - -export const EnterKeyWithSubmitPlugin = React.forwardRef(({ onKeyDown }: { onKeyDown: () => void }, ref): null => { +export const EnterKeySubmitPlugin = ({ onKeyDown }: { onKeyDown: () => void }): null => { const [editor] = useLexicalComposerContext(); const handleEnterKey = React.useCallback( @@ -127,21 +107,14 @@ export const EnterKeyWithSubmitPlugin = React.forwardRef(({ onKeyDown }: { onKey [editor, onKeyDown], ); - React.useImperativeHandle(ref, () => ({ - handleSubmit() { - onKeyDown(); - editor.dispatchCommand(CLEAR_EDITOR_COMMAND, undefined); - }, - })); - React.useEffect(() => { return editor.registerCommand(KEY_ENTER_COMMAND, handleEnterKey, COMMAND_PRIORITY_HIGH); }, [editor, handleEnterKey]); return null; -}); +}; export const ChatDialogExample = `const ChatDialog = () => { - const {chats, push} = useChatLogger( + const { chats, push } = useChatLogger( { content: ( @@ -153,7 +126,7 @@ export const ChatDialogExample = `const ChatDialog = () => { ), }, { - variant: 'inbound', + variant: "inbound", content: ( Quisque ullamcorper ipsum vitae lorem euismod sodales. @@ -177,7 +150,7 @@ export const ChatDialogExample = `const ChatDialog = () => { ), }, { - variant: 'inbound', + variant: "inbound", content: ( Lorem ipsum dolor sit amet, consectetur adipiscing elit. @@ -189,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); @@ -203,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) => { @@ -214,11 +187,15 @@ export const ChatDialogExample = `const ChatDialog = () => { }; const submitMessage = () => { - if (message === '') return; + if (message === "") return; push(createNewMessage(message)); }; - const submitPluginRef = useRef(); + const editorRef = React.useRef(null); + + React.useEffect(() => { + console.log(editorRef.current); + }, [editorRef.current]); return ( @@ -230,6 +207,7 @@ export const ChatDialogExample = `const ChatDialog = () => { borderWidth="borderWidth0" borderTopWidth="borderWidth10" borderColor="colorBorderWeak" + columnGap="space30" paddingX="space70" paddingTop="space50" > @@ -237,7 +215,7 @@ export const ChatDialogExample = `const ChatDialog = () => { { throw error; }, @@ -245,15 +223,23 @@ export const ChatDialogExample = `const ChatDialog = () => { ariaLabel="Message" placeholder="Type here..." onChange={handleComposerChange} + editorInstanceRef={editorRef} > - + - 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 b0209b0562..3d4d35d3e5 100644 --- a/packages/paste-website/src/pages/components/chat-composer/index.mdx +++ b/packages/paste-website/src/pages/components/chat-composer/index.mdx @@ -43,19 +43,25 @@ 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 } from "@twilio-paste/lexical-library"; +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, - EnterKeyWithSubmitPlugin, + EnterKeySubmitPlugin, createNewMessage, ResponsiveContainedAttachmentsExample, - ContainedDisabledExample + ContainedDisabledExample, } from "../../../component-examples/ChatComposerExamples"; import packageJson from "@twilio-paste/chat-composer/package.json"; import ComponentPageLayout from "../../../layouts/ComponentPageLayout"; @@ -145,7 +151,7 @@ The Chat Composer component suite offers a variety of components designed to enh #### Variants ChatComposerContainer has a `default` and `contained` variant. - + ## Examples ### ChatComposer with placeholder @@ -234,57 +240,274 @@ When the container is disabled, styling is applied to the container component. T Use Chat Composer alongside other Paste components such as [Chat Log](/components/chat-log) to build more complex chat UI. - - {ChatDialogExample} - + { + 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 editorRef = React.useRef(null); + return ( + + + + + + + { + throw error; + }, + }} + ariaLabel="Message" + placeholder="Type here..." + onChange={handleComposerChange} + editorInstanceRef={editorRef} + > + + + + + + + + + + + ); +}:`} +/> + +### 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 + + 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. + + + ), + }, + { + variant: "bot", + content: ( + + 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. + + + + + + + + + + + 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(createNewAIMessage(message)); + }; + const editorRef = React.useRef(null); + return ( + + + + + + { + throw error; + }, + }} + ariaLabel="Message" + placeholder="Type here..." + onChange={handleComposerChange} + editorInstanceRef={editorRef} + > + + + + + + + + + + ); +};`} +/> ### 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, `EnterKeyWithSubmitPlugin`. We also keep track of the content provided to the composer via the `onChange` handler. Together we can add custom interactivity such as: +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 and submit button handler using `EnterKeyWithSubmitPlugin` +- 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. @@ -307,7 +530,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 a plugin: `EnterKeyWithSubmitPlugin` which 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 @@ -317,19 +540,19 @@ When passed as a child to `ChatComposer`, it will automatically register a `CLEA #### Custom plugins -`EnterKeyWithSubmitPlugin` 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. +`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. It is recommended to avoid putting buttons in the Composer, instead use the container with `ChatComposerActionGroup`: ```jsx -export const EnterKeyWithSubmitPlugin = React.forwardRef(({ onKeyDown }: { onKeyDown: () => void }, ref): null => { +export const EnterKeySubmitPlugin = ({ onKeyDown }: { onKeyDown: () => void }): null => { // get the editor from the composer context const [editor] = useLexicalComposerContext(); @@ -340,29 +563,18 @@ export const EnterKeyWithSubmitPlugin = React.forwardRef(({ onKeyDown }: { onKey event.preventDefault(); event.stopPropagation(); onKeyDown(); - // uses the event to clear input provided by the `ClearEditorPlugin` from Lexical editor.dispatchCommand(CLEAR_EDITOR_COMMAND, undefined); return true; }, [editor, onKeyDown] ); - // In order to access the Lexical editor context you must use a function within the plugin. This is called by the submit button in the ChatComposerActionGroup - React.useImperativeHandle(ref, () => ({ - handleSubmit() { - onKeyDown(); - editor.dispatchCommand(CLEAR_EDITOR_COMMAND, undefined); - }, - })); - 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; -}); +}; ``` -Here we're rendering a button that when clicked can call a callback function, and dispatch the `CLEAR_EDITOR_COMMAND` for the `ClearEditorPlugin` respond to. We use it to add a new chat message in the chat log, and then clear the composer ready for the next message to be typed., ChatComposer, ChatComposerContainerimport { Composer } from "../../../components/assistant/AssistantLayout";import { add } from "lodash"; -import { Logger } from "winston"; - +Here we're rendering a button that when clicked can call a callback function, and dispatch the `CLEAR_EDITOR_COMMAND` for the `ClearEditorPlugin` respond to. We use it to add a new chat message in the chat log, and then clear the composer ready for the next message to be typed. From 405f374bfdb97487e5d3ad7c21e8db871b78b389 Mon Sep 17 00:00:00 2001 From: Kristian Antrobus Date: Wed, 3 Jul 2024 15:45:33 -0500 Subject: [PATCH 10/29] chore(docs): update AI Chat Log page name --- packages/paste-website/src/pages/components/ai-chat-log/api.mdx | 1 + .../paste-website/src/pages/components/ai-chat-log/changelog.mdx | 1 + .../paste-website/src/pages/components/ai-chat-log/index.mdx | 1 + 3 files changed, 3 insertions(+) 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..d491ca93f7 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], 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..bd5ee01fa8 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,6 +23,7 @@ export const getStaticProps = async () => { data: { ...packageJson, ...feature, + nameOverride: 'AI Chat Log', }, navigationData, mdxHeadings, 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..486142630b 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,6 +58,7 @@ export const getStaticProps = async () => { data: { ...packageJson, ...feature, + nameOverride: 'AI Chat Log', }, navigationData, mdxHeadings, From c581489bf19a3f6c211fc6b938826949670abd05 Mon Sep 17 00:00:00 2001 From: Kristian Antrobus Date: Wed, 10 Jul 2024 17:56:48 -0500 Subject: [PATCH 11/29] chore(chat-composer): clenaup unused imports --- .changeset/quick-yaks-poke.md | 6 ++++++ .../components/chat-composer/src/ChatComposer.tsx | 1 - .../components/chat-composer/src/ChatComposerContainer.tsx | 1 - 3 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 .changeset/quick-yaks-poke.md diff --git a/.changeset/quick-yaks-poke.md b/.changeset/quick-yaks-poke.md new file mode 100644 index 0000000000..c00f85d563 --- /dev/null +++ b/.changeset/quick-yaks-poke.md @@ -0,0 +1,6 @@ +--- +"@twilio-paste/chat-composer": patch +"@twilio-paste/core": patch +--- + +[ChatComposer] removed internal unused imports diff --git a/packages/paste-core/components/chat-composer/src/ChatComposer.tsx b/packages/paste-core/components/chat-composer/src/ChatComposer.tsx index cbfa0a31b0..331fec1ee9 100644 --- a/packages/paste-core/components/chat-composer/src/ChatComposer.tsx +++ b/packages/paste-core/components/chat-composer/src/ChatComposer.tsx @@ -38,7 +38,6 @@ import type { OnChangeFunction, } from "@twilio-paste/lexical-library"; import { StylingGlobals } from "@twilio-paste/styling-library"; -import { ThemeShape } from "@twilio-paste/theme"; import merge from "deepmerge"; import * as React from "react"; diff --git a/packages/paste-core/components/chat-composer/src/ChatComposerContainer.tsx b/packages/paste-core/components/chat-composer/src/ChatComposerContainer.tsx index 1e9974c6fc..dc31b1d14d 100644 --- a/packages/paste-core/components/chat-composer/src/ChatComposerContainer.tsx +++ b/packages/paste-core/components/chat-composer/src/ChatComposerContainer.tsx @@ -1,6 +1,5 @@ import type { BoxProps, BoxStyleProps } from "@twilio-paste/box"; import { Box, safelySpreadBoxProps } from "@twilio-paste/box"; -import { ThemeShape } from "@twilio-paste/theme"; import * as React from "react"; import { ChatComposerContext } from "./ChatComposerContext"; From e69f614c26069a24764065ff6d73bac6da618245 Mon Sep 17 00:00:00 2001 From: Kristian Antrobus Date: Wed, 10 Jul 2024 18:01:56 -0500 Subject: [PATCH 12/29] chore(chat-composer): remove duplicate story --- .../stories/container.stories.tsx | 56 ------------------- 1 file changed, 56 deletions(-) diff --git a/packages/paste-core/components/chat-composer/stories/container.stories.tsx b/packages/paste-core/components/chat-composer/stories/container.stories.tsx index dc75854fae..399dbb7a46 100644 --- a/packages/paste-core/components/chat-composer/stories/container.stories.tsx +++ b/packages/paste-core/components/chat-composer/stories/container.stories.tsx @@ -156,62 +156,6 @@ export const ResponsiveContainedVariantWithAttachments: StoryFn = () => { ResponsiveContainedVariantWithAttachments.storyName = "Responsive Contained Variant with Attachments"; -export const ResponsiveContainedVariantWithAttatchments: StoryFn = () => { - return ( - - - - - - - - {}}> - }> - Document-FINAL.doc - 123 MB - - - {}}> - }> - Document-FINAL.doc - 123 MB - - - {}}> - }> - Document-FINAL.doc - 123 MB - - - {}}> - }> - Document-FINAL.doc - 123 MB - - - {}}> - }> - Document-FINAL.doc - 123 MB - - - {}}> - }> - Document-FINAL.doc - 123 MB - - - - - ); -}; - -ResponsiveContainedVariantWithAttatchments.storyName = "Responsive Contained Variant with Attatchments"; - export const ContainedDisabledVariant: StoryFn = () => { const [isDisabled, setIsDisabled] = React.useState(true); return ( From 25346721f9bb75dd0b256fb72f90c0f6127c124c Mon Sep 17 00:00:00 2001 From: Kristian Antrobus Date: Wed, 10 Jul 2024 18:26:17 -0500 Subject: [PATCH 13/29] chore(docs/chat-composer): content changes for latest component updates --- .../ChatComposerExamples.tsx | 64 +++++-------------- .../pages/components/chat-composer/index.mdx | 58 +++++++++++------ 2 files changed, 55 insertions(+), 67 deletions(-) diff --git a/packages/paste-website/src/component-examples/ChatComposerExamples.tsx b/packages/paste-website/src/component-examples/ChatComposerExamples.tsx index dedbd15b84..27dfd2df50 100644 --- a/packages/paste-website/src/component-examples/ChatComposerExamples.tsx +++ b/packages/paste-website/src/component-examples/ChatComposerExamples.tsx @@ -104,7 +104,7 @@ export const EnterKeySubmitPlugin = ({ onKeyDown }: { onKeyDown: () => void }): editor.dispatchCommand(CLEAR_EDITOR_COMMAND, undefined); return true; }, - [editor, onKeyDown], + [editor, onKeyDown] ); React.useEffect(() => { @@ -191,11 +191,7 @@ export const ChatDialogExample = `const ChatDialog = () => { push(createNewMessage(message)); }; - const editorRef = React.useRef(null); - - React.useEffect(() => { - console.log(editorRef.current); - }, [editorRef.current]); + const editorInstanceRef = React.useRef(null); return ( @@ -223,21 +219,21 @@ export const ChatDialogExample = `const ChatDialog = () => { ariaLabel="Message" placeholder="Type here..." onChange={handleComposerChange} - editorInstanceRef={editorRef} + editorInstanceRef={editorInstanceRef} > - {}}> - }> - Document-FINAL.doc - 123 MB - - - {}}> - }> - Document-FINAL.doc - 123 MB - - - {}}> - }> - Document-FINAL.doc - 123 MB - - - {}}> - }> - Document-FINAL.doc - 123 MB - - - {}}> - }> - Document-FINAL.doc - 123 MB - - - {}}> - }> - Document-FINAL.doc - 123 MB - - + {Array.from({ length: 6 }).map((_, index) => ( + + ))} ) @@ -339,7 +309,7 @@ export const ContainedDisabledExample = `const ContainedDisabledExample = () => /> + + + + ); +} + +render()`.trim(); 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 539584e4ee..46bd72f264 100644 --- a/packages/paste-website/src/pages/components/chat-composer/index.mdx +++ b/packages/paste-website/src/pages/components/chat-composer/index.mdx @@ -61,6 +61,7 @@ import { createNewMessage, ResponsiveContainedAttachmentsExample, ContainedDisabledExample, + ContainedExample, } from "../../../component-examples/ChatComposerExamples"; import packageJson from "@twilio-paste/chat-composer/package.json"; import ComponentPageLayout from "../../../layouts/ComponentPageLayout"; @@ -146,10 +147,6 @@ The Chat Composer component suite offers a variety of components designed to enh - **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. -#### Variants - -ChatComposerContainer has `default` and `contained` variants. The contained variant is used as part of the new AI experience. - ## Examples ### ChatComposer with placeholder @@ -210,6 +207,27 @@ For responsive attachment cards when using the Chat Composer component suite, us {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. @@ -423,29 +441,24 @@ export const ChatComposerAIChatLogExample = () => { } ); 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({ @@ -458,15 +471,12 @@ export const ChatComposerAIChatLogExample = () => { ), }); }; - const editorInstanceRef = React.useRef(null); - return ( - Date: Thu, 11 Jul 2024 13:28:50 -0500 Subject: [PATCH 29/29] chore(docs/chat-composer): formatting fix --- .../components/chat-composer/stories/logs.stories.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/paste-core/components/chat-composer/stories/logs.stories.tsx b/packages/paste-core/components/chat-composer/stories/logs.stories.tsx index 67a1357f38..9319000b2a 100644 --- a/packages/paste-core/components/chat-composer/stories/logs.stories.tsx +++ b/packages/paste-core/components/chat-composer/stories/logs.stories.tsx @@ -71,7 +71,7 @@ const EnterKeySubmitPlugin = ({ onKeyDown }: { onKeyDown: () => void }): null => editor.dispatchCommand(CLEAR_EDITOR_COMMAND, undefined); return true; }, - [editor, onKeyDown] + [editor, onKeyDown], ); React.useEffect(() => { @@ -155,7 +155,7 @@ export const ChatLogStory: StoryFn = () => { ), - } + }, ); const [message, setMessage] = React.useState(""); @@ -308,7 +308,7 @@ export const AIChatLogComposer = (): React.ReactNode => { ), - } + }, ); const [message, setMessage] = React.useState("");