import { Meta, StoryObj } from '@storybook/react-vite';
import { SlotPlaceholder } from '../_story-helpers/SlotPlaceholder';
import { ChatMessage } from '../ChatMessage/ChatMessage';
import { ChatMessageList } from '../ChatMessage/ChatMessageList';
import { ChatPanel } from './ChatPanel';
import './chat-panel-story.css';
const meta: Meta<typeof ChatPanel> = {
tags: ['new-pattern', 'ready-for-dev'],
title: 'Component Proposals/AI Chat/Chat Panel',
component: ChatPanel,
decorators: [
(Story) => (
<div className='chat-panel-story__frame'>
<Story />
</div>
),
],
parameters: {
docs: {
// The frame is viewport height, so each docs story gets its own iframe.
story: { iframeHeight: 640, inline: false },
description: {
component:
'**New pattern:** not in PDS v2 today. The frame of an AI chat drawer or docked panel: a title row with a close button, a pinned header, a scrolling content area and a pinned footer. Every region is a slot; the panel only owns the layout, the scroll and the inset.',
},
},
layout: 'fullscreen',
},
argTypes: {
children: { control: false, table: { category: 'Slots' } },
className: { table: { category: 'Base attributes' } },
closeButtonLabel: { control: 'text', table: { category: 'Content' } },
contentInset: {
control: 'inline-radio',
options: ['default', 'none'],
table: { category: 'Component configuration' },
},
footer: { control: false, table: { category: 'Slots' } },
footerInset: {
control: 'inline-radio',
options: ['default', 'none'],
table: { category: 'Component configuration' },
},
hasCloseButton: { table: { category: 'Component configuration' } },
hasContentFade: { table: { category: 'Component configuration' } },
hasFooterDivider: { table: { category: 'Component configuration' } },
hasHeaderDivider: { table: { category: 'Component configuration' } },
hasScrollToLatest: { table: { category: 'Component configuration' } },
header: { control: false, table: { category: 'Slots' } },
headerInset: {
control: 'inline-radio',
options: ['default', 'none'],
table: { category: 'Component configuration' },
},
onClose: { table: { category: 'Events' } },
scrollToLatestLabel: { control: 'text', table: { category: 'Content' } },
size: {
control: 'inline-radio',
options: ['m', 'l'],
table: { category: 'Component configuration' },
},
title: { control: false, table: { category: 'Slots' } },
},
args: {
closeButtonLabel: 'Close chat',
contentInset: 'default',
footerInset: 'default',
hasCloseButton: true,
hasFooterDivider: false,
hasHeaderDivider: false,
headerInset: 'default',
size: 'm',
},
};
export default meta;
type Story = StoryObj<typeof meta>;
const fill = { height: '100%' };
export const Default: Story = {
args: {
children: <SlotPlaceholder label='Content area' style={fill} />,
footer: (
<SlotPlaceholder label='Footer slot' style={{ minHeight: '4rem' }} />
),
header: (
<SlotPlaceholder label='Header slot' style={{ minHeight: '6rem' }} />
),
title: <SlotPlaceholder label='Title slot' />,
},
};
export const Bleed: Story = {
args: {
children: <SlotPlaceholder label='Content area' style={fill} />,
footer: (
<SlotPlaceholder label='Footer slot' style={{ minHeight: '4rem' }} />
),
header: (
<SlotPlaceholder label='Header slot' style={{ minHeight: '6rem' }} />
),
title: <SlotPlaceholder label='Title slot' />,
contentInset: 'none',
footerInset: 'none',
hasFooterDivider: true,
hasHeaderDivider: true,
headerInset: 'none',
},
parameters: {
docs: {
description: {
story:
'`headerInset`, `contentInset` and `footerInset` set to `none` remove the inline padding of that region (block padding stays). The child supplies its own, so a tab bar or a divider can run edge to edge.',
},
},
},
};
export const ContentOnly: Story = {
args: {
children: <SlotPlaceholder label='Content area' style={fill} />,
},
parameters: {
docs: {
description: {
story:
'Every region is optional. With only `children` you get the close button and a scrolling pane. Turn `hasCloseButton` off for a bare pane when the screen supplies its own way to close.',
},
},
},
};
const LINES = [
'Can you check the homepage for accessibility problems?',
'I found three issues: the hero image has no alt text, the subtitle has low contrast and two footer links are broken.',
'Fix the hero.',
'Done. I added alt text and raised the contrast of the subtitle.',
'What about the footer links?',
'They point to pages that no longer exist. I can remove them or point them to the new pages.',
];
export const Scrolling: Story = {
args: {
children: (
<ChatMessageList>
{Array.from({ length: 24 }, (_, index) => (
<ChatMessage
key={index}
label={index % 2 === 0 ? 'You' : 'Zappy'}
sender={index % 2 === 0 ? 'user' : 'agent'}
>
{LINES[index % LINES.length]}
</ChatMessage>
))}
</ChatMessageList>
),
footer: (
<SlotPlaceholder label='Footer slot' style={{ minHeight: '4rem' }} />
),
hasContentFade: true,
hasScrollToLatest: true,
scrollToLatestLabel: 'Scroll to latest',
title: <SlotPlaceholder label='Title slot' />,
},
parameters: {
docs: {
description: {
story:
'A long conversation. It opens at the end. Scroll up and a button floats at the bottom center of the content area; choose it to jump back to the latest. The bottom edge fades into the footer (`hasContentFade`). With `hasScrollToLatest`, the view also stays at the end as content grows, but only while the person is at the end: scroll up and it stops following.',
},
},
},
};
Chat Panel
A layout shell for the content of an AI chat drawer or docked side panel.
Net-new componentOpen in Storybook