Skip to content

Commit 8976ab2

Browse files
authored
[LG-5439] feat: ChatLayout integration branch (#3289)
* feat(chat-layout): new @lg-chat/chat-layout package with ChatLayout and ChatLayoutContext (#3251) * [LG-5645] feat(chat-layout): implement ChatMain component (#3252) * feat(chat-layout): implement ChatMain component * refactor(chat-layout): remove PropsWithChildren * [LG-5646] feat(chat-layout): implement ChatSideNav component with Header and Content subcomponents (#3271) * chore(chat-layout): add deps for ChatSideNav * feat(chat-layout): implement ChatSideNav and subcomponents * test(chat-layout): update stories * docs(chat-layout): README * fix(chat-layout): apply focus styles on :focus-visible * refactor(chat-layout): PR feedback * refactor(chat-layout): ChatSideNav only renders header and content * [LG-5649] feat(chat-layout): add SideNavItem subcomponent (#3273) * chore(chat-layout): add LG palette dep * feat(chat-layout): implement ChatSideNavItem subcomponent * test(chat-layout): update stories * docs(chat-layout): README * fix(chat-layout): add missing polymorphic dep * fix(chat-layout): ChatSideNavItem default is div * style(chat-layout): use constant for side nav item height * [LG-5650] feat(chat-layout): add hover expand and collapse functionality (#3278) * feat(chat-layout): add header to ChatSideNavContent * fix(input-bar): rm redundant z-index and increase disclaimer text spacing * chore(input-bar): changeset * chore(chat-layout): rm icon-button dependency * feat(chat-layout): enhance ChatLayout with side nav hover state and transition effects * test(chat-layout): update stories * docs(chat-layout): README * refactor(input-bar): revert gap changes * refactor(chat-layout): expose shouldRenderExpanded from context and fix overflow * fix(chat-layout): peer dep and init changeset
1 parent 68b59a0 commit 8976ab2

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+2389
-1
lines changed

.changeset/chat-layout.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
'@lg-chat/chat-layout': minor
3+
---
4+
5+
Initial release with `ChatLayout`, `ChatMain`, and `ChatSideNav`
6+
- `ChatSideNav` is a compound component with the following subcomponents:
7+
- `ChatSideNav.Header`
8+
- `ChatSideNav.Content`
9+
- `ChatSideNav.SideNavItem`

.changeset/gold-goats-visit.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@lg-chat/input-bar': patch
3+
---
4+
5+
Remove redundant `z-index: 2;` in `InputBar` content wrapping node.

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ import Button from '@leafygreen-ui/button';
149149
| [@lg-charts/legend](./charts/legend) | [![version](https://img.shields.io/npm/v/@lg-charts/legend)](https://www.npmjs.com/package/@lg-charts/legend) | ![downloads](https://img.shields.io/npm/dm/@lg-charts/legend?color=white) | [Live Example](http://mongodb.design/component/legend/live-example) |
150150
| [@lg-charts/series-provider](./charts/series-provider) | [![version](https://img.shields.io/npm/v/@lg-charts/series-provider)](https://www.npmjs.com/package/@lg-charts/series-provider) | ![downloads](https://img.shields.io/npm/dm/@lg-charts/series-provider?color=white) | [Live Example](http://mongodb.design/component/series-provider/live-example) |
151151
| [@lg-chat/avatar](./chat/avatar) | [![version](https://img.shields.io/npm/v/@lg-chat/avatar)](https://www.npmjs.com/package/@lg-chat/avatar) | ![downloads](https://img.shields.io/npm/dm/@lg-chat/avatar?color=white) | [Live Example](http://mongodb.design/component/avatar/live-example) |
152+
| [@lg-chat/chat-layout](./chat/chat-layout) | [![version](https://img.shields.io/npm/v/@lg-chat/chat-layout)](https://www.npmjs.com/package/@lg-chat/chat-layout) | ![downloads](https://img.shields.io/npm/dm/@lg-chat/chat-layout?color=white) | [Live Example](http://mongodb.design/component/chat-layout/live-example) |
152153
| [@lg-chat/chat-window](./chat/chat-window) | [![version](https://img.shields.io/npm/v/@lg-chat/chat-window)](https://www.npmjs.com/package/@lg-chat/chat-window) | ![downloads](https://img.shields.io/npm/dm/@lg-chat/chat-window?color=white) | [Live Example](http://mongodb.design/component/chat-window/live-example) |
153154
| [@lg-chat/fixed-chat-window](./chat/fixed-chat-window) | [![version](https://img.shields.io/npm/v/@lg-chat/fixed-chat-window)](https://www.npmjs.com/package/@lg-chat/fixed-chat-window) | ![downloads](https://img.shields.io/npm/dm/@lg-chat/fixed-chat-window?color=white) | [Live Example](http://mongodb.design/component/fixed-chat-window/live-example) |
154155
| [@lg-chat/input-bar](./chat/input-bar) | [![version](https://img.shields.io/npm/v/@lg-chat/input-bar)](https://www.npmjs.com/package/@lg-chat/input-bar) | ![downloads](https://img.shields.io/npm/dm/@lg-chat/input-bar?color=white) | [Live Example](http://mongodb.design/component/input-bar/live-example) |

chat/chat-layout/README.md

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
# Chat Layout
2+
3+
![npm (scoped)](https://img.shields.io/npm/v/@lg-chat/chat-layout.svg)
4+
5+
#### [View on MongoDB.design](https://www.mongodb.design/component/chat-layout/live-example/)
6+
7+
## Installation
8+
9+
### PNPM
10+
11+
```shell
12+
pnpm add @lg-chat/chat-layout
13+
```
14+
15+
### Yarn
16+
17+
```shell
18+
yarn add @lg-chat/chat-layout
19+
```
20+
21+
### NPM
22+
23+
```shell
24+
npm install @lg-chat/chat-layout
25+
```
26+
27+
## Overview
28+
29+
`@lg-chat/chat-layout` provides a CSS Grid-based layout system for building full-screen chat interfaces with a side nav that can be collapsed or pinned.
30+
31+
This package exports:
32+
33+
- `ChatLayout`: The grid container and context provider
34+
- `ChatMain`: The primary content area of the chat interface, automatically positioned within the grid layout.
35+
- `ChatSideNav`: A compound component representing the side navigation, exposing subcomponents such as `ChatSideNav.Header`, `ChatSideNav.Content`, and `ChatSideNav.SideNavItem` for flexible composition.
36+
- `useChatLayoutContext`: Hook for accessing layout state
37+
38+
## Examples
39+
40+
### Basic
41+
42+
```tsx
43+
import { useState } from 'react';
44+
import { ChatLayout, ChatMain, ChatSideNav } from '@lg-chat/chat-layout';
45+
46+
function MyChatApp() {
47+
const [activeChatId, setActiveChatId] = useState('1');
48+
49+
const chatItems = [
50+
{ id: '1', name: 'MongoDB Atlas Setup', href: '/chat/1' },
51+
{ id: '2', name: 'Database Query Help', href: '/chat/2' },
52+
{ id: '3', name: 'Schema Design Discussion', href: '/chat/3' },
53+
];
54+
55+
const handleNewChat = () => {
56+
console.log('Start new chat');
57+
};
58+
59+
return (
60+
<ChatLayout>
61+
<ChatSideNav>
62+
<ChatSideNav.Header onClickNewChat={handleNewChat} />
63+
<ChatSideNav.Content>
64+
{chatItems.map(({ href, id, item, name }) => (
65+
<ChatSideNav.SideNavItem
66+
key={id}
67+
href={href}
68+
active={id === activeChatId}
69+
onClick={e => {
70+
e.preventDefault();
71+
setActiveChatId(id);
72+
}}
73+
>
74+
{name}
75+
</ChatSideNav.SideNavItem>
76+
))}
77+
</ChatSideNav.Content>
78+
</ChatSideNav>
79+
<ChatMain>{/* Main chat content here */}</ChatMain>
80+
</ChatLayout>
81+
);
82+
}
83+
```
84+
85+
### With Initial State and Toggle Pinned Callback
86+
87+
```tsx
88+
import { ChatLayout, ChatMain, ChatSideNav } from '@lg-chat/chat-layout';
89+
90+
function MyChatApp() {
91+
const handleTogglePinned = (isPinned: boolean) => {
92+
console.log('Side nav is now:', isPinned ? 'pinned' : 'collapsed');
93+
};
94+
95+
return (
96+
<ChatLayout initialIsPinned={false} onTogglePinned={handleTogglePinned}>
97+
<ChatSideNav>{/* Side nav subcomponents */}</ChatSideNav>
98+
<ChatMain>{/* Main chat content */}</ChatMain>
99+
</ChatLayout>
100+
);
101+
}
102+
```
103+
104+
## Properties
105+
106+
### ChatLayout
107+
108+
| Prop | Type | Description | Default |
109+
| ------------------------------ | ----------------------------- | --------------------------------------------------------------------------------------------- | ------- |
110+
| `children` | `ReactNode` | The content to render inside the grid layout (`ChatSideNav` and `ChatMain` components) | - |
111+
| `className` _(optional)_ | `string` | Custom CSS class to apply to the grid container | - |
112+
| `initialIsPinned` _(optional)_ | `boolean` | Initial state for whether the side nav is pinned (expanded) | `true` |
113+
| `onTogglePinned` _(optional)_ | `(isPinned: boolean) => void` | Callback fired when the side nav is toggled. Receives the new `isPinned` state as an argument | - |
114+
115+
All other props are passed through to the underlying `<div>` element.
116+
117+
### ChatMain
118+
119+
| Prop | Type | Description | Default |
120+
| ---------- | ----------- | -------------------------- | ------- |
121+
| `children` | `ReactNode` | The main content to render | - |
122+
123+
All other props are passed through to the underlying `<div>` element.
124+
125+
**Note:** `ChatMain` must be used as a direct child of `ChatLayout` to work correctly within the grid system.
126+
127+
### ChatSideNav
128+
129+
| Prop | Type | Description | Default |
130+
| ------------------------ | ------------------------- | -------------------------------------------------------------- | ------- |
131+
| `children` | `ReactNode` | Should include `ChatSideNav.Header` and `ChatSideNav.Content`. | - |
132+
| `className` _(optional)_ | `string` | Root class name | - |
133+
| `...` | `HTMLElementProps<'nav'>` | Props spread on the root `<nav>` element | - |
134+
135+
### ChatSideNav.Header
136+
137+
| Prop | Type | Description | Default |
138+
| ----------------------------- | -------------------------------------- | ------------------------------------------- | ------- |
139+
| `onClickNewChat` _(optional)_ | `MouseEventHandler<HTMLButtonElement>` | Fired when the "New Chat" button is clicked | - |
140+
| `className` _(optional)_ | `string` | Header class name | - |
141+
| `...` | `HTMLElementProps<'div'>` | Props spread on the header container | - |
142+
143+
### ChatSideNav.Content
144+
145+
| Prop | Type | Description | Default |
146+
| ------------------------ | ------------------------- | ------------------------------------- | ------- |
147+
| `className` _(optional)_ | `string` | Content class name | - |
148+
| `...` | `HTMLElementProps<'div'>` | Props spread on the content container | - |
149+
150+
### ChatSideNav.SideNavItem
151+
152+
| Prop | Type | Description | Default |
153+
| ------------------------ | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
154+
| `active` _(optional)_ | `boolean` | Whether or not the component should be rendered in an active state. When active, applies active styling and sets `aria-current="page"` | `false` |
155+
| `as` _(optional)_ | `React.ElementType` | When provided, the component will be rendered as the component or html tag indicated by this prop. Other additional props will be spread on the element. For example, `Link` or `a` tags can be supplied. Defaults to `'a'` | - |
156+
| `children` | `ReactNode` | Content that will be rendered inside the root-level element (typically the chat name) | - |
157+
| `className` _(optional)_ | `string` | Class name that will be applied to the root-level element | - |
158+
| `href` _(optional)_ | `string` | The URL that the hyperlink points to. When provided, the component will be rendered as an anchor element | - |
159+
| `onClick` _(optional)_ | `MouseEventHandler` | The event handler function for the 'onclick' event. Receives the associated `event` object as the first argument | - |
160+
161+
## Context API
162+
163+
### useChatLayoutContext
164+
165+
Hook that returns the current chat layout context:
166+
167+
```tsx
168+
const {
169+
isPinned,
170+
togglePin,
171+
isSideNavHovered,
172+
setIsSideNavHovered,
173+
shouldRenderExpanded,
174+
} = useChatLayoutContext();
175+
```
176+
177+
**Returns:**
178+
179+
| Property | Type | Description |
180+
| ---------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------- |
181+
| `isPinned` | `boolean` | Whether the side nav is currently pinned |
182+
| `togglePin` | `() => void` | Function to toggle the pinned state |
183+
| `isSideNavHovered` | `boolean` | Whether the side nav is currently being hovered |
184+
| `setIsSideNavHovered` | `(isHovered: boolean) => void` | Function to set the hover state of the side nav |
185+
| `shouldRenderExpanded` | `boolean` | Whether the side nav should render in expanded state. This is `true` when the nav is pinned OR hovered. |
186+
187+
## Behavior
188+
189+
### State Management
190+
191+
- `ChatLayout` manages the `isPinned` and `isSideNavHovered` state internally and provides it to all descendants via `ChatLayoutContext`
192+
- `shouldRenderExpanded` is computed as `isPinned || isSideNavHovered` and provided in the context for convenience
193+
- When `togglePin` is called:
194+
1. The `isPinned` state updates
195+
2. Grid columns resize smoothly via CSS transition
196+
3. The `onTogglePinned` callback fires (if provided) with the new state value
197+
- Descendant components can consume the context to:
198+
- Read the current `isPinned` state
199+
- Call `togglePin()` to toggle the sidebar
200+
- Read the current `isSideNavHovered` state
201+
- Call `setIsSideNavHovered()` to update the hover state
202+
- Use `shouldRenderExpanded` to determine if the side nav should render in expanded state

chat/chat-layout/package.json

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
2+
{
3+
"name": "@lg-chat/chat-layout",
4+
"version": "0.0.1",
5+
"description": "LeafyGreen UI Kit Chat Layout",
6+
"main": "./dist/umd/index.js",
7+
"module": "./dist/esm/index.js",
8+
"types": "./dist/types/index.d.ts",
9+
"license": "Apache-2.0",
10+
"exports": {
11+
".": {
12+
"require": "./dist/umd/index.js",
13+
"import": "./dist/esm/index.js",
14+
"types": "./dist/types/index.d.ts"
15+
},
16+
"./testing": {
17+
"require": "./dist/umd/testing/index.js",
18+
"import": "./dist/esm/testing/index.js",
19+
"types": "./dist/types/testing/index.d.ts"
20+
}
21+
},
22+
"scripts": {
23+
"build": "lg-build bundle",
24+
"tsc": "lg-build tsc",
25+
"docs": "lg-build docs"
26+
},
27+
"publishConfig": {
28+
"access": "public"
29+
},
30+
"dependencies": {
31+
"@leafygreen-ui/avatar": "workspace:^",
32+
"@leafygreen-ui/button": "workspace:^",
33+
"@leafygreen-ui/compound-component": "workspace:^",
34+
"@leafygreen-ui/emotion": "workspace:^",
35+
"@leafygreen-ui/icon": "workspace:^",
36+
"@leafygreen-ui/lib": "workspace:^",
37+
"@leafygreen-ui/palette": "workspace:^",
38+
"@leafygreen-ui/polymorphic": "workspace:^",
39+
"@leafygreen-ui/tokens": "workspace:^",
40+
"@leafygreen-ui/typography": "workspace:^",
41+
"@lg-tools/test-harnesses": "workspace:^"
42+
},
43+
"peerDependencies": {
44+
"@leafygreen-ui/leafygreen-provider": "workspace:^3.2.0 || workspace:^4.0.0 || workspace:^5.0.0",
45+
"@lg-chat/leafygreen-chat-provider": "workspace:^"
46+
},
47+
"devDependencies": {
48+
"@lg-chat/chat-window": "workspace:^",
49+
"@lg-chat/input-bar": "workspace:^",
50+
"@lg-chat/message": "workspace:^",
51+
"@lg-chat/message-feed": "workspace:^",
52+
"@lg-chat/title-bar": "workspace:^"
53+
},
54+
"homepage": "https://github.com/mongodb/leafygreen-ui/tree/main/chat/chat-layout",
55+
"repository": {
56+
"type": "git",
57+
"url": "https://github.com/mongodb/leafygreen-ui"
58+
},
59+
"bugs": {
60+
"url": "https://jira.mongodb.org/projects/LG/summary"
61+
}
62+
}

0 commit comments

Comments
 (0)