diff --git a/packages/compass-assistant/src/compass-assistant-drawer.tsx b/packages/compass-assistant/src/compass-assistant-drawer.tsx index c61af06e546..f21ced59d11 100644 --- a/packages/compass-assistant/src/compass-assistant-drawer.tsx +++ b/packages/compass-assistant/src/compass-assistant-drawer.tsx @@ -35,9 +35,10 @@ const assistantTitleTextStyles = css({ * it's within an AssistantProvider. */ export const CompassAssistantDrawer: React.FunctionComponent<{ + appName: string; autoOpen?: boolean; hasNonGenuineConnections?: boolean; -}> = ({ autoOpen, hasNonGenuineConnections = false }) => { +}> = ({ appName, autoOpen, hasNonGenuineConnections = false }) => { const chat = useContext(AssistantContext); const { clearChat } = useContext(AssistantActionsContext); @@ -92,6 +93,14 @@ export const CompassAssistantDrawer: React.FunctionComponent<{ label="MongoDB Assistant" glyph="Sparkle" autoOpen={autoOpen} + guideCue={{ + cueId: 'assistant-drawer', + title: 'Introducing MongoDB Assistant', + description: `AI-powered assistant to intelligently guide you through your database tasks. Get expert MongoDB help and streamline your workflow directly within ${appName}.`, + buttonText: 'Got it', + tooltipAlign: 'left', + tooltipJustify: 'start', + }} >
Provider children
diff --git a/packages/compass-components/src/components/drawer-portal.spec.tsx b/packages/compass-components/src/components/drawer-portal.spec.tsx index 7c4c4587daf..2bce21401b5 100644 --- a/packages/compass-components/src/components/drawer-portal.spec.tsx +++ b/packages/compass-components/src/components/drawer-portal.spec.tsx @@ -16,7 +16,7 @@ import { expect } from 'chai'; describe('DrawerSection', function () { it('renders DrawerSection in the portal and updates the content when it updates', async function () { - let setCount; + let setCount: React.Dispatch> = () => {}; function TestDrawer() { const [count, _setCount] = useState(0); @@ -228,4 +228,40 @@ describe('DrawerSection', function () { expect(screen.queryByText('This is the controlled section')).not.to.exist; }); }); + + it('renders guide cue when passed in props', async function () { + localStorage.compass_guide_cues = '[]'; + function TestDrawer() { + return ( + + + + This is a test section + + + + ); + } + + render(); + + await waitFor(() => { + expect(screen.getByText('Introducing this new test drawer')).to.be + .visible; + }); + }); }); diff --git a/packages/compass-components/src/components/drawer-portal.tsx b/packages/compass-components/src/components/drawer-portal.tsx index 97ed50a6605..57c7a048acd 100644 --- a/packages/compass-components/src/components/drawer-portal.tsx +++ b/packages/compass-components/src/components/drawer-portal.tsx @@ -16,6 +16,7 @@ import { import { css, cx } from '@leafygreen-ui/emotion'; import { isEqual } from 'lodash'; import { rafraf } from '../utils/rafraf'; +import { GuideCue, type GuideCueProps } from './guide-cue/guide-cue'; import { BaseFontSize, fontWeights } from '@leafygreen-ui/tokens'; type ToolbarData = Required['toolbarData']; @@ -37,6 +38,7 @@ type DrawerSectionProps = Omit & { * provided will stay unordered at the bottom of the list */ order?: number; + guideCue?: GuideCueProps; }; type DrawerOpenStateContextValue = boolean; @@ -271,21 +273,96 @@ export const DrawerAnchor: React.FunctionComponent = ({ children }) => { return orderB < orderA ? 1 : orderB > orderA ? -1 : 0; }); }, [drawerSectionItems]); + + const [toolbarIconNodes, setToolbarIconNodes] = useState< + Record + >({}); + + useLayoutEffect( + function () { + const drawerEl = document.querySelector('.compass-drawer-anchor'); + if (!drawerEl) { + throw new Error( + 'Can not use DrawerSection without DrawerAnchor being mounted on the page' + ); + } + + function check() { + if (!drawerEl) { + return; + } + const nodes: Record = {}; + for (const item of toolbarData) { + if (!item.guideCue) { + continue; + } + + const button = drawerEl.querySelector( + `button[aria-label="${item.label}"]` + ); + if (button) { + nodes[item.id] = button; + } + } + + setToolbarIconNodes((oldNodes) => { + // account for removed nodes by checking all keys of both old and new + for (const id of Object.keys({ ...oldNodes, ...nodes })) { + if (nodes[id] !== oldNodes[id]) { + return nodes; + } + } + return oldNodes; + }); + } + check(); + + const mutationObserver = new MutationObserver(() => { + check(); + }); + + // use a mutation observer because at least in unit tests the button + // elements don't exist immediately + mutationObserver.observe(drawerEl, { + subtree: true, + childList: true, + }); + return () => { + mutationObserver.disconnect(); + }; + }, + [toolbarData] + ); + return ( - - {children} - + <> + {toolbarData.map((item) => { + return ( + toolbarIconNodes[item.id] && + item.guideCue && ( + + key={item.id} + {...item.guideCue} + triggerNode={toolbarIconNodes[item.id]} + /> + ) + ); + })} + + {children} + + ); }; diff --git a/packages/compass-components/src/components/guide-cue/guide-cue.tsx b/packages/compass-components/src/components/guide-cue/guide-cue.tsx index 46ed242596c..d036c6a9b50 100644 --- a/packages/compass-components/src/components/guide-cue/guide-cue.tsx +++ b/packages/compass-components/src/components/guide-cue/guide-cue.tsx @@ -89,13 +89,15 @@ export type GuideCueProps = Omit< GroupAndStep & { cueId: string; description: React.ReactChild; - trigger: ({ ref }: { ref: React.Ref }) => React.ReactElement; + triggerNode?: T; + trigger?: ({ ref }: { ref: React.Ref }) => React.ReactElement; onOpenChange?: (isOpen: boolean) => void; }; export const GuideCue = ({ description, trigger, + triggerNode, cueId, groupId, step, @@ -106,7 +108,7 @@ export const GuideCue = ({ }: GuideCueProps) => { const [isCueOpen, setIsCueOpen] = useState(false); const [isIntersecting, setIsIntersecting] = useState(true); - const refEl = useRef(null); + const refEl = useRef(triggerNode ?? null); const [readyToRender, setReadyToRender] = useState(false); const context = useContext(GuideCueContext); @@ -276,7 +278,7 @@ export const GuideCue = ({ {description} )} - {trigger({ ref: refEl })} + {trigger?.({ ref: refEl })} ); }; diff --git a/packages/compass-web/src/compass-assistant-drawer.tsx b/packages/compass-web/src/compass-assistant-drawer.tsx index b09ac7683c0..bae915fbe71 100644 --- a/packages/compass-web/src/compass-assistant-drawer.tsx +++ b/packages/compass-web/src/compass-assistant-drawer.tsx @@ -6,7 +6,11 @@ import { CompassAssistantDrawer } from '@mongodb-js/compass-assistant'; // TODO(COMPASS-7830): This is a temporary solution to pass the // hasNonGenuineConnections prop to the CompassAssistantDrawer as otherwise // we end up with a circular dependency. -export function CompassAssistantDrawerWithConnections() { +export function CompassAssistantDrawerWithConnections({ + appName, +}: { + appName: string; +}) { // Check for non-genuine connections const activeConnectionIds = useConnectionIds( (conn) => @@ -15,6 +19,7 @@ export function CompassAssistantDrawerWithConnections() { ); return ( 0} /> ); diff --git a/packages/compass-web/src/entrypoint.tsx b/packages/compass-web/src/entrypoint.tsx index 1fc74322fdd..68cd0522b57 100644 --- a/packages/compass-web/src/entrypoint.tsx +++ b/packages/compass-web/src/entrypoint.tsx @@ -305,7 +305,7 @@ function CompassWorkspace({ - + ); }} diff --git a/packages/compass/src/app/components/compass-assistant-drawer.tsx b/packages/compass/src/app/components/compass-assistant-drawer.tsx index b09ac7683c0..bae915fbe71 100644 --- a/packages/compass/src/app/components/compass-assistant-drawer.tsx +++ b/packages/compass/src/app/components/compass-assistant-drawer.tsx @@ -6,7 +6,11 @@ import { CompassAssistantDrawer } from '@mongodb-js/compass-assistant'; // TODO(COMPASS-7830): This is a temporary solution to pass the // hasNonGenuineConnections prop to the CompassAssistantDrawer as otherwise // we end up with a circular dependency. -export function CompassAssistantDrawerWithConnections() { +export function CompassAssistantDrawerWithConnections({ + appName, +}: { + appName: string; +}) { // Check for non-genuine connections const activeConnectionIds = useConnectionIds( (conn) => @@ -15,6 +19,7 @@ export function CompassAssistantDrawerWithConnections() { ); return ( 0} /> ); diff --git a/packages/compass/src/app/components/workspace.tsx b/packages/compass/src/app/components/workspace.tsx index e53a87fe1ae..fd534d73401 100644 --- a/packages/compass/src/app/components/workspace.tsx +++ b/packages/compass/src/app/components/workspace.tsx @@ -112,7 +112,7 @@ export default function Workspace({ - + )} >