Feat/v3 UI updates (#6618)

* style: enhance button layout and input styles across multiple components for improved UI consistency

* style: update RequestsNotLoaded component with new warning styles and enhance theme color definitions for status indicators

* refactor: update theme usage across components for consistency

- Changed color references from theme.brand to theme.primary.text in various StyledWrapper components.
- Added hover effects to enhance UI interactivity in CollectionSettings and FolderSettings.
- Removed unnecessary margin and padding adjustments in several components for cleaner layout.
- Improved accessibility by ensuring aria attributes are correctly set in MenuDropdown.
- Standardized styling for method indicators in RequestPane components.

These changes aim to create a more cohesive look and feel across the application while adhering to the updated theme guidelines.

* refactor: clean up method selector styling in NewRequest component

* chore: temp playwright test fixes

* refactor: update modal sizes across various components for consistency

- Changed modal size from "sm" to "md" in RenameWorkspace, CreateApiSpec, CloneCollection, DeleteCollectionItem, and RenameCollection components.
- Improved styling in HttpMethodSelector by adding padding for better layout.
- Updated theme color references in multiple theme files to use a new palette structure for consistency and maintainability.

* refactor: enhance styling and theme integration in TimelineItem components

- Updated HttpMethodSelector to clarify padding calculation in comments.
- Integrated theme colors for OAuth2 indicator and timestamp in TimelineItem for better visual consistency.
- Adjusted Method component to use uppercase styling for method display.
- Modified RelativeTime component to apply muted text color for improved readability.
- Updated INFO color in dark and light themes for better contrast and accessibility.

* refactor: remove duplicate import statements in theme files

- Cleaned up import statements in vscode.js and light-pastel.js by removing redundant lines for improved code clarity and maintainability.

* refactor: improve styling and theme integration in various components

- Added accent color and cursor style for checkbox inputs in Modal's StyledWrapper.
- Updated border-radius values in HttpMethodSelector and NewRequest StyledWrapper components to use theme variables for consistency.
- Introduced a new textbox class in NewRequest StyledWrapper for better styling control.
- Changed modal size from "sm" to "md" in CreateEnvironment for improved layout.

---------

Co-authored-by: Bijin A B <bijin@usebruno.com>
This commit is contained in:
Abhishek S Lal
2026-01-02 16:48:47 +05:30
committed by GitHub
parent 058d2e0e61
commit 849465d62a
94 changed files with 756 additions and 297 deletions

View File

@@ -19,7 +19,7 @@ const StyledWrapper = styled.div`
}
.selected-body-mode {
color: ${(props) => props.theme.brand};
color: ${(props) => props.theme.primary.text};
}
.dropdown-icon {

View File

@@ -7,7 +7,7 @@ const Wrapper = styled.div`
background: transparent;
.auth-mode-label {
color: ${(props) => props.theme.brand};
color: ${(props) => props.theme.primary.text};
.caret {
color: rgb(140, 140, 140);

View File

@@ -1,16 +1,23 @@
import styled from 'styled-components';
import { rgba } from 'polished';
const StyledWrapper = styled.div`
&.card {
background-color: ${(props) => props.theme.requestTabPanel.card.bg};
.title {
border-top: 1px solid ${(props) => props.theme.border.BORDER0};
border-left: 1px solid ${(props) => props.theme.border.BORDER0};
border-right: 1px solid ${(props) => props.theme.border.BORDER0};
border-top: 1px solid ${(props) => props.theme.table.border};
border-left: 1px solid ${(props) => props.theme.table.border};
border-right: 1px solid ${(props) => props.theme.table.border};
border-top-left-radius: 3px;
border-top-right-radius: 3px;
background-color: ${(props) => props.theme.status.warning.background};
}
.warning-icon {
color: ${(props) => props.theme.status.warning.text};
}
.table {

View File

@@ -41,8 +41,8 @@ const RequestsNotLoaded = ({ collection }) => {
return (
<StyledWrapper className="w-full card my-2">
<div className="flex items-center gap-2 px-3 py-2 title bg-yellow-50 dark:bg-yellow-900/20">
<IconAlertTriangle size={16} className="text-yellow-500" />
<div className="flex items-center gap-2 px-3 py-2 title">
<IconAlertTriangle size={16} className="warning-icon" />
<span className="font-medium">Following requests were not loaded</span>
</div>
<table className="w-full border-collapse">

View File

@@ -36,7 +36,7 @@ const PresetsSettings = ({ collection }) => {
return (
<StyledWrapper className="h-full w-full">
<div className="text-xs mb-4 mt-4 text-muted">
<div className="text-xs mb-4 text-muted">
These presets will be used as the default values for new requests in this collection.
</div>
<div className="bruno-form">

View File

@@ -56,7 +56,7 @@ const Script = ({ collection }) => {
};
return (
<StyledWrapper className="w-full flex flex-col h-full pt-4">
<StyledWrapper className="w-full flex flex-col h-full">
<div className="text-xs mb-4 text-muted">
Write pre and post-request scripts that will run before and after any request in this collection is sent.
</div>

View File

@@ -19,6 +19,10 @@ const StyledWrapper = styled.div`
box-shadow: none !important;
}
&:hover {
color: ${(props) => props.theme.tabs.active.color} !important;
}
&.active {
font-weight: ${(props) => props.theme.tabs.active.fontWeight} !important;
color: ${(props) => props.theme.tabs.active.color} !important;

View File

@@ -14,7 +14,7 @@ const Vars = ({ collection }) => {
return (
<StyledWrapper className="w-full flex flex-col">
<div className="flex-1 mt-2">
<div className="flex-1">
<div className="mb-3 title text-xs">Pre Request</div>
<VarsTable collection={collection} vars={requestVars} varType="request" />
</div>

View File

@@ -19,7 +19,7 @@ const StyledWrapper = styled.div`
/* Checkbox */
input[type='checkbox'] {
cursor: pointer;
accent-color: ${(props) => props.theme.colors.accent};
accent-color: ${(props) => props.theme.primary.solid};
}
`;

View File

@@ -30,8 +30,8 @@ const ConfirmCloseEnvironment = ({ onCancel, onCloseWithoutSave, onSaveAndClose,
Don't Save
</Button>
</div>
<div>
<Button className="mr-2" color="secondary" variant="ghost" onClick={onCancel}>
<div className="flex gap-2">
<Button size="sm" color="secondary" variant="ghost" onClick={onCancel}>
Cancel
</Button>
<Button onClick={onSaveAndClose}>

View File

@@ -7,7 +7,7 @@ const StyledWrapper = styled.div`
background: transparent;
.auth-mode-label {
color: ${(props) => props.theme.brand};
color: ${(props) => props.theme.primary.text};
.caret {
color: rgb(140, 140, 140);

View File

@@ -38,7 +38,7 @@ const Documentation = ({ collection, folder }) => {
}
return (
<StyledWrapper className="mt-1 w-full relative flex flex-col">
<StyledWrapper className="w-full relative flex flex-col">
<div className="editing-mode flex justify-between items-center flex-shrink-0" role="tab" onClick={toggleViewMode}>
{isEditing ? 'Preview' : 'Edit'}
</div>

View File

@@ -58,7 +58,7 @@ const Script = ({ collection, folder }) => {
};
return (
<StyledWrapper className="w-full flex flex-col h-full pt-4">
<StyledWrapper className="w-full flex flex-col h-full">
<div className="text-xs mb-4 text-muted">
Pre and post-request scripts that will run before and after any request inside this folder is sent.
</div>

View File

@@ -21,6 +21,10 @@ const StyledWrapper = styled.div`
box-shadow: none !important;
}
&:hover {
color: ${(props) => props.theme.tabs.active.color} !important;
}
&.active {
font-weight: ${(props) => props.theme.tabs.active.fontWeight} !important;
color: ${(props) => props.theme.tabs.active.color} !important;

View File

@@ -14,7 +14,7 @@ const Vars = ({ collection, folder }) => {
return (
<StyledWrapper className="w-full flex flex-col">
<div className="flex-1 mt-2">
<div className="flex-1">
<div className="mb-3 title text-xs">Pre Request</div>
<VarsTable folder={folder} collection={collection} vars={requestVars} varType="request" />
</div>

View File

@@ -58,7 +58,7 @@ const RenameWorkspace = ({ onClose, workspace }) => {
return (
<Portal>
<Modal
size="sm"
size="md"
title="Rename Workspace"
confirmText="Rename"
handleConfirm={onSubmit}

View File

@@ -127,6 +127,15 @@ const Wrapper = styled.div`
}
}
select.textbox {
appearance: none;
padding-right: 30px;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23999' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 0.5rem center;
cursor: pointer;
}
.bruno-form {
color: ${(props) => props.theme.modal.body.color};
}
@@ -167,6 +176,16 @@ const Wrapper = styled.div`
border-bottom-right-radius: ${(props) => props.theme.border.radius.base};
}
}
input[type='radio'] {
cursor: pointer;
accent-color: ${(props) => props.theme.primary.solid};
}
input[type='checkbox'] {
cursor: pointer;
accent-color: ${(props) => props.theme.primary.solid};
}
`;
export default Wrapper;

View File

@@ -367,6 +367,7 @@ const General = ({ close }) => {
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
readOnly={true}
onChange={formik.handleChange}
value={formik.values.defaultCollectionLocation || ''}
onClick={browseDefaultLocation}

View File

@@ -7,7 +7,7 @@ const Wrapper = styled.div`
background: transparent;
.auth-mode-label {
color: ${(props) => props.theme.brand};
color: ${(props) => props.theme.primary.text};
.caret {
color: rgb(140, 140, 140);

View File

@@ -38,7 +38,7 @@ const Wrapper = styled.div`
.grant-type-label {
width: fit-content;
color: ${(props) => props.theme.brand};
color: ${(props) => props.theme.primary.text};
justify-content: space-between;
padding: 0 0.5rem;
}

View File

@@ -103,7 +103,7 @@ const Auth = ({ item, collection }) => {
const source = getEffectiveAuthSource();
return (
<>
<div className="flex flex-row w-full mt-2 gap-2">
<div className="flex flex-row w-full gap-2">
<div>Auth inherited from {source.name}: </div>
<div className="inherit-mode-text">{humanizeRequestAuthMode(source.auth?.mode)}</div>
</div>
@@ -114,7 +114,7 @@ const Auth = ({ item, collection }) => {
};
return (
<StyledWrapper className="w-full mt-1 overflow-auto">
<StyledWrapper className="w-full overflow-auto">
{getAuthView()}
</StyledWrapper>
);

View File

@@ -21,8 +21,6 @@ import HeightBoundContainer from 'ui/HeightBoundContainer';
import Settings from 'components/RequestPane/Settings';
import ResponsiveTabs from 'ui/ResponsiveTabs';
const MULTIPLE_CONTENT_TABS = new Set(['script', 'vars', 'auth', 'docs']);
const TAB_CONFIG = [
{ key: 'query', label: 'Query' },
{ key: 'variables', label: 'Variables' },
@@ -136,8 +134,6 @@ const GraphQLRequestPane = ({ item, collection, onSchemaLoad, toggleDocs, handle
return <div className="pb-4 px-4">An error occurred!</div>;
}
const isMultipleContentTab = MULTIPLE_CONTENT_TABS.has(requestPaneTab);
const rightContent = (
<div ref={schemaActionsRef}>
<GraphQLSchemaActions item={item} collection={collection} onSchemaLoad={setSchema} toggleDocs={toggleDocs} />
@@ -154,7 +150,7 @@ const GraphQLRequestPane = ({ item, collection, onSchemaLoad, toggleDocs, handle
rightContentRef={schemaActionsRef}
/>
<section className={classnames('flex w-full flex-1', { 'mt-5': !isMultipleContentTab })}>
<section className={classnames('flex w-full flex-1 mt-4')}>
<HeightBoundContainer>{tabPanel}</HeightBoundContainer>
</section>
</div>

View File

@@ -50,6 +50,7 @@ const Wrapper = styled.div`
justify-content: flex-end;
gap: 4px;
padding: 4px 0px;
padding-top: 0px;
height: 32px;
flex-shrink: 0;

View File

@@ -296,8 +296,8 @@ const GrpcQueryUrl = ({ item, collection, handleRun }) => {
return (
<StyledWrapper className="flex items-center relative" data-testid="grpc-query-url-container">
<div className="flex items-center h-full method-selector-container">
<div className="flex items-center justify-center h-full w-16" data-testid="grpc-method-indicator">
<span className="text-xs font-bold" style={{ color: theme.request.grpc }}>gRPC</span>
<div className="flex items-center justify-center h-full px-[10px]" data-testid="grpc-method-indicator">
<span className="text-xs font-medium" style={{ color: theme.request.grpc }}>gRPC</span>
</div>
</div>
<div className="flex items-center w-full input-container h-full relative">

View File

@@ -121,7 +121,7 @@ const GrpcAuth = ({ item, collection }) => {
};
return (
<StyledWrapper className="w-full mt-1 overflow-y-scroll">
<StyledWrapper className="w-full overflow-y-scroll">
<div className="flex flex-grow justify-start items-center">
<GrpcAuthMode item={item} collection={collection} />
</div>

View File

@@ -62,7 +62,6 @@ const GrpcRequestPane = ({ item, collection, handleRun }) => {
});
};
const isMultipleContentTab = ['script', 'vars', 'auth', 'docs'].includes(focusedTab.requestPaneTab);
const body = getPropertyFromDraftOrRequest(item, 'request.body');
const headers = getPropertyFromDraftOrRequest(item, 'request.headers');
const docs = getPropertyFromDraftOrRequest(item, 'request.docs');
@@ -109,9 +108,7 @@ const GrpcRequestPane = ({ item, collection, handleRun }) => {
</div>
</div>
<section
className={classnames('flex w-full flex-1 h-full', {
'mt-2': !isMultipleContentTab
})}
className={classnames('flex w-full flex-1 h-full mt-4')}
>
<HeightBoundContainer>
{getTabPanel(focusedTab.requestPaneTab)}

View File

@@ -19,8 +19,6 @@ import ResponsiveTabs from 'ui/ResponsiveTabs';
import HeightBoundContainer from 'ui/HeightBoundContainer';
import AuthMode from '../Auth/AuthMode/index';
const MULTIPLE_CONTENT_TABS = new Set(['params', 'script', 'vars', 'auth', 'docs']);
const TAB_CONFIG = [
{ key: 'params', label: 'Params' },
{ key: 'body', label: 'Body' },
@@ -120,8 +118,6 @@ const HttpRequestPane = ({ item, collection }) => {
return <div className="pb-4 px-4">An error occurred!</div>;
}
const isMultipleContentTab = MULTIPLE_CONTENT_TABS.has(requestPaneTab);
const rightContent = requestPaneTab === 'body' ? (
<div ref={rightContentRef}>
<RequestBodyMode item={item} collection={collection} />
@@ -143,7 +139,7 @@ const HttpRequestPane = ({ item, collection }) => {
delayedTabs={['body']}
/>
<section className={classnames('flex w-full flex-1', { 'mt-3': !isMultipleContentTab })}>
<section className={classnames('flex w-full flex-1 mt-4')}>
<HeightBoundContainer>{tabPanel}</HeightBoundContainer>
</section>
</div>

View File

@@ -135,7 +135,7 @@ const QueryParams = ({ item, collection }) => {
return (
<StyledWrapper className="w-full flex flex-col">
<div className="flex-1 mt-2">
<div className="flex-1">
<div className="mb-3 title text-xs">Query</div>
<EditableTable
columns={queryColumns}

View File

@@ -2,13 +2,29 @@ import styled from 'styled-components';
const Wrapper = styled.div`
font-size: ${(props) => props.theme.font.size.base};
height: 100%;
display: flex;
align-items: stretch;
border-radius: 4px;
transition: background-color 0.15s ease;
.dropdown {
width: 100%;
display: flex;
align-items: stretch;
}
.method-selector {
border-radius: 3px;
display: flex;
align-items: center;
margin: 2px;
border-radius: ${(props) => props.theme.border.radius.sm};
&:not(.custom-input-mode):hover,
&:has(button[aria-expanded="true"]) {
background-color: color-mix(in srgb, currentColor 15%, transparent);
}
.tippy-box {
max-width: 150px !important;
@@ -37,20 +53,19 @@ const Wrapper = styled.div`
}
.method-span {
width: fit-content;
max-width: 10ch;
display: block;
max-width: 15ch;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
display: inline-block;
text-align: center;
font-size: ${(props) => props.theme.font.size.sm};
font-weight: 500;
line-height: 1.5;
}
.caret {
color: ${(props) => props.theme.colors.text.muted};
fill: ${(props) => props.theme.colors.text.muted};
color: currentColor;
fill: currentColor;
}
`;

View File

@@ -10,27 +10,18 @@ const KEY = Object.freeze({ ENTER: 'Enter', ESCAPE: 'Escape' });
const DEFAULT_METHOD = 'GET';
const TriggerButton = ({ method, showCaret, ...props }) => {
const { theme } = useTheme();
const methodColor = useMemo(() => {
const colorMap = {
...theme.request.methods,
...theme.request
};
return colorMap[method.toLocaleLowerCase()];
}, [method, theme]);
const TriggerButton = ({ method, methodSpanRef, showCaret, ...props }) => {
return (
<button
type="button"
className="cursor-pointer flex items-center text-left w-full select-none"
className="cursor-pointer flex items-center gap-2 text-left w-full select-none px-2"
{...props}
>
<span
className="px-3 truncate method-span"
ref={methodSpanRef}
className="truncate method-span"
id="create-new-request-method"
title={method}
style={{ color: methodColor }}
>
{method}
</span>
@@ -43,6 +34,17 @@ const HttpMethodSelector = ({ method = DEFAULT_METHOD, onMethodSelect, showCaret
const [isCustomMode, setIsCustomMode] = useState(false);
const inputRef = useRef();
const selectedMethodRef = useRef(method);
const methodSpanRef = useRef();
const [previousMethodWidth, setPreviousMethodWidth] = useState(null);
const { theme } = useTheme();
const methodColor = useMemo(() => {
const colorMap = {
...theme.request.methods,
...theme.request
};
return colorMap[method.toLocaleLowerCase()];
}, [method, theme]);
const blurInput = () => inputRef.current?.blur();
@@ -72,6 +74,10 @@ const HttpMethodSelector = ({ method = DEFAULT_METHOD, onMethodSelect, showCaret
};
const handleAddCustomMethod = useCallback(() => {
// Capture the width of the current method span before switching to custom mode
if (methodSpanRef.current) {
setPreviousMethodWidth(methodSpanRef.current.offsetWidth);
}
setIsCustomMode(true);
onMethodSelect('');
@@ -131,14 +137,28 @@ const HttpMethodSelector = ({ method = DEFAULT_METHOD, onMethodSelect, showCaret
// If in custom mode, render input field instead of dropdown
if (isCustomMode) {
// Calculate width based on content length (in ch units), clamped to max 16ch
// Add 1ch for cursor space
const contentWidth = Math.min(method.length + 1, 16);
// Use previous method width as minimum, content-based width as current
const minWidthPx = previousMethodWidth ? `${previousMethodWidth}px` : '5ch';
// Use calc to add padding space (px-2 = 0.5rem per side = 1rem total) to the ch width
const currentWidth = `calc(${Math.max(contentWidth, 1)}ch + 1rem)`;
return (
<StyledWrapper>
<div className="flex method-selector">
<div className="flex method-selector custom-input-mode" style={{ color: methodColor }}>
<div className="flex flex-col w-full">
<input
ref={inputRef}
type="text"
className="px-3 w-fit max-w-[10ch] focus:bg-transparent"
className="px-2 focus:bg-transparent"
style={{
minWidth: minWidthPx,
width: currentWidth,
maxWidth: 'calc(16ch + 1rem)',
fontSize: '12px'
}}
value={method}
onChange={handleInputChange}
onBlur={handleBlur}
@@ -154,13 +174,13 @@ const HttpMethodSelector = ({ method = DEFAULT_METHOD, onMethodSelect, showCaret
return (
<StyledWrapper>
<div className="flex method-selector">
<div className="flex method-selector" style={{ color: methodColor }}>
<MenuDropdown
items={menuItems}
placement="bottom-start"
selectedItemId={selectedItemId}
>
<TriggerButton method={method} showCaret={showCaret} />
<TriggerButton method={method} showCaret={showCaret} methodSpanRef={methodSpanRef} />
</MenuDropdown>
</div>
</StyledWrapper>

View File

@@ -9,7 +9,7 @@ const Wrapper = styled.div`
border-radius: 3px;
.selected-body-mode {
color: ${(props) => props.theme.brand};
color: ${(props) => props.theme.primary.text};
}
}

View File

@@ -56,7 +56,7 @@ const Script = ({ item, collection }) => {
const onSave = () => dispatch(saveRequest(item.uid, collection.uid));
return (
<div className="w-full h-full flex flex-col pt-4">
<div className="w-full h-full flex flex-col">
<Tabs value={activeTab} onValueChange={setActiveTab}>
<TabsList>
<TabsTrigger value="pre-request">Pre Request</TabsTrigger>

View File

@@ -9,7 +9,7 @@ const Vars = ({ item, collection }) => {
return (
<StyledWrapper className="w-full flex flex-col">
<div className="mt-2">
<div>
<div className="mb-3 title text-xs">Pre Request</div>
<VarsTable item={item} collection={collection} vars={requestVars} varType="request" />
</div>

View File

@@ -118,7 +118,7 @@ const WSAuth = ({ item, collection }) => {
};
return (
<StyledWrapper className="w-full mt-1 overflow-y-scroll">
<StyledWrapper className="w-full overflow-y-scroll">
<div className="flex flex-grow justify-start items-center">
<WSAuthMode item={item} collection={collection} />
</div>

View File

@@ -71,7 +71,6 @@ const WSRequestPane = ({ item, collection, handleRun }) => {
});
};
const isMultipleContentTab = ['script', 'vars', 'auth', 'docs'].includes(focusedTab.requestPaneTab);
const headers = getPropertyFromDraftOrRequest(item, 'request.headers');
const docs = getPropertyFromDraftOrRequest(item, 'request.docs');
const auth = getPropertyFromDraftOrRequest(item, 'request.auth');
@@ -107,9 +106,7 @@ const WSRequestPane = ({ item, collection, handleRun }) => {
</div>
</div>
<section
className={classnames('flex w-full flex-1 h-full', {
'mt-2': !isMultipleContentTab
})}
className={classnames('flex w-full flex-1 h-full mt-4')}
>
<HeightBoundContainer>{getTabPanel(focusedTab.requestPaneTab)}</HeightBoundContainer>
</section>

View File

@@ -53,7 +53,7 @@ const WSSettingsPane = ({ item, collection }) => {
};
return (
<StyledWrapper className="flex flex-col mt-4 gap-4 w-full">
<StyledWrapper className="flex flex-col gap-4 w-full">
<section className="grid gap-4 items-center grid-cols-2">
<div>
<label className="font-medium mb-2">Timeout</label>

View File

@@ -17,7 +17,7 @@ const Wrapper = styled.div`
}
.selected-body-mode {
color: ${(props) => props.theme.brand};
color: ${(props) => props.theme.primary.text};
}
}

View File

@@ -27,6 +27,7 @@ const StyledWrapper = styled.div`
justify-content: flex-end;
gap: 4px;
padding: 4px 0px;
padding-top: 0px;
height: 32px;
flex-shrink: 0;

View File

@@ -124,8 +124,8 @@ const WsQueryUrl = ({ item, collection, handleRun }) => {
<StyledWrapper>
<div className="flex items-center h-full">
<div className="flex items-center input-container flex-1 w-full input-container pr-2 h-full relative">
<div className="flex items-center justify-center w-16">
<span className="text-xs font-bold method-ws">WS</span>
<div className="flex items-center justify-center px-[10px]">
<span className="text-xs font-medium method-ws">WS</span>
</div>
<SingleLineEditor
value={url}

View File

@@ -34,8 +34,8 @@ const ConfirmCollectionClose = ({ collection, onCancel, onCloseWithoutSave, onSa
Don't Save
</Button>
</div>
<div>
<Button className="mr-2" color="secondary" variant="ghost" onClick={onCancel}>
<div className="flex gap-2">
<Button size="sm" color="secondary" variant="ghost" onClick={onCancel}>
Cancel
</Button>
<Button onClick={onSaveAndClose}>

View File

@@ -34,8 +34,8 @@ const ConfirmFolderClose = ({ folder, onCancel, onCloseWithoutSave, onSaveAndClo
Don't Save
</Button>
</div>
<div>
<Button className="mr-2" color="secondary" variant="ghost" onClick={onCancel}>
<div className="flex gap-2">
<Button size="sm" color="secondary" variant="ghost" onClick={onCancel}>
Cancel
</Button>
<Button onClick={onSaveAndClose}>

View File

@@ -38,8 +38,8 @@ const ConfirmRequestClose = ({ item, example, onCancel, onCloseWithoutSave, onSa
Don't Save
</Button>
</div>
<div>
<Button className="mr-2" color="secondary" variant="ghost" onClick={onCancel}>
<div className="flex gap-2">
<Button size="sm" color="secondary" variant="ghost" onClick={onCancel}>
Cancel
</Button>
<Button onClick={onSaveAndClose}>Save</Button>

View File

@@ -8,7 +8,7 @@ const SpecialTab = ({ handleCloseClick, type, tabName, handleDoubleClick, hasDra
case 'collection-settings': {
return (
<>
<IconSettings size={14} strokeWidth={1.5} className="text-yellow-600 flex-shrink-0" />
<IconSettings size={14} strokeWidth={1.5} className="special-tab-icon flex-shrink-0" />
<span className="ml-1 tab-name">Collection</span>
</>
);
@@ -16,7 +16,7 @@ const SpecialTab = ({ handleCloseClick, type, tabName, handleDoubleClick, hasDra
case 'collection-overview': {
return (
<>
<IconSettings size={14} strokeWidth={1.5} className="text-yellow-600 flex-shrink-0" />
<IconSettings size={14} strokeWidth={1.5} className="special-tab-icon flex-shrink-0" />
<span className="ml-1 tab-name">Overview</span>
</>
);
@@ -24,7 +24,7 @@ const SpecialTab = ({ handleCloseClick, type, tabName, handleDoubleClick, hasDra
case 'folder-settings': {
return (
<>
<IconFolder size={14} strokeWidth={1.5} className="text-yellow-600 flex-shrink-0" />
<IconFolder size={14} strokeWidth={1.5} className="special-tab-icon flex-shrink-0" />
<span className="ml-1 tab-name">{tabName || 'Folder'}</span>
</>
);
@@ -32,7 +32,7 @@ const SpecialTab = ({ handleCloseClick, type, tabName, handleDoubleClick, hasDra
case 'variables': {
return (
<>
<IconVariable size={14} strokeWidth={1.5} className="text-yellow-600 flex-shrink-0" />
<IconVariable size={14} strokeWidth={1.5} className="special-tab-icon flex-shrink-0" />
<span className="ml-1 tab-name">Variables</span>
</>
);
@@ -40,7 +40,7 @@ const SpecialTab = ({ handleCloseClick, type, tabName, handleDoubleClick, hasDra
case 'collection-runner': {
return (
<>
<IconRun size={14} strokeWidth={1.5} className="text-yellow-600 flex-shrink-0" />
<IconRun size={14} strokeWidth={1.5} className="special-tab-icon flex-shrink-0" />
<span className="ml-1 tab-name">Runner</span>
</>
);
@@ -48,7 +48,7 @@ const SpecialTab = ({ handleCloseClick, type, tabName, handleDoubleClick, hasDra
case 'environment-settings': {
return (
<>
<IconDatabase size={14} strokeWidth={1.5} className="text-yellow-600 flex-shrink-0" />
<IconDatabase size={14} strokeWidth={1.5} className="special-tab-icon flex-shrink-0" />
<span className="ml-1 tab-name">Environments</span>
</>
);
@@ -56,7 +56,7 @@ const SpecialTab = ({ handleCloseClick, type, tabName, handleDoubleClick, hasDra
case 'global-environment-settings': {
return (
<>
<IconWorld size={14} strokeWidth={1.5} className="text-yellow-600 flex-shrink-0" />
<IconWorld size={14} strokeWidth={1.5} className="special-tab-icon flex-shrink-0" />
<span className="ml-1 tab-name">Global Environments</span>
</>
);
@@ -67,7 +67,7 @@ const SpecialTab = ({ handleCloseClick, type, tabName, handleDoubleClick, hasDra
return (
<>
<div
className="flex items-baseline tab-label"
className="flex items-center tab-label"
onDoubleClick={handleDoubleClick}
>
{getTabInfo(type, tabName)}

View File

@@ -17,8 +17,6 @@ const Wrapper = styled.div`
.tabs-scroll-container {
overflow-x: auto;
overflow-y: clip;
padding-bottom: 10px;
margin-bottom: -10px;
&::-webkit-scrollbar {
display: none;
@@ -197,6 +195,10 @@ const Wrapper = styled.div`
&.has-chevrons ul {
padding-left: 0;
}
.special-tab-icon {
color: ${(props) => props.theme.primary.text};
}
`;
export default Wrapper;

View File

@@ -12,6 +12,7 @@ import StyledWrapper from './StyledWrapper';
import DraggableTab from './DraggableTab';
import CreateUntitledRequest from 'components/CreateUntitledRequest';
import { IconPlus } from '@tabler/icons';
import ActionIcon from 'ui/ActionIcon/index';
const RequestTabs = () => {
const dispatch = useDispatch();
@@ -116,22 +117,19 @@ const RequestTabs = () => {
{collectionRequestTabs && collectionRequestTabs.length ? (
<>
<CollectionToolBar collection={activeCollection} />
<div className="flex items-center pl-2" ref={collectionTabsRef}>
<ul role="tablist">
{showChevrons ? (
<li className="select-none short-tab" onClick={leftSlide}>
<div className="flex items-center">
<IconChevronLeft size={18} strokeWidth={1.5} />
</div>
</li>
) : null}
{/* Moved to post mvp */}
{/* <li className="select-none new-tab mr-1" onClick={createNewTab}>
<div className="flex items-center home-icon-container">
<IconHome2 size={18} strokeWidth={1.5}/>
</div>
</li> */}
</ul>
<div className="flex items-center gap-2 pl-2" ref={collectionTabsRef}>
{showChevrons ? (
<ActionIcon size="lg" onClick={leftSlide} aria-label="Left Chevron" style={{ marginBottom: '3px' }}>
<IconChevronLeft size={18} strokeWidth={1.5} />
</ActionIcon>
) : null}
{/* Moved to post mvp */}
{/* <li className="select-none new-tab mr-1" onClick={createNewTab}>
<div className="flex items-center home-icon-container">
<IconHome2 size={18} strokeWidth={1.5}/>
</div>
</li> */}
<div className="tabs-scroll-container" style={{ maxWidth: maxTablistWidth }} ref={scrollContainerRef}>
<ul role="tablist" ref={tabsRef}>
{collectionRequestTabs && collectionRequestTabs.length
@@ -169,30 +167,27 @@ const RequestTabs = () => {
</div>
{activeCollection && (
<div className="flex items-center cursor-pointer short-tab px-2" onClick={() => setNewRequestModalOpen(true)}>
<ActionIcon onClick={() => setNewRequestModalOpen(true)} aria-label="New Request" size="lg" style={{ marginBottom: '3px' }}>
<IconPlus
size={18}
strokeWidth={1.5}
/>
</div>
</ActionIcon>
)}
<ul role="tablist">
{showChevrons ? (
<li className="select-none short-tab" onClick={rightSlide}>
<div className="flex items-center">
<IconChevronRight size={18} strokeWidth={1.5} />
</div>
</li>
) : null}
{/* Moved to post mvp */}
{/* <li className="select-none new-tab choose-request">
{showChevrons ? (
<ActionIcon size="lg" onClick={rightSlide} aria-label="Right Chevron" style={{ marginBottom: '3px' }}>
<IconChevronRight size={18} strokeWidth={1.5} />
</ActionIcon>
) : null}
{/* Moved to post mvp */}
{/* <li className="select-none new-tab choose-request">
<div className="flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 16 16">
<path d="M3 9.5a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm5 0a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm5 0a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3z"/>
</svg>
</div>
</li> */}
</ul>
</div>
</>
) : null}

View File

@@ -26,12 +26,12 @@ const StyledWrapper = styled.div`
}
.selected-body-mode {
color: ${(props) => props.theme.brand};
color: ${(props) => props.theme.primary.text};
}
&.cursor-default {
opacity: 0.6;
.selected-body-mode {
color: ${(props) => props.theme.colors.text.muted};
}
@@ -53,11 +53,11 @@ const StyledWrapper = styled.div`
align-items: center;
justify-content: center;
color: ${(props) => props.theme.colors.text.muted};
&:hover {
opacity: 0.9;
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;

View File

@@ -18,7 +18,7 @@ const StyledWrapper = styled.div`
}
.selected-body-mode {
color: ${(props) => props.theme.brand};
color: ${(props) => props.theme.primary.text};
}
}

View File

@@ -19,6 +19,10 @@ const StyledWrapper = styled.div`
box-shadow: none !important;
}
&:hover {
color: ${(props) => props.theme.tabs.active.color} !important;
}
&.active {
font-weight: ${(props) => props.theme.tabs.active.fontWeight} !important;
color: ${(props) => props.theme.tabs.active.color} !important;

View File

@@ -6,7 +6,6 @@ const StyledWrapper = styled.div`
.empty-state {
color: ${(props) => props.theme.colors.text.muted};
padding: 1rem;
}
.responses-container {

View File

@@ -141,7 +141,7 @@ const GrpcResponsePane = ({ item, collection }) => {
) : null}
</div>
<section
className={`flex flex-col flex-grow pl-3 pr-4 h-0 ${focusedTab.responsePaneTab === 'response' ? '' : 'mt-4'}`}
className="flex flex-col flex-grow pl-3 pr-4 h-0 mt-4"
>
{isLoading ? <Overlay item={item} collection={collection} /> : null}
{!item?.response ? (

View File

@@ -4,6 +4,7 @@ import { useDispatch, useSelector } from 'react-redux';
import { cancelRequest } from 'providers/ReduxStore/slices/collections/actions';
import StopWatch from '../../StopWatch';
import StyledWrapper from './StyledWrapper';
import Button from 'ui/Button/index';
const ResponseLoadingOverlay = ({ item, collection }) => {
const dispatch = useDispatch();
@@ -23,13 +24,12 @@ const ResponseLoadingOverlay = ({ item, collection }) => {
</div>
</div>
<IconRefresh size={24} className="loading-icon" />
<button
<Button
onClick={handleCancelRequest}
className="mt-4 uppercase btn-sm rounded btn-secondary ease-linear transition-all duration-150 relative z-50"
type="button"
className="mt-4"
>
Cancel Request
</button>
</Button>
</div>
</StyledWrapper>
);

View File

@@ -20,7 +20,7 @@ const StyledMenuIcon = styled.button`
border-radius: 4px;
&:hover {
background-color: ${(props) => props.theme.workspace.button.bg};
border-color: ${(props) => props.theme.app.collection.toolbar.environmentSelector.hoverBorder} !important;
color: ${(props) => props.theme.text};
}
`;

View File

@@ -10,8 +10,11 @@ const StyledWrapper = styled.div`
flex: 1;
}
.timeline-item {
border-color: ${(props) => props.theme.border.border1};
}
.timeline-event {
padding: 8px 0 0 0;
cursor: pointer;
}
@@ -65,7 +68,7 @@ const StyledWrapper = styled.div`
.tabs-switcher {
border-bottom: 1px solid ${(props) => props.theme.border.border1};
margin-bottom: 16px;
button {
position: relative;
padding: 8px 16px;
@@ -126,7 +129,7 @@ const StyledWrapper = styled.div`
color: ${(props) => props.theme.colors.text.purple};
}
}
.request-label {
font-size: ${(props) => props.theme.font.size.base};
padding: 2px 6px;

View File

@@ -1,13 +1,17 @@
import { useMemo } from 'react';
import { useTheme } from 'providers/Theme';
const Method = ({ method }) => {
const { theme } = useTheme();
const methodUpper = method?.toUpperCase();
const methodColor = theme.request.methods[methodUpper?.toLowerCase()] || theme.text;
const methodColor = useMemo(() => {
const methodLower = method?.toLowerCase();
return theme.request.methods[methodLower] || theme.text;
}, [method, theme]);
return (
<span className="timeline-method" style={{ color: methodColor, fontWeight: 'bold' }}>
{methodUpper}
<span className="font-medium uppercase" style={{ color: methodColor, fontSize: theme.font.size.xs }}>
{method}
</span>
);
};

View File

@@ -1,4 +1,5 @@
import { useState, useEffect } from 'react';
import { useTheme } from 'providers/Theme';
const getRelativeTime = (date) => {
const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });
@@ -23,6 +24,7 @@ const getRelativeTime = (date) => {
export const RelativeTime = ({ timestamp }) => {
const [relativeTime, setRelativeTime] = useState(getRelativeTime(new Date(timestamp)));
const { theme } = useTheme();
useEffect(() => {
const interval = setInterval(() => {
@@ -32,5 +34,15 @@ export const RelativeTime = ({ timestamp }) => {
return () => clearInterval(interval);
}, [timestamp]);
return <pre className="text-xs">{relativeTime}</pre>;
return (
<span
title={new Date(timestamp).toLocaleString()}
style={{
fontSize: theme.font.size.xs,
color: theme.colors.text.muted
}}
>
{relativeTime}
</span>
);
};

View File

@@ -3,11 +3,11 @@ import { rgba } from 'polished';
const StyledWrapper = styled.div`
.timeline-item {
border-bottom: 2px solid ${(props) => rgba(props.theme.colors.text.warning, 0.5)};
border-bottom: 1px solid ${(props) => props.theme.border.border1};
padding: 0.5rem 0;
&--oauth2 {
border-bottom: 2px solid ${(props) => rgba(props.theme.primary.solid, 0.5)};
border-bottom: 1px solid ${(props) => props.theme.border.border1};
}
}

View File

@@ -1,4 +1,5 @@
import { useState } from 'react';
import { useTheme } from 'providers/Theme';
import Network from './Network/index';
import Request from './Request/index';
import Response from './Response/index';
@@ -8,6 +9,7 @@ import { RelativeTime } from './Common/Time/index';
import StyledWrapper from './StyledWrapper';
const TimelineItem = ({ timestamp, request, response, item, collection, isOauth2, hideTimestamp = false }) => {
const { theme } = useTheme();
const [isCollapsed, _toggleCollapse] = useState(false);
const [activeTab, setActiveTab] = useState('request');
const toggleCollapse = () => _toggleCollapse((prev) => !prev);
@@ -18,24 +20,18 @@ const TimelineItem = ({ timestamp, request, response, item, collection, isOauth2
return (
<StyledWrapper>
<div className={`timeline-item ${isOauth2 ? 'timeline-item--oauth2' : ''}`}>
<div className="timeline-item-header" onClick={toggleCollapse}>
<div className="timeline-item-header-content">
<div className="timeline-item-header-items">
<Status statusCode={responseStatus || responseStatusCode} statusText={responseStatusText} />
<Method method={method} />
<Status statusCode={status || statusCode} statusText={statusText} />
{isOauth2 && <pre className="timeline-item-oauth-label">[oauth2.0]</pre>}
{!hideTimestamp && (
<>
<pre className="timeline-item-timestamp-iso">[{new Date(timestamp).toISOString()}]</pre>
<span className="timeline-item-timestamp">
<RelativeTime timestamp={timestamp} />
</span>
</>
)}
</div>
<div className="oauth-request-item-header relative cursor-pointer flex items-center justify-between gap-3 min-w-0" onClick={toggleCollapse}>
<Status statusCode={responseStatus || responseStatusCode} statusText={responseStatusText} />
<div className="flex items-center gap-1">
<Method method={method} />
<div className="truncate flex-1 min-w-0">{url}</div>
{isOauth2 && <span className="text-xs flex-shrink-0" style={{ color: theme.colors.text.muted }}>[oauth2.0]</span>}
</div>
<div className="timeline-item-url">{url}</div>
{!hideTimestamp && (
<span className="flex-shrink-0 ml-auto">
<RelativeTime timestamp={timestamp} />
</span>
)}
</div>
{isCollapsed && (
<div className="timeline-item-content">

View File

@@ -185,7 +185,7 @@ const WSMessagesList = ({ order = -1, messages = [] }) => {
}
const ordered = order === -1 ? messages : messages.slice().reverse();
return (
<StyledWrapper className="ws-messages-list mt-2 flex flex-col">
<StyledWrapper className="ws-messages-list flex flex-col">
{ordered.map((msg, idx, src) => {
const inFocus = order === -1 ? src.length - 1 === idx : idx === 0;
return <WSMessageItem key={msg.timestamp} inFocus={inFocus} id={idx} message={msg} />;

View File

@@ -135,7 +135,7 @@ const WSResponsePane = ({ item, collection }) => {
) : null}
</div>
<section
className={`flex flex-col flex-grow pl-3 pr-4 h-0 ${focusedTab.responsePaneTab === 'response' ? '' : 'mt-4'}`}
className="flex flex-col flex-grow pl-3 pr-4 h-0 mt-4"
>
{isLoading ? <Overlay item={item} collection={collection} /> : null}
{!item?.response ? (

View File

@@ -284,7 +284,7 @@ const ResponsePane = ({ item, collection }) => {
/>
</div>
<section
className="flex flex-col min-h-0 relative px-4 pt-3 auto overflow-auto"
className="flex flex-col min-h-0 relative px-4 auto overflow-auto mt-4"
style={{
flex: '1 1 0',
height: hasScriptError && showScriptErrorCard ? 'auto' : '100%'

View File

@@ -166,7 +166,7 @@ const CreateApiSpec = ({ onClose }) => {
return (
<StyledWrapper>
<Modal size="sm" title="Create API Spec" confirmText="Create" handleConfirm={onSubmit} handleCancel={onClose}>
<Modal size="md" title="Create API Spec" confirmText="Create" handleConfirm={onSubmit} handleCancel={onClose}>
<form className="bruno-form" onSubmit={(e) => e.preventDefault()}>
<div>
<label htmlFor="api-spec-location" className="block font-semibold mb-2">

View File

@@ -85,7 +85,7 @@ const CloneCollection = ({ onClose, collectionUid }) => {
const onSubmit = () => formik.handleSubmit();
return (
<Modal size="sm" title="Clone Collection" confirmText="Create" handleConfirm={onSubmit} handleCancel={onClose}>
<Modal size="md" title="Clone Collection" confirmText="Create" handleConfirm={onSubmit} handleCancel={onClose}>
<form className="bruno-form" onSubmit={(e) => e.preventDefault()}>
<div>
<label htmlFor="collection-name" className="flex items-center font-medium">

View File

@@ -40,7 +40,7 @@ const DeleteCollectionItem = ({ onClose, item, collectionUid }) => {
return (
<StyledWrapper>
<Modal
size="sm"
size="md"
title={`Delete ${isFolder ? 'Folder' : 'Request'}`}
confirmText="Delete"
confirmButtonColor="danger"

View File

@@ -42,7 +42,7 @@ const RenameCollection = ({ collectionUid, onClose }) => {
const onSubmit = () => formik.handleSubmit();
return (
<Modal size="sm" title="Rename Collection" confirmText="Rename" handleConfirm={onSubmit} handleCancel={onClose}>
<Modal size="md" title="Rename Collection" confirmText="Rename" handleConfirm={onSubmit} handleCancel={onClose}>
<form className="bruno-form" onSubmit={(e) => e.preventDefault()}>
<div>
<label htmlFor="name" className="block font-medium">

View File

@@ -30,13 +30,12 @@ const StyledWrapper = styled.div`
}
&:hover {
background: ${(props) => props.theme.sidebar.collection.item.hoverBg};
border-color: ${(props) => props.theme.sidebar.muted}40;
border-color: ${(props) => props.theme.input.border};
}
&:focus {
background: ${(props) => props.theme.sidebar.bg};
border-color: ${(props) => props.theme.sidebar.muted}80;
background: ${(props) => props.theme.input.bg};
border-color: ${(props) => props.theme.input.border};
}
}

View File

@@ -135,6 +135,7 @@ const CreateCollection = ({ onClose, defaultLocation: propDefaultLocation }) =>
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
readOnly={true}
value={formik.values.collectionLocation || ''}
onClick={browse}
onChange={(e) => {

View File

@@ -5,11 +5,8 @@ const StyledWrapper = styled.div`
border: solid 1px ${(props) => props.theme.input.border};
border-right: none;
background-color: ${(props) => props.theme.input.bg};
border-top-left-radius: 3px;
border-bottom-left-radius: 3px;
.method-selector {
min-width: 80px;
}
border-top-left-radius: ${(props) => props.theme.border.radius.base};
border-bottom-left-radius: ${(props) => props.theme.border.radius.base};
}
div.method-selector-container,
div.input-container {
@@ -18,8 +15,8 @@ const StyledWrapper = styled.div`
}
div.input-container {
border: solid 1px ${(props) => props.theme.input.border};
border-top-right-radius: 3px;
border-bottom-right-radius: 3px;
border-top-right-radius: ${(props) => props.theme.border.radius.base};
border-bottom-right-radius: ${(props) => props.theme.border.radius.base};
input {
background-color: ${(props) => props.theme.input.bg};
outline: none;
@@ -31,12 +28,17 @@ const StyledWrapper = styled.div`
}
}
.textbox {
border-radius: ${(props) => props.theme.border.radius.base} !important;
height: 2.1rem;
}
textarea.curl-command {
min-height: 150px;
}
.dropdown {
width: fit-content;
.dropdown-item {
padding: 0.2rem 0.6rem !important;
}

View File

@@ -503,7 +503,7 @@ const NewRequest = ({ collectionUid, item, isEphemeral, onClose }) => {
</label>
<div className="flex items-center mt-2 ">
{!['grpc-request', 'ws-request'].includes(formik.values.requestType) ? (
<div className="flex items-center h-full method-selector-container w-1/5">
<div className="flex items-center h-full method-selector-container">
<HttpMethodSelector
method={formik.values.requestMethod}
onMethodSelect={(val) => formik.setFieldValue('requestMethod', val)}

View File

@@ -61,7 +61,7 @@ const CreateEnvironment = ({ onClose, onEnvironmentCreated }) => {
return (
<Portal>
<Modal
size="sm"
size="md"
title="Create Global Environment"
confirmText="Create"
handleConfirm={onSubmit}