Skip to content

Commit f1c859c

Browse files
authored
Chat composer update (#4339)
* feat(chat-composer): add ChatComposerActionRow * feat(chat-composer): update example * feat(chat-composer): update imports * feat(chat-composer): update example * feat(chat-composer): update typedocs * feat(chat-composer): add changeset * feat(chat-composer): update docs * feat(chat-composer): fix lint * feat(chat-composer): update typedocs * feat(chat-composer): update tooltip * feat(chat-composer): fix layout
1 parent 219e25c commit f1c859c

File tree

12 files changed

+306
-12
lines changed

12 files changed

+306
-12
lines changed

.changeset/healthy-fishes-accept.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@twilio-paste/codemods": minor
3+
"@twilio-paste/chat-composer": minor
4+
"@twilio-paste/core": minor
5+
---
6+
7+
[Chat Composer] add `ChatComposerActionRow` to render below `ChatComposer`

packages/paste-codemods/tools/.cache/mappings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
"ChartProvider": "@twilio-paste/core/chart-provider",
5454
"ChatComposer": "@twilio-paste/core/chat-composer",
5555
"ChatComposerActionGroup": "@twilio-paste/core/chat-composer",
56+
"ChatComposerActionRow": "@twilio-paste/core/chat-composer",
5657
"ChatComposerAttachmentCard": "@twilio-paste/core/chat-composer",
5758
"ChatComposerAttachmentDescription": "@twilio-paste/core/chat-composer",
5859
"ChatComposerAttachmentGroup": "@twilio-paste/core/chat-composer",

packages/paste-core/components/chat-composer/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,17 +48,20 @@
4848
"@twilio-paste/animation-library": "^3.0.1",
4949
"@twilio-paste/box": "^11.0.1",
5050
"@twilio-paste/button": "^15.0.2",
51+
"@twilio-paste/checkbox": "^14.0.2",
5152
"@twilio-paste/color-contrast-utils": "^5.0.0",
5253
"@twilio-paste/customization": "^9.0.1",
5354
"@twilio-paste/design-tokens": "^10.14.1",
5455
"@twilio-paste/icons": "^13.1.0",
5556
"@twilio-paste/lexical-library": "^5.1.0",
57+
"@twilio-paste/menu": "^15.0.1",
5658
"@twilio-paste/screen-reader-only": "^14.0.1",
5759
"@twilio-paste/stack": "^9.0.1",
5860
"@twilio-paste/style-props": "^10.0.1",
5961
"@twilio-paste/styling-library": "^4.0.1",
6062
"@twilio-paste/text": "^11.0.1",
6163
"@twilio-paste/theme": "^12.0.1",
64+
"@twilio-paste/tooltip": "^13.0.1",
6265
"@twilio-paste/truncate": "^15.0.1",
6366
"@twilio-paste/types": "^7.0.1",
6467
"@types/react": "^19.0.8",
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import type { BoxProps } from "@twilio-paste/box";
2+
import { Box, safelySpreadBoxProps } from "@twilio-paste/box";
3+
import * as React from "react";
4+
5+
export interface ChatComposerActionRowProps {
6+
children?: React.ReactNode;
7+
/**
8+
* Overrides the default element name to apply unique styles with the Customization Provider
9+
* @default 'CHAT_COMPOSER_ACTION_GROUP'
10+
* @type {BoxProps['element']}
11+
* @memberof ChatComposerActionRowProps
12+
*/
13+
element?: BoxProps["element"];
14+
}
15+
16+
export const ChatComposerActionRow = React.forwardRef<HTMLDivElement, ChatComposerActionRowProps>(
17+
({ element = "CHAT_COMPOSER_ACTION_ROW", children, ...props }, ref) => (
18+
<Box
19+
{...safelySpreadBoxProps(props)}
20+
element={element}
21+
ref={ref}
22+
gridRow="2"
23+
gridColumn="1/-1"
24+
display="inline-flex"
25+
alignSelf="end"
26+
role="group"
27+
columnGap="space30"
28+
padding="space30"
29+
paddingRight="space20"
30+
>
31+
{children}
32+
</Box>
33+
),
34+
);
35+
36+
ChatComposerActionRow.displayName = "ChatComposerActionRow";

packages/paste-core/components/chat-composer/src/ChatComposerAttachmentGroup.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ export const ChatComposerAttachmentGroup = React.forwardRef<HTMLDivElement, Chat
3434
{...safelySpreadBoxProps(props)}
3535
element={element}
3636
ref={ref}
37-
gridArea="2/1/3/3"
37+
gridRow="3"
38+
gridColumn="1/-1"
3839
role="group"
3940
display="grid"
4041
gridTemplateColumns={getColumnStyles()}
@@ -43,6 +44,7 @@ export const ChatComposerAttachmentGroup = React.forwardRef<HTMLDivElement, Chat
4344
paddingBottom="space30"
4445
paddingX="space10"
4546
rowGap="space40"
47+
marginTop="space50"
4648
>
4749
{children}
4850
</Box>

packages/paste-core/components/chat-composer/src/ChatComposerContainer.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ export const ChatComposerContainer = React.forwardRef<HTMLDivElement, ChatCompos
6262
padding="space30"
6363
maxHeight={maxHeight}
6464
overflowY="auto"
65-
rowGap="space50"
6665
width="100%"
6766
{...Styles[variant]}
6867
>

packages/paste-core/components/chat-composer/src/index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,5 @@ export { ChatComposerAttachmentDescription } from "./ChatComposerAttachmentDescr
1212
export type { ChatComposerAttachmentDescriptionProps } from "./ChatComposerAttachmentDescription";
1313
export { ChatComposerAttachmentLink } from "./ChatComposerAttachmentLink";
1414
export type { ChatComposerAttachmentLinkProps } from "./ChatComposerAttachmentLink";
15+
export { ChatComposerActionRow } from "./ChatComposerActionRow";
16+
export type { ChatComposerActionRowProps } from "./ChatComposerActionRow";

packages/paste-core/components/chat-composer/stories/container.stories.tsx

Lines changed: 147 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,30 @@ import { Box } from "@twilio-paste/box";
33
import { Button } from "@twilio-paste/button";
44
import { Checkbox } from "@twilio-paste/checkbox";
55
import { CustomizationProvider } from "@twilio-paste/customization";
6+
import { AgentIcon } from "@twilio-paste/icons/esm/AgentIcon";
67
import { AttachIcon } from "@twilio-paste/icons/esm/AttachIcon";
8+
import { DocumentationIcon } from "@twilio-paste/icons/esm/DocumentationIcon";
79
import { DownloadIcon } from "@twilio-paste/icons/esm/DownloadIcon";
10+
import { EmojiIcon } from "@twilio-paste/icons/esm/EmojiIcon";
11+
import { HistoryIcon } from "@twilio-paste/icons/esm/HistoryIcon";
12+
import { MoreIcon } from "@twilio-paste/icons/esm/MoreIcon";
813
import { SendIcon } from "@twilio-paste/icons/esm/SendIcon";
14+
import { Menu, MenuButton, MenuItem, MenuSeparator, useMenuState } from "@twilio-paste/menu";
915
import { useTheme } from "@twilio-paste/theme";
16+
import { Tooltip, useTooltipState } from "@twilio-paste/tooltip";
1017
import * as React from "react";
1118

19+
import type { ChatComposerProps } from "../src";
1220
import {
1321
ChatComposer,
1422
ChatComposerActionGroup,
23+
ChatComposerActionRow,
1524
ChatComposerAttachmentCard,
1625
ChatComposerAttachmentDescription,
1726
ChatComposerAttachmentGroup,
1827
ChatComposerAttachmentLink,
1928
ChatComposerContainer,
2029
} from "../src";
21-
import type { ChatComposerProps } from "../src";
2230

2331
export default {
2432
title: "Components/Chat Composer/Container",
@@ -68,6 +76,144 @@ export const ContainedVariant: StoryFn = () => {
6876

6977
ContainedVariant.storyName = "Contained Variant";
7078

79+
export const ContainedVariantActionRow: StoryFn = () => {
80+
const tooltip1 = useTooltipState();
81+
const tooltip2 = useTooltipState();
82+
const menu = useMenuState();
83+
84+
return (
85+
<ChatComposerContainer variant="contained">
86+
<ChatComposer config={defaultConfig} ariaLabel="Basic chat composer" placeholder="Type here..." />
87+
<ChatComposerActionGroup>
88+
<Button variant="primary_icon" size="reset">
89+
<SendIcon decorative={false} title="Send" />
90+
</Button>
91+
</ChatComposerActionGroup>
92+
<ChatComposerActionRow>
93+
<Tooltip state={tooltip1} text="Attach">
94+
<Button variant="secondary" size="circle_small">
95+
<AttachIcon decorative={false} title="attach a file to your message" />
96+
</Button>
97+
</Tooltip>
98+
<Tooltip state={tooltip2} text="Emoji">
99+
<Button variant="secondary" size="circle_small">
100+
<EmojiIcon decorative={false} title="Chat history" />
101+
</Button>
102+
</Tooltip>
103+
104+
<>
105+
<MenuButton {...menu} variant="secondary" size="circle_small">
106+
<MoreIcon decorative={false} title="More actions" />
107+
</MenuButton>
108+
<Menu {...menu} aria-label="Preferences">
109+
<MenuItem {...menu}>
110+
<Box display="flex" alignItems="center" columnGap="space20">
111+
<HistoryIcon decorative color="colorTextIcon" /> Chat history
112+
</Box>
113+
</MenuItem>
114+
<MenuItem {...menu}>
115+
<Box display="flex" alignItems="center" columnGap="space20">
116+
<AgentIcon decorative color="colorTextIcon" /> Contact an agent
117+
</Box>
118+
</MenuItem>
119+
<MenuItem {...menu}>
120+
<Box display="flex" alignItems="center" columnGap="space20">
121+
<DocumentationIcon decorative color="colorTextIcon" /> Quick Start Guide
122+
</Box>
123+
</MenuItem>
124+
<MenuSeparator {...menu} />
125+
<MenuItem {...menu}>Privacy Policy</MenuItem>
126+
<MenuItem {...menu}>Terms of Service</MenuItem>
127+
</Menu>
128+
</>
129+
</ChatComposerActionRow>
130+
</ChatComposerContainer>
131+
);
132+
};
133+
134+
ContainedVariantActionRow.storyName = "Contained Variant with Action Row";
135+
136+
export const ContainedVariantActionRowWithAttachment: StoryFn = () => {
137+
const tooltip1 = useTooltipState();
138+
const tooltip2 = useTooltipState();
139+
const menu = useMenuState();
140+
141+
return (
142+
<ChatComposerContainer variant="contained">
143+
<ChatComposer config={defaultConfig} ariaLabel="Basic chat composer" placeholder="Type here..." />
144+
<ChatComposerActionGroup>
145+
<Button variant="primary_icon" size="reset">
146+
<SendIcon decorative={false} title="Send" />
147+
</Button>
148+
</ChatComposerActionGroup>
149+
<ChatComposerActionRow>
150+
<Tooltip state={tooltip1} text="Attach">
151+
<Button variant="secondary" size="circle_small">
152+
<AttachIcon decorative={false} title="attach a file to your message" />
153+
</Button>
154+
</Tooltip>
155+
<Tooltip state={tooltip2} text="Emoji">
156+
<Button variant="secondary" size="circle_small">
157+
<EmojiIcon decorative={false} title="Chat history" />
158+
</Button>
159+
</Tooltip>
160+
161+
<>
162+
<MenuButton {...menu} variant="secondary" size="circle_small">
163+
<MoreIcon decorative={false} title="More actions" />
164+
</MenuButton>
165+
<Menu {...menu} aria-label="Preferences">
166+
<MenuItem {...menu}>
167+
<Box display="flex" alignItems="center" columnGap="space20">
168+
<HistoryIcon decorative color="colorTextIcon" /> Chat history
169+
</Box>
170+
</MenuItem>
171+
<MenuItem {...menu}>
172+
<Box display="flex" alignItems="center" columnGap="space20">
173+
<AgentIcon decorative color="colorTextIcon" /> Contact an agent
174+
</Box>
175+
</MenuItem>
176+
<MenuItem {...menu}>
177+
<Box display="flex" alignItems="center" columnGap="space20">
178+
<DocumentationIcon decorative color="colorTextIcon" /> Quick Start Guide
179+
</Box>
180+
</MenuItem>
181+
<MenuSeparator {...menu} />
182+
<MenuItem {...menu}>Privacy Policy</MenuItem>
183+
<MenuItem {...menu}>Terms of Service</MenuItem>
184+
</Menu>
185+
</>
186+
</ChatComposerActionRow>
187+
<ChatComposerAttachmentGroup>
188+
<ChatComposerAttachmentCard onDismiss={() => {}} attachmentIcon={<DownloadIcon decorative />}>
189+
<ChatComposerAttachmentLink href="www.google.com">Document-FINAL.doc</ChatComposerAttachmentLink>
190+
<ChatComposerAttachmentDescription>123 MB</ChatComposerAttachmentDescription>
191+
</ChatComposerAttachmentCard>
192+
<ChatComposerAttachmentCard onDismiss={() => {}} attachmentIcon={<DownloadIcon decorative />}>
193+
<ChatComposerAttachmentLink href="www.google.com">Document-FINAL.doc</ChatComposerAttachmentLink>
194+
<ChatComposerAttachmentDescription>123 MB</ChatComposerAttachmentDescription>
195+
</ChatComposerAttachmentCard>
196+
<ChatComposerAttachmentCard onDismiss={() => {}} attachmentIcon={<DownloadIcon decorative />}>
197+
<ChatComposerAttachmentLink href="www.google.com">Document-FINAL.doc</ChatComposerAttachmentLink>
198+
<ChatComposerAttachmentDescription>123 MB</ChatComposerAttachmentDescription>
199+
</ChatComposerAttachmentCard>
200+
<ChatComposerAttachmentCard onDismiss={() => {}} attachmentIcon={<DownloadIcon decorative />}>
201+
<ChatComposerAttachmentLink href="www.google.com">Document-FINAL.doc</ChatComposerAttachmentLink>
202+
<ChatComposerAttachmentDescription>123 MB</ChatComposerAttachmentDescription>
203+
</ChatComposerAttachmentCard>
204+
<ChatComposerAttachmentCard onDismiss={() => {}} attachmentIcon={<DownloadIcon decorative />}>
205+
<ChatComposerAttachmentLink href="www.google.com">Document-FINAL.doc</ChatComposerAttachmentLink>
206+
<ChatComposerAttachmentDescription>123 MB</ChatComposerAttachmentDescription>
207+
</ChatComposerAttachmentCard>
208+
<ChatComposerAttachmentCard onDismiss={() => {}} attachmentIcon={<DownloadIcon decorative />}>
209+
<ChatComposerAttachmentLink href="www.google.com">Document-FINAL.doc</ChatComposerAttachmentLink>
210+
<ChatComposerAttachmentDescription>123 MB</ChatComposerAttachmentDescription>
211+
</ChatComposerAttachmentCard>
212+
</ChatComposerAttachmentGroup>
213+
</ChatComposerContainer>
214+
);
215+
};
216+
71217
export const ContainedVariantWithAttachments: StoryFn = () => {
72218
return (
73219
<ChatComposerContainer variant="contained">

packages/paste-core/components/chat-composer/stories/logs.stories.tsx

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import type { StoryFn } from "@storybook/react";
22
import {
3-
AIChat,
43
AIChatLogger,
54
AIChatMessage,
65
AIChatMessageActionCard,
@@ -15,7 +14,7 @@ import { Box } from "@twilio-paste/box";
1514
import { Button } from "@twilio-paste/button";
1615
import { ButtonGroup } from "@twilio-paste/button-group";
1716
import {
18-
Chat,
17+
type Chat,
1918
ChatAttachment,
2019
ChatAttachmentDescription,
2120
ChatAttachmentLink,
@@ -29,8 +28,13 @@ import {
2928
ChatMessageMetaItem,
3029
useChatLogger,
3130
} from "@twilio-paste/chat-log";
31+
import { AgentIcon } from "@twilio-paste/icons/esm/AgentIcon";
3232
import { AttachIcon } from "@twilio-paste/icons/esm/AttachIcon";
33+
import { DocumentationIcon } from "@twilio-paste/icons/esm/DocumentationIcon";
3334
import { DownloadIcon } from "@twilio-paste/icons/esm/DownloadIcon";
35+
import { EmojiIcon } from "@twilio-paste/icons/esm/EmojiIcon";
36+
import { HistoryIcon } from "@twilio-paste/icons/esm/HistoryIcon";
37+
import { MoreIcon } from "@twilio-paste/icons/esm/MoreIcon";
3438
import { SendIcon } from "@twilio-paste/icons/esm/SendIcon";
3539
import { ThumbsDownIcon } from "@twilio-paste/icons/esm/ThumbsDownIcon";
3640
import { ThumbsUpIcon } from "@twilio-paste/icons/esm/ThumbsUpIcon";
@@ -43,10 +47,12 @@ import {
4347
LexicalEditor,
4448
useLexicalComposerContext,
4549
} from "@twilio-paste/lexical-library";
50+
import { Menu, MenuButton, MenuItem, MenuSeparator, useMenuState } from "@twilio-paste/menu";
51+
import { Tooltip, useTooltipState } from "@twilio-paste/tooltip";
4652
import * as React from "react";
4753
import type { JSX } from "react";
4854

49-
import { ChatComposer, ChatComposerActionGroup, ChatComposerContainer } from "../src";
55+
import { ChatComposer, ChatComposerActionGroup, ChatComposerActionRow, ChatComposerContainer } from "../src";
5056

5157
export default {
5258
title: "Components/Chat Composer/LogsExperience",
@@ -349,6 +355,9 @@ export const AIChatLogComposer = (): React.ReactNode => {
349355
};
350356

351357
const editorInstanceRef = React.useRef<LexicalEditor>(null);
358+
const tooltip1 = useTooltipState();
359+
const tooltip2 = useTooltipState();
360+
const menu = useMenuState();
352361

353362
return (
354363
<Box>
@@ -374,9 +383,6 @@ export const AIChatLogComposer = (): React.ReactNode => {
374383
<EnterKeySubmitPlugin onKeyDown={submitMessage} />
375384
</ChatComposer>
376385
<ChatComposerActionGroup>
377-
<Button variant="secondary_icon" size="reset">
378-
<AttachIcon decorative={false} title="attach a file to your message" />
379-
</Button>
380386
<Button
381387
variant="primary_icon"
382388
size="reset"
@@ -388,6 +394,44 @@ export const AIChatLogComposer = (): React.ReactNode => {
388394
<SendIcon decorative={false} title="Send" />
389395
</Button>
390396
</ChatComposerActionGroup>
397+
<ChatComposerActionRow>
398+
<Tooltip state={tooltip1} text="Attach">
399+
<Button variant="secondary" size="circle_small">
400+
<AttachIcon decorative={false} title="attach a file to your message" />
401+
</Button>
402+
</Tooltip>
403+
<Tooltip state={tooltip2} text="Emoji">
404+
<Button variant="secondary" size="circle_small">
405+
<EmojiIcon decorative={false} title="Chat history" />
406+
</Button>
407+
</Tooltip>
408+
409+
<>
410+
<MenuButton {...menu} variant="secondary" size="circle_small">
411+
<MoreIcon decorative={false} title="More actions" />
412+
</MenuButton>
413+
<Menu {...menu} aria-label="Preferences">
414+
<MenuItem {...menu}>
415+
<Box display="flex" alignItems="center" columnGap="space20">
416+
<HistoryIcon decorative color="colorTextIcon" /> Chat history
417+
</Box>
418+
</MenuItem>
419+
<MenuItem {...menu}>
420+
<Box display="flex" alignItems="center" columnGap="space20">
421+
<AgentIcon decorative color="colorTextIcon" /> Contact an agent
422+
</Box>
423+
</MenuItem>
424+
<MenuItem {...menu}>
425+
<Box display="flex" alignItems="center" columnGap="space20">
426+
<DocumentationIcon decorative color="colorTextIcon" /> Quick Start Guide
427+
</Box>
428+
</MenuItem>
429+
<MenuSeparator {...menu} />
430+
<MenuItem {...menu}>Privacy Policy</MenuItem>
431+
<MenuItem {...menu}>Terms of Service</MenuItem>
432+
</Menu>
433+
</>
434+
</ChatComposerActionRow>
391435
</ChatComposerContainer>
392436
</Box>
393437
);

0 commit comments

Comments
 (0)