Skip to content

poc(components): drawer portal interface #7125

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { SignalHooksProvider } from './signal-popover';
import { RequiredURLSearchParamsProvider } from './links/link';
import { StackedComponentProvider } from '../hooks/use-stacked-component';
import { ContextMenuProvider } from './context-menu';
import { DrawerContentProvider } from './drawer-portal';

type GuideCueProviderProps = React.ComponentProps<typeof GuideCueProvider>;

Expand Down Expand Up @@ -131,33 +132,35 @@ export const CompassComponentsProvider = ({
darkMode={darkMode}
popoverPortalContainer={popoverPortalContainer}
>
<StackedComponentProvider zIndex={stackedElementsZIndex}>
<RequiredURLSearchParamsProvider
utmSource={utmSource}
utmMedium={utmMedium}
>
<GuideCueProvider
onNext={onNextGuideGue}
onNextGroup={onNextGuideCueGroup}
<DrawerContentProvider>
<StackedComponentProvider zIndex={stackedElementsZIndex}>
<RequiredURLSearchParamsProvider
utmSource={utmSource}
utmMedium={utmMedium}
>
<SignalHooksProvider {...signalHooksProviderProps}>
<ConfirmationModalArea>
<ContextMenuProvider disabled={disableContextMenus}>
<ToastArea>
{typeof children === 'function'
? children({
darkMode,
portalContainerRef: setPortalContainer,
scrollContainerRef: setScrollContainer,
})
: children}
</ToastArea>
</ContextMenuProvider>
</ConfirmationModalArea>
</SignalHooksProvider>
</GuideCueProvider>
</RequiredURLSearchParamsProvider>
</StackedComponentProvider>
<GuideCueProvider
onNext={onNextGuideGue}
onNextGroup={onNextGuideCueGroup}
>
<SignalHooksProvider {...signalHooksProviderProps}>
<ConfirmationModalArea>
<ContextMenuProvider disabled={disableContextMenus}>
<ToastArea>
{typeof children === 'function'
? children({
darkMode,
portalContainerRef: setPortalContainer,
scrollContainerRef: setScrollContainer,
})
: children}
</ToastArea>
</ContextMenuProvider>
</ConfirmationModalArea>
</SignalHooksProvider>
</GuideCueProvider>
</RequiredURLSearchParamsProvider>
</StackedComponentProvider>
</DrawerContentProvider>
</LeafyGreenProvider>
);
};
133 changes: 133 additions & 0 deletions packages/compass-components/src/components/drawer-portal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import React, { useContext, useEffect, useRef, useState } from 'react';

import {
DrawerLayout,
DisplayMode as DrawerDisplayMode,
useDrawerToolbarContext,
type DrawerLayoutProps,
} from './drawer';

type ToolbarData = Required<DrawerLayoutProps>['toolbarData'];

type DrawerStateContextValue = ToolbarData;

type DrawerActionsContextValue = {
current: {
openDrawer: (id: string) => void;
closeDrawer: () => void;
updateToolbarData: (data: ToolbarData[number]) => void;
};
};

const DrawerStateContext = React.createContext<DrawerStateContextValue>([]);

const DrawerActionsContext = React.createContext<DrawerActionsContextValue>({
current: {
openDrawer: () => undefined,
closeDrawer: () => undefined,
updateToolbarData: () => undefined,
},
});

/**
* Drawer component that keeps track of drawer rendering state and provides
* context to all places that require it. Separating it from DrawerAnchor and
* DrawerSection allows to freely move the actual drawer around while allowing
* the whole application access to the Drawer state, not only parts of it
* wrapped in the Drawer
*
* @example
*
* function App() {
* return (
* <DrawerContentProvider>
* <DrawerAnchor>
* <Content></Content>
* </DrawerAnchor>
* </DrawerContentProvider>
* )
* }
*
* function Content() {
* const [showDrawerSection, setShowDrawerSection] = useState(false);
* return (
* <>
* <button onClick={() => setShowDrawerSection(true)}></button>
* {showDrawerSection &&
* <DrawerSection id="section-1" title="Drawer Title">
* This will be rendered inside the drawer
* </>
* )}
* </>
* )
* }
*/
export const DrawerContentProvider: React.FunctionComponent = ({
children,
}) => {
const [drawerState, setDrawerState] = useState<ToolbarData>([]);
const drawerActions = useRef({
openDrawer: () => undefined,
closeDrawer: () => undefined,
updateToolbarData: (data: ToolbarData[number]) => {
setDrawerState((prevState) => {
return prevState.map((item) => {
return item.id === data.id ? data : item;
});
});
},
});

return (
<DrawerStateContext.Provider value={drawerState}>
<DrawerActionsContext.Provider value={drawerActions}>
{children}
</DrawerActionsContext.Provider>
</DrawerStateContext.Provider>
);
};

