WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content

Commit 59b664a

Browse files
committed
chore: POC for selectable bubbles
1 parent 0c6e055 commit 59b664a

File tree

7 files changed

+136
-6
lines changed

7 files changed

+136
-6
lines changed

pages/app/templates.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,6 @@ export function Page({
3939
navigationHide={true}
4040
activeDrawerId={toolsOpen ? "settings" : null}
4141
onDrawerChange={({ detail }) => setToolsOpen(!!detail.activeDrawerId)}
42-
tools={settings && <Drawer header={<Header variant="h2">Page settings</Header>}>{settings}</Drawer>}
43-
toolsHide={!settings}
4442
drawers={drawers}
4543
content={
4644
<Box>
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import { useState } from "react";
5+
6+
import Box from "@cloudscape-design/components/box";
7+
import Button from "@cloudscape-design/components/button";
8+
import Container from "@cloudscape-design/components/container";
9+
import Header from "@cloudscape-design/components/header";
10+
import SpaceBetween from "@cloudscape-design/components/space-between";
11+
12+
import { ChatBubble } from "../../lib/components";
13+
import { Page } from "../app/templates";
14+
import { ChatBubbleAvatarGenAI, ChatBubbleAvatarUser } from "./util-components";
15+
16+
export default function ChatBubbleSelectablePage() {
17+
const [selectedCardId, setSelectedCardId] = useState<null | string>(null);
18+
return (
19+
<Page title="Chat bubble: selectable">
20+
<SpaceBetween direction="horizontal" size="l">
21+
<Container header={<Header variant="h3">Chat container</Header>}>
22+
<SpaceBetween size="m">
23+
<ChatBubble avatar={<ChatBubbleAvatarUser />} type="outgoing" ariaLabel="User at 4:24:12pm">
24+
<Box color="text-body-secondary">User request example</Box>
25+
</ChatBubble>
26+
27+
<ChatBubble avatar={<ChatBubbleAvatarGenAI />} type="incoming" ariaLabel="Gen AI at 4:24:24pm">
28+
<Box color="text-body-secondary">Normal response example</Box>
29+
</ChatBubble>
30+
31+
<ChatBubble
32+
avatar={<ChatBubbleAvatarGenAI />}
33+
hideAvatar={true}
34+
type="incoming"
35+
ariaLabel="Gen AI at 4:24:25pm"
36+
selectionType="card-click"
37+
onSelect={() => setSelectedCardId((prev) => (prev !== "1" ? "1" : null))}
38+
selected={selectedCardId === "1"}
39+
>
40+
<Box variant="h4">Selectable response example</Box>
41+
<Box variant="p">This entire card is clickable</Box>
42+
</ChatBubble>
43+
44+
<ChatBubble
45+
avatar={<ChatBubbleAvatarGenAI />}
46+
hideAvatar={true}
47+
type="incoming"
48+
ariaLabel="Gen AI at 4:24:25pm"
49+
selectionType="custom"
50+
selected={selectedCardId === "2"}
51+
>
52+
<div style={{ display: "flex", gap: 8, justifyContent: "space-between" }}>
53+
<Box variant="h4">Selectable response example</Box>
54+
{selectedCardId !== "2" ? (
55+
<Button variant="inline-link" onClick={() => setSelectedCardId("2")}>
56+
Show
57+
</Button>
58+
) : (
59+
<Button variant="inline-link" onClick={() => setSelectedCardId(null)}>
60+
Hide
61+
</Button>
62+
)}
63+
</div>
64+
<Box variant="p">
65+
This entire card is not clickable. Instead, there is a custom action in the top-right.
66+
</Box>
67+
</ChatBubble>
68+
</SpaceBetween>
69+
</Container>
70+
71+
<Container header={<Header variant="h3">Presentation container</Header>}>
72+
{selectedCardId ? (
73+
<SpaceBetween size="m">
74+
<Box>{selectedCardId === "1" ? "First" : "Second"} card is selected</Box>
75+
<Button onClick={() => setSelectedCardId(null)}>Clear selection</Button>
76+
</SpaceBetween>
77+
) : null}
78+
</Container>
79+
</SpaceBetween>
80+
</Page>
81+
);
82+
}

src/chat-bubble/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
3+
34
import useBaseComponent from "../internal/base-component/use-base-component";
45
import { applyDisplayName } from "../internal/utils/apply-display-name";
56
import { ChatBubbleProps } from "./interfaces";

src/chat-bubble/interfaces.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
33

4+
import { NonCancelableEventHandler } from "../internal/events";
5+
46
export interface ChatBubbleProps {
57
/** Avatar slot paired with the chat bubble content. Use [avatar](/components/avatar/). */
68
avatar: React.ReactNode;
@@ -31,6 +33,11 @@ export interface ChatBubbleProps {
3133
* Useful for when there are multiple consecutive messages coming from the same author.
3234
*/
3335
hideAvatar?: boolean;
36+
37+
// TBA
38+
selectionType?: "card-click" | "custom";
39+
selected?: boolean;
40+
onSelect?: NonCancelableEventHandler<null>;
3441
}
3542

3643
export namespace ChatBubbleProps {

src/chat-bubble/internal.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
3+
34
import { useEffect, useRef } from "react";
45
import clsx from "clsx";
56

67
import { getDataAttributes } from "../internal/base-component/get-data-attributes";
78
import { InternalBaseComponentProps } from "../internal/base-component/use-base-component";
9+
import { fireNonCancelableEvent } from "../internal/events";
810
import { InternalLoadingBar } from "../loading-bar/internal";
911
import { ChatBubbleProps } from "./interfaces.js";
1012

@@ -20,6 +22,9 @@ export default function InternalChatBubble({
2022
showLoadingBar,
2123
hideAvatar = false,
2224
ariaLabel,
25+
selectionType,
26+
selected,
27+
onSelect,
2328
__internalRootRef = null,
2429
...rest
2530
}: InternalChatBubbleProps) {
@@ -36,6 +41,8 @@ export default function InternalChatBubble({
3641
}
3742
}, [hideAvatar]);
3843

44+
const Tag = selectionType === "card-click" && onSelect ? "button" : "div";
45+
3946
return (
4047
<div
4148
className={styles.root}
@@ -50,17 +57,22 @@ export default function InternalChatBubble({
5057
</div>
5158
)}
5259

53-
<div
60+
<Tag
61+
aria-pressed={selected}
62+
onClick={() => fireNonCancelableEvent(onSelect)}
5463
className={clsx(styles["message-area"], styles[`chat-bubble-type-${type}`], {
5564
[styles["with-loading-bar"]]: showLoadingBar,
65+
[styles["message-area-selectable"]]: !!selectionType,
66+
[styles["message-area-clickable"]]: selectionType === "card-click",
67+
[styles["message-area-selected"]]: selected,
5668
})}
5769
>
5870
<div className={styles.content}>{children}</div>
5971

6072
{actions && <div className={styles.actions}>{actions}</div>}
6173

6274
{showLoadingBar && <InternalLoadingBar variant="gen-ai-masked" />}
63-
</div>
75+
</Tag>
6476
</div>
6577
);
6678
}

src/chat-bubble/styles.scss

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,14 @@
1818
}
1919

2020
.message-area {
21+
@include shared.styles-reset;
22+
2123
display: flex;
2224
flex-direction: column;
2325
gap: cs.$space-scaled-s;
2426
padding: cs.$space-scaled-s;
2527
min-width: 0;
26-
overflow-x: auto;
28+
// overflow-x: auto;
2729

2830
border-start-start-radius: cs.$border-radius-chat-bubble;
2931
border-start-end-radius: cs.$border-radius-chat-bubble;
@@ -39,6 +41,34 @@
3941
background-color: cs.$color-background-chat-bubble-outgoing;
4042
}
4143

44+
// TODO: create new tokens for outgoing-selectable (background, border, selected background, selected border)
45+
// Potentially, need some for border width, too.
46+
&.message-area-selectable.message-area-selectable {
47+
border-color: cs.$color-border-divider-default;
48+
border-width: 2px;
49+
border-style: solid;
50+
background-color: cs.$color-background-layout-main;
51+
}
52+
53+
&.message-area-clickable.message-area-clickable {
54+
cursor: pointer;
55+
56+
&:focus-visible {
57+
@include shared.focus-highlight(6px, cs.$border-radius-chat-bubble);
58+
}
59+
60+
&:hover {
61+
background-color: cs.$color-background-dropdown-item-hover;
62+
}
63+
}
64+
65+
&.message-area-selected.message-area-selected.message-area-selected {
66+
border-color: cs.$color-border-item-selected;
67+
border-width: 2px;
68+
border-style: solid;
69+
background-color: cs.$color-background-item-selected;
70+
}
71+
4272
&.chat-bubble-type-incoming {
4373
color: cs.$color-text-chat-bubble-incoming;
4474
background-color: cs.$color-background-chat-bubble-incoming;

tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"types": [],
88
"lib": ["es2019", "dom", "dom.iterable"],
99
"module": "ESNext",
10-
"moduleResolution": "nodenext",
10+
"moduleResolution": "Node",
1111
"esModuleInterop": true,
1212
"forceConsistentCasingInFileNames": true,
1313
"isolatedModules": true,

0 commit comments

Comments
 (0)