diff --git a/packages/bruno-app/src/components/AppTitleBar/StyledWrapper.js b/packages/bruno-app/src/components/AppTitleBar/StyledWrapper.js index b625d59be..1b22ea215 100644 --- a/packages/bruno-app/src/components/AppTitleBar/StyledWrapper.js +++ b/packages/bruno-app/src/components/AppTitleBar/StyledWrapper.js @@ -29,7 +29,6 @@ const Wrapper = styled.div` .workspace-name-container, .dropdown-item, .home-button, - .env-selector-trigger, .dropdown, button { -webkit-app-region: no-drag; @@ -49,25 +48,6 @@ const Wrapper = styled.div` margin-left: 0px; } - /* Home button */ - .home-button { - display: flex; - align-items: center; - justify-content: center; - width: 28px; - height: 28px; - border: none; - background: transparent; - border-radius: 6px; - cursor: pointer; - color: ${(props) => props.theme.sidebar.color}; - transition: background 0.15s ease; - - &:hover { - background: ${(props) => props.theme.sidebar.collection.item.hoverBg}; - } - } - /* Workspace Name Dropdown Trigger */ .workspace-name-container { display: flex; @@ -112,7 +92,7 @@ const Wrapper = styled.div` .bruno-text { font-size: 13px; font-weight: 600; - color: ${(props) => props.theme.sidebar.muted}; + color: ${(props) => props.theme.text}; letter-spacing: 0.5px; } } @@ -125,36 +105,6 @@ const Wrapper = styled.div` flex-shrink: 0; } - /* Action buttons in right section */ - .titlebar-action-button { - display: flex; - align-items: center; - justify-content: center; - width: 28px; - height: 28px; - border: none; - background: transparent; - border-radius: 6px; - cursor: pointer; - color: ${(props) => props.theme.sidebar.color}; - transition: background 0.15s ease; - - &:hover { - background: ${(props) => props.theme.sidebar.collection.item.hoverBg}; - } - - svg { - color: ${(props) => props.theme.sidebar.color}; - } - } - - /* Draggable region */ - .drag-region { - flex: 1; - height: 100%; - -webkit-app-region: drag; - } - /* Workspace Dropdown Styles */ .workspace-item { display: flex; diff --git a/packages/bruno-app/src/components/AppTitleBar/index.js b/packages/bruno-app/src/components/AppTitleBar/index.js index 5f3dce40c..d573ed656 100644 --- a/packages/bruno-app/src/components/AppTitleBar/index.js +++ b/packages/bruno-app/src/components/AppTitleBar/index.js @@ -1,5 +1,6 @@ +import React from 'react'; import { IconCheck, IconChevronDown, IconFolder, IconHome, IconLayoutColumns, IconLayoutRows, IconPin, IconPinned, IconPlus } from '@tabler/icons'; -import { forwardRef, useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { forwardRef, useCallback, useEffect, useMemo, useState } from 'react'; import toast from 'react-hot-toast'; import { useDispatch, useSelector } from 'react-redux'; @@ -9,7 +10,8 @@ import { openWorkspaceDialog, switchWorkspace } from 'providers/ReduxStore/slice import { sortWorkspaces, toggleWorkspacePin } from 'utils/workspaces'; import Bruno from 'components/Bruno'; -import Dropdown from 'components/Dropdown'; +import MenuDropdown from 'ui/MenuDropdown'; +import ActionIcon from 'ui/ActionIcon'; import IconSidebarToggle from 'components/Icons/IconSidebarToggle'; import CreateWorkspace from 'components/WorkspaceSidebar/CreateWorkspace'; @@ -53,13 +55,10 @@ const AppTitleBar = () => { }, [workspaces, preferences]); const [createWorkspaceModalOpen, setCreateWorkspaceModalOpen] = useState(false); - const [showWorkspaceDropdown, setShowWorkspaceDropdown] = useState(false); - const workspaceDropdownTippyRef = useRef(); - const onWorkspaceDropdownCreate = (ref) => (workspaceDropdownTippyRef.current = ref); const WorkspaceName = forwardRef((props, ref) => { return ( -
setShowWorkspaceDropdown(!showWorkspaceDropdown)}> +
{toTitleCase(activeWorkspace?.name) || 'Default Workspace'}
@@ -72,12 +71,10 @@ const AppTitleBar = () => { const handleWorkspaceSwitch = (workspaceUid) => { dispatch(switchWorkspace(workspaceUid)); - setShowWorkspaceDropdown(false); toast.success(`Switched to ${workspaces.find((w) => w.uid === workspaceUid)?.name}`); }; const handleOpenWorkspace = async () => { - setShowWorkspaceDropdown(false); try { await dispatch(openWorkspaceDialog()); toast.success('Workspace opened successfully'); @@ -87,7 +84,6 @@ const AppTitleBar = () => { }; const handleCreateWorkspace = () => { - setShowWorkspaceDropdown(false); setCreateWorkspaceModalOpen(true); }; @@ -124,6 +120,59 @@ const AppTitleBar = () => { dispatch(savePreferences(updatedPreferences)); }; + // Build workspace menu items + const workspaceMenuItems = useMemo(() => { + const items = sortedWorkspaces.map((workspace) => { + const isActive = workspace.uid === activeWorkspaceUid; + const isPinned = preferences?.workspaces?.pinnedWorkspaceUids?.includes(workspace.uid); + + return { + id: workspace.uid, + label: toTitleCase(workspace.name), + onClick: () => handleWorkspaceSwitch(workspace.uid), + className: `workspace-item ${isActive ? 'active' : ''}`, + rightSection: ( +
+ {workspace.type !== 'default' && ( + handlePinWorkspace(workspace.uid, e)} + label={isPinned ? 'Unpin workspace' : 'Pin workspace'} + size="sm" + > + {isPinned ? ( + + ) : ( + + )} + + )} + {isActive && } +
+ ) + }; + }); + + // Add label and action items + items.push( + { type: 'label', label: 'Workspaces' }, + { + id: 'create-workspace', + leftSection: IconPlus, + label: 'Create workspace', + onClick: handleCreateWorkspace + }, + { + id: 'open-workspace', + leftSection: IconFolder, + label: 'Open workspace', + onClick: handleOpenWorkspace + } + ); + + return items; + }, [sortedWorkspaces, activeWorkspaceUid, preferences, handlePinWorkspace]); + return ( {createWorkspaceModalOpen && ( @@ -133,61 +182,24 @@ const AppTitleBar = () => {
{/* Left section: Home + Workspace */}
- + {/* Workspace Dropdown */} - } + setShowWorkspaceDropdown(false)} + selectedItemId={activeWorkspaceUid} > - {sortedWorkspaces.map((workspace) => { - const isActive = workspace.uid === activeWorkspaceUid; - const isPinned = preferences?.workspaces?.pinnedWorkspaceUids?.includes(workspace.uid); - - return ( -
handleWorkspaceSwitch(workspace.uid)} - > - {toTitleCase(workspace.name)} -
- {workspace.type !== 'default' && ( - - )} - {isActive && } -
-
- ); - })} - -
Workspaces
- -
- - Create workspace -
-
- - Open workspace -
-
+ +
{/* Center section: Bruno logo + text */} @@ -199,33 +211,30 @@ const AppTitleBar = () => { {/* Right section: Action buttons */}
{/* Toggle sidebar */} - + {/* Toggle devtools */} - + {/* Toggle vertical layout */} - - +
diff --git a/packages/bruno-app/src/components/Dropdown/StyledWrapper.js b/packages/bruno-app/src/components/Dropdown/StyledWrapper.js index c9a44bd9f..7452a4f75 100644 --- a/packages/bruno-app/src/components/Dropdown/StyledWrapper.js +++ b/packages/bruno-app/src/components/Dropdown/StyledWrapper.js @@ -25,6 +25,16 @@ const Wrapper = styled.div` padding-top: 0; padding-bottom: 0; + [role="menu"] { + outline: none; + &:focus { + outline: none; + } + &:focus-visible { + outline: none; + } + } + .label-item { display: flex; align-items: center; @@ -59,6 +69,10 @@ const Wrapper = styled.div` } } + .dropdown-label { + flex: 1; + } + .dropdown-icon { flex-shrink: 0; width: 16px; @@ -70,10 +84,31 @@ const Wrapper = styled.div` 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; diff --git a/packages/bruno-app/src/components/Sidebar/SidebarHeader/StyledWrapper.js b/packages/bruno-app/src/components/Sidebar/SidebarHeader/StyledWrapper.js index 4c20e241b..0285c8b0b 100644 --- a/packages/bruno-app/src/components/Sidebar/SidebarHeader/StyledWrapper.js +++ b/packages/bruno-app/src/components/Sidebar/SidebarHeader/StyledWrapper.js @@ -1,7 +1,7 @@ import styled from 'styled-components'; const StyledWrapper = styled.div` - padding: 8px 4px 6px 10px; + .sidebar-header { display: flex; @@ -9,6 +9,7 @@ const StyledWrapper = styled.div` justify-content: space-between; gap: 8px; border-bottom: 1px solid ${(props) => props.theme.sidebar.collection.item.hoverBg}; + padding: 6px 4px 6px 10px; } /* Section Title (single view mode) */ diff --git a/packages/bruno-app/src/components/Sidebar/SidebarHeader/index.js b/packages/bruno-app/src/components/Sidebar/SidebarHeader/index.js index 397a3f056..b53aff940 100644 --- a/packages/bruno-app/src/components/Sidebar/SidebarHeader/index.js +++ b/packages/bruno-app/src/components/Sidebar/SidebarHeader/index.js @@ -13,7 +13,7 @@ import { IconSquareX, IconTrash } from '@tabler/icons'; -import { useRef, useState } from 'react'; +import { useState } from 'react'; import toast from 'react-hot-toast'; import { useDispatch, useSelector } from 'react-redux'; @@ -22,7 +22,8 @@ import { sortCollections } from 'providers/ReduxStore/slices/collections/index'; import { importCollectionInWorkspace } from 'providers/ReduxStore/slices/workspaces/actions'; import { openApiSpec } from 'providers/ReduxStore/slices/apiSpec'; -import Dropdown from 'components/Dropdown'; +import MenuDropdown from 'ui/MenuDropdown'; +import ActionIcon from 'ui/ActionIcon'; import ImportCollection from 'components/Sidebar/ImportCollection'; import ImportCollectionLocation from 'components/Sidebar/ImportCollectionLocation'; import CreateApiSpec from 'components/Sidebar/ApiSpecs/CreateApiSpec'; @@ -75,12 +76,6 @@ const SidebarHeader = ({ setShowSearch }) => { }); }; - const addDropdownTippyRef = useRef(); - const onAddDropdownCreate = (ref) => (addDropdownTippyRef.current = ref); - - const actionsDropdownTippyRef = useRef(); - const onActionsDropdownCreate = (ref) => (actionsDropdownTippyRef.current = ref); - const handleToggleSearch = () => { if (setShowSearch) { setShowSearch((prev) => !prev); @@ -183,123 +178,112 @@ const SidebarHeader = ({ setShowSearch }) => { ); + // Configuration for Add/Create dropdown items + const addDropdownItems = [ + { + id: 'create', + leftSection: IconPlus, + label: 'Create collection', + onClick: () => { + setCreateCollectionModalOpen(true); + } + }, + { + id: 'import', + leftSection: IconDownload, + label: 'Import collection', + onClick: () => { + setImportCollectionModalOpen(true); + } + }, + { + id: 'open', + leftSection: IconFolder, + label: 'Open collection', + onClick: () => { + handleOpenCollection(); + } + }, + { + type: 'label', + label: 'API Specs' + }, + { + id: 'create-api-spec', + leftSection: IconPlus, + label: 'Create API Spec', + onClick: () => { + setCreateApiSpecModalOpen(true); + } + }, + { + id: 'open-api-spec', + leftSection: IconFileCode, + label: 'Open API Spec', + onClick: () => { + handleOpenApiSpec(); + } + } + ]; + + // Configuration for Actions dropdown items + const actionsDropdownItems = [ + { + id: 'sort', + leftSection: getSortIcon(), + label: getSortLabel(), + onClick: () => { + handleSortCollections(); + } + }, + { + id: 'close-all', + leftSection: IconSquareX, + label: 'Close all', + onClick: () => { + selectAllCollectionsToClose(); + } + } + ]; + // Render Collections-specific actions const renderCollectionsActions = () => ( <> - - {/* Add/Create dropdown */} - - - - )} - placement="bottom-end" - style="new" - > -
Collections
-
{ - setCreateCollectionModalOpen(true); - addDropdownTippyRef.current?.hide(); - }} - > - - Create collection -
-
{ - addDropdownTippyRef.current?.hide(); - setImportCollectionModalOpen(true); - }} - > - - Import collection -
-
{ - handleOpenCollection(); - addDropdownTippyRef.current?.hide(); - }} - > - - Open collection -
+
- - {/* Actions dropdown (sort, close all, etc.) */} - - - - )} + {/* Add Collection dropdown */} + -
{ - handleSortCollections(); - actionsDropdownTippyRef.current?.hide(); - }} - aria-label="Sort collections" - title="Sort collections" - data-testid="sort-collections-button" + - {(() => { - const SortIcon = getSortIcon(); - return ; - })()} - {getSortLabel()} -
-
{ - selectAllCollectionsToClose(); - actionsDropdownTippyRef.current?.hide(); - }} - aria-label="Close all collections" - title="Close all collections" - data-testid="close-all-collections-button" +
-
+