` element.
+### ChatMain
+
+| Prop | Type | Description | Default |
+| ---------- | ----------- | -------------------------- | ------- |
+| `children` | `ReactNode` | The main content to render | - |
+
+All other props are passed through to the underlying `
` element.
+
+**Note:** `ChatMain` must be used as a direct child of `ChatLayout` to work correctly within the grid system.
+
## Context API
### useChatLayoutContext
diff --git a/chat/chat-layout/package.json b/chat/chat-layout/package.json
index ac788b8a79..3d3b26e0c6 100644
--- a/chat/chat-layout/package.json
+++ b/chat/chat-layout/package.json
@@ -33,6 +33,16 @@
"@leafygreen-ui/tokens": "workspace:^",
"@lg-tools/test-harnesses": "workspace:^"
},
+ "peerDependencies": {
+ "@lg-chat/leafygreen-chat-provider": "workspace:^"
+ },
+ "devDependencies": {
+ "@lg-chat/chat-window": "workspace:^",
+ "@lg-chat/input-bar": "workspace:^",
+ "@lg-chat/message": "workspace:^",
+ "@lg-chat/message-feed": "workspace:^",
+ "@lg-chat/title-bar": "workspace:^"
+ },
"homepage": "https://github.com/mongodb/leafygreen-ui/tree/main/chat/chat-layout",
"repository": {
"type": "git",
diff --git a/chat/chat-layout/src/ChatLayout.stories.tsx b/chat/chat-layout/src/ChatLayout.stories.tsx
index a1b6c90f4e..85108f5011 100644
--- a/chat/chat-layout/src/ChatLayout.stories.tsx
+++ b/chat/chat-layout/src/ChatLayout.stories.tsx
@@ -1,8 +1,19 @@
import React from 'react';
+import { ChatWindow } from '@lg-chat/chat-window';
+import { InputBar } from '@lg-chat/input-bar';
+import {
+ LeafyGreenChatProvider,
+ Variant,
+} from '@lg-chat/leafygreen-chat-provider';
+import { Message } from '@lg-chat/message';
+import { MessageFeed } from '@lg-chat/message-feed';
+import { TitleBar } from '@lg-chat/title-bar';
import { StoryMetaType } from '@lg-tools/storybook-utils';
import { StoryFn, StoryObj } from '@storybook/react';
-import { ChatLayout, type ChatLayoutProps } from '.';
+import { css } from '@leafygreen-ui/emotion';
+
+import { ChatLayout, type ChatLayoutProps, ChatMain } from '.';
const meta: StoryMetaType
= {
title: 'Composition/Chat/ChatLayout',
@@ -10,10 +21,68 @@ const meta: StoryMetaType = {
parameters: {
default: 'LiveExample',
},
+ decorators: [
+ Story => (
+
+
+
+ ),
+ ],
};
export default meta;
-const Template: StoryFn = props => ;
+const sideNavPlaceholderStyles = css`
+ background-color: rgba(0, 0, 0, 0.05);
+ padding: 16px;
+ min-width: 200px;
+`;
+
+const testMessages = [
+ {
+ id: '1',
+ messageBody: 'Hello! How can I help you today?',
+ isSender: false,
+ },
+ {
+ id: '2',
+ messageBody: 'I need help with my database query.',
+ },
+ {
+ id: '3',
+ messageBody:
+ 'Sure! I can help with that. What specific issue are you encountering?',
+ isSender: false,
+ },
+];
+
+const Template: StoryFn = props => (
+
+
+ ChatSideNav Placeholder
+
+
+
+
+ {testMessages.map(msg => (
+
+ ))}
+
+ {}} />
+
+
+
+
+);
export const LiveExample: StoryObj = {
render: Template,
diff --git a/chat/chat-layout/src/ChatLayout/ChatLayout.tsx b/chat/chat-layout/src/ChatLayout/ChatLayout.tsx
index fbe818f86f..73f0386e84 100644
--- a/chat/chat-layout/src/ChatLayout/ChatLayout.tsx
+++ b/chat/chat-layout/src/ChatLayout/ChatLayout.tsx
@@ -6,9 +6,8 @@ import { ChatLayoutContext } from './ChatLayoutContext';
/**
* ChatLayout is a context provider that manages the pinned state of the side nav
- * and provides it to all child components.
- *
- * Context is primarily used by ChatSideNav and ChatMain.
+ * and provides it to all child components. It uses CSS Grid to control the layout
+ * and positioning the side nav and main content.
*/
export function ChatLayout({
children,
diff --git a/chat/chat-layout/src/ChatLayout/ChatLayout.types.ts b/chat/chat-layout/src/ChatLayout/ChatLayout.types.ts
index 0550d18606..f2d86c2bb4 100644
--- a/chat/chat-layout/src/ChatLayout/ChatLayout.types.ts
+++ b/chat/chat-layout/src/ChatLayout/ChatLayout.types.ts
@@ -1,19 +1,19 @@
-import { ComponentPropsWithRef, PropsWithChildren } from 'react';
+import { ComponentPropsWithRef } from 'react';
import { DarkModeProps } from '@leafygreen-ui/lib';
-export type ChatLayoutProps = ComponentPropsWithRef<'div'> &
- DarkModeProps &
- PropsWithChildren<{
- /**
- * Initial state for whether the side nav is pinned (expanded).
- * @default true
- */
- initialIsPinned?: boolean;
+export interface ChatLayoutProps
+ extends ComponentPropsWithRef<'div'>,
+ DarkModeProps {
+ /**
+ * Initial state for whether the side nav is pinned (expanded).
+ * @default true
+ */
+ initialIsPinned?: boolean;
- /**
- * Callback fired when the side nav is toggled (pinned/unpinned).
- * Receives the new `isPinned` state as an argument.
- */
- onTogglePinned?: (isPinned: boolean) => void;
- }>;
+ /**
+ * Callback fired when the side nav is toggled (pinned/unpinned).
+ * Receives the new `isPinned` state as an argument.
+ */
+ onTogglePinned?: (isPinned: boolean) => void;
+}
diff --git a/chat/chat-layout/src/ChatMain/ChatMain.spec.tsx b/chat/chat-layout/src/ChatMain/ChatMain.spec.tsx
new file mode 100644
index 0000000000..2f0376ad1e
--- /dev/null
+++ b/chat/chat-layout/src/ChatMain/ChatMain.spec.tsx
@@ -0,0 +1,56 @@
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+
+import { ChatLayout } from '../ChatLayout';
+
+import { ChatMain } from '.';
+
+describe('packages/chat-layout/ChatMain', () => {
+ describe('ChatMain', () => {
+ test('renders children', () => {
+ render(
+
+
+ Main Content
+
+ ,
+ );
+ expect(screen.getByText('Main Content')).toBeInTheDocument();
+ });
+
+ test('forwards HTML attributes to the div element', () => {
+ render(
+
+
+ Content
+
+ ,
+ );
+ const element = screen.getByTestId('chat-main');
+ expect(element).toHaveAttribute('aria-label', 'Chat content');
+ });
+
+ test('forwards ref to the div element', () => {
+ const ref = React.createRef();
+ render(
+
+ Content
+ ,
+ );
+ expect(ref.current).toBeInstanceOf(HTMLDivElement);
+ expect(ref.current?.tagName).toBe('DIV');
+ });
+
+ test('applies custom className', () => {
+ render(
+
+
+ Content
+
+ ,
+ );
+ const element = screen.getByTestId('chat-main');
+ expect(element).toHaveClass('custom-class');
+ });
+ });
+});
diff --git a/chat/chat-layout/src/ChatMain/ChatMain.styles.ts b/chat/chat-layout/src/ChatMain/ChatMain.styles.ts
new file mode 100644
index 0000000000..a8279dd2c4
--- /dev/null
+++ b/chat/chat-layout/src/ChatMain/ChatMain.styles.ts
@@ -0,0 +1,14 @@
+import { css, cx } from '@leafygreen-ui/emotion';
+
+import { gridAreas } from '../constants';
+
+const baseContainerStyles = css`
+ grid-area: ${gridAreas.main};
+ display: flex;
+ flex-direction: column;
+ min-width: 0;
+ height: 100%;
+`;
+
+export const getContainerStyles = ({ className }: { className?: string }) =>
+ cx(baseContainerStyles, className);
diff --git a/chat/chat-layout/src/ChatMain/ChatMain.tsx b/chat/chat-layout/src/ChatMain/ChatMain.tsx
new file mode 100644
index 0000000000..7e62d19d72
--- /dev/null
+++ b/chat/chat-layout/src/ChatMain/ChatMain.tsx
@@ -0,0 +1,21 @@
+import React, { forwardRef } from 'react';
+
+import { getContainerStyles } from './ChatMain.styles';
+import { ChatMainProps } from './ChatMain.types';
+
+/**
+ * ChatMain represents the main content area of the chat layout.
+ * It automatically positions itself in the second column of the parent
+ * ChatLayout's CSS Grid, allowing the layout to control spacing for the sidebar.
+ */
+export const ChatMain = forwardRef(
+ ({ children, className, ...rest }, ref) => {
+ return (
+
+ {children}
+
+ );
+ },
+);
+
+ChatMain.displayName = 'ChatMain';
diff --git a/chat/chat-layout/src/ChatMain/ChatMain.types.ts b/chat/chat-layout/src/ChatMain/ChatMain.types.ts
new file mode 100644
index 0000000000..9070c8ea0e
--- /dev/null
+++ b/chat/chat-layout/src/ChatMain/ChatMain.types.ts
@@ -0,0 +1,7 @@
+import { ComponentPropsWithRef } from 'react';
+
+import { DarkModeProps } from '@leafygreen-ui/lib';
+
+export interface ChatMainProps
+ extends ComponentPropsWithRef<'div'>,
+ DarkModeProps {}
diff --git a/chat/chat-layout/src/ChatMain/index.ts b/chat/chat-layout/src/ChatMain/index.ts
new file mode 100644
index 0000000000..a212ee6fff
--- /dev/null
+++ b/chat/chat-layout/src/ChatMain/index.ts
@@ -0,0 +1,2 @@
+export { ChatMain } from './ChatMain';
+export { type ChatMainProps } from './ChatMain.types';
diff --git a/chat/chat-layout/src/index.ts b/chat/chat-layout/src/index.ts
index af5533d3db..8daa958273 100644
--- a/chat/chat-layout/src/index.ts
+++ b/chat/chat-layout/src/index.ts
@@ -4,3 +4,4 @@ export {
type ChatLayoutProps,
useChatLayoutContext,
} from './ChatLayout';
+export { ChatMain, type ChatMainProps } from './ChatMain';
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 3897def354..b330dd06dc 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -349,9 +349,28 @@ importers:
'@leafygreen-ui/tokens':
specifier: workspace:^
version: link:../../packages/tokens
+ '@lg-chat/leafygreen-chat-provider':
+ specifier: workspace:^
+ version: link:../leafygreen-chat-provider
'@lg-tools/test-harnesses':
specifier: workspace:^
version: link:../../tools/test-harnesses
+ devDependencies:
+ '@lg-chat/chat-window':
+ specifier: workspace:^
+ version: link:../chat-window
+ '@lg-chat/input-bar':
+ specifier: workspace:^
+ version: link:../input-bar
+ '@lg-chat/message':
+ specifier: workspace:^
+ version: link:../message
+ '@lg-chat/message-feed':
+ specifier: workspace:^
+ version: link:../message-feed
+ '@lg-chat/title-bar':
+ specifier: workspace:^
+ version: link:../title-bar
chat/chat-window:
dependencies: