Redesign dropdowns (#6235)

* redesign dropdowns

* fix: colors

---------

Co-authored-by: Anoop M D <anoop@usebruno.com>
This commit is contained in:
naman-bruno
2025-11-27 22:20:51 +05:30
committed by GitHub
parent 59514127d5
commit 7ee366eb81
5 changed files with 289 additions and 81 deletions

View File

@@ -8,44 +8,66 @@ const Wrapper = styled.div`
}
.tippy-box {
min-width: 135px;
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.dropdown.shadow};
border-radius: 3px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.15), 0 0 0 1px rgba(0, 0, 0, 0.05);
border-radius: 10px;
max-height: 90vh;
overflow-y: auto;
max-width: unset !important;
padding: 0.25rem;
.tippy-content {
padding-left: 0;
padding-right: 0;
padding-top: 0.25rem;
padding-bottom: 0.25rem;
padding-top: 0;
padding-bottom: 0;
.label-item {
display: flex;
align-items: center;
padding: 0.35rem 0.6rem;
background-color: ${(props) => props.theme.dropdown.labelBg};
padding: 0.375rem 0.625rem 0.25rem 0.625rem;
font-size: 0.6875rem;
font-weight: 600;
text-transform: uppercase;
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;
padding: 0.35rem 0.6rem;
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;
.icon {
.dropdown-icon {
color: ${(props) => props.theme.colors.text.yellow} !important;
}
}
.icon {
.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;
}
&:hover:not(:disabled) {
@@ -54,13 +76,39 @@ const Wrapper = styled.div`
&:disabled {
cursor: not-allowed;
color: gray;
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-separator {
height: 1px;
background-color: ${(props) => props.theme.dropdown.separator};
margin: 0.25rem 0;
}
}
}
`;

View File

@@ -1,6 +1,15 @@
import React, { useRef, forwardRef } from 'react';
import get from 'lodash/get';
import { IconCaretDown } from '@tabler/icons';
import {
IconCaretDown,
IconForms,
IconBraces,
IconCode,
IconFileText,
IconDatabase,
IconFile,
IconX
} from '@tabler/icons';
import Dropdown from 'components/Dropdown';
import { useDispatch } from 'react-redux';
import { updateRequestBodyMode } from 'providers/ReduxStore/slices/collections';
@@ -70,7 +79,7 @@ const RequestBodyMode = ({ item, collection }) => {
<StyledWrapper>
<div className="inline-flex items-center cursor-pointer body-mode-selector">
<Dropdown onCreate={onDropdownCreate} icon={<Icon />} placement="bottom-end">
<div className="label-item font-medium">Form</div>
<div className="label-item">Form</div>
<div
className="dropdown-item"
onClick={() => {
@@ -78,6 +87,9 @@ const RequestBodyMode = ({ item, collection }) => {
onModeChange('multipartForm');
}}
>
<span className="dropdown-icon">
<IconForms size={16} strokeWidth={2} />
</span>
Multipart Form
</div>
<div
@@ -87,9 +99,12 @@ const RequestBodyMode = ({ item, collection }) => {
onModeChange('formUrlEncoded');
}}
>
<span className="dropdown-icon">
<IconForms size={16} strokeWidth={2} />
</span>
Form URL Encoded
</div>
<div className="label-item font-medium">Raw</div>
<div className="label-item">Raw</div>
<div
className="dropdown-item"
onClick={() => {
@@ -97,6 +112,9 @@ const RequestBodyMode = ({ item, collection }) => {
onModeChange('json');
}}
>
<span className="dropdown-icon">
<IconBraces size={16} strokeWidth={2} />
</span>
JSON
</div>
<div
@@ -106,6 +124,9 @@ const RequestBodyMode = ({ item, collection }) => {
onModeChange('xml');
}}
>
<span className="dropdown-icon">
<IconCode size={16} strokeWidth={2} />
</span>
XML
</div>
<div
@@ -115,6 +136,9 @@ const RequestBodyMode = ({ item, collection }) => {
onModeChange('text');
}}
>
<span className="dropdown-icon">
<IconFileText size={16} strokeWidth={2} />
</span>
TEXT
</div>
<div
@@ -124,9 +148,12 @@ const RequestBodyMode = ({ item, collection }) => {
onModeChange('sparql');
}}
>
<span className="dropdown-icon">
<IconDatabase size={16} strokeWidth={2} />
</span>
SPARQL
</div>
<div className="label-item font-medium">Other</div>
<div className="label-item">Other</div>
<div
className="dropdown-item"
onClick={() => {
@@ -134,6 +161,9 @@ const RequestBodyMode = ({ item, collection }) => {
onModeChange('file');
}}
>
<span className="dropdown-icon">
<IconFile size={16} strokeWidth={2} />
</span>
File / Binary
</div>
<div
@@ -143,6 +173,9 @@ const RequestBodyMode = ({ item, collection }) => {
onModeChange('none');
}}
>
<span className="dropdown-icon">
<IconX size={16} strokeWidth={2} />
</span>
No Body
</div>
</Dropdown>

View File

@@ -4,7 +4,22 @@ import range from 'lodash/range';
import filter from 'lodash/filter';
import classnames from 'classnames';
import { useDrag, useDrop } from 'react-dnd';
import { IconChevronRight, IconDots } from '@tabler/icons';
import {
IconChevronRight,
IconDots,
IconFilePlus,
IconFolderPlus,
IconPlayerPlay,
IconEdit,
IconCopy,
IconClipboard,
IconCode,
IconPhoto,
IconFolder,
IconTrash,
IconSettings,
IconInfoCircle
} from '@tabler/icons';
import { useSelector, useDispatch } from 'react-redux';
import { addTab, focusTab, makeTabPermanent } from 'providers/ReduxStore/slices/tabs';
import { handleCollectionItemDrop, sendRequest, showInFolder, pasteItem, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
@@ -489,6 +504,18 @@ const CollectionItem = ({ item, collectionUid, collectionPathname, searchText })
</div>
<div className="menu-icon pr-2">
<Dropdown onCreate={onDropdownCreate} icon={<MenuIcon />} placement="bottom-start">
<div
className="dropdown-item"
onClick={(e) => {
dropdownTippyRef.current.hide();
setItemInfoModalOpen(true);
}}
>
<span className="dropdown-icon">
<IconInfoCircle size={16} strokeWidth={2} />
</span>
Info
</div>
{isFolder && (
<>
<div
@@ -498,6 +525,9 @@ const CollectionItem = ({ item, collectionUid, collectionPathname, searchText })
setNewRequestModalOpen(true);
}}
>
<span className="dropdown-icon">
<IconFilePlus size={16} strokeWidth={2} />
</span>
New Request
</div>
<div
@@ -507,6 +537,9 @@ const CollectionItem = ({ item, collectionUid, collectionPathname, searchText })
setNewFolderModalOpen(true);
}}
>
<span className="dropdown-icon">
<IconFolderPlus size={16} strokeWidth={2} />
</span>
New Folder
</div>
<div
@@ -516,19 +549,13 @@ const CollectionItem = ({ item, collectionUid, collectionPathname, searchText })
setRunCollectionModalOpen(true);
}}
>
<span className="dropdown-icon">
<IconPlayerPlay size={16} strokeWidth={2} />
</span>
Run
</div>
</>
)}
<div
className="dropdown-item"
onClick={(e) => {
dropdownTippyRef.current.hide();
setRenameItemModalOpen(true);
}}
>
Rename
</div>
<div
className="dropdown-item"
onClick={(e) => {
@@ -536,6 +563,9 @@ const CollectionItem = ({ item, collectionUid, collectionPathname, searchText })
setCloneItemModalOpen(true);
}}
>
<span className="dropdown-icon">
<IconCopy size={16} strokeWidth={2} />
</span>
Clone
</div>
{!isFolder && (
@@ -543,6 +573,9 @@ const CollectionItem = ({ item, collectionUid, collectionPathname, searchText })
className="dropdown-item"
onClick={handleCopyRequest}
>
<span className="dropdown-icon">
<IconCopy size={16} strokeWidth={2} />
</span>
Copy
</div>
)}
@@ -551,21 +584,36 @@ const CollectionItem = ({ item, collectionUid, collectionPathname, searchText })
className="dropdown-item"
onClick={handlePasteRequest}
>
<span className="dropdown-icon">
<IconClipboard size={16} strokeWidth={2} />
</span>
Paste
</div>
)}
{!isFolder && (
<div
className="dropdown-item"
onClick={(e) => {
dropdownTippyRef.current.hide();
handleClick(null);
handleRun();
}}
>
Run
</div>
)}
<div
className="dropdown-item"
onClick={(e) => {
dropdownTippyRef.current.hide();
setRenameItemModalOpen(true);
}}
>
<span className="dropdown-icon">
<IconEdit size={16} strokeWidth={2} />
</span>
Rename
</div>
<div
className="dropdown-item"
onClick={(e) => {
dropdownTippyRef.current.hide();
handleShowInFolder();
}}
>
<span className="dropdown-icon">
<IconFolder size={16} strokeWidth={2} />
</span>
Show in Folder
</div>
{!isFolder && (item.type === 'http-request' || item.type === 'graphql-request') && (
<div
className="dropdown-item"
@@ -573,6 +621,9 @@ const CollectionItem = ({ item, collectionUid, collectionPathname, searchText })
handleGenerateCode(e);
}}
>
<span className="dropdown-icon">
<IconCode size={16} strokeWidth={2} />
</span>
Generate Code
</div>
)}
@@ -584,27 +635,13 @@ const CollectionItem = ({ item, collectionUid, collectionPathname, searchText })
setCreateExampleModalOpen(true);
}}
>
<span className="dropdown-icon">
<IconPhoto size={16} strokeWidth={2} />
</span>
Create Example
</div>
)}
<div
className="dropdown-item"
onClick={(e) => {
dropdownTippyRef.current.hide();
handleShowInFolder();
}}
>
Show in Folder
</div>
<div
className="dropdown-item delete-item"
onClick={(e) => {
dropdownTippyRef.current.hide();
setDeleteItemModalOpen(true);
}}
>
Delete
</div>
<div className="dropdown-separator"></div>
{isFolder && (
<div
className="dropdown-item"
@@ -613,17 +650,23 @@ const CollectionItem = ({ item, collectionUid, collectionPathname, searchText })
viewFolderSettings();
}}
>
<span className="dropdown-icon">
<IconSettings size={16} strokeWidth={2} />
</span>
Settings
</div>
)}
<div
className="dropdown-item item-info"
className="dropdown-item delete-item"
onClick={(e) => {
dropdownTippyRef.current.hide();
setItemInfoModalOpen(true);
setDeleteItemModalOpen(true);
}}
>
Info
<span className="dropdown-icon">
<IconTrash size={16} strokeWidth={2} />
</span>
Delete
</div>
</Dropdown>
</div>

View File

@@ -4,7 +4,21 @@ import classnames from 'classnames';
import { uuid } from 'utils/common';
import filter from 'lodash/filter';
import { useDrop, useDrag } from 'react-dnd';
import { IconChevronRight, IconDots, IconLoader2 } from '@tabler/icons';
import {
IconChevronRight,
IconDots,
IconLoader2,
IconFilePlus,
IconFolderPlus,
IconCopy,
IconClipboard,
IconPlayerPlay,
IconEdit,
IconShare,
IconFoldDown,
IconX,
IconSettings
} from '@tabler/icons';
import Dropdown from 'components/Dropdown';
import { toggleCollection, collapseFullCollection } from 'providers/ReduxStore/slices/collections';
import { mountCollection, moveCollectionAndPersist, handleCollectionItemDrop, pasteItem } from 'providers/ReduxStore/slices/collections/actions';
@@ -277,6 +291,9 @@ const Collection = ({ collection, searchText }) => {
setShowNewRequestModal(true);
}}
>
<span className="dropdown-icon">
<IconFilePlus size={16} strokeWidth={2} />
</span>
New Request
</div>
<div
@@ -286,8 +303,24 @@ const Collection = ({ collection, searchText }) => {
setShowNewFolderModal(true);
}}
>
<span className="dropdown-icon">
<IconFolderPlus size={16} strokeWidth={2} />
</span>
New Folder
</div>
<div
className="dropdown-item"
onClick={(_e) => {
menuDropdownTippyRef.current.hide();
ensureCollectionIsMounted();
handleRun();
}}
>
<span className="dropdown-icon">
<IconPlayerPlay size={16} strokeWidth={2} />
</span>
Run
</div>
<div
className="dropdown-item"
data-testid="clone-collection"
@@ -296,6 +329,9 @@ const Collection = ({ collection, searchText }) => {
setShowCloneCollectionModalOpen(true);
}}
>
<span className="dropdown-icon">
<IconCopy size={16} strokeWidth={2} />
</span>
Clone
</div>
{hasCopiedItems && (
@@ -303,19 +339,12 @@ const Collection = ({ collection, searchText }) => {
className="dropdown-item"
onClick={handlePasteRequest}
>
<span className="dropdown-icon">
<IconClipboard size={16} strokeWidth={2} />
</span>
Paste
</div>
)}
<div
className="dropdown-item"
onClick={(_e) => {
menuDropdownTippyRef.current.hide();
ensureCollectionIsMounted();
handleRun();
}}
>
Run
</div>
<div
className="dropdown-item"
onClick={(_e) => {
@@ -323,6 +352,9 @@ const Collection = ({ collection, searchText }) => {
setShowRenameCollectionModal(true);
}}
>
<span className="dropdown-icon">
<IconEdit size={16} strokeWidth={2} />
</span>
Rename
</div>
<div
@@ -333,6 +365,9 @@ const Collection = ({ collection, searchText }) => {
setShowShareCollectionModal(true);
}}
>
<span className="dropdown-icon">
<IconShare size={16} strokeWidth={2} />
</span>
Share
</div>
<div
@@ -342,8 +377,24 @@ const Collection = ({ collection, searchText }) => {
handleCollapseFullCollection();
}}
>
<span className="dropdown-icon">
<IconFoldDown size={16} strokeWidth={2} />
</span>
Collapse
</div>
<div className="dropdown-separator"></div>
<div
className="dropdown-item"
onClick={(_e) => {
menuDropdownTippyRef.current.hide();
viewCollectionSettings();
}}
>
<span className="dropdown-icon">
<IconSettings size={16} strokeWidth={2} />
</span>
Settings
</div>
<div
className="dropdown-item"
onClick={(_e) => {
@@ -351,17 +402,11 @@ const Collection = ({ collection, searchText }) => {
setShowRemoveCollectionModal(true);
}}
>
<span className="dropdown-icon">
<IconX size={16} strokeWidth={2} />
</span>
Close
</div>
<div
className="dropdown-item"
onClick={(_e) => {
menuDropdownTippyRef.current.hide();
viewCollectionSettings();
}}
>
Settings
</div>
</Dropdown>
</div>
</div>

View File

@@ -5,7 +5,7 @@ import CreateCollection from '../CreateCollection';
import ImportCollection from 'components/Sidebar/ImportCollection';
import ImportCollectionLocation from 'components/Sidebar/ImportCollectionLocation';
import { IconDots } from '@tabler/icons';
import { IconDots, IconPlus, IconFolder, IconDownload, IconDeviceDesktop } from '@tabler/icons';
import { useState, forwardRef, useRef } from 'react';
import { useDispatch } from 'react-redux';
import { showHomePage } from 'providers/ReduxStore/slices/app';
@@ -90,6 +90,7 @@ const TitleBar = () => {
</button>
<div className="collection-dropdown flex flex-grow items-center justify-end">
<Dropdown onCreate={onMenuDropdownCreate} icon={<MenuIcon />} placement="bottom-start">
<div className="label-item">Collections</div>
<div
className="dropdown-item"
onClick={(e) => {
@@ -97,6 +98,9 @@ const TitleBar = () => {
menuDropdownTippyRef.current.hide();
}}
>
<span className="dropdown-icon">
<IconPlus size={16} strokeWidth={2} />
</span>
Create Collection
</div>
<div
@@ -106,7 +110,10 @@ const TitleBar = () => {
menuDropdownTippyRef.current.hide();
}}
>
Open Collection
<span className="dropdown-icon">
<IconFolder size={16} strokeWidth={2} />
</span>
Open
</div>
<div
className="dropdown-item"
@@ -115,8 +122,37 @@ const TitleBar = () => {
setImportCollectionModalOpen(true);
}}
>
Import Collection
<span className="dropdown-icon">
<IconDownload size={16} strokeWidth={2} />
</span>
Import
</div>
<div className="label-item">API Specs</div>
<div
className="dropdown-item"
onClick={(e) => {
setCreateCollectionModalOpen(true);
menuDropdownTippyRef.current.hide();
}}
>
<span className="dropdown-icon">
<IconPlus size={16} strokeWidth={2} />
</span>
Create Collection
</div>
<div
className="dropdown-item"
onClick={(e) => {
handleOpenCollection();
menuDropdownTippyRef.current.hide();
}}
>
<span className="dropdown-icon">
<IconFolder size={16} strokeWidth={2} />
</span>
Open
</div>
<div className="dropdown-separator"></div>
<div
className="dropdown-item"
onClick={(e) => {
@@ -124,6 +160,9 @@ const TitleBar = () => {
openDevTools();
}}
>
<span className="dropdown-icon">
<IconDeviceDesktop size={16} strokeWidth={2} />
</span>
Devtools
</div>
</Dropdown>