Skip to content

Commit 4797af9

Browse files
authored
[LG-5712] feat(chat-layout): handle overflow in ChatSideNavItem and add compact tooltip (#3329)
* chore(chat-layout): add LG tooltip dep * feat(chat-layout): handle overflow in ChatSideNav and add compact tooltip * test(chat-layout): update stories * chore(chat-layout): changeset * feat(chat-layout): conditionally render tooltip if sidenav item text is truncated * chore(chat-layout): add LG hooks dep * fix(chat-layout): expand sidenav when descendant element is focused * chore(chat-layout): changesets * fix(chat-layout): increase chromatic snapshot delay
1 parent 91e979d commit 4797af9

File tree

11 files changed

+279
-92
lines changed

11 files changed

+279
-92
lines changed

.changeset/four-horses-dress.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@lg-chat/chat-layout': patch
3+
---
4+
5+
Expand `ChatSideNav` when descendant element is focused

.changeset/tender-moments-make.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@lg-chat/chat-layout': minor
3+
---
4+
5+
[LG-5712](https://jira.mongodb.org/browse/LG-5712): handle overflow in `ChatSideNav.SideNavItem` and add compact tooltip for truncated text

chat/chat-layout/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,13 @@
3131
"@leafygreen-ui/button": "workspace:^",
3232
"@leafygreen-ui/compound-component": "workspace:^",
3333
"@leafygreen-ui/emotion": "workspace:^",
34+
"@leafygreen-ui/hooks": "workspace:^",
3435
"@leafygreen-ui/icon": "workspace:^",
3536
"@leafygreen-ui/lib": "workspace:^",
3637
"@leafygreen-ui/palette": "workspace:^",
3738
"@leafygreen-ui/polymorphic": "workspace:^",
3839
"@leafygreen-ui/tokens": "workspace:^",
40+
"@leafygreen-ui/tooltip": "workspace:^",
3941
"@leafygreen-ui/typography": "workspace:^",
4042
"@lg-tools/test-harnesses": "workspace:^"
4143
},

chat/chat-layout/src/ChatLayout.stories.tsx

Lines changed: 93 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,34 @@ export default meta;
5757

5858
const chatItems = [
5959
{ id: '1', name: 'MongoDB Atlas Setup', href: '/chat/1' },
60-
{ id: '2', name: 'Writing a Database Query', href: '/chat/2' },
60+
{
61+
id: '2',
62+
name: 'Writing a very very very long Database Query',
63+
href: '/chat/2',
64+
},
6165
{ id: '3', name: 'Schema Design Discussion', href: '/chat/3' },
6266
{ id: '4', name: 'Performance Optimization', href: '/chat/4' },
6367
{ id: '5', name: 'Migration Planning', href: '/chat/5' },
6468
];
6569

70+
const hoverSideNav = async (canvasElement: HTMLElement) => {
71+
const canvas = within(canvasElement);
72+
const sideNav = canvas.getByLabelText('Side navigation');
73+
await userEvent.hover(sideNav);
74+
};
75+
76+
const hoverSideNavItem = async ({
77+
canvasElement,
78+
itemText,
79+
}: {
80+
canvasElement: HTMLElement;
81+
itemText: string;
82+
}) => {
83+
const canvas = within(canvasElement);
84+
const item = canvas.getByText(itemText);
85+
await userEvent.hover(item);
86+
};
87+
6688
const Template: StoryFn<ChatLayoutProps> = props => {
6789
const [activeId, setActiveId] = useState<string | null>('1');
6890

@@ -172,13 +194,7 @@ export const UnpinnedAndHoveredLight: StoryObj<ChatLayoutProps> = {
172194
initialIsPinned: false,
173195
},
174196
play: async ({ canvasElement }) => {
175-
const canvas = within(canvasElement);
176-
177-
// Find the side nav
178-
const sideNav = canvas.getByLabelText('Side navigation');
179-
180-
// Hover over the side nav
181-
await userEvent.hover(sideNav);
197+
await hoverSideNav(canvasElement);
182198
},
183199
parameters: {
184200
chromatic: {
@@ -194,17 +210,81 @@ export const UnpinnedAndHoveredDark: StoryObj<ChatLayoutProps> = {
194210
initialIsPinned: false,
195211
},
196212
play: async ({ canvasElement }) => {
197-
const canvas = within(canvasElement);
213+
await hoverSideNav(canvasElement);
214+
},
215+
parameters: {
216+
chromatic: {
217+
delay: 350,
218+
},
219+
},
220+
};
198221

199-
// Find the side nav
200-
const sideNav = canvas.getByLabelText('Side navigation');
222+
export const UnpinnedAndFocusedLight: StoryObj<ChatLayoutProps> = {
223+
render: Template,
224+
args: {
225+
darkMode: false,
226+
initialIsPinned: false,
227+
},
228+
play: async ({ canvasElement: _canvasElement }) => {
229+
userEvent.tab();
230+
},
231+
parameters: {
232+
chromatic: {
233+
delay: 350,
234+
},
235+
},
236+
};
201237

202-
// Hover over the side nav
203-
await userEvent.hover(sideNav);
238+
export const UnpinnedAndFocusedDark: StoryObj<ChatLayoutProps> = {
239+
render: Template,
240+
args: {
241+
darkMode: true,
242+
initialIsPinned: false,
243+
},
244+
play: async ({ canvasElement: _canvasElement }) => {
245+
userEvent.tab();
204246
},
205247
parameters: {
206248
chromatic: {
207249
delay: 350,
208250
},
209251
},
210252
};
253+
254+
export const SideNavItemHoveredLight: StoryObj<ChatLayoutProps> = {
255+
render: Template,
256+
args: {
257+
darkMode: false,
258+
initialIsPinned: true,
259+
},
260+
play: async ({ canvasElement }) => {
261+
await hoverSideNavItem({
262+
canvasElement,
263+
itemText: 'Writing a very very very long Database Query',
264+
});
265+
},
266+
parameters: {
267+
chromatic: {
268+
delay: 550,
269+
},
270+
},
271+
};
272+
273+
export const SideNavItemHoveredDark: StoryObj<ChatLayoutProps> = {
274+
render: Template,
275+
args: {
276+
darkMode: true,
277+
initialIsPinned: true,
278+
},
279+
play: async ({ canvasElement }) => {
280+
await hoverSideNavItem({
281+
canvasElement,
282+
itemText: 'Writing a very very very long Database Query',
283+
});
284+
},
285+
parameters: {
286+
chromatic: {
287+
delay: 550,
288+
},
289+
},
290+
};

chat/chat-layout/src/ChatSideNav/ChatSideNav.spec.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@ const Providers = ({ children }: { children: React.ReactNode }) => (
1010
);
1111

1212
describe('ChatSideNav', () => {
13+
beforeAll(() => {
14+
window.ResizeObserver = jest.fn().mockImplementation(() => ({
15+
observe: jest.fn(),
16+
unobserve: jest.fn(),
17+
disconnect: jest.fn(),
18+
}));
19+
});
20+
1321
test('Header shows "New Chat" button when onClickNewChat provided', async () => {
1422
const onClickNewChat = jest.fn();
1523

chat/chat-layout/src/ChatSideNav/ChatSideNav.tsx

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import React, { forwardRef } from 'react';
1+
import React, { FocusEventHandler, forwardRef, useRef } from 'react';
22

33
import {
44
CompoundComponent,
55
findChild,
66
} from '@leafygreen-ui/compound-component';
7+
import { useMergeRefs } from '@leafygreen-ui/hooks';
78
import LeafyGreenProvider, {
89
useDarkMode,
910
} from '@leafygreen-ui/leafygreen-provider';
@@ -23,10 +24,12 @@ import { ChatSideNavItem } from './ChatSideNavItem';
2324
export const ChatSideNav = CompoundComponent(
2425
// eslint-disable-next-line react/display-name
2526
forwardRef<HTMLElement, ChatSideNavProps>(
26-
({ children, className, darkMode: darkModeProp, ...rest }, ref) => {
27+
({ children, className, darkMode: darkModeProp, ...rest }, fwdRef) => {
2728
const { darkMode, theme } = useDarkMode(darkModeProp);
2829
const { isPinned, setIsSideNavHovered, shouldRenderExpanded } =
2930
useChatLayoutContext();
31+
const navRef = useRef<HTMLElement>(null);
32+
const ref = useMergeRefs([navRef, fwdRef]);
3033

3134
const handleMouseEnter = () => {
3235
setIsSideNavHovered(true);
@@ -36,7 +39,24 @@ export const ChatSideNav = CompoundComponent(
3639
setIsSideNavHovered(false);
3740
};
3841

39-
// Find subcomponents
42+
const handleFocus: FocusEventHandler<HTMLElement> = ({ target }) => {
43+
const navElement = navRef.current;
44+
45+
if (navElement?.contains(target as Node)) {
46+
setIsSideNavHovered(true);
47+
}
48+
};
49+
50+
const handleBlur: FocusEventHandler<HTMLElement> = ({
51+
relatedTarget,
52+
}) => {
53+
const navElement = navRef.current;
54+
55+
if (relatedTarget && !navElement?.contains(relatedTarget as Node)) {
56+
setIsSideNavHovered(false);
57+
}
58+
};
59+
4060
const header = findChild(
4161
children,
4262
ChatSideNavSubcomponentProperty.Header,
@@ -59,6 +79,8 @@ export const ChatSideNav = CompoundComponent(
5979
aria-label="Side navigation"
6080
onMouseEnter={handleMouseEnter}
6181
onMouseLeave={handleMouseLeave}
82+
onFocus={handleFocus}
83+
onBlur={handleBlur}
6284
{...rest}
6385
>
6486
<div

0 commit comments

Comments
 (0)