From 1b8eece1735a0a90b6de355f384ba16987e3146c Mon Sep 17 00:00:00 2001 From: Abhishek S Lal Date: Wed, 24 Dec 2025 21:08:53 +0530 Subject: [PATCH] Add right-click context menu to request tabs with MenuDropdown # (#6502) * refactor: replace Dropdown with MenuDropdown in RequestTab component; update Dropdown props handling in Dropdown component * refactor: remove Portal import and simplify menuDropdown rendering in RequestTab component * refactor: streamline RequestTabMenu functionality and improve tab closing methods with async handling * refactor: enhance Dropdown and MenuDropdown components with improved props handling and styling adjustments * refactor: enhance Dropdown and MenuDropdown components by improving structure and removing unused styles * refactor: update Dropdown and MenuDropdown components to append to sidebar sections container for improved layout * refactor: integrate dropdownContainerRef for improved MenuDropdown positioning in RequestTabs and Sidebar components * refactor: update Dropdown component to include 'tippy-box' class for e2e test selections * refactor: update dropdown item selection logic in selectRequestPaneTab function for improved accuracy * refactor: add fixed positioning to popperOptions in Collection and CollectionItem components for improved dropdown behavior --------- Co-authored-by: sanjai --- .../src/components/Dropdown/StyledWrapper.js | 290 +++++++++--------- .../src/components/Dropdown/index.js | 32 +- .../RequestTabs/RequestTab/index.js | 187 +++++------ .../src/components/RequestTabs/index.js | 4 +- .../Collection/CollectionItem/index.js | 18 +- .../Sidebar/Collections/Collection/index.js | 4 + .../Sidebar/SidebarAccordionContext.js | 10 +- .../src/ui/MenuDropdown/StyledWrapper.js | 150 --------- .../bruno-app/src/ui/MenuDropdown/index.js | 57 ++-- tests/utils/page/actions.ts | 2 +- 10 files changed, 321 insertions(+), 433 deletions(-) delete mode 100644 packages/bruno-app/src/ui/MenuDropdown/StyledWrapper.js diff --git a/packages/bruno-app/src/components/Dropdown/StyledWrapper.js b/packages/bruno-app/src/components/Dropdown/StyledWrapper.js index d4068b42f..8eada99ab 100644 --- a/packages/bruno-app/src/components/Dropdown/StyledWrapper.js +++ b/packages/bruno-app/src/components/Dropdown/StyledWrapper.js @@ -1,153 +1,165 @@ import styled from 'styled-components'; const Wrapper = styled.div` - .dropdown-toggle { - &:hover { - color: black; + min-width: 160px; + font-size: ${(props) => props.theme.font.size.base}; + color: ${(props) => props.theme.dropdown.color}; + background-color: ${(props) => props.theme.dropdown.bg}; + box-shadow: ${(props) => props.theme.shadow.sm}; + border-radius: ${(props) => props.theme.border.radius.base}; + max-height: 90vh; + overflow-y: auto; + max-width: unset !important; + padding: 0.25rem; + + [role="menu"] { + outline: none; + &:focus { + outline: none; + } + &:focus-visible { + outline: none; } } - .tippy-box { - min-width: 160px; - font-size: ${(props) => props.theme.font.size.base}; + .label-item { + display: flex; + align-items: center; + padding: 0.375rem 0.625rem 0.25rem 0.625rem; + font-size: 0.6875rem; + font-weight: 600; + letter-spacing: 0.025em; color: ${(props) => props.theme.dropdown.color}; - background-color: ${(props) => props.theme.dropdown.bg}; - box-shadow: ${(props) => props.theme.shadow.sm}; - border-radius: ${(props) => props.theme.border.radius.base}; - max-height: 90vh; - overflow-y: auto; - max-width: unset !important; - padding: 0.25rem; + opacity: 0.6; + margin-top: 0.25rem; + &:first-child { + margin-top: 0; + } + } - .tippy-content { - padding-left: 0; - padding-right: 0; - padding-top: 0; - padding-bottom: 0; + .dropdown-item { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.275rem 0.625rem; + cursor: pointer; + border-radius: 6px; + margin: 0.0625rem 0; + font-size: 0.8125rem; - [role="menu"] { - outline: none; - &:focus { - outline: none; - } - &:focus-visible { - outline: none; - } - } - - .label-item { - display: flex; - align-items: center; - padding: 0.375rem 0.625rem 0.25rem 0.625rem; - font-size: 0.6875rem; - font-weight: 600; - letter-spacing: 0.025em; - color: ${(props) => props.theme.dropdown.color}; - opacity: 0.6; - margin-top: 0.25rem; - - &:first-child { - margin-top: 0; - } - } - - .dropdown-item { - display: flex; - align-items: center; - gap: 0.5rem; - padding: 0.275rem 0.625rem; - cursor: pointer; - border-radius: 6px; - margin: 0.0625rem 0; - font-size: 0.8125rem; - - &.active { - color: ${(props) => props.theme.colors.text.yellow} !important; - .dropdown-icon { - color: ${(props) => props.theme.colors.text.yellow} !important; - } - } - - .dropdown-label { - flex: 1; - } - - .dropdown-icon { - flex-shrink: 0; - width: 16px; - height: 16px; - display: flex; - align-items: center; - justify-content: center; - color: ${(props) => props.theme.dropdown.iconColor}; - opacity: 0.8; - } - - .dropdown-right-section { - margin-left: auto; - flex-shrink: 0; - display: flex; - align-items: center; - justify-content: center; - } - - &:hover:not(:disabled) { - background-color: ${(props) => props.theme.dropdown.hoverBg}; - } - - &.selected-focused:not(:disabled) { - background-color: ${(props) => props.theme.dropdown.hoverBg}; - } - - &:focus-visible:not(:disabled) { - outline: none; - background-color: ${(props) => props.theme.dropdown.hoverBg}; - } - - &:focus:not(:focus-visible) { - outline: none; - } - - &:disabled { - cursor: not-allowed; - opacity: 0.5; - } - - &.delete-item { - color: ${(props) => props.theme.colors.text.danger}; - .dropdown-icon { - color: ${(props) => props.theme.colors.text.danger}; - } - &:hover { - background-color: ${({ theme }) => { - const hex = theme.colors.text.danger.replace('#', ''); - const r = parseInt(hex.substring(0, 2), 16); - const g = parseInt(hex.substring(2, 4), 16); - const b = parseInt(hex.substring(4, 6), 16); - return `rgba(${r}, ${g}, ${b}, 0.04)`; // 4% opacity - }} !important; - - color: ${(props) => props.theme.colors.text.danger} !important; - } - } - - &.border-top { - border-top: solid 1px ${(props) => props.theme.dropdown.separator}; - margin-top: 0.25rem; - padding-top: 0.375rem; - } - - &.dropdown-item-select { - padding-left: 1.5rem; - } - } - - .dropdown-separator { - height: 1px; - background-color: ${(props) => props.theme.dropdown.separator}; - margin: 0.25rem 0; + &.active { + color: ${(props) => props.theme.colors.text.yellow} !important; + .dropdown-icon { + color: ${(props) => props.theme.colors.text.yellow} !important; } } + + .dropdown-label { + flex: 1; + } + + .dropdown-icon { + flex-shrink: 0; + width: 16px; + height: 16px; + display: flex; + align-items: center; + justify-content: center; + color: ${(props) => props.theme.dropdown.iconColor}; + opacity: 0.8; + } + + .dropdown-right-section { + margin-left: auto; + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: center; + } + + &:hover:not(:disabled):not(.disabled) { + background-color: ${(props) => props.theme.dropdown.hoverBg}; + } + + &.selected-focused:not(:disabled):not(.disabled) { + background-color: ${(props) => props.theme.dropdown.hoverBg}; + } + + &:focus-visible:not(:disabled):not(.disabled) { + outline: none; + background-color: ${(props) => props.theme.dropdown.hoverBg}; + } + + &:focus:not(:focus-visible) { + outline: none; + } + + &:disabled, + &.disabled { + cursor: not-allowed; + opacity: 0.5; + } + + &.delete-item { + color: ${(props) => props.theme.colors.text.danger}; + .dropdown-icon { + color: ${(props) => props.theme.colors.text.danger}; + } + &:hover { + background-color: ${({ theme }) => { + const hex = theme.colors.text.danger.replace('#', ''); + const r = parseInt(hex.substring(0, 2), 16); + const g = parseInt(hex.substring(2, 4), 16); + const b = parseInt(hex.substring(4, 6), 16); + return `rgba(${r}, ${g}, ${b}, 0.04)`; // 4% opacity + }} !important; + + color: ${(props) => props.theme.colors.text.danger} !important; + } + } + + &.border-top { + border-top: solid 1px ${(props) => props.theme.dropdown.separator}; + margin-top: 0.25rem; + padding-top: 0.375rem; + } + + &.dropdown-item-select { + padding-left: 1.5rem; + } + + /* Focused state - applied during keyboard navigation */ + &.dropdown-item-focused { + background-color: ${({ theme }) => theme.dropdown.hoverBg}; + outline: none; + } + + /* Active/selected state - applied to the currently selected item */ + &.dropdown-item-active { + color: ${({ theme }) => theme.colors.text.yellow}; + background-color: ${({ theme }) => theme.dropdown.activeBg}; + font-weight: 500; + .dropdown-icon { + color: ${({ theme }) => theme.colors.text.yellow}; + } + } + + /* Combined state - when active item is also focused */ + &.dropdown-item-active.dropdown-item-focused { + background-color: ${({ theme }) => theme.dropdown.activeHoverBg}; + } + + /* Focus visible for accessibility */ + &:focus-visible { + outline: 2px solid ${({ theme }) => theme.dropdown.focusRing}; + outline-offset: -2px; + } + } + + .dropdown-separator { + height: 1px; + background-color: ${(props) => props.theme.dropdown.separator}; + margin: 0.25rem 0; } `; diff --git a/packages/bruno-app/src/components/Dropdown/index.js b/packages/bruno-app/src/components/Dropdown/index.js index 7c2d0f98a..5f1771a23 100644 --- a/packages/bruno-app/src/components/Dropdown/index.js +++ b/packages/bruno-app/src/components/Dropdown/index.js @@ -2,25 +2,27 @@ import React from 'react'; import Tippy from '@tippyjs/react'; import StyledWrapper from './StyledWrapper'; -const Dropdown = ({ icon, children, onCreate, placement, transparent, visible, ...props }) => { +const Dropdown = ({ icon, children, onCreate, placement, transparent, visible, appendTo, ...props }) => { // When in controlled mode (visible prop is provided), don't use trigger prop const tippyProps = visible !== undefined - ? { ...props, visible, interactive: true, appendTo: 'parent' } - : { ...props, trigger: 'click', interactive: true, appendTo: 'parent' }; + ? { ...props, visible, interactive: true, appendTo: appendTo || 'parent' } + : { ...props, trigger: 'click', interactive: true, appendTo: appendTo || 'parent' }; return ( - - - {icon} - - + ( + + {children} + + )} + placement={placement || 'bottom-end'} + animation={false} + arrow={false} + onCreate={onCreate} + {...tippyProps} + > + {icon} + ); }; diff --git a/packages/bruno-app/src/components/RequestTabs/RequestTab/index.js b/packages/bruno-app/src/components/RequestTabs/RequestTab/index.js index 8998ca9cc..2ad6f9cd7 100644 --- a/packages/bruno-app/src/components/RequestTabs/RequestTab/index.js +++ b/packages/bruno-app/src/components/RequestTabs/RequestTab/index.js @@ -17,7 +17,7 @@ import ConfirmCloseEnvironment from 'components/Environments/ConfirmCloseEnviron import RequestTabNotFound from './RequestTabNotFound'; import SpecialTab from './SpecialTab'; import StyledWrapper from './StyledWrapper'; -import Dropdown from 'components/Dropdown'; +import MenuDropdown from 'ui/MenuDropdown'; import CloneCollectionItem from 'components/Sidebar/Collections/Collection/CollectionItem/CloneCollectionItem/index'; import NewRequest from 'components/Sidebar/NewRequest/index'; import GradientCloseButton from './GradientCloseButton'; @@ -26,11 +26,12 @@ import { closeWsConnection } from 'utils/network/index'; import ExampleTab from '../ExampleTab'; import toast from 'react-hot-toast'; -const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUid, hasOverflow, setHasOverflow }) => { +const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUid, hasOverflow, setHasOverflow, dropdownContainerRef }) => { const dispatch = useDispatch(); const { storedTheme } = useTheme(); const theme = storedTheme === 'dark' ? darkTheme : lightTheme; const tabNameRef = useRef(null); + const tabLabelRef = useRef(null); const lastOverflowStateRef = useRef(null); const [showConfirmClose, setShowConfirmClose] = useState(false); const [showConfirmCollectionClose, setShowConfirmCollectionClose] = useState(false); @@ -38,8 +39,7 @@ const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUi const [showConfirmEnvironmentClose, setShowConfirmEnvironmentClose] = useState(false); const [showConfirmGlobalEnvironmentClose, setShowConfirmGlobalEnvironmentClose] = useState(false); - const dropdownTippyRef = useRef(); - const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref); + const menuDropdownRef = useRef(); const item = findItemInCollection(collection, tab.uid); @@ -99,17 +99,10 @@ const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUi ); }; - const handleRightClick = (_event) => { - const menuDropdown = dropdownTippyRef.current; - if (!menuDropdown) { - return; - } - - if (menuDropdown.state.isShown) { - menuDropdown.hide(); - } else { - menuDropdown.show(); - } + const handleRightClick = (event) => { + event.preventDefault(); + event.stopPropagation(); + menuDropdownRef.current?.show(); }; const handleMouseUp = (e) => { @@ -383,6 +376,7 @@ const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUi /> )}
dispatch(makeTabPermanent({ uid: tab.uid }))} @@ -403,13 +397,13 @@ const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUi {item.name}
{ + if (!tabLabelRef.current) { + return { width: 0, height: 0, top: 0, bottom: 0, left: 0, right: 0 }; + } + return tabLabelRef.current.getBoundingClientRect(); + }; + const totalTabs = collectionRequestTabs.length || 0; const currentTabUid = collectionRequestTabs[tabIndex]?.uid; const currentTabItem = findItemInCollection(collection, currentTabUid); @@ -442,10 +445,7 @@ function RequestTabMenu({ onDropdownCreate, collectionRequestTabs, tabIndex, col const hasRightTabs = totalTabs > tabIndex + 1; const hasOtherTabs = totalTabs > 1; - async function handleCloseTab(event, tabUid) { - event.stopPropagation(); - dropdownTippyRef.current.hide(); - + async function handleCloseTab(tabUid) { if (!tabUid) { return; } @@ -461,10 +461,7 @@ function RequestTabMenu({ onDropdownCreate, collectionRequestTabs, tabIndex, col } catch (err) { } } - function handleRevertChanges(event) { - event.stopPropagation(); - dropdownTippyRef.current.hide(); - + function handleRevertChanges() { if (!currentTabUid) { return; } @@ -480,40 +477,96 @@ function RequestTabMenu({ onDropdownCreate, collectionRequestTabs, tabIndex, col } catch (err) { } } - function handleCloseOtherTabs(event) { - dropdownTippyRef.current.hide(); - + async function handleCloseOtherTabs() { const otherTabs = collectionRequestTabs.filter((_, index) => index !== tabIndex); - otherTabs.forEach((tab) => handleCloseTab(event, tab.uid)); + await Promise.all(otherTabs.map((tab) => handleCloseTab(tab.uid))); } - function handleCloseTabsToTheLeft(event) { - dropdownTippyRef.current.hide(); - + async function handleCloseTabsToTheLeft() { const leftTabs = collectionRequestTabs.filter((_, index) => index < tabIndex); - leftTabs.forEach((tab) => handleCloseTab(event, tab.uid)); + await Promise.all(leftTabs.map((tab) => handleCloseTab(tab.uid))); } - function handleCloseTabsToTheRight(event) { - dropdownTippyRef.current.hide(); - + async function handleCloseTabsToTheRight() { const rightTabs = collectionRequestTabs.filter((_, index) => index > tabIndex); - rightTabs.forEach((tab) => handleCloseTab(event, tab.uid)); + await Promise.all(rightTabs.map((tab) => handleCloseTab(tab.uid))); } - function handleCloseSavedTabs(event) { - event.stopPropagation(); - + function handleCloseSavedTabs() { const items = flattenItems(collection?.items); const savedTabs = items?.filter?.((item) => !hasRequestChanges(item)); const savedTabIds = savedTabs?.map((item) => item.uid) || []; dispatch(closeTabs({ tabUids: savedTabIds })); } - function handleCloseAllTabs(event) { - collectionRequestTabs.forEach((tab) => handleCloseTab(event, tab.uid)); + async function handleCloseAllTabs() { + await Promise.all(collectionRequestTabs.map((tab) => handleCloseTab(tab.uid))); } + const menuItems = useMemo(() => [ + { + id: 'new-request', + label: 'New Request', + onClick: () => setShowAddNewRequestModal(true) + }, + { + id: 'clone-request', + label: 'Clone Request', + onClick: () => setShowCloneRequestModal(true) + }, + { + id: 'revert-changes', + label: 'Revert Changes', + onClick: handleRevertChanges, + disabled: !currentTabItem?.draft + }, + { + id: 'close', + label: 'Close', + onClick: () => handleCloseTab(currentTabUid) + }, + { + id: 'close-others', + label: 'Close Others', + onClick: handleCloseOtherTabs, + disabled: !hasOtherTabs + }, + { + id: 'close-left', + label: 'Close to the Left', + onClick: handleCloseTabsToTheLeft, + disabled: !hasLeftTabs + }, + { + id: 'close-right', + label: 'Close to the Right', + onClick: handleCloseTabsToTheRight, + disabled: !hasRightTabs + }, + { + id: 'close-saved', + label: 'Close Saved', + onClick: handleCloseSavedTabs + }, + { + id: 'close-all', + label: 'Close All', + onClick: handleCloseAllTabs + } + ], [currentTabUid, currentTabItem, hasOtherTabs, hasLeftTabs, hasRightTabs, collection, collectionRequestTabs, tabIndex, dispatch]); + + const menuDropdown = ( + + + + ); + return ( {showAddNewRequestModal && ( @@ -528,51 +581,7 @@ function RequestTabMenu({ onDropdownCreate, collectionRequestTabs, tabIndex, col /> )} - } placement="bottom-start"> - - - - - - - - - - + {menuDropdown} ); } diff --git a/packages/bruno-app/src/components/RequestTabs/index.js b/packages/bruno-app/src/components/RequestTabs/index.js index 34c8f479b..79498cbb6 100644 --- a/packages/bruno-app/src/components/RequestTabs/index.js +++ b/packages/bruno-app/src/components/RequestTabs/index.js @@ -17,6 +17,7 @@ const RequestTabs = () => { const dispatch = useDispatch(); const tabsRef = useRef(); const scrollContainerRef = useRef(); + const collectionTabsRef = useRef(); const [newRequestModalOpen, setNewRequestModalOpen] = useState(false); const [tabOverflowStates, setTabOverflowStates] = useState({}); const [showChevrons, setShowChevrons] = useState(false); @@ -115,7 +116,7 @@ const RequestTabs = () => { {collectionRequestTabs && collectionRequestTabs.length ? ( <> -
+
    {showChevrons ? (
  • @@ -158,6 +159,7 @@ const RequestTabs = () => { folderUid={tab.folderUid} hasOverflow={tabOverflowStates[tab.uid]} setHasOverflow={createSetHasOverflow(tab.uid)} + dropdownContainerRef={collectionTabsRef} /> ); diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js index 8aaf15e4e..3ddbe96e4 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js @@ -53,8 +53,10 @@ import CreateExampleModal from 'components/ResponseExample/CreateExampleModal'; import { openDevtoolsAndSwitchToTerminal } from 'utils/terminal'; import ActionIcon from 'ui/ActionIcon'; import MenuDropdown from 'ui/MenuDropdown'; +import { useSidebarAccordion } from 'components/Sidebar/SidebarAccordionContext'; const CollectionItem = ({ item, collectionUid, collectionPathname, searchText }) => { + const { dropdownContainerRef } = useSidebarAccordion(); const _isTabForItemActiveSelector = isTabForItemActiveSelector({ itemUid: item.uid }); const isTabForItemActive = useSelector(_isTabForItemActiveSelector, isEqual); @@ -640,8 +642,9 @@ const CollectionItem = ({ item, collectionUid, collectionPathname, searchText }) onClick={handleClick} onDoubleClick={handleDoubleClick} > - - {isFolder ? ( + + {isFolder ? ( + - ) : hasExamples ? ( + + ) : hasExamples ? ( + - ) : null} - + + ) : null} +
    @@ -676,6 +682,8 @@ const CollectionItem = ({ item, collectionUid, collectionPathname, searchText }) items={buildMenuItems()} placement="bottom-start" data-testid="collection-item-menu" + popperOptions={{ strategy: 'fixed' }} + appendTo={dropdownContainerRef?.current || document.body} > diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js index b9ecb1210..98256e073 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js @@ -45,8 +45,10 @@ import { sortByNameThenSequence } from 'utils/common/index'; import { openDevtoolsAndSwitchToTerminal } from 'utils/terminal'; import ActionIcon from 'ui/ActionIcon'; import MenuDropdown from 'ui/MenuDropdown'; +import { useSidebarAccordion } from 'components/Sidebar/SidebarAccordionContext'; const Collection = ({ collection, searchText }) => { + const { dropdownContainerRef } = useSidebarAccordion(); const [showNewFolderModal, setShowNewFolderModal] = useState(false); const [showNewRequestModal, setShowNewRequestModal] = useState(false); const [showRenameCollectionModal, setShowRenameCollectionModal] = useState(false); @@ -434,6 +436,8 @@ const Collection = ({ collection, searchText }) => { ref={menuDropdownRef} items={menuItems} placement="bottom-start" + appendTo={dropdownContainerRef?.current || document.body} + popperOptions={{ strategy: 'fixed' }} data-testid="collection-actions" > diff --git a/packages/bruno-app/src/components/Sidebar/SidebarAccordionContext.js b/packages/bruno-app/src/components/Sidebar/SidebarAccordionContext.js index d093753d6..ba51b8894 100644 --- a/packages/bruno-app/src/components/Sidebar/SidebarAccordionContext.js +++ b/packages/bruno-app/src/components/Sidebar/SidebarAccordionContext.js @@ -1,4 +1,4 @@ -import React, { createContext, useContext, useState, useCallback } from 'react'; +import React, { createContext, useContext, useState, useCallback, useRef } from 'react'; const SidebarAccordionContext = createContext(); @@ -12,6 +12,7 @@ export const useSidebarAccordion = () => { export const SidebarAccordionProvider = ({ children, defaultExpanded = ['collections'] }) => { const [expandedSections, setExpandedSections] = useState(new Set(defaultExpanded)); + const dropdownContainerRef = useRef(null); const toggleSection = useCallback((sectionId) => { setExpandedSections((prev) => { @@ -52,10 +53,13 @@ export const SidebarAccordionProvider = ({ children, defaultExpanded = ['collect toggleSection, setSectionExpanded, isExpanded, - getExpandedCount + getExpandedCount, + dropdownContainerRef }} > - {children} +
    + {children} +
    ); }; diff --git a/packages/bruno-app/src/ui/MenuDropdown/StyledWrapper.js b/packages/bruno-app/src/ui/MenuDropdown/StyledWrapper.js deleted file mode 100644 index fbe183bbb..000000000 --- a/packages/bruno-app/src/ui/MenuDropdown/StyledWrapper.js +++ /dev/null @@ -1,150 +0,0 @@ -import styled from 'styled-components'; - -const StyledWrapper = styled.div` - .tippy-box { - .tippy-content { - .label-item { - display: flex; - align-items: center; - padding: 0.375rem 0.625rem 0.25rem 0.625rem; - font-size: 0.6875rem; - font-weight: 600; - letter-spacing: 0.025em; - color: ${(props) => props.theme.dropdown.color}; - opacity: 0.6; - margin-top: 0.25rem; - - &:first-child { - margin-top: 0; - } - } - - .dropdown-item { - display: flex; - align-items: center; - gap: 0.5rem; - padding: 0.275rem 0.625rem; - cursor: pointer; - border-radius: 6px; - margin: 0.0625rem 0; - font-size: 0.8125rem; - - &.active { - color: ${(props) => props.theme.colors.text.yellow} !important; - .dropdown-icon { - color: ${(props) => props.theme.colors.text.yellow} !important; - } - } - - .dropdown-label { - flex: 1; - } - - .dropdown-icon { - flex-shrink: 0; - width: 16px; - height: 16px; - display: flex; - align-items: center; - justify-content: center; - color: ${(props) => props.theme.dropdown.iconColor}; - opacity: 0.8; - } - - .dropdown-right-section { - margin-left: auto; - flex-shrink: 0; - display: flex; - align-items: center; - justify-content: center; - } - - &:hover:not(:disabled):not(.disabled) { - background-color: ${(props) => props.theme.dropdown.hoverBg}; - } - - &.selected-focused:not(:disabled):not(.disabled) { - background-color: ${(props) => props.theme.dropdown.hoverBg}; - } - - &:focus-visible:not(:disabled):not(.disabled) { - outline: none; - background-color: ${(props) => props.theme.dropdown.hoverBg}; - } - - &:focus:not(:focus-visible) { - outline: none; - } - - &:disabled, - &.disabled { - cursor: not-allowed; - opacity: 0.5; - } - - &.delete-item { - color: ${(props) => props.theme.colors.text.danger}; - .dropdown-icon { - color: ${(props) => props.theme.colors.text.danger}; - } - &:hover { - background-color: ${({ theme }) => { - const hex = theme.colors.text.danger.replace('#', ''); - const r = parseInt(hex.substring(0, 2), 16); - const g = parseInt(hex.substring(2, 4), 16); - const b = parseInt(hex.substring(4, 6), 16); - return `rgba(${r}, ${g}, ${b}, 0.04)`; // 4% opacity - }} !important; - - color: ${(props) => props.theme.colors.text.danger} !important; - } - } - - &.border-top { - border-top: solid 1px ${(props) => props.theme.dropdown.separator}; - margin-top: 0.25rem; - padding-top: 0.375rem; - } - - &.dropdown-item-select { - padding-left: 1.5rem; - } - - /* Focused state - applied during keyboard navigation */ - &.dropdown-item-focused { - background-color: ${({ theme }) => theme.dropdown.hoverBg}; - outline: none; - } - - /* Active/selected state - applied to the currently selected item */ - &.dropdown-item-active { - color: ${({ theme }) => theme.colors.text.yellow}; - background-color: ${({ theme }) => theme.dropdown.activeBg}; - font-weight: 500; - .dropdown-icon { - color: ${({ theme }) => theme.colors.text.yellow}; - } - } - - /* Combined state - when active item is also focused */ - &.dropdown-item-active.dropdown-item-focused { - background-color: ${({ theme }) => theme.dropdown.activeHoverBg}; - } - - /* Focus visible for accessibility */ - &:focus-visible { - outline: 2px solid ${({ theme }) => theme.dropdown.focusRing}; - outline-offset: -2px; - } - } - - .dropdown-separator { - height: 1px; - background-color: ${(props) => props.theme.dropdown.separator}; - margin: 0.25rem 0; - } - } - } -`; - -export default StyledWrapper; diff --git a/packages/bruno-app/src/ui/MenuDropdown/index.js b/packages/bruno-app/src/ui/MenuDropdown/index.js index 8fc171b15..06dd87964 100644 --- a/packages/bruno-app/src/ui/MenuDropdown/index.js +++ b/packages/bruno-app/src/ui/MenuDropdown/index.js @@ -1,6 +1,5 @@ import React, { forwardRef, useRef, useCallback, useState, useImperativeHandle, useEffect, useMemo } from 'react'; import Dropdown from 'components/Dropdown'; -import StyledWrapper from './StyledWrapper'; // Constants const NAVIGATION_KEYS = ['ArrowDown', 'ArrowUp', 'Home', 'End', 'Escape']; @@ -432,37 +431,35 @@ const MenuDropdown = forwardRef(({ :
    {children}
    ; return ( - - -
    - {header && ( -
    - {header} -
    -
    - )} -
    - {renderMenuContent()} + +
    + {header && ( +
    + {header} +
    - {footer && ( - <> -
    -
    - {footer} -
    - - )} + )} +
    + {renderMenuContent()}
    - - + {footer && ( + <> +
    +
    + {footer} +
    + + )} +
    +
    ); }); diff --git a/tests/utils/page/actions.ts b/tests/utils/page/actions.ts index 7e9fe7fbb..9aca75e4f 100644 --- a/tests/utils/page/actions.ts +++ b/tests/utils/page/actions.ts @@ -650,7 +650,7 @@ const selectRequestPaneTab = async (page: Page, tabName: string) => { await overflowButton.click(); // Wait for dropdown to appear and click the menu item (overflow tabs are rendered as menuitems) - const dropdownItem = page.locator('.tippy-content').getByRole('menuitem', { name: tabName }); + const dropdownItem = page.locator('.tippy-box .dropdown-item').filter({ hasText: tabName }); await expect(dropdownItem).toBeVisible(); await dropdownItem.click(); return;