From c2462153ceaf5d60af60d04612403f65b863469d Mon Sep 17 00:00:00 2001 From: Sundram Gupta Date: Sun, 16 Aug 2026 02:56:43 +0530 Subject: [PATCH] fix(docs): apply env change from inline playground sidebar Tippy portals the env switcher menu to , so pressing a menu item counted as a click outside the inline dock's sidebar overlay: the overlay closed on pointerdown, unmounting the menu before its click could dispatch the environment change. Ignore [data-tippy-root] in the sidebar's outside-click handler, matching MenuDropdown's own guard. --- .../PlaygroundBody/PlaygroundBody.tsx | 39 ++----------------- 1 file changed, 4 insertions(+), 35 deletions(-) diff --git a/packages/bruno-api-docs/src/components/Playground/PlaygroundBody/PlaygroundBody.tsx b/packages/bruno-api-docs/src/components/Playground/PlaygroundBody/PlaygroundBody.tsx index 3ff60ebd..4691d594 100644 --- a/packages/bruno-api-docs/src/components/Playground/PlaygroundBody/PlaygroundBody.tsx +++ b/packages/bruno-api-docs/src/components/Playground/PlaygroundBody/PlaygroundBody.tsx @@ -36,9 +36,6 @@ import { StyledWrapper } from './StyledWrapper'; const ORIENTATION_BREAKPOINT = 640; -// The applied-guard key combines request + example so switching examples under -// the same request still re-applies, while a null example collapses to the bare -// request slug (matching what handleNavigate / openEnvironments store). const applyKey = (requestSlug: string | null, exampleSlug: string | null): string => exampleSlug ? `${requestSlug} ${exampleSlug}` : requestSlug ?? ''; @@ -85,8 +82,7 @@ const PlaygroundBody: React.FC = ({ () => findItemByUuid(collection?.items, selectedItemId), [collection, selectedItemId] ); - // In example mode only the example row is active; do not also light up its - // parent request row (selectedItemId still points at the parent request). + const activeSlug = viewMode !== 'example' && selectedItemId ? uuidToSlug.get(selectedItemId) ?? '' : ''; const exampleCount = @@ -105,18 +101,12 @@ const PlaygroundBody: React.FC = ({ useResizableSidebar('oc-docs:playgroundSidebarWidth', onCloseSidebar, onOpenSidebar); const orientation = viewWidth > 0 && viewWidth < ORIENTATION_BREAKPOINT ? 'vertical' : 'horizontal'; - // Close the inline-dock overlay when the pointer goes down anywhere outside - // the sidebar, including outside the playground. The backdrop still handles - // clicks over the view (so they don't reach a control underneath); this adds - // the rest of the page. The toggle is excluded so closing via it isn't undone - // by its own click reopening the sidebar; the resize handle sits just outside - // the sidebar too, so grabbing it must not dismiss the overlay mid-drag. const sidebarRef = useRef(null); useClickOutside( sidebarRef, onCloseSidebar, sidebarOpen && dock === 'inline', - '[data-testid="playground-sidebar-toggle"], [data-testid="playground-sidebar-resizer"]' + '[data-testid="playground-sidebar-toggle"], [data-testid="playground-sidebar-resizer"], [data-tippy-root]' ); // Reopen whatever the URL says was last open. `pgReq` holds a request, a @@ -126,15 +116,8 @@ const PlaygroundBody: React.FC = ({ const key = applyKey(requestSlug, exampleSlug); if (!requestSlug || appliedSlugRef.current === key) return; const target = resolvePlaygroundTarget(requestSlug, model); - if (!target) return; // sidebar list hasn't loaded this item yet - retry once it does - // A request or folder needs the playground's own copy of the collection to - // select it and open its parent folders. On reload that copy can arrive just - // after the sidebar list, so if it isn't ready we wait instead of marking - // this done - otherwise the folders would never open. The environments and - // collection views need no collection, so they open right away. + if (!target) return; if (target.uuid && !collection?.items) return; - // Resolve the URL's example against the request; an unmatched slug (renamed/ - // removed) leaves the example unset so we open the live request instead. const item = target.uuid ? findItemByUuid(collection?.items, target.uuid) : undefined; const exampleIndex = exampleSlug && item && !isFolder(item) ? exampleIndexForSlug(item as HttpRequest, exampleSlug) : null; @@ -145,20 +128,13 @@ const PlaygroundBody: React.FC = ({ if (target.expandUuids.length) dispatch(expandFolders(target.expandUuids)); }, [requestSlug, exampleSlug, model, collection, dispatch, appliedSlugRef]); - // In the inline dock the sidebar is an overlay, so close it once the user has - // picked something to view (mirrors a mobile navigation drawer). const closeSidebarIfInline = () => { if (dock === 'inline') onCloseSidebar(); }; - // A sidebar click applies the target right away, then writes the slug so a - // reload restores it. Applying directly (not only through the reopen effect) is - // what makes leaving an example work: an example keeps the slug on its parent - // request, so clicking that request is a no-op slug write and the effect would - // never fire. appliedSlugRef is synced so the effect treats it as already done. const handleNavigate = (slug: string) => { const target = resolvePlaygroundTarget(slug, model); - if (!target || !target.uuid) return; // not a resolvable item, ignore the click + if (!target || !target.uuid) return; dispatch(setSelectedItemId(target.uuid)); dispatch(setSelectedExampleIndex(null)); dispatch(setViewMode(target.view)); @@ -171,9 +147,6 @@ const PlaygroundBody: React.FC = ({ const handleToggleFolder = (uuid: string) => dispatch(toggleFolderCollapse(uuid)); const handleExpandFolder = (uuid: string) => dispatch(expandFolders([uuid])); - // An example is applied directly and its slug written to the URL (pgReq+pgEx), - // so a reload / share restores it. Mark the combined key applied first so the - // reopen effect treats it as done and doesn't re-run. const handleExampleClick = (requestUuid: string, index: number) => { dispatch(setSelectedItemId(requestUuid)); dispatch(setSelectedExampleIndex(index)); @@ -226,8 +199,6 @@ const PlaygroundBody: React.FC = ({ return ; } } - // Also render the live request when an example index no longer resolves (e.g. - // the examples array shrank), so we never land on the empty prompt instead. if ((viewMode === 'playground' || viewMode === 'example') && selectedItem && !isFolder(selectedItem) && collection) { return ( = ({ style={{ '--sidebar-width': `${sidebarWidth}px` } as React.CSSProperties} > {sidebarOpen && dock === 'inline' && ( - // In the inline dock the sidebar overlays the view, so a click outside it - // dismisses it, same as the docs navigation drawer's backdrop.