diff --git a/packages/bruno-app/src/components/Sidebar/ApiSpecs/ApiSpecItem/index.js b/packages/bruno-app/src/components/Sidebar/ApiSpecs/ApiSpecItem/index.js index d175d55ae..17a171bbe 100644 --- a/packages/bruno-app/src/components/Sidebar/ApiSpecs/ApiSpecItem/index.js +++ b/packages/bruno-app/src/components/Sidebar/ApiSpecs/ApiSpecItem/index.js @@ -1,7 +1,7 @@ import { setActiveApiSpecUid } from 'providers/ReduxStore/slices/apiSpec'; import { showApiSpecPage as _showApiSpecPage } from 'providers/ReduxStore/slices/app'; import Dropdown from 'components/Dropdown'; -import { IconDots } from '@tabler/icons'; +import { IconDots, IconX } from '@tabler/icons'; import { useState, useRef } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import CloseApiSpec from '../CloseApiSpec/index'; @@ -53,7 +53,10 @@ const ApiSpecItem = ({ apiSpec }) => { setCloseApiSpecModal(true); }} > - Close + + + + Remove diff --git a/packages/bruno-app/src/components/Sidebar/ApiSpecs/StyledWrapper.js b/packages/bruno-app/src/components/Sidebar/ApiSpecs/StyledWrapper.js index 992087dc5..6b520eb53 100644 --- a/packages/bruno-app/src/components/Sidebar/ApiSpecs/StyledWrapper.js +++ b/packages/bruno-app/src/components/Sidebar/ApiSpecs/StyledWrapper.js @@ -1,7 +1,27 @@ import styled from 'styled-components'; const Wrapper = styled.div` + display: flex; + flex-direction: column; + flex: 1 1 0%; + min-height: 0; + height: 100%; + overflow: hidden; + padding-top: 4px; + padding-bottom: 4px; + + .api-specs-list { + flex: 1 1 0%; + min-height: 0; + padding-top: 4px; + padding-bottom: 4px; + overflow-y: auto; + overflow-x: hidden; + } + .api-spec-item { + height: 1.6rem; + cursor: pointer; &.active { background: ${(props) => props.theme.sidebar.collection.item.bg}; } @@ -36,14 +56,6 @@ const Wrapper = styled.div` top: -0.625rem; } - div.dropdown-item.close-item { - color: ${(props) => props.theme.colors.danger}; - &:hover { - background-color: ${(props) => props.theme.colors.bg.danger}; - color: white; - } - } - .placeholder { color: ${(props) => props.theme.colors.text.muted}; } diff --git a/packages/bruno-app/src/components/Sidebar/ApiSpecs/index.js b/packages/bruno-app/src/components/Sidebar/ApiSpecs/index.js index 37801bd32..8bedb9581 100644 --- a/packages/bruno-app/src/components/Sidebar/ApiSpecs/index.js +++ b/packages/bruno-app/src/components/Sidebar/ApiSpecs/index.js @@ -4,7 +4,6 @@ import { useDispatch, useSelector } from 'react-redux'; import { useTheme } from 'providers/Theme'; import { openApiSpec } from 'providers/ReduxStore/slices/apiSpec'; import ApiSpecItem from './ApiSpecItem'; -import ApiSpecsBadge from './ApiSpecBadge'; import StyledWrapper from './StyledWrapper'; import toast from 'react-hot-toast'; @@ -47,8 +46,7 @@ const ApiSpecs = () => { if (!apiSpecs || !apiSpecs.length) { return ( - -
+
No API Specs found.
API Spec. @@ -60,15 +58,12 @@ const ApiSpecs = () => { return ( -
- -
- {apiSpecs && apiSpecs.length - ? apiSpecs.map((apiSpec) => { - return ; - }) - : null} -
+
+ {apiSpecs && apiSpecs.length + ? apiSpecs.map((apiSpec) => { + return ; + }) + : null}
); diff --git a/packages/bruno-app/src/components/Sidebar/Collections/StyledWrapper.js b/packages/bruno-app/src/components/Sidebar/Collections/StyledWrapper.js index 822559324..07af84710 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/StyledWrapper.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/StyledWrapper.js @@ -3,32 +3,20 @@ import styled from 'styled-components'; const Wrapper = styled.div` display: flex; flex-direction: column; - flex: 1; + flex: 1 1 0%; min-height: 0; overflow: hidden; padding-top: 4px; + padding-bottom: 4px; .collections-list { + flex: 1 1 0%; min-height: 0; padding-top: 4px; + padding-bottom: 4px; overflow-y: auto; + overflow-x: hidden; - &::-webkit-scrollbar { - width: 6px; - } - - &::-webkit-scrollbar-track { - background: transparent; - } - - &::-webkit-scrollbar-thumb { - background: ${(props) => props.theme.scrollbar.color}; - border-radius: 3px; - } - - &::-webkit-scrollbar-thumb:hover { - background: ${(props) => props.theme.scrollbar.color}; - } } `; diff --git a/packages/bruno-app/src/components/Sidebar/Collections/index.js b/packages/bruno-app/src/components/Sidebar/Collections/index.js index f38abccf1..60ac62a6a 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/index.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/index.js @@ -7,7 +7,6 @@ import CreateOrOpenCollection from './CreateOrOpenCollection'; import CollectionSearch from './CollectionSearch/index'; import { useMemo } from 'react'; import { normalizePath } from 'utils/common/path'; -import ApiSpecs from '../ApiSpecs/index'; const Collections = ({ showSearch }) => { const [searchText, setSearchText] = useState(''); @@ -44,7 +43,7 @@ const Collections = ({ showSearch }) => { )} -
+
{workspaceCollections && workspaceCollections.length ? workspaceCollections.map((c) => { return ( @@ -52,8 +51,6 @@ const Collections = ({ showSearch }) => { ); }) : null} -
-
); diff --git a/packages/bruno-app/src/components/Sidebar/Sections/ApiSpecsSection/index.js b/packages/bruno-app/src/components/Sidebar/Sections/ApiSpecsSection/index.js new file mode 100644 index 000000000..23d84966e --- /dev/null +++ b/packages/bruno-app/src/components/Sidebar/Sections/ApiSpecsSection/index.js @@ -0,0 +1,79 @@ +import { useState } from 'react'; +import toast from 'react-hot-toast'; +import { useDispatch } from 'react-redux'; +import { IconFileCode, IconPlus } from '@tabler/icons'; + +import { openApiSpec } from 'providers/ReduxStore/slices/apiSpec'; +import MenuDropdown from 'ui/MenuDropdown'; +import ActionIcon from 'ui/ActionIcon'; +import CreateApiSpec from 'components/Sidebar/ApiSpecs/CreateApiSpec'; +import ApiSpecs from 'components/Sidebar/ApiSpecs'; +import SidebarSection from 'components/Sidebar/SidebarSection'; + +const ApiSpecsSection = () => { + const dispatch = useDispatch(); + const [createApiSpecModalOpen, setCreateApiSpecModalOpen] = useState(false); + + const handleOpenApiSpec = () => { + dispatch(openApiSpec()).catch((err) => { + console.error(err); + toast.error('An error occurred while opening the API spec'); + }); + }; + + const addDropdownItems = [ + { + 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(); + } + } + ]; + + const sectionActions = ( + <> + + + + + + ); + + return ( + <> + {createApiSpecModalOpen && ( + setCreateApiSpecModalOpen(false)} + /> + )} + + + + + ); +}; + +export default ApiSpecsSection; diff --git a/packages/bruno-app/src/components/Sidebar/Sections/CollectionsSection/index.js b/packages/bruno-app/src/components/Sidebar/Sections/CollectionsSection/index.js new file mode 100644 index 000000000..e28414959 --- /dev/null +++ b/packages/bruno-app/src/components/Sidebar/Sections/CollectionsSection/index.js @@ -0,0 +1,255 @@ +import { useState } from 'react'; +import toast from 'react-hot-toast'; +import { useDispatch, useSelector } from 'react-redux'; +import { + IconArrowsSort, + IconDotsVertical, + IconDownload, + IconFolder, + IconPlus, + IconSearch, + IconSortAscendingLetters, + IconSortDescendingLetters, + IconSquareX +} from '@tabler/icons'; + +import { importCollection, openCollection } from 'providers/ReduxStore/slices/collections/actions'; +import { sortCollections } from 'providers/ReduxStore/slices/collections/index'; +import { importCollectionInWorkspace } from 'providers/ReduxStore/slices/workspaces/actions'; + +import MenuDropdown from 'ui/MenuDropdown'; +import ActionIcon from 'ui/ActionIcon'; +import ImportCollection from 'components/Sidebar/ImportCollection'; +import ImportCollectionLocation from 'components/Sidebar/ImportCollectionLocation'; +import RemoveCollectionsModal from 'components/Sidebar/Collections/RemoveCollectionsModal/index'; +import CreateCollection from 'components/Sidebar/CreateCollection'; +import Collections from 'components/Sidebar/Collections'; +import SidebarSection from 'components/Sidebar/SidebarSection'; +import { IconBox } from '@tabler/icons'; + +const CollectionsSection = () => { + const [showSearch, setShowSearch] = useState(false); + const dispatch = useDispatch(); + + const { workspaces, activeWorkspaceUid } = useSelector((state) => state.workspaces); + const activeWorkspace = workspaces.find((w) => w.uid === activeWorkspaceUid); + + const { collections } = useSelector((state) => state.collections); + const { collectionSortOrder } = useSelector((state) => state.collections); + const [collectionsToClose, setCollectionsToClose] = useState([]); + + const [importData, setImportData] = useState(null); + const [createCollectionModalOpen, setCreateCollectionModalOpen] = useState(false); + const [importCollectionModalOpen, setImportCollectionModalOpen] = useState(false); + const [importCollectionLocationModalOpen, setImportCollectionLocationModalOpen] = useState(false); + + const handleImportCollection = ({ rawData, type }) => { + setImportCollectionModalOpen(false); + + if (activeWorkspace && activeWorkspace.type !== 'default') { + dispatch(importCollectionInWorkspace(rawData, activeWorkspace.uid, undefined, type)) + .catch((err) => { + toast.error('An error occurred while importing the collection'); + }); + } else { + setImportData({ rawData, type }); + setImportCollectionLocationModalOpen(true); + } + }; + + const handleImportCollectionLocation = (convertedCollection, collectionLocation) => { + dispatch(importCollection(convertedCollection, collectionLocation)) + .then(() => { + setImportCollectionLocationModalOpen(false); + setImportData(null); + toast.success('Collection imported successfully'); + }) + .catch((err) => { + console.error(err); + toast.error('An error occurred while importing the collection'); + }); + }; + + const handleToggleSearch = () => { + setShowSearch((prev) => !prev); + }; + + const handleSortCollections = () => { + let order; + switch (collectionSortOrder) { + case 'default': + order = 'alphabetical'; + break; + case 'alphabetical': + order = 'reverseAlphabetical'; + break; + case 'reverseAlphabetical': + order = 'default'; + break; + default: + order = 'default'; + break; + } + dispatch(sortCollections({ order })); + }; + + const getSortIcon = () => { + switch (collectionSortOrder) { + case 'alphabetical': + return IconSortDescendingLetters; + case 'reverseAlphabetical': + return IconArrowsSort; + default: + return IconSortAscendingLetters; + } + }; + + const getSortLabel = () => { + switch (collectionSortOrder) { + case 'alphabetical': + return 'Sort Z-A'; + case 'reverseAlphabetical': + return 'Clear sort'; + default: + return 'Sort A-Z'; + } + }; + + const selectAllCollectionsToClose = () => { + setCollectionsToClose(collections.map((c) => c.uid)); + }; + + const clearCollectionsToClose = () => { + setCollectionsToClose([]); + }; + + const handleOpenCollection = () => { + const options = {}; + if (activeWorkspace?.pathname) { + options.workspaceId = activeWorkspace.pathname; + } + + dispatch(openCollection(options)).catch((err) => { + toast.error('An error occurred while opening the collection'); + }); + }; + + 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(); + } + } + ]; + + const actionsDropdownItems = [ + { + id: 'sort', + leftSection: getSortIcon(), + label: getSortLabel(), + onClick: () => { + handleSortCollections(); + } + }, + { + id: 'close-all', + leftSection: IconSquareX, + label: 'Close all', + onClick: () => { + selectAllCollectionsToClose(); + } + } + ]; + + const sectionActions = ( + <> + + + + + + + + + + + + + + {collectionsToClose.length > 0 && ( + + )} + + ); + + return ( + <> + {createCollectionModalOpen && ( + setCreateCollectionModalOpen(false)} + /> + )} + {importCollectionModalOpen && ( + setImportCollectionModalOpen(false)} + handleSubmit={handleImportCollection} + /> + )} + {importCollectionLocationModalOpen && importData && ( + setImportCollectionLocationModalOpen(false)} + handleSubmit={handleImportCollectionLocation} + /> + )} + + + + + ); +}; + +export default CollectionsSection; diff --git a/packages/bruno-app/src/components/Sidebar/SidebarAccordionContext.js b/packages/bruno-app/src/components/Sidebar/SidebarAccordionContext.js new file mode 100644 index 000000000..d093753d6 --- /dev/null +++ b/packages/bruno-app/src/components/Sidebar/SidebarAccordionContext.js @@ -0,0 +1,61 @@ +import React, { createContext, useContext, useState, useCallback } from 'react'; + +const SidebarAccordionContext = createContext(); + +export const useSidebarAccordion = () => { + const context = useContext(SidebarAccordionContext); + if (!context) { + throw new Error('useSidebarAccordion must be used within SidebarAccordionProvider'); + } + return context; +}; + +export const SidebarAccordionProvider = ({ children, defaultExpanded = ['collections'] }) => { + const [expandedSections, setExpandedSections] = useState(new Set(defaultExpanded)); + + const toggleSection = useCallback((sectionId) => { + setExpandedSections((prev) => { + const newSet = new Set(prev); + if (newSet.has(sectionId)) { + newSet.delete(sectionId); + } else { + newSet.add(sectionId); + } + return newSet; + }); + }, []); + + const setSectionExpanded = useCallback((sectionId, expanded) => { + setExpandedSections((prev) => { + const newSet = new Set(prev); + if (expanded) { + newSet.add(sectionId); + } else { + newSet.delete(sectionId); + } + return newSet; + }); + }, []); + + const isExpanded = useCallback((sectionId) => { + return expandedSections.has(sectionId); + }, [expandedSections]); + + const getExpandedCount = useCallback(() => { + return expandedSections.size; + }, [expandedSections]); + + return ( + + {children} + + ); +}; diff --git a/packages/bruno-app/src/components/Sidebar/SidebarContent.js b/packages/bruno-app/src/components/Sidebar/SidebarContent.js new file mode 100644 index 000000000..8c913ffec --- /dev/null +++ b/packages/bruno-app/src/components/Sidebar/SidebarContent.js @@ -0,0 +1,69 @@ +import { useSidebarAccordion } from './SidebarAccordionContext'; + +/** + * Sections configuration + * + * All sections use the same generic accordion behavior with the class 'accordion-section-wrapper'. + * Layout behavior is fully automatic based on section order and expansion state: + * - Single expanded: When only one section is expanded, it fills available space + * - Multi-expanded: When multiple sections are expanded, they split space equally + * - Automatic pinning: Sections below an expanded section are automatically pinned to bottom + * + * To add a new section, simply add a new entry to this array: + * + * { + * id: 'my-section', // Unique identifier + * component: MySectionComponent, // React component to render + * getProps: (context) => ({ ... }) // Function to get props for component + * } + */ + +const SidebarContent = ({ sections }) => { + const { isExpanded, getExpandedCount } = useSidebarAccordion(); + + const expandedCount = getExpandedCount(); + + const getWrapperClassName = (section, sectionIndex) => { + const sectionExpanded = isExpanded(section.id); + // Use generic accordion-section-wrapper class for all sections + const classes = ['accordion-section-wrapper']; + + // Multi-expanded: when multiple sections are expanded + if (expandedCount > 1 && sectionExpanded) { + classes.push('multi-expanded'); + } + + // Single expanded wrapper behavior: when only one section is expanded, it fills space + if (sectionExpanded && expandedCount === 1) { + classes.push('single-expanded-wrapper'); + } + + // Automatic pinning: if section is not expanded and any section above it (earlier in array) is expanded + if (!sectionExpanded) { + // Check if any section before this one (earlier in array) is expanded + const hasExpandedAbove = sections.slice(0, sectionIndex).some((s) => isExpanded(s.id)); + if (hasExpandedAbove) { + classes.push('pinned-to-bottom'); + } + } + + return classes.join(' '); + }; + + return ( + <> + {sections.map((section, index) => { + const SectionComponent = section.component; + const wrapperClassName = getWrapperClassName(section, index); + + return ( +
+ +
+ ); + })} + + ); +}; + +export default SidebarContent; diff --git a/packages/bruno-app/src/components/Sidebar/SidebarSection/StyledWrapper.js b/packages/bruno-app/src/components/Sidebar/SidebarSection/StyledWrapper.js new file mode 100644 index 000000000..dff0ee5d8 --- /dev/null +++ b/packages/bruno-app/src/components/Sidebar/SidebarSection/StyledWrapper.js @@ -0,0 +1,116 @@ +import styled from 'styled-components'; + +const StyledWrapper = styled.div` + height: 100%; + + .sidebar-section { + display: flex; + flex-direction: column; + min-height: 0; + height: 100%; + + &.expanded { + flex: 1 1 0%; + min-height: 0; + } + + &:not(.expanded) { + flex: 0 0 auto; + } + + &.multi-expanded { + flex: 1 1 0%; + margin-bottom: 0; + } + } + + .section-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + padding: 6px 4px 6px 8px; + min-height: 28px; + height: 28px; + user-select: none; + transition: background-color 0.15s ease; + flex-shrink: 0; + border-bottom: 1px solid transparent; + + .section-header-left { + display: flex; + align-items: center; + gap: 6px; + flex: 1; + min-width: 0; + cursor: pointer; + + + &:hover { + .section-toggle { + display: flex; + } + + .section-toggle { + background: ${(props) => props.theme.dropdown.hoverBg}; + color: ${(props) => props.theme.text} !important; + } + + .section-icon { + display: none; + } + } + } + } + + .section-icon-wrapper { + width: 24px; + height: 24px; + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: center; + } + + .section-toggle { + display: none; + } + + .section-icon { + display: flex; + align-items: center; + justify-content: center; + width: 16px; + height: 16px; + color: ${(props) => props.theme.sidebar.muted}; + } + + .section-title { + color: ${(props) => props.theme.sidebar.color}; + font-size: 12px; + font-weight: 600; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .section-actions { + display: flex; + align-items: center; + gap: 1px; + flex-shrink: 0; + } + } + + .section-content { + display: flex; + flex-direction: column; + flex: 1 1 0%; + min-height: 0; + overflow-y: auto; + overflow-x: hidden; + position: relative; + } +`; + +export default StyledWrapper; diff --git a/packages/bruno-app/src/components/Sidebar/SidebarSection/index.js b/packages/bruno-app/src/components/Sidebar/SidebarSection/index.js new file mode 100644 index 000000000..c43e2ccf2 --- /dev/null +++ b/packages/bruno-app/src/components/Sidebar/SidebarSection/index.js @@ -0,0 +1,85 @@ +import { useState, useEffect, useRef } from 'react'; +import { IconChevronRight, IconChevronDown } from '@tabler/icons'; +import StyledWrapper from './StyledWrapper'; +import { useSidebarAccordion } from '../SidebarAccordionContext'; +import ActionIcon from 'ui/ActionIcon/index'; + +const SidebarSection = ({ + id, + title, + icon: Icon, + actions, + children, + className = '' +}) => { + const { isExpanded, setSectionExpanded, getExpandedCount } = useSidebarAccordion(); + const [localExpanded, setLocalExpanded] = useState(() => isExpanded(id)); + const sectionRef = useRef(null); + + // Sync with context + useEffect(() => { + const expanded = isExpanded(id); + setLocalExpanded(expanded); + }, [id, isExpanded]); + + const handleToggle = () => { + const newExpanded = !localExpanded; + setLocalExpanded(newExpanded); + setSectionExpanded(id, newExpanded); + }; + + const expandedCount = getExpandedCount(); + // Check if this is the only expanded section + const isOnlyExpanded = expandedCount === 1 && localExpanded; + + return ( + +
1 && localExpanded ? 'multi-expanded' : ''}`} + > +
+
+
{ + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); handleToggle(); + } + }} + > + + {localExpanded ? ( + + ) : ( + + )} + + {Icon && } +
+ {title} +
+ {actions && ( +
e.stopPropagation()} + > + {actions} +
+ )} +
+ {localExpanded && ( +
+ {children} +
+ )} +
+
+ ); +}; + +export default SidebarSection; diff --git a/packages/bruno-app/src/components/Sidebar/StyledWrapper.js b/packages/bruno-app/src/components/Sidebar/StyledWrapper.js index 40ab5a299..fd48f9c32 100644 --- a/packages/bruno-app/src/components/Sidebar/StyledWrapper.js +++ b/packages/bruno-app/src/components/Sidebar/StyledWrapper.js @@ -2,32 +2,104 @@ import styled from 'styled-components'; const Wrapper = styled.div` color: ${(props) => props.theme.sidebar.color}; + max-height: 100%; aside { background-color: ${(props) => props.theme.sidebar.bg}; overflow: hidden; - .collection-title { - line-height: 1.5; - .collection-dropdown { - .dropdown-icon { - display: none; - color: rgb(110 110 110); - } - } + .sidebar-sections-container { + display: flex; + flex-direction: column; + } - &:hover { - background: #f7f7f7; - .dropdown-icon { - display: flex; - } - } + .sidebar-sections { + min-height: 0; + display: flex; + flex-direction: column; + height: 100%; + } - div.tippy-box { - position: relative; - top: -0.625rem; + /* Expanded sections grow to fill available space but are constrained */ + .sidebar-section.expanded { + flex: 1 1 0%; + min-height: 0; + + .section-header { + border-bottom: 1px solid ${(props) => props.theme.sidebar.collection.item.hoverBg}; } } + + /* Single expanded section: add margin-bottom to push others down */ + .sidebar-section.single-expanded { + margin-bottom: auto !important; + flex: 1 1 0% !important; + min-height: 0; + max-height: 100%; + } + + /* Multiple expanded sections: equal split, no margin-bottom */ + .sidebar-section.multi-expanded { + margin-bottom: 0; + flex: 1 1 0% !important; + + min-height: 0; + overflow: hidden; + max-height: 100%; + } + + /* Collapsed sections only take header height */ + .sidebar-section:not(.expanded) { + flex: 0 0 auto; + } + + /* Always push bottom accordions wrapper to the bottom */ + .bottom-accordions-wrapper { + display: flex; + flex-direction: column; + flex: 0 0 auto; + } + + /* Generic accordion section wrapper - applies to all accordion sections */ + .accordion-section-wrapper { + display: flex; + flex-direction: column; + min-height: 0; + position: relative; + overflow: visible; + } + + /* Add border-top to all accordion items except the first child */ + .accordion-section-wrapper:not(:first-child) { + border-top: 1px solid ${(props) => props.theme.sidebar.collection.item.hoverBg}; + } + + /* When a section is single expanded, wrapper should fill space but respect pinned sections */ + .accordion-section-wrapper.single-expanded-wrapper { + flex: 1 1 0% !important; + min-height: 0; + overflow: hidden; + } + + /* Normal flow: sections not pinned and not multi-expanded */ + .accordion-section-wrapper:not(.pinned-to-bottom):not(.multi-expanded) { + flex: 0 0 auto; + } + + /* When a section is pinned to bottom */ + .accordion-section-wrapper.pinned-to-bottom { + flex: 0 0 auto; + margin-top: auto; + } + + /* When multiple sections are expanded, split space equally */ + .accordion-section-wrapper.multi-expanded { + flex: 1 1 0% !important; + min-height: 0; + margin-top: 0 !important; + height: auto !important; + } + } div.sidebar-drag-handle { diff --git a/packages/bruno-app/src/components/Sidebar/index.js b/packages/bruno-app/src/components/Sidebar/index.js index 2071909ea..050d1a456 100644 --- a/packages/bruno-app/src/components/Sidebar/index.js +++ b/packages/bruno-app/src/components/Sidebar/index.js @@ -1,20 +1,32 @@ -import SidebarHeader from './SidebarHeader'; -import Collections from './Collections'; +import { SidebarAccordionProvider } from './SidebarAccordionContext'; +import SidebarContent from './SidebarContent'; import StyledWrapper from './StyledWrapper'; import { useState, useEffect, useRef } from 'react'; import { useSelector, useDispatch } from 'react-redux'; import { updateLeftSidebarWidth, updateIsDragging } from 'providers/ReduxStore/slices/app'; +import CollectionsSection from './Sections/CollectionsSection/index'; +import ApiSpecsSection from './Sections/ApiSpecsSection/index'; const MIN_LEFT_SIDEBAR_WIDTH = 220; const MAX_LEFT_SIDEBAR_WIDTH = 600; +const SIDEBAR_SECTIONS = [ + { + id: 'collections', + component: CollectionsSection + }, + { + id: 'api-specs', + component: ApiSpecsSection + } +]; + const Sidebar = () => { const leftSidebarWidth = useSelector((state) => state.app.leftSidebarWidth); const sidebarCollapsed = useSelector((state) => state.app.sidebarCollapsed); const [asideWidth, setAsideWidth] = useState(leftSidebarWidth); const lastWidthRef = useRef(leftSidebarWidth); - const [showSearch, setShowSearch] = useState(false); const dispatch = useDispatch(); const [dragging, setDragging] = useState(false); @@ -77,26 +89,29 @@ const Sidebar = () => { }, [leftSidebarWidth]); return ( - - + - {!sidebarCollapsed && ( -
-
-
- )} - + {!sidebarCollapsed && ( +
+
+
+ )} + + ); };