const DrawerContextGrabber: React.FunctionComponent = ({ children }) => {
const drawerToolbarContext = useDrawerToolbarContext();
const actions = useContext(DrawerActionsContext);
actions.current.openDrawer = drawerToolbarContext.openDrawer;
actions.current.closeDrawer = drawerToolbarContext.closeDrawer;
return <>{children}</>;
};

/**
* DrawerAnchor component will render the drawer in any place it is rendered.
* This component has to wrap any content that Drawer will be shown near
*/
export const DrawerAnchor: React.FunctionComponent<{
displayMode?: DrawerDisplayMode;
}> = ({ displayMode, children }) => {
const toolbarData = useContext(DrawerStateContext);
return (
<DrawerLayout
displayMode={displayMode ?? DrawerDisplayMode.Embedded}
toolbarData={toolbarData}
>
<DrawerContextGrabber>{children}</DrawerContextGrabber>
</DrawerLayout>
);
};

type DrawerSectionProps = ToolbarData[number];

/**
* DrawerSection allows to declaratively render sections inside the drawer
* independantly from the Drawer itself
*/
export const DrawerSection: React.FunctionComponent<DrawerSectionProps> = ({
children,
...props
}) => {
const actions = useContext(DrawerActionsContext);
useEffect(() => {
actions.current.updateToolbarData({ ...props, content: children });
});
return null;
};

export { DrawerDisplayMode };
10 changes: 0 additions & 10 deletions packages/compass-components/src/components/leafygreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,16 +134,6 @@ const TextInput: typeof LeafyGreenTextInput = React.forwardRef(

TextInput.displayName = 'TextInput';

export {
Drawer,
DrawerLayout,
DisplayMode as DrawerDisplayMode,
DrawerStackProvider,
useDrawerStackContext,
useDrawerToolbarContext,
type DrawerLayoutProps,
} from './drawer';

// 3. Export the leafygreen components.
export {
AtlasNavGraphic,
Expand Down
1 change: 1 addition & 0 deletions packages/compass-components/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,3 +219,4 @@ export {
export { SelectList } from './components/select-list';
export { ParagraphSkeleton } from '@leafygreen-ui/skeleton-loader';
export { InsightsChip } from './components/insights-chip';
export * from './components/drawer-portal';
18 changes: 11 additions & 7 deletions packages/compass-workspaces/src/components/workspaces.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { useCallback, useMemo } from 'react';
import {
DrawerAnchor,
ErrorBoundary,
MongoDBLogoMark,
WorkspaceTabs,
Expand Down Expand Up @@ -215,13 +216,16 @@ const CompassWorkspaces: React.FunctionComponent<CompassWorkspacesProps> = ({
selectedTabIndex={activeTabIndex}
></WorkspaceTabs>

<div className={workspacesContentStyles}>
{activeTab && workspaceTabContent ? (
workspaceTabContent
) : (
<EmptyWorkspaceContent></EmptyWorkspaceContent>
)}
</div>
{/* TODO: not only this breaks layout in an unexpected way, it also causes a ton of ResizeObserver errors to pop up, seems like wrapping anything in leafygreen Drawer breaks our virtualized lisis completely */}
<DrawerAnchor>
<div className={workspacesContentStyles}>
{activeTab && workspaceTabContent ? (
workspaceTabContent
) : (
<EmptyWorkspaceContent></EmptyWorkspaceContent>
)}
</div>
</DrawerAnchor>
</div>
);
};
Expand Down
Loading