mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-23 12:45:38 +00:00
fix: collection settings access, UI overflow fixes, and auto-focus URL bar (#7861)
* fix: collection settings access, UI overflow fixes, and auto-focus URL bar - Move collection icon outside dropdown; clicking it opens collection settings/overview, clicking the name opens the switcher dropdown - Auto-focus URL bar when creating a transient request (#2919) - Fix long collection/folder name overflow with ellipsis truncation - Reduce dropdown width and truncate large collection names - Simplify breadcrumb collapse: show collection name and last folder, collapse middle items into a dropdown - Fix modal width to prevent shrinking with short collection names - Show "Create Collection" option when saving a draft with zero collections - Use IconBox consistently for collection icons * Replace Chevron component with IconChevronRight --------- Co-authored-by: Chirag Chandrashekhar <cchirag85@gmail.com> Co-authored-by: Sid <siddharth@usebruno.com>
This commit is contained in:
committed by
GitHub
parent
8269d51df4
commit
118ba801aa
@@ -8,10 +8,12 @@ const Overview = ({ collection }) => {
|
||||
return (
|
||||
<div className="h-full">
|
||||
<div className="grid grid-cols-5 gap-5 h-full">
|
||||
<div className="col-span-2">
|
||||
<div className="text-lg font-medium flex items-center gap-2">
|
||||
<IconBox size={20} stroke={1.5} />
|
||||
{collection?.name}
|
||||
<div className="col-span-2 overflow-clip text-ellipsis">
|
||||
<div className="flex gap-2 items-center min-w-0">
|
||||
<IconBox size={20} stroke={1.5} className="flex-shrink-0" />
|
||||
<span className="overflow-hidden text-lg font-medium whitespace-nowrap text-ellipsis">
|
||||
{collection?.name}
|
||||
</span>
|
||||
</div>
|
||||
<Info collection={collection} />
|
||||
<RequestsNotLoaded collection={collection} />
|
||||
|
||||
@@ -179,6 +179,17 @@ const Wrapper = styled.div`
|
||||
}
|
||||
}
|
||||
|
||||
.breadcrumb-collapsed-dropdown {
|
||||
max-width: 250px;
|
||||
}
|
||||
|
||||
.breadcrumb-collapsed-item {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.dropdown-separator {
|
||||
height: 1px;
|
||||
background-color: ${(props) => props.theme.dropdown.separator};
|
||||
|
||||
@@ -38,6 +38,12 @@ const QueryUrl = ({ item, collection, handleRun }) => {
|
||||
const [generateCodeItemModalOpen, setGenerateCodeItemModalOpen] = useState(false);
|
||||
const hasChanges = useMemo(() => hasRequestChanges(item), [item]);
|
||||
|
||||
useEffect(() => {
|
||||
if (item.isTransient && !url && editorRef.current?.editor) {
|
||||
setTimeout(() => editorRef.current?.editor?.focus(), 0);
|
||||
}
|
||||
}, [item.uid]);
|
||||
|
||||
const onSave = () => {
|
||||
dispatch(saveRequest(item.uid, collection.uid));
|
||||
};
|
||||
|
||||
@@ -25,7 +25,7 @@ const StyledWrapper = styled.div`
|
||||
}
|
||||
|
||||
.switcher-name {
|
||||
max-width: 300px;
|
||||
max-width: 124px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
@@ -151,6 +151,14 @@ const StyledWrapper = styled.div`
|
||||
color: ${(props) => props.theme.colors.text.danger};
|
||||
margin-left: 8px;
|
||||
}
|
||||
.display-icon{
|
||||
padding: 4px;
|
||||
box-sizing: content-box;
|
||||
&:hover {
|
||||
background: ${(props) => props.theme.sidebar.collection.item.hoverBg};
|
||||
border-radius: ${(props) => props.theme.border.radius.sm}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
|
||||
@@ -393,6 +393,16 @@ const CollectionHeader = ({ collection, isScratchCollection }) => {
|
||||
&& currentWorkspace.type !== 'default'
|
||||
&& !isRenamingWorkspace;
|
||||
|
||||
const handleDisplayIconClick = (e) => {
|
||||
const uid = isScratchCollection ? `${collection.uid}-overview` : collection.uid;
|
||||
const type = isScratchCollection ? 'workspaceOverview' : 'collection-settings';
|
||||
dispatch(addTab({
|
||||
uid: uid,
|
||||
collectionUid: collection.uid,
|
||||
type: type
|
||||
}));
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper>
|
||||
{closeWorkspaceModalOpen && currentWorkspace?.uid && (
|
||||
@@ -411,7 +421,7 @@ const CollectionHeader = ({ collection, isScratchCollection }) => {
|
||||
<div className="collection-switcher">
|
||||
{isRenamingWorkspace ? (
|
||||
<div className="workspace-rename-container" ref={workspaceRenameContainerRef}>
|
||||
<DisplayIcon size={18} strokeWidth={1.5} />
|
||||
<DisplayIcon size={18} strokeWidth={1.5} className="cursor-pointer display-icon" />
|
||||
<div className="workspace-input-wrapper">
|
||||
<input
|
||||
ref={workspaceNameInputRef}
|
||||
@@ -459,69 +469,71 @@ const CollectionHeader = ({ collection, isScratchCollection }) => {
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<Dropdown
|
||||
placement="bottom-start"
|
||||
onCreate={onSwitcherCreate}
|
||||
appendTo={() => document.body}
|
||||
icon={(
|
||||
<button className="switcher-trigger">
|
||||
<DisplayIcon size={18} strokeWidth={1.5} />
|
||||
<span className={classNames('switcher-name', { 'scratch-collection': isScratchCollection })}>{displayName}</span>
|
||||
<IconChevronDown size={14} strokeWidth={1.5} className="chevron" />
|
||||
</button>
|
||||
)}
|
||||
>
|
||||
{/* Workspace section */}
|
||||
{currentWorkspace && (
|
||||
<>
|
||||
<div className="label-item">Workspace</div>
|
||||
<div
|
||||
className={classNames('dropdown-item', {
|
||||
'dropdown-item-active': isScratchCollection
|
||||
})}
|
||||
onClick={() => handleSwitchToWorkspace(currentWorkspace.uid)}
|
||||
>
|
||||
<div className="dropdown-icon">
|
||||
<IconCategory size={16} strokeWidth={1.5} />
|
||||
</div>
|
||||
<span className="dropdown-label">
|
||||
{currentWorkspace.name || 'Untitled Workspace'}
|
||||
</span>
|
||||
{workspaceTabCount > 0 && (
|
||||
<span className="dropdown-tab-count">{workspaceTabCount}</span>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Collections section */}
|
||||
{mountedCollections.length > 0 && (
|
||||
<>
|
||||
<div className="dropdown-separator" />
|
||||
<div className="label-item">Collections</div>
|
||||
{mountedCollections.map((col) => {
|
||||
const colTabCount = getTabCount(col.uid);
|
||||
return (
|
||||
<div className="flex flex-row justify-center items-center gap-x-1">
|
||||
<DisplayIcon size={18} strokeWidth={1.5} className="cursor-pointer display-icon" onClick={handleDisplayIconClick} />
|
||||
<Dropdown
|
||||
placement="bottom-start"
|
||||
onCreate={onSwitcherCreate}
|
||||
appendTo={() => document.body}
|
||||
icon={(
|
||||
<button className="switcher-trigger">
|
||||
<span className={classNames('switcher-name', { 'scratch-collection': isScratchCollection })}>{displayName}</span>
|
||||
<IconChevronDown size={14} strokeWidth={1.5} className="chevron" />
|
||||
</button>
|
||||
)}
|
||||
>
|
||||
<div className="max-w-124 overflow-hidden">
|
||||
{currentWorkspace && (
|
||||
<>
|
||||
<div className="label-item">Workspace</div>
|
||||
<div
|
||||
key={col.uid}
|
||||
className={classNames('dropdown-item', {
|
||||
'dropdown-item-active': !isScratchCollection && collection.uid === col.uid
|
||||
'dropdown-item-active': isScratchCollection
|
||||
})}
|
||||
onClick={() => handleSwitchToCollection(col)}
|
||||
onClick={() => handleSwitchToWorkspace(currentWorkspace.uid)}
|
||||
>
|
||||
<div className="dropdown-icon">
|
||||
<IconBox size={16} strokeWidth={1.5} />
|
||||
<IconCategory size={16} strokeWidth={1.5} />
|
||||
</div>
|
||||
<span className="dropdown-label">{col.name || 'Untitled Collection'}</span>
|
||||
{colTabCount > 0 && (
|
||||
<span className="dropdown-tab-count">{colTabCount}</span>
|
||||
<span className="dropdown-label collection-header-dropdown-label">
|
||||
{currentWorkspace.name || 'Untitled Workspace'}
|
||||
</span>
|
||||
{workspaceTabCount > 0 && (
|
||||
<span className="dropdown-tab-count">{workspaceTabCount}</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
</Dropdown>
|
||||
</>
|
||||
)}
|
||||
|
||||
{mountedCollections.length > 0 && (
|
||||
<>
|
||||
<div className="dropdown-separator" />
|
||||
<div className="label-item">Collections</div>
|
||||
{mountedCollections.map((col) => {
|
||||
const colTabCount = getTabCount(col.uid);
|
||||
return (
|
||||
<div
|
||||
key={col.uid}
|
||||
className={classNames('dropdown-item', {
|
||||
'dropdown-item-active': !isScratchCollection && collection.uid === col.uid
|
||||
})}
|
||||
onClick={() => handleSwitchToCollection(col)}
|
||||
>
|
||||
<div className="dropdown-icon">
|
||||
<IconBox size={16} strokeWidth={1.5} />
|
||||
</div>
|
||||
<span className="dropdown-label collection-header-dropdown-label">{col.name || 'Untitled Collection'}</span>
|
||||
{colTabCount > 0 && (
|
||||
<span className="dropdown-tab-count">{colTabCount}</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Dropdown>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Workspace actions dropdown */}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useMemo, useCallback, memo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { IconDatabase, IconLoader2 } from '@tabler/icons';
|
||||
import { IconBox, IconLoader2 } from '@tabler/icons';
|
||||
import { areItemsLoading } from 'utils/collections';
|
||||
|
||||
const CollectionListItem = memo(({ collectionUid, collectionPath, collectionName, isSelected, onSelect }) => {
|
||||
@@ -26,7 +26,7 @@ const CollectionListItem = memo(({ collectionUid, collectionPath, collectionName
|
||||
onClick={handleClick}
|
||||
>
|
||||
<div className="collection-item-content">
|
||||
<IconDatabase size={16} strokeWidth={1.5} />
|
||||
<IconBox size={16} strokeWidth={1.5} />
|
||||
<span className="collection-item-name">{collectionName}</span>
|
||||
</div>
|
||||
{isLoading && (
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import { IconChevronRight } from '@tabler/icons';
|
||||
import { IconChevronRight, IconDots } from '@tabler/icons';
|
||||
import Dropdown from 'components/Dropdown';
|
||||
|
||||
const FolderBreadcrumbs = ({
|
||||
collectionName,
|
||||
@@ -8,30 +9,64 @@ const FolderBreadcrumbs = ({
|
||||
onNavigateToRoot,
|
||||
onNavigateToBreadcrumb
|
||||
}) => {
|
||||
const collapsed = breadcrumbs.length > 1 ? breadcrumbs.slice(0, -1) : [];
|
||||
const last = breadcrumbs.length > 0 ? breadcrumbs[breadcrumbs.length - 1] : null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="breadcrumb-container">
|
||||
<span
|
||||
className={!isAtRoot ? 'collection-name-breadcrumb' : ''}
|
||||
className={`breadcrumb-collection-name ${!isAtRoot ? 'collection-name-breadcrumb' : ''}`}
|
||||
onClick={!isAtRoot ? onNavigateToRoot : undefined}
|
||||
title={collectionName}
|
||||
>
|
||||
{collectionName}
|
||||
</span>
|
||||
{breadcrumbs.map((breadcrumb, index) => (
|
||||
<React.Fragment key={breadcrumb.uid}>
|
||||
|
||||
{collapsed.length > 0 && (
|
||||
<>
|
||||
<IconChevronRight size={16} strokeWidth={1.5} className="collection-name-chevron" />
|
||||
<Dropdown
|
||||
placement="bottom-start"
|
||||
icon={(
|
||||
<span className="breadcrumb-ellipsis-btn">
|
||||
<IconDots size={16} strokeWidth={2} />
|
||||
</span>
|
||||
)}
|
||||
>
|
||||
<div className="breadcrumb-collapsed-dropdown">
|
||||
{collapsed.map((breadcrumb, i) => (
|
||||
<div
|
||||
key={breadcrumb.uid}
|
||||
className="dropdown-item breadcrumb-collapsed-item"
|
||||
onClick={() => onNavigateToBreadcrumb(i)}
|
||||
title={breadcrumb.name}
|
||||
>
|
||||
{breadcrumb.name}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Dropdown>
|
||||
</>
|
||||
)}
|
||||
|
||||
{last && (
|
||||
<>
|
||||
<IconChevronRight size={16} strokeWidth={1.5} className="collection-name-chevron" />
|
||||
<span
|
||||
className="collection-name-breadcrumb"
|
||||
className="collection-name-breadcrumb breadcrumb-last"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onNavigateToBreadcrumb(index);
|
||||
onNavigateToBreadcrumb(breadcrumbs.length - 1);
|
||||
}}
|
||||
title={last.name}
|
||||
>
|
||||
{breadcrumb.name}
|
||||
{last.name}
|
||||
</span>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
|
||||
{isAtRoot && <IconChevronRight size={16} strokeWidth={1.5} className="collection-name-chevron" />}
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
.bruno-modal-card.modal-sm {
|
||||
width: 500px;
|
||||
}
|
||||
|
||||
.save-request-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -54,6 +58,7 @@ const StyledWrapper = styled.div`
|
||||
font-size: 14px;
|
||||
margin-bottom: 12px;
|
||||
color: ${(props) => props.theme.colors.text.muted};
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.collection-name-clickable {
|
||||
@@ -66,6 +71,49 @@ const StyledWrapper = styled.div`
|
||||
|
||||
.collection-name-chevron {
|
||||
margin: 0 4px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.breadcrumb-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.breadcrumb-collection-name,
|
||||
.breadcrumb-last {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
min-width: 40px;
|
||||
flex: 0 1 auto;
|
||||
}
|
||||
|
||||
.breadcrumb-ellipsis-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
padding: 2px 4px;
|
||||
border-radius: ${(props) => props.theme.border.radius.sm};
|
||||
flex-shrink: 0;
|
||||
color: ${(props) => props.theme.colors.text.yellow};
|
||||
|
||||
&:hover {
|
||||
background-color: ${(props) => props.theme.plainGrid.hoverBg};
|
||||
}
|
||||
}
|
||||
|
||||
.breadcrumb-dropdown {
|
||||
min-width: 120px;
|
||||
max-width: 250px;
|
||||
|
||||
.dropdown-item {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.search-container {
|
||||
@@ -114,10 +162,19 @@ const StyledWrapper = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
|
||||
svg {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.folder-item-name {
|
||||
color: ${(props) => props.theme.text};
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.folder-empty-state {
|
||||
@@ -157,6 +214,7 @@ const StyledWrapper = styled.div`
|
||||
border-radius: ${(props) => props.theme.border.radius.sm};
|
||||
user-select: none;
|
||||
border: 1px solid ${(props) => props.theme.border.border1};
|
||||
overflow: hidden;
|
||||
|
||||
&:hover {
|
||||
background-color: ${(props) => props.theme.plainGrid.hoverBg};
|
||||
@@ -168,11 +226,20 @@ const StyledWrapper = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
|
||||
svg {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.collection-item-name {
|
||||
color: ${(props) => props.theme.text};
|
||||
font-weight: 500;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.collection-empty-state {
|
||||
|
||||
@@ -361,7 +361,7 @@ const SaveTransientRequest = ({ item: itemProp, collection: collectionProp, isOp
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<Modal
|
||||
size="md"
|
||||
size="sm"
|
||||
title={isSelectingCollection ? 'Select Collection' : 'Save Request'}
|
||||
handleCancel={handleCancel}
|
||||
handleConfirm={handleConfirm}
|
||||
|
||||
@@ -187,6 +187,12 @@ const GlobalStyle = createGlobalStyle`
|
||||
}
|
||||
|
||||
|
||||
.collection-header-dropdown-label {
|
||||
max-width: 124px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
// scrollbar styling
|
||||
// the below media query targets non-touch devices
|
||||
@media not all and (pointer: coarse) {
|
||||
|
||||
Reference in New Issue
Block a user