Skip to content

Commit b32395d

Browse files
committed
WIP
1 parent 863f975 commit b32395d

18 files changed

+405
-1
lines changed

chat/chat-layout/package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,18 @@
2828
"access": "public"
2929
},
3030
"dependencies": {
31+
"@leafygreen-ui/avatar": "workspace:^",
32+
"@leafygreen-ui/compound-component": "workspace:^",
3133
"@leafygreen-ui/emotion": "workspace:^",
34+
"@leafygreen-ui/icon": "workspace:^",
35+
"@leafygreen-ui/icon-button": "workspace:^",
3236
"@leafygreen-ui/lib": "workspace:^",
3337
"@leafygreen-ui/tokens": "workspace:^",
38+
"@leafygreen-ui/typography": "workspace:^",
3439
"@lg-tools/test-harnesses": "workspace:^"
3540
},
3641
"peerDependencies": {
42+
"@leafygreen-ui/leafygreen-provider": ">=3.2.0",
3743
"@lg-chat/leafygreen-chat-provider": "workspace:^"
3844
},
3945
"devDependencies": {

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { StoryFn, StoryObj } from '@storybook/react';
1313

1414
import { css } from '@leafygreen-ui/emotion';
1515

16+
import { ChatSideNav } from './ChatSideNav';
1617
import { ChatLayout, type ChatLayoutProps, ChatMain } from '.';
1718

1819
const meta: StoryMetaType<typeof ChatLayout> = {
@@ -64,7 +65,12 @@ const testMessages = [
6465
const Template: StoryFn<ChatLayoutProps> = props => (
6566
<LeafyGreenChatProvider variant={Variant.Compact}>
6667
<ChatLayout {...props}>
67-
<div className={sideNavPlaceholderStyles}>ChatSideNav Placeholder</div>
68+
<ChatSideNav>
69+
<ChatSideNav.Header
70+
onClickNewChat={() => console.log('Clicked new chat')}
71+
/>
72+
<ChatSideNav.Content>Content</ChatSideNav.Content>
73+
</ChatSideNav>
6874
<ChatMain>
6975
<TitleBar title="Chat Assistant" />
7076
<ChatWindow>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { css, cx } from '@leafygreen-ui/emotion';
2+
import { Theme } from '@leafygreen-ui/lib';
3+
import { color, InteractionState, Variant } from '@leafygreen-ui/tokens';
4+
5+
import { gridAreas } from '../constants';
6+
7+
const getBaseContainerStyles = (theme: Theme) => css`
8+
grid-area: ${gridAreas.sideNav};
9+
background: ${color[theme].background[Variant.Secondary][
10+
InteractionState.Default
11+
]};
12+
border-right: 1px solid
13+
${color[theme].border[Variant.Secondary][InteractionState.Default]};
14+
display: flex;
15+
flex-direction: column;
16+
height: 100%;
17+
overflow: hidden;
18+
`;
19+
20+
export const getContainerStyles = ({
21+
className,
22+
theme,
23+
}: {
24+
className?: string;
25+
theme: Theme;
26+
}) => cx(getBaseContainerStyles(theme), className);
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import React, { forwardRef } from 'react';
2+
3+
import {
4+
CompoundComponent,
5+
filterChildren,
6+
findChild,
7+
} from '@leafygreen-ui/compound-component';
8+
import LeafyGreenProvider, {
9+
useDarkMode,
10+
} from '@leafygreen-ui/leafygreen-provider';
11+
12+
import { getContainerStyles } from './ChatSideNav.styles';
13+
import {
14+
type ChatSideNavProps,
15+
ChatSideNavSubcomponentProperty,
16+
} from './ChatSideNav.types';
17+
import { ChatSideNavContent } from './ChatSideNavContent';
18+
import { ChatSideNavFooter } from './ChatSideNavFooter';
19+
import { ChatSideNavHeader } from './ChatSideNavHeader';
20+
21+
export const ChatSideNav = CompoundComponent(
22+
forwardRef<HTMLElement, ChatSideNavProps>(
23+
({ children, className, darkMode: darkModeProp, ...rest }, ref) => {
24+
const { darkMode, theme } = useDarkMode(darkModeProp);
25+
// Find subcomponents
26+
const header = findChild(
27+
children,
28+
ChatSideNavSubcomponentProperty.Header,
29+
);
30+
const content = findChild(
31+
children,
32+
ChatSideNavSubcomponentProperty.Content,
33+
);
34+
35+
// Filter out subcomponents from remaining children
36+
const remainingChildren = filterChildren(
37+
children,
38+
Object.values(ChatSideNavSubcomponentProperty),
39+
);
40+
41+
return (
42+
<LeafyGreenProvider darkMode={darkMode}>
43+
<nav
44+
ref={ref}
45+
className={getContainerStyles({ className, theme })}
46+
aria-label="Side navigation"
47+
{...rest}
48+
>
49+
{header}
50+
{content}
51+
{remainingChildren}
52+
<ChatSideNavFooter />
53+
</nav>
54+
</LeafyGreenProvider>
55+
);
56+
},
57+
),
58+
{
59+
displayName: 'ChatSideNav',
60+
Header: ChatSideNavHeader,
61+
Content: ChatSideNavContent,
62+
},
63+
);
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { ComponentPropsWithRef, MouseEventHandler } from 'react';
2+
3+
import { DarkModeProps } from '@leafygreen-ui/lib';
4+
5+
export interface ChatSideNavProps
6+
extends ComponentPropsWithRef<'nav'>,
7+
DarkModeProps {}
8+
9+
export interface ChatSideNavHeaderProps
10+
extends ComponentPropsWithRef<'div'>,
11+
DarkModeProps {
12+
/**
13+
* Optional callback fired when the "New Chat" button is clicked.
14+
*/
15+
onClickNewChat?: MouseEventHandler<HTMLButtonElement>;
16+
}
17+
18+
export interface ChatSideNavContentProps
19+
extends ComponentPropsWithRef<'div'>,
20+
DarkModeProps {}
21+
22+
export interface ChatSideNavFooterProps extends ComponentPropsWithRef<'div'> {}
23+
24+
/**
25+
* Static property names used to identify ChatSideNav compound components.
26+
* These are implementation details for the compound component pattern and should not be exported.
27+
*/
28+
export const ChatSideNavSubcomponentProperty = {
29+
Header: 'isChatSideNavHeader',
30+
Content: 'isChatSideNavContent',
31+
} as const;
32+
33+
/**
34+
* Type representing the possible static property names for ChatSideNav subcomponents.
35+
*/
36+
export type ChatSideNavSubcomponentProperty =
37+
(typeof ChatSideNavSubcomponentProperty)[keyof typeof ChatSideNavSubcomponentProperty];
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { css, cx } from '@leafygreen-ui/emotion';
2+
3+
const baseContentStyles = css`
4+
flex: 1;
5+
overflow-y: auto;
6+
min-height: 0;
7+
background: green;
8+
`;
9+
10+
export const getContentStyles = ({ className }: { className?: string }) =>
11+
cx(baseContentStyles, className);
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import React, { forwardRef } from 'react';
2+
3+
import { CompoundSubComponent } from '@leafygreen-ui/compound-component';
4+
5+
import {
6+
ChatSideNavContentProps,
7+
ChatSideNavSubcomponentProperty,
8+
} from '../ChatSideNav.types';
9+
10+
import { getContentStyles } from './ChatSideNavContent.styles';
11+
12+
export const ChatSideNavContent = CompoundSubComponent(
13+
forwardRef<HTMLDivElement, ChatSideNavContentProps>(
14+
({ children, className, ...rest }, ref) => {
15+
return (
16+
<div ref={ref} className={getContentStyles({ className })} {...rest}>
17+
{children}
18+
</div>
19+
);
20+
},
21+
),
22+
{
23+
displayName: 'ChatSideNavContent',
24+
key: ChatSideNavSubcomponentProperty.Content,
25+
},
26+
);
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { ChatSideNavContent } from './ChatSideNavContent';
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { css, cx } from '@leafygreen-ui/emotion';
2+
3+
/** does not use tokens in order to have alignment with header */
4+
const FOOTER_PADDING = 10;
5+
6+
const baseFooterStyles = css`
7+
width: 100%;
8+
padding: ${FOOTER_PADDING}px;
9+
display: flex;
10+
justify-content: flex-end;
11+
align-items: center;
12+
`;
13+
14+
export const getFooterStyles = ({ className }: { className?: string }) =>
15+
cx(baseFooterStyles, className);
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import React, { forwardRef } from 'react';
2+
3+
import NavCollapseIcon from '@leafygreen-ui/icon/dist/NavCollapse';
4+
import NavExpandIcon from '@leafygreen-ui/icon/dist/NavExpand';
5+
import { IconButton } from '@leafygreen-ui/icon-button';
6+
import { useDarkMode } from '@leafygreen-ui/leafygreen-provider';
7+
8+
import { useChatLayoutContext } from '../../ChatLayout/ChatLayoutContext';
9+
import { type ChatSideNavFooterProps } from '../ChatSideNav.types';
10+
11+
import { getFooterStyles } from './ChatSideNavFooter.styles';
12+
13+
/** @internal */
14+
export const ChatSideNavFooter = forwardRef<
15+
HTMLDivElement,
16+
ChatSideNavFooterProps
17+
>(({ className, ...rest }, ref) => {
18+
const { isPinned, togglePin } = useChatLayoutContext();
19+
const { theme } = useDarkMode();
20+
return (
21+
<div ref={ref} className={getFooterStyles({ className })} {...rest}>
22+
<IconButton
23+
aria-label={`${isPinned ? 'Unpin' : 'Pin'} side nav`}
24+
onClick={togglePin}
25+
>
26+
{isPinned ? <NavCollapseIcon /> : <NavExpandIcon />}
27+
</IconButton>
28+
</div>
29+
);
30+
});
31+
32+
ChatSideNavFooter.displayName = 'ChatSideNavFooter';

0 commit comments

Comments
 (0)