feat: use theme colors for Console method badges (#6603)

* feat: use theme colors for Console method badges

* chore(theme): bruno devtools UX updates

---------

Co-authored-by: Bijin A B <bijin@usebruno.com>
This commit is contained in:
gopu-bruno
2026-01-02 04:06:55 +05:30
committed by GitHub
parent f611c05fe8
commit 5fda0866f8
11 changed files with 56 additions and 340 deletions

View File

@@ -38,20 +38,6 @@ const StyledWrapper = styled.div`
gap: 8px;
}
.control-button {
display: flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
background: transparent;
border: 1px solid ${(props) => props.theme.console.border};
border-radius: 4px;
color: ${(props) => props.theme.console.buttonColor};
cursor: pointer;
transition: all 0.2s ease;
}
.debug-content {
flex: 1;
overflow: hidden;

View File

@@ -32,12 +32,6 @@ const StyledWrapper = styled.div`
}
}
.network-controls {
display: flex;
align-items: center;
gap: 8px;
}
.network-content {
flex: 1;
overflow: hidden;
@@ -81,11 +75,10 @@ const StyledWrapper = styled.div`
display: grid;
grid-template-columns: 80px 80px 150px 1fr 100px 80px 80px;
gap: 12px;
padding: 8px 16px;
padding: 4px 16px;
background: ${(props) => props.theme.console.headerBg};
border-bottom: 1px solid ${(props) => props.theme.console.border};
font-size: ${(props) => props.theme.font.size.xs};
font-weight: 500;
font-size: 10px;
color: ${(props) => props.theme.console.titleColor};
text-transform: uppercase;
letter-spacing: 0.5px;
@@ -103,8 +96,7 @@ const StyledWrapper = styled.div`
display: grid;
grid-template-columns: 80px 80px 150px 1fr 100px 80px 80px;
gap: 12px;
padding: 6px 16px;
border-bottom: 1px solid ${(props) => props.theme.console.border};
padding: 2px 16px;
cursor: pointer;
transition: background-color 0.1s ease;
font-size: ${(props) => props.theme.font.size.sm};
@@ -115,7 +107,8 @@ const StyledWrapper = styled.div`
}
&.selected {
background: ${(props) => props.theme.console.buttonHoverBg};
padding-left: 13px;
background: ${(props) => props.theme.console.logHoverBg};
border-left: 3px solid ${(props) => props.theme.console.checkboxColor};
}
}
@@ -123,25 +116,19 @@ const StyledWrapper = styled.div`
.method-badge {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 2px 6px;
border-radius: 4px;
justify-content: start;
font-size: 10px;
font-weight: 500;
color: white;
text-transform: uppercase;
letter-spacing: 0.5px;
min-width: 45px;
}
.status-badge {
font-weight: 500;
font-size: ${(props) => props.theme.font.size.sm};
}
.request-domain {
color: ${(props) => props.theme.console.messageColor};
font-weight: 500;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
@@ -174,120 +161,6 @@ const StyledWrapper = styled.div`
font-size: ${(props) => props.theme.font.size.xs};
text-align: right;
}
.filter-dropdown {
position: relative;
}
.filter-dropdown-trigger {
display: flex;
align-items: center;
gap: 6px;
padding: 6px 8px;
background: transparent;
border: 1px solid ${(props) => props.theme.console.border};
border-radius: 4px;
color: ${(props) => props.theme.console.buttonColor};
cursor: pointer;
transition: all 0.2s ease;
font-size: ${(props) => props.theme.font.size.sm};
&:hover {
background: ${(props) => props.theme.console.buttonHoverBg};
color: ${(props) => props.theme.console.buttonHoverColor};
}
.filter-summary {
font-weight: 500;
min-width: 24px;
text-align: center;
}
}
.filter-dropdown-menu {
position: absolute;
top: calc(100% + 4px);
right: 0;
min-width: 200px;
max-width: 250px;
background: ${(props) => props.theme.console.dropdownBg};
border: 1px solid ${(props) => props.theme.console.border};
border-radius: 6px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
z-index: 1000;
overflow: hidden;
}
.filter-dropdown-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 12px;
background: ${(props) => props.theme.console.dropdownHeaderBg};
border-bottom: 1px solid ${(props) => props.theme.console.border};
font-size: ${(props) => props.theme.font.size.sm};
font-weight: 500;
color: ${(props) => props.theme.console.titleColor};
}
.filter-toggle-all {
background: transparent;
border: none;
color: ${(props) => props.theme.console.buttonColor};
cursor: pointer;
font-size: ${(props) => props.theme.font.size.xs};
font-weight: 500;
padding: 2px 4px;
border-radius: 2px;
transition: all 0.2s ease;
&:hover {
background: ${(props) => props.theme.console.buttonHoverBg};
}
}
.filter-dropdown-options {
padding: 4px 0;
}
.filter-option {
display: flex;
align-items: center;
padding: 6px 12px;
cursor: pointer;
transition: background-color 0.2s ease;
&:hover {
background: ${(props) => props.theme.console.optionHoverBg};
}
input[type="checkbox"] {
margin: 0 8px 0 0;
width: 14px;
height: 14px;
accent-color: ${(props) => props.theme.console.checkboxColor};
}
}
.filter-option-content {
display: flex;
align-items: center;
gap: 8px;
flex: 1;
}
.filter-option-label {
color: ${(props) => props.theme.console.optionLabelColor};
font-size: ${(props) => props.theme.font.size.sm};
font-weight: 400;
}
.filter-option-count {
color: ${(props) => props.theme.console.optionCountColor};
font-size: ${(props) => props.theme.font.size.xs};
font-weight: 400;
margin-left: auto;
}
`;
export default StyledWrapper;

View File

@@ -1,128 +1,33 @@
import React, { useState, useRef, useEffect, useMemo } from 'react';
import React, { useMemo } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import {
IconFilter,
IconChevronDown,
IconNetwork
} from '@tabler/icons';
import {
updateNetworkFilter,
toggleAllNetworkFilters,
setSelectedRequest
} from 'providers/ReduxStore/slices/logs';
import StyledWrapper from './StyledWrapper';
const MethodBadge = ({ method }) => {
const getMethodColor = (method) => {
switch (method?.toUpperCase()) {
case 'GET': return '#10b981';
case 'POST': return '#8b5cf6';
case 'PUT': return '#f59e0b';
case 'DELETE': return '#ef4444';
case 'PATCH': return '#06b6d4';
case 'HEAD': return '#6b7280';
case 'OPTIONS': return '#84cc16';
default: return '#6b7280';
}
};
const methodLower = method?.toLowerCase() || 'get';
return (
<span
className="method-badge"
style={{ backgroundColor: getMethodColor(method) }}
>
<span className={`method-badge ${methodLower}`}>
{method?.toUpperCase() || 'GET'}
</span>
);
};
const StatusBadge = ({ status, statusCode }) => {
const getStatusColor = (code) => {
if (code >= 200 && code < 300) return '#10b981';
if (code >= 300 && code < 400) return '#f59e0b';
if (code >= 400 && code < 500) return '#ef4444';
if (code >= 500) return '#dc2626';
return '#6b7280';
};
const displayStatus = statusCode || status;
return (
<span
className="status-badge"
style={{ color: getStatusColor(statusCode) }}
>
<span className="status-badge">
{displayStatus}
</span>
);
};
const NetworkFilterDropdown = ({ filters, requestCounts, onFilterToggle, onToggleAll }) => {
const [isOpen, setIsOpen] = useState(false);
const dropdownRef = useRef(null);
const allFiltersEnabled = Object.values(filters).every((f) => f);
const activeFilters = Object.entries(filters).filter(([_, enabled]) => enabled);
useEffect(() => {
const handleClickOutside = (event) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
setIsOpen(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, []);
return (
<div className="filter-dropdown" ref={dropdownRef}>
<button
className="filter-dropdown-trigger"
onClick={() => setIsOpen(!isOpen)}
title="Filter requests by method"
>
<IconFilter size={16} strokeWidth={1.5} />
<span className="filter-summary">
{activeFilters.length === Object.keys(filters).length ? 'All' : `${activeFilters.length}/${Object.keys(filters).length}`}
</span>
<IconChevronDown size={14} strokeWidth={1.5} />
</button>
{isOpen && (
<div className="filter-dropdown-menu right">
<div className="filter-dropdown-header">
<span>Filter by Method</span>
<button
className="filter-toggle-all"
onClick={() => onToggleAll(!allFiltersEnabled)}
>
{allFiltersEnabled ? 'Hide All' : 'Show All'}
</button>
</div>
<div className="filter-dropdown-options">
{Object.keys(filters).map((method) => (
<label key={method} className="filter-option">
<input
type="checkbox"
checked={filters[method]}
onChange={(e) => onFilterToggle(method, e.target.checked)}
/>
<div className="filter-option-content">
<MethodBadge method={method} />
<span className="filter-option-label">{method}</span>
<span className="filter-option-count">({requestCounts[method] || 0})</span>
</div>
</label>
))}
</div>
</div>
)}
</div>
);
};
const RequestRow = ({ request, isSelected, onClick }) => {
const { data } = request;
const { request: req, response: res, timestamp } = data;
@@ -241,22 +146,6 @@ const NetworkTab = () => {
});
}, [allRequests, networkFilters]);
const requestCounts = useMemo(() => {
return allRequests.reduce((counts, request) => {
const method = request.data?.request?.method?.toUpperCase() || 'GET';
counts[method] = (counts[method] || 0) + 1;
return counts;
}, {});
}, [allRequests]);
const handleFilterToggle = (method, enabled) => {
dispatch(updateNetworkFilter({ method, enabled }));
};
const handleToggleAllFilters = (enabled) => {
dispatch(toggleAllNetworkFilters(enabled));
};
const handleRequestClick = (request) => {
dispatch(setSelectedRequest(request));
};

View File

@@ -15,7 +15,7 @@ const StyledWrapper = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 16px;
padding: 2px 8px;
background: ${(props) => props.theme.console.headerBg};
border-bottom: 1px solid ${(props) => props.theme.console.border};
flex-shrink: 0;
@@ -27,7 +27,6 @@ const StyledWrapper = styled.div`
gap: 8px;
color: ${(props) => props.theme.console.titleColor};
font-size: ${(props) => props.theme.font.size.base};
font-weight: 500;
.request-time {
color: ${(props) => props.theme.console.countColor};
@@ -66,7 +65,7 @@ const StyledWrapper = styled.div`
display: flex;
align-items: center;
gap: 6px;
padding: 8px 16px;
padding: 4px 8px;
background: transparent;
border: none;
border-bottom: 2px solid transparent;
@@ -92,7 +91,7 @@ const StyledWrapper = styled.div`
flex: 1;
overflow-y: auto;
overflow-x: hidden;
padding: 16px;
padding: 8px;
min-height: 0;
height: 0;
}
@@ -170,8 +169,7 @@ const StyledWrapper = styled.div`
z-index: 10;
td {
padding: 8px 12px;
font-weight: 500;
padding: 4px 8px;
color: ${(props) => props.theme.console.titleColor};
text-transform: uppercase;
font-size: ${(props) => props.theme.font.size.xs};
@@ -198,7 +196,7 @@ const StyledWrapper = styled.div`
}
td {
padding: 8px 12px;
padding: 2px 8px;
vertical-align: top;
word-break: break-word;
}
@@ -256,11 +254,15 @@ const StyledWrapper = styled.div`
}
.response-body-container {
border-radius: 4px;
border-radius: ${(props) => props.theme.border.radius.sm};
overflow: hidden;
height: 400px;
display: flex;
flex-direction: column;
pre {
padding: 8px !important;
}
.w-full.h-full.relative.flex {
height: 100% !important;
@@ -270,7 +272,7 @@ const StyledWrapper = styled.div`
}
div[role="tablist"] {
padding: 8px 12px;
padding: 4px 8px;
border-bottom: 1px solid ${(props) => props.theme.console.border};
display: flex !important;
gap: 8px !important;

View File

@@ -13,7 +13,7 @@ const StyledWrapper = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 16px;
padding: 0 8px;
background: ${(props) => props.theme.console.headerBg};
border-bottom: 1px solid ${(props) => props.theme.console.border};
flex-shrink: 0;
@@ -30,7 +30,7 @@ const StyledWrapper = styled.div`
display: flex;
align-items: center;
gap: 6px;
padding: 6px 12px;
padding: 4px 8px;
background: transparent;
border: none;
border-bottom: 2px solid transparent;
@@ -38,8 +38,6 @@ const StyledWrapper = styled.div`
cursor: pointer;
transition: all 0.2s ease;
font-size: ${(props) => props.theme.font.size.sm};
font-weight: 500;
border-radius: 4px 4px 0 0;
&:hover {
background: ${(props) => props.theme.console.buttonHoverBg};
@@ -47,9 +45,9 @@ const StyledWrapper = styled.div`
}
&.active {
color: ${(props) => props.theme.console.checkboxColor};
border-bottom-color: ${(props) => props.theme.console.checkboxColor};
background: ${(props) => props.theme.console.contentBg};
color: ${(props) => props.theme.primary.strong};
border-bottom-color: ${(props) => props.theme.primary.strong};
background: ${(props) => props.theme.background.mantle};
}
}
@@ -144,9 +142,6 @@ const StyledWrapper = styled.div`
display: flex;
align-items: center;
gap: 4px;
margin-right: 8px;
padding-right: 8px;
border-right: 1px solid ${(props) => props.theme.console.border};
}
.action-controls {
@@ -159,23 +154,21 @@ const StyledWrapper = styled.div`
display: flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
width: 24px;
height: 24px;
background: transparent;
border: none;
border-radius: 4px;
color: ${(props) => props.theme.console.buttonColor};
color: ${(props) => props.theme.text};
cursor: pointer;
transition: all 0.2s ease;
&:hover {
background: ${(props) => props.theme.console.buttonHoverBg};
color: ${(props) => props.theme.console.buttonHoverColor};
background: ${(props) => props.theme.background.surface0};
}
&.close-button:hover {
background: #e81123;
color: white;
background: ${(props) => props.theme.background.surface0};
}
}
@@ -187,19 +180,17 @@ const StyledWrapper = styled.div`
display: flex;
align-items: center;
gap: 6px;
padding: 6px 8px;
padding: 2px 8px;
background: transparent;
border: 1px solid ${(props) => props.theme.console.border};
border-radius: 4px;
color: ${(props) => props.theme.console.buttonColor};
border: 1px solid ${(props) => props.theme.border.border0};
border-radius: ${(props) => props.theme.border.radius.sm};
color: ${(props) => props.theme.text};
cursor: pointer;
transition: all 0.2s ease;
font-size: ${(props) => props.theme.font.size.sm};
&:hover {
background: ${(props) => props.theme.console.buttonHoverBg};
color: ${(props) => props.theme.console.buttonHoverColor};
border-color: ${(props) => props.theme.console.border};
background: ${(props) => props.theme.background.surface0};
}
.filter-summary {
@@ -232,7 +223,7 @@ const StyledWrapper = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 12px;
padding: 4px 12px;
background: ${(props) => props.theme.console.dropdownHeaderBg};
border-bottom: 1px solid ${(props) => props.theme.console.border};
font-size: ${(props) => props.theme.font.size.sm};
@@ -263,7 +254,7 @@ const StyledWrapper = styled.div`
.filter-option {
display: flex;
align-items: center;
padding: 6px 12px;
padding: 4px 12px;
cursor: pointer;
transition: background-color 0.2s ease;
@@ -325,20 +316,6 @@ const StyledWrapper = styled.div`
.logs-container {
padding: 8px 0;
}
.method-badge {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 2px 6px;
border-radius: 4px;
font-size: 10px;
font-weight: 500;
color: white;
text-transform: uppercase;
letter-spacing: 0.5px;
min-width: 45px;
}
.log-entry {
display: flex;

View File

@@ -5,10 +5,9 @@ import ToolHint from 'components/ToolHint/index';
const StyledSessionList = styled.div`
.session-list-item {
padding: 10px 12px;
padding: 2px 6px;
cursor: pointer;
border-bottom: 1px solid ${(props) => props.theme.border || 'rgba(255, 255, 255, 0.05)'};
transition: all 0.2s;
display: flex;
flex-direction: column;
gap: 4px;
@@ -24,7 +23,8 @@ const StyledSessionList = styled.div`
&.active {
background: ${(props) => props.theme.sidebarActive || 'rgba(59, 142, 234, 0.12)'};
border-left: 2px solid ${(props) => props.theme.brandColor || '#3b8eea'};
border-left: 2px solid ${(props) => props.theme.primary.subtle};
padding-left: 4px;
}
&:last-child {

View File

@@ -30,7 +30,7 @@ const StyledWrapper = styled.div`
}
.terminal-sessions-header {
padding: 12px;
padding: 6px 8px;
font-weight: 600;
font-size: 13px;
color: ${(props) => props.theme.text};

View File

@@ -182,19 +182,6 @@ const NetworkFilterDropdown = ({ filters, requestCounts, onFilterToggle, onToggl
const allFiltersEnabled = Object.values(filters).every((f) => f);
const activeFilters = Object.entries(filters).filter(([_, enabled]) => enabled);
const getMethodColor = (method) => {
switch (method?.toUpperCase()) {
case 'GET': return '#10b981';
case 'POST': return '#8b5cf6';
case 'PUT': return '#f59e0b';
case 'DELETE': return '#ef4444';
case 'PATCH': return '#06b6d4';
case 'HEAD': return '#6b7280';
case 'OPTIONS': return '#84cc16';
default: return '#6b7280';
}
};
useEffect(() => {
const handleClickOutside = (event) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
@@ -241,9 +228,6 @@ const NetworkFilterDropdown = ({ filters, requestCounts, onFilterToggle, onToggl
onChange={(e) => onFilterToggle(method, e.target.checked)}
/>
<div className="filter-option-content">
<span className="method-badge" style={{ backgroundColor: getMethodColor(method) }}>
{method}
</span>
<span className="filter-option-label">{method}</span>
<span className="filter-option-count">({requestCounts[method] || 0})</span>
</div>

View File

@@ -1,6 +1,8 @@
import React, { useCallback, useEffect, useState } from 'react';
import React, { useCallback, useEffect, useState, useMemo } from 'react';
import { useSelector } from 'react-redux';
import { darken } from 'polished';
import Console from './Console';
import { useTheme } from 'providers/Theme';
const MIN_DEVTOOLS_HEIGHT = 150;
const MAX_DEVTOOLS_HEIGHT = window.innerHeight * 0.7;
@@ -10,6 +12,9 @@ const Devtools = ({ mainSectionRef }) => {
const isDevtoolsOpen = useSelector((state) => state.logs.isConsoleOpen);
const [devtoolsHeight, setDevtoolsHeight] = useState(DEFAULT_DEVTOOLS_HEIGHT);
const [isResizingDevtools, setIsResizingDevtools] = useState(false);
const { theme } = useTheme();
const dragHandleColor = useMemo(() => darken(0.1, theme.primary.subtle), [theme.primary.subtle]);
const handleDevtoolsResizeStart = useCallback((e) => {
e.preventDefault();
@@ -68,15 +73,15 @@ const Devtools = ({ mainSectionRef }) => {
<div
onMouseDown={handleDevtoolsResizeStart}
style={{
height: '4px',
height: '2px',
cursor: 'row-resize',
backgroundColor: isResizingDevtools ? '#0078d4' : 'transparent',
backgroundColor: isResizingDevtools ? dragHandleColor : 'transparent',
transition: 'background-color 0.2s ease',
zIndex: 20,
position: 'relative'
}}
onMouseEnter={(e) => e.target.style.backgroundColor = '#0078d4'}
onMouseLeave={(e) => e.target.style.backgroundColor = isResizingDevtools ? '#0078d4' : 'transparent'}
onMouseEnter={(e) => e.target.style.backgroundColor = dragHandleColor}
onMouseLeave={(e) => e.target.style.backgroundColor = isResizingDevtools ? dragHandleColor : 'transparent'}
/>
<div style={{ height: `${devtoolsHeight}px`, overflow: 'hidden', position: 'relative' }}>
<Console />

View File

@@ -18,7 +18,7 @@ export const palette = {
TEAL: 'hsl(170, 70%, 60%)',
CYAN: 'hsl(190, 82%, 72%)',
BLUE: 'hsl(210, 90%, 76%)',
INDIGO: 'hsl(202, 88%, 66%)',
INDIGO: 'hsl(202, 88%, 72%)',
VIOLET: 'hsl(260, 75%, 78%)',
PURPLE: 'hsl(285, 72%, 75%)',
PINK: 'hsl(305, 59%, 74%)'
@@ -470,7 +470,7 @@ const darkTheme = {
console: {
bg: '#1e1e1e',
headerBg: '#2d2d30',
headerBg: '#242424',
contentBg: '#1e1e1e',
border: '#3c3c3c',
titleColor: '#cccccc',

View File

@@ -431,7 +431,7 @@ const lightPastelTheme = {
console: {
bg: colors.GRAY_1,
headerBg: colors.GRAY_1,
contentBg: colors.WHITE,
contentBg: colors.BACKGROUND,
border: colors.GRAY_3,
titleColor: colors.TEXT,
countColor: colors.TEXT_MUTED,
@@ -444,7 +444,7 @@ const lightPastelTheme = {
logHoverBg: colors.GRAY_1,
resizeHandleHover: colors.BRAND,
resizeHandleActive: colors.BRAND,
dropdownBg: colors.WHITE,
dropdownBg: colors.BACKGROUND,
dropdownHeaderBg: colors.GRAY_1,
optionHoverBg: colors.GRAY_1,
optionLabelColor: colors.TEXT,