Skip to content

Commit

Permalink
feat: add components to ChatComposer (#3964)
Browse files Browse the repository at this point in the history
* chore(chat-composer): add fontSize and lineHeight to props

* feat(composer): wip base elements

* feat(composer): addition of all attatchment components

* feat(composer): lint fix

* feat(composer): fix disabled styling

* feat(composer): customization stories

* feat(composer): testing

* feat(composer): import cleanup

* feat(composer): build changes

* feat(composer): responsive columns and JS doc

* feat(anchor): border radius

* feat(composer): changesets

* chore(composer): update typedocs

* chore(composer): fix lint

* chore(composer): missing codemod

* feat(composer): add new plugin to access state

* feat(composer): fix lint issues

* chore(chat-composer): fix spellings

* chore(chat-composer): styling updates from PR comments

* chore(chat-composer): udpate typedocs

* chore(lexical-library): added changeset for export

* chore(lexical-library): update changeset

* chore(chat-composer): naming updates

* chore(chat-composer): chnage stlying types

* chore(ai-chatl-log): added changeset

* chore(ai-chatl-log): updated changeset

* chore(chat-composer): pr cleanup

* chore(chat-composer): refactor ChatCompaserAttachment

* chore(chat-composer): add fontSize and lineHeight to props

* feat(composer): wip base elements

* feat(composer): addition of all attatchment components

* feat(composer): lint fix

* feat(composer): fix disabled styling

* feat(composer): customization stories

* feat(composer): testing

* feat(composer): import cleanup

* feat(composer): build changes

* feat(composer): responsive columns and JS doc

* feat(anchor): border radius

* feat(composer): changesets

* chore(composer): update typedocs

* chore(composer): fix lint

* chore(composer): missing codemod

* feat(composer): add new plugin to access state

* feat(composer): fix lint issues

* chore(chat-composer): fix spellings

* chore(chat-composer): styling updates from PR comments

* chore(chat-composer): udpate typedocs

* chore(lexical-library): added changeset for export

* chore(lexical-library): update changeset

* chore(chat-composer): naming updates

* chore(chat-composer): chnage stlying types

* chore(ai-chatl-log): added changeset

* chore(ai-chatl-log): updated changeset

* chore(chat-composer): pr cleanup

* chore(chat-composer): refactor ChatCompaserAttachment

* chore(chat-composer): rename editorRef in stories

* chore(chat-composer): change CSS grid so rowGap conditionally applied if attachemnt group present

* chore(chat-composer): add fontSize and lineHeight to props

* feat(composer): wip base elements

* feat(composer): addition of all attatchment components

* feat(composer): lint fix

* feat(composer): fix disabled styling

* feat(composer): responsive columns and JS doc

* feat(composer): add new plugin to access state

* feat(composer): fix lint issues

* chore(chat-composer): fix spellings

* chore(chat-composer): styling updates from PR comments

* chore(chat-composer): chnage stlying types

* chore(chat-composer): pr cleanup

* chore(chat-composer): refactor ChatCompaserAttachment

* chore(chat-composer): change CSS grid so rowGap conditionally applied if attachemnt group present

* chore(chat-composer): changeset update

* chore(chat-composer): codemods& lint

* chore(lexical): changeset

* chore(chat-composer): flex attachments group to full width

---------

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
  • Loading branch information
krisantrobus and kodiakhq[bot] authored Jul 10, 2024
1 parent d3aefd3 commit 7779a24
Show file tree
Hide file tree
Showing 28 changed files with 6,633 additions and 111 deletions.
5 changes: 5 additions & 0 deletions .changeset/fair-beds-exist.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@twilio-paste/codemods": minor
---

[ChatComposer] Added new components to allow contained variants, actions buttons and attachments
6 changes: 6 additions & 0 deletions .changeset/great-coins-judge.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@twilio-paste/chat-composer": minor
"@twilio-paste/core": minor
---

[ChatComposer] Added new components to allow contained variants, actions buttons and attachments
6 changes: 6 additions & 0 deletions .changeset/khaki-boats-help.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@twilio-paste/ai-chat-log": patch
"@twilio-paste/core": patch
---

[AIChatLog] Updated internal styling types
6 changes: 6 additions & 0 deletions .changeset/nasty-beans-fetch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@twilio-paste/lexical-library": minor
"@twilio-paste/core": minor
---

[Lexical] added export for EditorRefPlugin
6 changes: 6 additions & 0 deletions .changeset/serious-kings-wink.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@twilio-paste/anchor": patch
"@twilio-paste/core": patch
---

[Anchor] Added border radius to focus styling
6 changes: 6 additions & 0 deletions packages/paste-codemods/tools/.cache/mappings.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@
"CalloutText": "@twilio-paste/core/callout",
"Card": "@twilio-paste/core/card",
"ChatComposer": "@twilio-paste/core/chat-composer",
"ChatComposerActionGroup": "@twilio-paste/core/chat-composer",
"ChatComposerAttachmentCard": "@twilio-paste/core/chat-composer",
"ChatComposerAttachmentDescription": "@twilio-paste/core/chat-composer",
"ChatComposerAttachmentGroup": "@twilio-paste/core/chat-composer",
"ChatComposerAttachmentLink": "@twilio-paste/core/chat-composer",
"ChatComposerContainer": "@twilio-paste/core/chat-composer",
"ChatAttachment": "@twilio-paste/core/chat-log",
"ChatAttachmentDescription": "@twilio-paste/core/chat-log",
"ChatAttachmentLink": "@twilio-paste/core/chat-log",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
import { Box, safelySpreadBoxProps } from "@twilio-paste/box";
import type { BoxElementProps } from "@twilio-paste/box";
import type { ThemeShape } from "@twilio-paste/theme";
import type { BoxElementProps, BoxStyleProps } from "@twilio-paste/box";
import type { HTMLPasteProps } from "@twilio-paste/types";
import * as React from "react";

import { AIMessageContext } from "./AIMessageContext";

const Sizes = {
const Sizes: Record<string, BoxStyleProps> = {
default: {
fontSize: "fontSize30" as ThemeShape["fontSizes"],
lineHeight: "lineHeight30" as ThemeShape["lineHeights"],
fontSize: "fontSize30",
lineHeight: "lineHeight30",
},
fullScreen: {
fontSize: "fontSize40" as ThemeShape["fontSizes"],
lineHeight: "lineHeight40" as ThemeShape["lineHeights"],
fontSize: "fontSize40",
lineHeight: "lineHeight40",
},
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import type { AIChat } from "@twilio-paste/ai-chat-log";
import { Box } from "@twilio-paste/box";
import { Button } from "@twilio-paste/button";
import { ButtonGroup } from "@twilio-paste/button-group";
import { ChatComposer } from "@twilio-paste/chat-composer";
import { ChatComposer, ChatComposerActionGroup, ChatComposerContainer } from "@twilio-paste/chat-composer";
import { AttachIcon } from "@twilio-paste/icons/esm/AttachIcon";
import { SendIcon } from "@twilio-paste/icons/esm/SendIcon";
import { ThumbsDownIcon } from "@twilio-paste/icons/esm/ThumbsDownIcon";
import { ThumbsUpIcon } from "@twilio-paste/icons/esm/ThumbsUpIcon";
Expand All @@ -14,6 +15,7 @@ import {
COMMAND_PRIORITY_HIGH,
ClearEditorPlugin,
KEY_ENTER_COMMAND,
LexicalEditor,
useLexicalComposerContext,
} from "@twilio-paste/lexical-library";
import * as React from "react";
Expand Down Expand Up @@ -202,22 +204,15 @@ export const AIChatLogComposer = (): React.ReactNode => {
if (message === "") return;
push(createNewMessage(message));
};

const editorInstanceRef = React.useRef<LexicalEditor>(null);

return (
<Box>
<Box ref={scrollerRef} overflowX="hidden" overflowY="auto" maxHeight="size50" tabIndex={0}>
<AIChatLogger ref={loggerRef} aiChats={aiChats} />
</Box>
<Box
borderStyle="solid"
borderWidth="borderWidth0"
borderTopWidth="borderWidth10"
borderColor="colorBorderWeak"
display="flex"
flexDirection="row"
columnGap="space30"
paddingX="space70"
paddingTop="space50"
>
<ChatComposerContainer variant="contained">
<ChatComposer
maxHeight="size10"
config={{
Expand All @@ -229,12 +224,27 @@ export const AIChatLogComposer = (): React.ReactNode => {
ariaLabel="Message"
placeholder="Type here..."
onChange={handleComposerChange}
editorInstanceRef={editorInstanceRef}
>
<ClearEditorPlugin />
<SendButtonPlugin onClick={submitMessage} />
<EnterKeySubmitPlugin onKeyDown={submitMessage} />
</ChatComposer>
</Box>
<ChatComposerActionGroup>
<Button variant="secondary_icon" size="reset">
<AttachIcon decorative={false} title="attach a file to your message" />
</Button>
<Button
variant="primary_icon"
size="reset"
onClick={() => {
submitMessage();
editorInstanceRef.current?.dispatchCommand(CLEAR_EDITOR_COMMAND, undefined);
}}
>
<SendIcon decorative={false} title="Send" />
</Button>
</ChatComposerActionGroup>
</ChatComposerContainer>
</Box>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const DefaultAnchor = React.forwardRef<HTMLAnchorElement, AnchorProps>((props, r
boxShadow: "shadowFocus",
color: "colorTextLink",
textDecoration: "underline",
borderRadius: "borderRadius20",
}}
_hover={{
color: "colorTextLinkStronger",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const InverseAnchor = React.forwardRef<HTMLAnchorElement, AnchorProps>((props, r
boxShadow: "shadowFocusInverse",
color: "colorTextInverse",
textDecoration: "underline",
borderRadius: "borderRadius20",
}}
_hover={{
color: "colorTextInverse",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { $createParagraphNode, $createTextNode, $getRoot } from "@twilio-paste/lexical-library";
import { Theme } from "@twilio-paste/theme";
import * as React from "react";

import { ChatComposer } from "../src";

const initialText = (): void => {
const root = $getRoot();

if (root.getFirstChild() === null) {
const paragraph = $createParagraphNode();
paragraph.append($createTextNode("Hello"));

root.append(paragraph);
}
};

const baseConfig = {
namespace: "foo",
onError: (error: Error) => {
throw error;
},
};

describe("ChatComposer", () => {
it("should render with placeholder text", () => {
render(<ChatComposer data-testid="my-composer" placeholder="Type here.." config={baseConfig} />);
expect(screen.getByRole("textbox")).toBeDefined();
expect(screen.getByText("Type here..")).toBeDefined();
});

it("should pass props to the content editable", async () => {
render(
<ChatComposer
data-testid="my-composer"
ariaLabel="Feedback"
ariaDescribedBy="foo"
ariaOwns="foo"
ariaActiveDescendant="foo"
config={baseConfig}
/>,
);

const contentEditable = screen.getByRole("textbox");
await waitFor(() => {
expect(contentEditable).toHaveAttribute("aria-label", "Feedback");
expect(contentEditable).toHaveAttribute("aria-activedescendant", "foo");
expect(contentEditable).toHaveAttribute("aria-owns", "foo");
expect(contentEditable).toHaveAttribute("aria-describedby", "foo");
expect(contentEditable.dataset.testid).toEqual("my-composer");
});
});

it("should render initial value with the initialValue prop", async () => {
render(<ChatComposer initialValue="Type here.." config={baseConfig} />);

const contentEditable = screen.getByRole("textbox");
await waitFor(() => {
expect(contentEditable).toHaveTextContent("Type here..");
});
});

it("should render custom initial value with the config prop", async () => {
render(
<ChatComposer
config={{
...baseConfig,
editorState: initialText,
}}
/>,
);

const contentEditable = screen.getByRole("textbox");
await waitFor(() => {
expect(contentEditable).toHaveTextContent("Hello");
});
});

it("should set maxHeight with the maxHeight prop", async () => {
render(
<Theme.Provider theme="default">
<ChatComposer maxHeight="size10" config={baseConfig} />
</Theme.Provider>,
);

const wrapper = screen.getByRole("textbox").parentElement;
await waitFor(() => {
expect(wrapper).toHaveStyleRule("max-height", "5.5rem");
});
});

it("should call onChange function", async () => {
const onChangeMock: jest.Mock = jest.fn();

render(<ChatComposer onChange={onChangeMock} config={baseConfig} />);

const contentEditable = screen.getByRole("textbox");

userEvent.type(contentEditable, "foo bar");
waitFor(() => {
expect(onChangeMock).toHaveBeenCalledTimes(1);
});
});
});
Loading

0 comments on commit 7779a24

Please sign in to comment.