mirror of
https://github.com/coder/coder.git
synced 2026-09-21 20:51:01 +08:00
fix(site/src/pages/AgentsPage/components): right-align user chat bubbles (#25000)
This commit is contained in:
+39
@@ -235,6 +235,13 @@ const buildStoryArgs = (...messages: TypesGen.ChatMessage[]) => ({
|
||||
parsedMessages: buildMessages(messages),
|
||||
});
|
||||
|
||||
const LONG_USER_MESSAGE = [
|
||||
"This is a deliberately long user message that should stay pinned to the",
|
||||
"right edge while the bubble stops short of filling the entire timeline",
|
||||
"column. It gives the Storybook test enough content to exercise the",
|
||||
"maximum width cap.",
|
||||
].join(" ");
|
||||
|
||||
const findAttachmentTile = async (
|
||||
canvas: ReturnType<typeof within>,
|
||||
label: string,
|
||||
@@ -291,6 +298,38 @@ const meta: Meta<typeof ConversationTimeline> = {
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof ConversationTimeline>;
|
||||
|
||||
/**
|
||||
* User bubbles should stay right-aligned, shrink to fit short content,
|
||||
* and cap long content so the timeline keeps some breathing room.
|
||||
*/
|
||||
export const UserMessageBubbleAlignment: Story = {
|
||||
args: buildStoryArgs(
|
||||
buildUserMessage({
|
||||
text: LONG_USER_MESSAGE,
|
||||
}),
|
||||
),
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
const messageText = canvas.getByText(/deliberately long user message/i);
|
||||
const userRow = messageText.closest('[data-role="user"]');
|
||||
expect(userRow).not.toBeNull();
|
||||
|
||||
const bubble = userRow?.firstElementChild;
|
||||
expect(bubble).not.toBeNull();
|
||||
|
||||
await userEvent.hover(userRow?.parentElement as HTMLElement);
|
||||
const actions = await canvas.findByTestId("message-actions");
|
||||
|
||||
const rowRect = (userRow as HTMLElement).getBoundingClientRect();
|
||||
const bubbleRect = (bubble as HTMLElement).getBoundingClientRect();
|
||||
const actionsRect = actions.getBoundingClientRect();
|
||||
|
||||
expect(bubbleRect.width).toBeLessThanOrEqual(rowRect.width * 0.81);
|
||||
expect(Math.abs(rowRect.right - bubbleRect.right)).toBeLessThanOrEqual(2);
|
||||
expect(Math.abs(rowRect.right - actionsRect.right)).toBeLessThanOrEqual(2);
|
||||
},
|
||||
};
|
||||
|
||||
/** Regression guard: a single image attachment must not be duplicated. */
|
||||
export const UserMessageWithSingleImage: Story = {
|
||||
args: {
|
||||
|
||||
@@ -592,7 +592,10 @@ const ChatMessageItem = memo<{
|
||||
(displayState.hasCopyableContent ||
|
||||
(isUser && onEditUserMessage)) && (
|
||||
<div
|
||||
className="mt-0.5 flex items-center gap-0.5 opacity-0 transition-opacity focus-within:opacity-100 group-hover/msg:opacity-100"
|
||||
className={cn(
|
||||
"mt-0.5 flex items-center gap-0.5 opacity-0 transition-opacity focus-within:opacity-100 group-hover/msg:opacity-100",
|
||||
isUser && "w-full justify-end",
|
||||
)}
|
||||
data-testid="message-actions"
|
||||
>
|
||||
{displayState.hasCopyableContent && (
|
||||
|
||||
@@ -43,7 +43,7 @@ export const UserMessageContent: FC<{
|
||||
onTextFileClick,
|
||||
}) => {
|
||||
return (
|
||||
<Message className="w-full max-w-none">
|
||||
<Message className="w-fit max-w-[min(80vw,80%)]">
|
||||
<MessageContent
|
||||
className={cn(
|
||||
"rounded-lg border border-solid border-border-default bg-surface-secondary px-3 py-2 font-sans shadow-sm transition-shadow",
|
||||
|
||||
@@ -27,7 +27,7 @@ export const ConversationWithMessages: Story = {
|
||||
return (
|
||||
<Conversation>
|
||||
<ConversationItem {...userItemProps}>
|
||||
<Message className="my-2 w-full max-w-none">
|
||||
<Message className="my-2 w-fit max-w-[min(80vw,80%)]">
|
||||
<MessageContent className="rounded-lg border border-solid border-border-default bg-surface-secondary px-3 py-2 font-sans shadow-sm">
|
||||
Check why `git fetch` is failing in this workspace.
|
||||
</MessageContent>
|
||||
|
||||
Reference in New Issue
Block a user