Compare commits

..

1 Commits

Author SHA1 Message Date
Bijin A B
33439b3840 chore: playwright fix 2025-12-25 14:47:56 +05:30
597 changed files with 6897 additions and 26857 deletions

View File

@@ -58,8 +58,6 @@ jobs:
run: npm run test --workspace=packages/bruno-converters
- name: Test Package bruno-electron
run: npm run test --workspace=packages/bruno-electron
- name: Test Package bruno-requests
run: npm run test --workspace=packages/bruno-requests
cli-test:
name: CLI Tests

1
.gitignore vendored
View File

@@ -48,7 +48,6 @@ yarn-error.log*
bruno.iml
.idea
.vscode
.cursor
# Playwright
/blob-report/

Binary file not shown.

Before

Width:  |  Height:  |  Size: 346 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 347 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 584 KiB

After

Width:  |  Height:  |  Size: 813 KiB

View File

@@ -3,7 +3,7 @@
### برونو - بيئة تطوير مفتوحة المصدر لاستكشاف واختبار واجهات برمجة التطبيقات (APIs).
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%2Fbruno)
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%bruno)
[![CI](https://github.com/usebruno/bruno/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/actions/workflows/tests.yml)
[![Commit Activity](https://img.shields.io/github/commit-activity/m/usebruno/bruno)](https://github.com/usebruno/bruno/pulse)
[![X](https://img.shields.io/twitter/follow/use_bruno?style=social&logo=x)](https://twitter.com/use_bruno)

View File

@@ -3,7 +3,7 @@
### ব্রুনো - API অন্বেষণ এবং পরীক্ষা করার জন্য ওপেনসোর্স IDE।
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%2Fbruno)
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%bruno)
[![CI](https://github.com/usebruno/bruno/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/actions/workflows/tests.yml)
[![Commit Activity](https://img.shields.io/github/commit-activity/m/usebruno/bruno)](https://github.com/usebruno/bruno/pulse)
[![X](https://img.shields.io/twitter/follow/use_bruno?style=social&logo=x)](https://twitter.com/use_bruno)

View File

@@ -3,7 +3,7 @@
### Bruno - 开源 IDE用于探索和测试 API。
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%2Fbruno)
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%bruno)
[![CI](https://github.com/usebruno/bruno/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/actions/workflows/tests.yml)
[![Commit Activity](https://img.shields.io/github/commit-activity/m/usebruno/bruno)](https://github.com/usebruno/bruno/pulse)
[![X](https://img.shields.io/twitter/follow/use_bruno?style=social&logo=x)](https://twitter.com/use_bruno)

View File

@@ -3,7 +3,7 @@
### Bruno - Opensource IDE zum Erkunden und Testen von APIs.
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%2Fbruno)
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%bruno)
[![CI](https://github.com/usebruno/bruno/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/actions/workflows/tests.yml)
[![Commit Activity](https://img.shields.io/github/commit-activity/m/usebruno/bruno)](https://github.com/usebruno/bruno/pulse)
[![X](https://img.shields.io/twitter/follow/use_bruno?style=social&logo=x)](https://twitter.com/use_bruno)

View File

@@ -3,7 +3,7 @@
### Bruno - IDE de código abierto para explorar y probar APIs.
[![Versión en Github](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%2Fbruno)
[![Versión en Github](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%bruno)
[![CI](https://github.com/usebruno/bruno/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/actions/workflows/tests.yml)
[![Actividad de Commits](https://img.shields.io/github/commit-activity/m/usebruno/bruno)](https://github.com/usebruno/bruno/pulse)
[![X](https://img.shields.io/twitter/follow/use_bruno?style=social&logo=x)](https://twitter.com/use_bruno)

View File

@@ -3,7 +3,7 @@
### برونو یا Bruno - محیط توسعه متن باز برای تست و توسعه API ها
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%2Fbruno)
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%bruno)
[![CI](https://github.com/usebruno/bruno/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/actions/workflows/tests.yml)
[![Commit Activity](https://img.shields.io/github/commit-activity/m/usebruno/bruno)](https://github.com/usebruno/bruno/pulse)
[![X](https://img.shields.io/twitter/follow/use_bruno?style=social&logo=x)](https://twitter.com/use_bruno)

View File

@@ -3,7 +3,7 @@
### Bruno - IDE Opensource pour explorer et tester des APIs.
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%2Fbruno)
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%bruno)
[![CI](https://github.com/usebruno/bruno/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/actions/workflows/tests.yml)
[![Commit Activity](https://img.shields.io/github/commit-activity/m/usebruno/bruno)](https://github.com/usebruno/bruno/pulse)
[![X](https://img.shields.io/twitter/follow/use_bruno?style=social&logo=x)](https://twitter.com/use_bruno)

View File

@@ -3,7 +3,7 @@
### Bruno - Opensource IDE per esplorare e testare gli APIs.
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%2Fbruno)
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%bruno)
[![CI](https://github.com/usebruno/bruno/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/actions/workflows/tests.yml)
[![Commit Activity](https://img.shields.io/github/commit-activity/m/usebruno/bruno)](https://github.com/usebruno/bruno/pulse)
[![X](https://img.shields.io/twitter/follow/use_bruno?style=social&logo=x)](https://twitter.com/use_bruno)

View File

@@ -3,7 +3,7 @@
### Bruno - API の検証・動作テストのためのオープンソース IDE.
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%2Fbruno)
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%bruno)
[![CI](https://github.com/usebruno/bruno/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/actions/workflows/tests.yml)
[![Commit Activity](https://img.shields.io/github/commit-activity/m/usebruno/bruno)](https://github.com/usebruno/bruno/pulse)
[![X](https://img.shields.io/twitter/follow/use_bruno?style=social&logo=x)](https://twitter.com/use_bruno)

View File

@@ -3,7 +3,7 @@
### ბრუნო - ღია წყაროების IDE API-ების შესწავლისა და ტესტირებისათვის.
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%2Fbruno)
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%bruno)
[![CI](https://github.com/usebruno/bruno/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/actions/workflows/tests.yml)
[![Commit Activity](https://img.shields.io/github/commit-activity/m/usebruno/bruno)](https://github.com/usebruno/bruno/pulse)
[![X](https://img.shields.io/twitter/follow/use_bruno?style=social&logo=x)](https://twitter.com/use_bruno)

View File

@@ -3,7 +3,7 @@
### Bruno - API 탐색 및 테스트를 위한 오픈소스 IDE.
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%2Fbruno)
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%bruno)
[![CI](https://github.com/usebruno/bruno/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/actions/workflows/tests.yml)
[![Commit Activity](https://img.shields.io/github/commit-activity/m/usebruno/bruno)](https://github.com/usebruno/bruno/pulse)
[![X](https://img.shields.io/twitter/follow/use_bruno?style=social&logo=x)](https://twitter.com/use_bruno)

View File

@@ -3,7 +3,7 @@
### Bruno - Open source IDE voor het verkennen en testen van API's.
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%2Fbruno)
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%bruno)
[![CI](https://github.com/usebruno/bruno/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/actions/workflows/tests.yml)
[![Commit Activity](https://img.shields.io/github/commit-activity/m/usebruno/bruno)](https://github.com/usebruno/bruno/pulse)
[![X](https://img.shields.io/twitter/follow/use_bruno?style=social&logo=x)](https://twitter.com/use_bruno)

View File

@@ -3,7 +3,7 @@
### Bruno - Otwartoźródłowe IDE do eksploracji i testów APIs.
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%2Fbruno)
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%bruno)
[![CI](https://github.com/usebruno/bruno/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/actions/workflows/tests.yml)
[![Commit Activity](https://img.shields.io/github/commit-activity/m/usebruno/bruno)](https://github.com/usebruno/bruno/pulse)
[![X](https://img.shields.io/twitter/follow/use_bruno?style=social&logo=x)](https://twitter.com/use_bruno)

View File

@@ -3,7 +3,7 @@
### Bruno - IDE de código aberto para explorar e testar APIs.
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%2Fbruno)
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%bruno)
[![CI](https://github.com/usebruno/bruno/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/actions/workflows/tests.yml)
[![Commit Activity](https://img.shields.io/github/commit-activity/m/usebruno/bruno)](https://github.com/usebruno/bruno/pulse)
[![X](https://img.shields.io/twitter/follow/use_bruno?style=social&logo=x)](https://twitter.com/use_bruno)

View File

@@ -3,7 +3,7 @@
### Bruno - Mediu integrat de dezvoltare cu sursă deschisă pentru explorarea și testarea API-urilor.
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%2Fbruno)
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%bruno)
[![CI](https://github.com/usebruno/bruno/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/actions/workflows/tests.yml)
[![Commit Activity](https://img.shields.io/github/commit-activity/m/usebruno/bruno)](https://github.com/usebruno/bruno/pulse)
[![X](https://img.shields.io/twitter/follow/use_bruno?style=social&logo=x)](https://twitter.com/use_bruno)

View File

@@ -3,7 +3,7 @@
### Bruno - IDE с открытым исходным кодом для изучения и тестирования API.
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%2Fbruno)
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%bruno)
[![CI](https://github.com/usebruno/bruno/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/actions/workflows/tests.yml)
[![Commit Activity](https://img.shields.io/github/commit-activity/m/usebruno/bruno)](https://github.com/usebruno/bruno/pulse)
[![X](https://img.shields.io/twitter/follow/use_bruno?style=social&logo=x)](https://twitter.com/use_bruno)

View File

@@ -3,7 +3,7 @@
### Bruno - API'leri keşfetmek ve test etmek için açık kaynaklı IDE.
[![GitHub sürümü](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%2Fbruno)
[![GitHub sürümü](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%bruno)
[![CI](https://github.com/usebruno/bruno/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/actions/workflows/tests.yml)
[![Commit Activity](https://img.shields.io/github/commit-activity/m/usebruno/bruno)](https://github.com/usebruno/bruno/pulse)
[![X](https://img.shields.io/twitter/follow/use_bruno?style=social&logo=x)](https://twitter.com/use_bruno)

View File

@@ -3,7 +3,7 @@
### Bruno - IDE із відкритим кодом для тестування та дослідження API
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%2Fbruno)
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%bruno)
[![CI](https://github.com/usebruno/bruno/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/actions/workflows/tests.yml)
[![Commit Activity](https://img.shields.io/github/commit-activity/m/usebruno/bruno)](https://github.com/usebruno/bruno/pulse)
[![X](https://img.shields.io/twitter/follow/use_bruno?style=social&logo=x)](https://twitter.com/use_bruno)

View File

@@ -3,7 +3,7 @@
### Bruno - 探索和測試 API 的開源 IDE 工具
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%2Fbruno)
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%bruno)
[![CI](https://github.com/usebruno/bruno/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/actions/workflows/tests.yml)
[![Commit Activity](https://img.shields.io/github/commit-activity/m/usebruno/bruno)](https://github.com/usebruno/bruno/pulse)
[![X](https://img.shields.io/twitter/follow/use_bruno?style=social&logo=x)](https://twitter.com/use_bruno)

653
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -20,16 +20,17 @@
],
"homepage": "https://usebruno.com",
"devDependencies": {
"@eslint/compat": "^1.3.2",
"@faker-js/faker": "^7.6.0",
"@jest/globals": "^29.2.0",
"@opencollection/types": "~0.7.0",
"@playwright/test": "^1.51.1",
"@rollup/plugin-json": "^6.1.0",
"@storybook/addon-webpack5-compiler-babel": "^4.0.0",
"@storybook/builder-webpack5": "^10.1.10",
"@storybook/react": "^10.1.10",
"@storybook/react-webpack5": "^10.1.10",
"storybook": "^10.1.10",
"@eslint/compat": "^1.3.2",
"@faker-js/faker": "^7.6.0",
"@jest/globals": "^29.2.0",
"@opencollection/types": "0.3.0",
"@playwright/test": "^1.51.1",
"@rollup/plugin-json": "^6.1.0",
"@stylistic/eslint-plugin": "^5.3.1",
"@types/jest": "^29.5.11",
"@types/lodash-es": "^4.17.12",
@@ -48,13 +49,12 @@
"pretty-quick": "^3.1.3",
"randomstring": "^1.2.2",
"rimraf": "^6.0.1",
"storybook": "^10.1.10",
"ts-jest": "^29.2.6"
},
"scripts": {
"setup": "node ./scripts/setup.js",
"watch:converters": "npm run watch --workspace=packages/bruno-converters",
"dev": "node ./scripts/dev.js",
"dev": "concurrently --kill-others \"npm run dev:web\" \"npm run dev:electron\"",
"watch": "npm run dev:watch",
"dev:watch": "node ./scripts/dev-hot-reload.js",
"dev:web": "npm run dev --workspace=packages/bruno-app",
@@ -62,7 +62,6 @@
"prettier:web": "npm run prettier --workspace=packages/bruno-app",
"dev:electron": "npm run dev --workspace=packages/bruno-electron",
"dev:electron:debug": "npm run debug --workspace=packages/bruno-electron",
"storybook": "npm run storybook --workspace=packages/bruno-app",
"build:bruno-common": "npm run build --workspace=packages/bruno-common",
"build:bruno-requests": "npm run build --workspace=packages/bruno-requests",
"build:bruno-filestore": "npm run build --workspace=packages/bruno-filestore",
@@ -98,8 +97,5 @@
"json-schema-typed": "8.0.1"
}
}
},
"dependencies": {
"ajv": "^8.17.1"
}
}

View File

@@ -34,5 +34,4 @@ yarn-error.log*
.next/
dist/
.env
storybook-static/
.env

View File

@@ -52,7 +52,6 @@
"json5": "^2.2.3",
"jsonc-parser": "^3.2.1",
"jsonpath-plus": "^10.3.0",
"jsonschema": "^1.5.0",
"know-your-http-well": "^0.5.0",
"linkify-it": "^5.0.0",
"lodash": "^4.17.21",
@@ -84,7 +83,6 @@
"react-player": "^2.16.0",
"react-redux": "^7.2.9",
"react-tooltip": "^5.5.2",
"react-virtuoso": "^4.18.1",
"sass": "^1.46.0",
"semver": "^7.7.1",
"shell-quote": "^1.8.3",

View File

@@ -55,10 +55,10 @@ const StyledWrapper = styled.div`
}
.cm-variable-valid {
color: ${(props) => props.theme.codemirror.variable.valid};
color: green;
}
.cm-variable-invalid {
color: ${(props) => props.theme.codemirror.variable.invalid};
color: red;
}
`;

View File

@@ -37,7 +37,7 @@ const FileEditor = ({ apiSpec }) => {
/>
<IconDeviceFloppy
onClick={onSave}
color={hasChanges ? theme.draftColor : theme.requestTabs.icon.color}
color={hasChanges ? theme.colors.text.yellow : theme.requestTabs.icon.color}
strokeWidth={1.5}
size={22}
className={`absolute right-0 top-0 m-4 ${

View File

@@ -7,7 +7,7 @@ const StyledWrapper = styled.div`
}
div.dropdown-item.menu-item {
color: ${(props) => props.theme.colors.text.danger};
color: ${(props) => props.theme.colors.danger};
&:hover {
background-color: ${(props) => props.theme.colors.bg.danger};
color: white;

View File

@@ -246,6 +246,12 @@ const Wrapper = styled.div`
color: white;
}
}
.dropdown-item-active {
font-weight: 400 !important;
background-color: ${(props) => props.theme.dropdown.selectedBg} !important;
color: ${(props) => props.theme.dropdown.selectedColor} !important;
}
`;
export default Wrapper;

View File

@@ -18,9 +18,9 @@ import ImportWorkspace from 'components/WorkspaceSidebar/ImportWorkspace';
import IconBottombarToggle from 'components/Icons/IconBottombarToggle/index';
import StyledWrapper from './StyledWrapper';
import { toTitleCase } from 'utils/common/index';
import ResponseLayoutToggle from 'components/ResponsePane/ResponseLayoutToggle';
import { isMacOS, isWindowsOS, isLinuxOS } from 'utils/common/platform';
import classNames from 'classnames';
const getOsClass = () => {
if (isMacOS()) return 'os-mac';
@@ -29,12 +29,6 @@ const getOsClass = () => {
return 'os-other';
};
// Helper to get display name for workspace
export const getWorkspaceDisplayName = (name) => {
if (!name) return 'Untitled Workspace';
return name;
};
const AppTitleBar = () => {
const dispatch = useDispatch();
const [isFullScreen, setIsFullScreen] = useState(false);
@@ -121,7 +115,7 @@ const AppTitleBar = () => {
const WorkspaceName = forwardRef((props, ref) => {
return (
<div ref={ref} className="workspace-name-container" {...props}>
<span data-testid="workspace-name" className={classNames('workspace-name', { 'italic text-muted': !activeWorkspace?.name })}>{getWorkspaceDisplayName(activeWorkspace?.name)}</span>
<span className="workspace-name">{toTitleCase(activeWorkspace?.name) || 'Default Workspace'}</span>
<IconChevronDown size={14} stroke={1.5} className="chevron-icon" />
</div>
);
@@ -133,7 +127,7 @@ const AppTitleBar = () => {
const handleWorkspaceSwitch = (workspaceUid) => {
dispatch(switchWorkspace(workspaceUid));
toast.success(`Switched to ${getWorkspaceDisplayName(workspaces.find((w) => w.uid === workspaceUid)?.name)}`);
toast.success(`Switched to ${workspaces.find((w) => w.uid === workspaceUid)?.name}`);
};
const handleOpenWorkspace = async () => {
@@ -184,7 +178,7 @@ const AppTitleBar = () => {
return {
id: workspace.uid,
label: getWorkspaceDisplayName(workspace.name),
label: toTitleCase(workspace.name),
onClick: () => handleWorkspaceSwitch(workspace.uid),
className: `workspace-item ${isActive ? 'active' : ''}`,
rightSection: (
@@ -196,7 +190,11 @@ const AppTitleBar = () => {
label={isPinned ? 'Unpin workspace' : 'Pin workspace'}
size="sm"
>
{isPinned ? <IconPinned size={14} stroke={1.5} /> : <IconPin size={14} stroke={1.5} />}
{isPinned ? (
<IconPinned size={14} stroke={1.5} />
) : (
<IconPin size={14} stroke={1.5} />
)}
</ActionIcon>
)}
{isActive && <IconCheck size={16} stroke={1.5} className="check-icon" />}
@@ -249,7 +247,12 @@ const AppTitleBar = () => {
<div className="titlebar-content">
{/* Left section: Home + Workspace */}
<div className="titlebar-left">
<ActionIcon onClick={handleHomeClick} label="Home" size="lg" className="home-button">
<ActionIcon
onClick={handleHomeClick}
label="Home"
size="lg"
className="home-button"
>
<IconHome size={16} stroke={1.5} />
</ActionIcon>

View File

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

View File

@@ -1,5 +1,4 @@
import React, { useMemo } from 'react';
import get from 'lodash/get';
import CodeEditor from 'components/CodeEditor';
import { useTheme } from 'providers/Theme';
import { useSelector } from 'react-redux';
@@ -22,8 +21,7 @@ const BulkEditor = ({ params, onChange, onToggle, onSave, onRun }) => {
<CodeEditor
mode="text/plain"
theme={displayedTheme}
font={get(preferences, 'font.codeFont', 'default')}
fontSize={get(preferences, 'font.codeFontSize')}
font={preferences.codeFont || 'default'}
value={parsedParams}
onEdit={handleEdit}
onSave={onSave}

View File

@@ -36,12 +36,12 @@ const StyledWrapper = styled.div`
/* Style line numbers when there's a lint issue */
.CodeMirror-lint-line-error .CodeMirror-linenumber {
color: ${(props) => props.theme.colors.text.danger} !important;
color: #d32f2f !important;
text-decoration: underline;
}
.CodeMirror-lint-line-warning .CodeMirror-linenumber {
color: ${(props) => props.theme.colors.text.warning} !important;
color: #f57c00 !important;
text-decoration: underline;
}
@@ -116,7 +116,7 @@ const StyledWrapper = styled.div`
span.cm-atom {
color: ${(props) => props.theme.codemirror.tokens.atom} !important;
}
span.cm-variable, span.cm-variable-2 {
span.cm-variable {
color: ${(props) => props.theme.codemirror.tokens.variable} !important;
}
span.cm-keyword {
@@ -128,20 +128,14 @@ const StyledWrapper = styled.div`
span.cm-operator {
color: ${(props) => props.theme.codemirror.tokens.operator} !important;
}
span.cm-tag {
color: ${(props) => props.theme.codemirror.tokens.tag} !important;
}
span.cm-tag.cm-bracket {
color: ${(props) => props.theme.codemirror.tokens.tagBracket} !important;
}
}
/* Variable validation colors */
.cm-variable-valid {
color: ${(props) => props.theme.codemirror.variable.valid} !important;
color: #5fad89 !important; /* Soft sage */
}
.cm-variable-invalid {
color: ${(props) => props.theme.codemirror.variable.invalid} !important;
color: #d17b7b !important; /* Soft coral */
}
.CodeMirror-search-hint {

View File

@@ -1,5 +1,4 @@
import styled from 'styled-components';
import { rgba } from 'polished';
const StyledWrapper = styled.div`
.bruno-search-bar {
@@ -10,15 +9,15 @@ const StyledWrapper = styled.div`
display: flex;
align-items: center;
flex-wrap: nowrap;
gap: 0;
padding: 1px 3px;
padding: 0 2px;
min-height: 36px;
background: ${(props) => props.theme.sidebar.search.bg} !important;
border-radius: 4px;
border: 1px solid ${(props) => props.theme.sidebar.search.bg} !important;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
width: auto;
min-width: 180px;
max-width: 320px;
min-height: 22px;
background: ${(props) => props.theme.background.base};
color: ${(props) => props.theme.text.base};
border: solid 1px ${(props) => props.theme.border.border2};
border-radius: ${(props) => props.theme.border.radius.sm};
}
.bruno-search-bar input {
@@ -39,7 +38,7 @@ const StyledWrapper = styled.div`
padding: 0 1px;
margin: 0 1px;
cursor: pointer;
color: ${(props) => props.theme.colors.text.subtext1};
color: #aaa;
border-radius: 3px;
height: 18px;
width: 18px;
@@ -52,14 +51,27 @@ const StyledWrapper = styled.div`
min-width: 28px;
text-align: center;
font-size: ${(props) => props.theme.font.size.xs};
color: ${(props) => props.theme.colors.text.subtext1};
color: #aaa;
margin: 0 8px 0 1px;
white-space: nowrap;
}
.bruno-search-bar.compact {
background: ${(props) => props.theme.codemirror.bg};
color: ${(props) => props.theme.codemirror.text || props.theme.text};
border: none;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
border-radius: 4px;
padding: 1px 3px;
min-height: 22px;
display: flex;
align-items: center;
gap: 0;
}
.bruno-search-bar input {
background: transparent;
color: ${(props) => props.theme.colors.text.subtext2};
color: inherit;
border: none;
outline: none;
font-size: ${(props) => props.theme.font.size.base};
@@ -80,9 +92,7 @@ const StyledWrapper = styled.div`
}
.searchbar-icon-btn.active {
color: ${(props) => props.theme.brand};
background-color: ${(props) => rgba(props.theme.brand, 0.1)};
font-weight: 500;
color: #f39c12 !important;
}
`;

View File

@@ -166,7 +166,7 @@ const CodeMirrorSearch = ({ visible, editor, onClose }) => {
return (
<StyledWrapper>
<div className="bruno-search-bar">
<div className="bruno-search-bar compact">
<input
autoFocus
type="text"

View File

@@ -2,8 +2,7 @@ import styled from 'styled-components';
const Wrapper = styled.div`
label {
font-size: ${(props) => props.theme.font.size.sm};
color: ${(props) => props.theme.colors.text.subtext1};
font-size: ${(props) => props.theme.font.size.base};
}
.single-line-editor-wrapper {
@@ -14,8 +13,7 @@ const Wrapper = styled.div`
}
.auth-placement-selector {
font-size: ${(props) => props.theme.font.size.sm};
padding: 0.2rem 0px;
padding: 0.5rem 0px;
border-radius: 3px;
border: solid 1px ${(props) => props.theme.input.border};
background-color: ${(props) => props.theme.input.bg};
@@ -39,6 +37,7 @@ const Wrapper = styled.div`
.auth-type-label {
width: fit-content;
color: ${(props) => props.theme.colors.text.yellow};
justify-content: space-between;
padding: 0 0.5rem;
}

View File

@@ -57,31 +57,29 @@ const ApiKeyAuth = ({ collection }) => {
return (
<StyledWrapper className="mt-2 w-full">
<label className="block mb-1">Key</label>
<div className="single-line-editor-wrapper mb-3">
<label className="block font-medium mb-2">Key</label>
<div className="single-line-editor-wrapper mb-2">
<SingleLineEditor
value={apikeyAuth.key || ''}
theme={storedTheme}
onSave={handleSave}
onChange={(val) => handleAuthChange('key', val)}
collection={collection}
isCompact
/>
</div>
<label className="block mb-1">Value</label>
<div className="single-line-editor-wrapper mb-3">
<label className="block font-medium mb-2">Value</label>
<div className="single-line-editor-wrapper mb-2">
<SingleLineEditor
value={apikeyAuth.value || ''}
theme={storedTheme}
onSave={handleSave}
onChange={(val) => handleAuthChange('value', val)}
collection={collection}
isCompact
/>
</div>
<label className="block mb-1">Add To</label>
<label className="block font-medium mb-2">Add To</label>
<div className="inline-flex items-center cursor-pointer auth-placement-selector w-fit">
<Dropdown onCreate={onDropdownCreate} icon={<Icon />} placement="bottom-end">
<div

View File

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

View File

@@ -2,8 +2,7 @@ import styled from 'styled-components';
const Wrapper = styled.div`
label {
font-size: ${(props) => props.theme.font.size.sm};
color: ${(props) => props.theme.colors.text.subtext1};
font-size: ${(props) => props.theme.font.size.base};
}
.single-line-editor-wrapper {

View File

@@ -123,20 +123,19 @@ const AwsV4Auth = ({ collection }) => {
return (
<StyledWrapper className="mt-2 w-full">
<label className="block mb-1">Access Key ID</label>
<div className="single-line-editor-wrapper mb-3">
<label className="block font-medium mb-2">Access Key ID</label>
<div className="single-line-editor-wrapper mb-2">
<SingleLineEditor
value={awsv4Auth.accessKeyId || ''}
theme={storedTheme}
onSave={handleSave}
onChange={(val) => handleAccessKeyIdChange(val)}
collection={collection}
isCompact
/>
</div>
<label className="block mb-1">Secret Access Key</label>
<div className="single-line-editor-wrapper mb-3 flex items-center">
<label className="block font-medium mb-2">Secret Access Key</label>
<div className="single-line-editor-wrapper mb-2 flex items-center">
<SingleLineEditor
value={awsv4Auth.secretAccessKey || ''}
theme={storedTheme}
@@ -144,56 +143,51 @@ const AwsV4Auth = ({ collection }) => {
onChange={(val) => handleSecretAccessKeyChange(val)}
collection={collection}
isSecret={true}
isCompact
/>
{showWarning && <SensitiveFieldWarning fieldName="awsv4-secret-access-key" warningMessage={warningMessage} />}
</div>
<label className="block mb-1">Session Token</label>
<div className="single-line-editor-wrapper mb-3">
<label className="block font-medium mb-2">Session Token</label>
<div className="single-line-editor-wrapper mb-2">
<SingleLineEditor
value={awsv4Auth.sessionToken || ''}
theme={storedTheme}
onSave={handleSave}
onChange={(val) => handleSessionTokenChange(val)}
collection={collection}
isCompact
/>
</div>
<label className="block mb-1">Service</label>
<div className="single-line-editor-wrapper mb-3">
<label className="block font-medium mb-2">Service</label>
<div className="single-line-editor-wrapper mb-2">
<SingleLineEditor
value={awsv4Auth.service || ''}
theme={storedTheme}
onSave={handleSave}
onChange={(val) => handleServiceChange(val)}
collection={collection}
isCompact
/>
</div>
<label className="block mb-1">Region</label>
<div className="single-line-editor-wrapper mb-3">
<label className="block font-medium mb-2">Region</label>
<div className="single-line-editor-wrapper mb-2">
<SingleLineEditor
value={awsv4Auth.region || ''}
theme={storedTheme}
onSave={handleSave}
onChange={(val) => handleRegionChange(val)}
collection={collection}
isCompact
/>
</div>
<label className="block mb-1">Profile Name</label>
<div className="single-line-editor-wrapper">
<label className="block font-medium mb-2">Profile Name</label>
<div className="single-line-editor-wrapper mb-2">
<SingleLineEditor
value={awsv4Auth.profileName || ''}
theme={storedTheme}
onSave={handleSave}
onChange={(val) => handleProfileNameChange(val)}
collection={collection}
isCompact
/>
</div>
</StyledWrapper>

View File

@@ -2,8 +2,7 @@ import styled from 'styled-components';
const Wrapper = styled.div`
label {
font-size: ${(props) => props.theme.font.size.sm};
color: ${(props) => props.theme.colors.text.subtext1};
font-size: ${(props) => props.theme.font.size.base};
}
.single-line-editor-wrapper {

View File

@@ -47,19 +47,18 @@ const BasicAuth = ({ collection }) => {
return (
<StyledWrapper className="mt-2 w-full">
<label className="block mb-1">Username</label>
<div className="single-line-editor-wrapper mb-3">
<label className="block font-medium mb-2">Username</label>
<div className="single-line-editor-wrapper mb-2">
<SingleLineEditor
value={basicAuth.username || ''}
theme={storedTheme}
onSave={handleSave}
onChange={(val) => handleUsernameChange(val)}
collection={collection}
isCompact
/>
</div>
<label className="block mb-1">Password</label>
<label className="block font-medium mb-2">Password</label>
<div className="single-line-editor-wrapper flex items-center">
<SingleLineEditor
value={basicAuth.password || ''}
@@ -68,7 +67,6 @@ const BasicAuth = ({ collection }) => {
onChange={(val) => handlePasswordChange(val)}
collection={collection}
isSecret={true}
isCompact
/>
{showWarning && <SensitiveFieldWarning fieldName="basic-password" warningMessage={warningMessage} />}
</div>

View File

@@ -2,8 +2,7 @@ import styled from 'styled-components';
const Wrapper = styled.div`
label {
font-size: ${(props) => props.theme.font.size.sm};
color: ${(props) => props.theme.colors.text.subtext1};
font-size: ${(props) => props.theme.font.size.base};
}
.single-line-editor-wrapper {

View File

@@ -33,7 +33,7 @@ const BearerAuth = ({ collection }) => {
return (
<StyledWrapper className="mt-2 w-full">
<label className="block mb-1">Token</label>
<label className="block font-medium mb-2">Token</label>
<div className="single-line-editor-wrapper flex items-center">
<SingleLineEditor
value={bearerToken}
@@ -42,7 +42,6 @@ const BearerAuth = ({ collection }) => {
onChange={(val) => handleTokenChange(val)}
collection={collection}
isSecret={true}
isCompact
/>
{showWarning && <SensitiveFieldWarning fieldName="bearer-token" warningMessage={warningMessage} />}
</div>

View File

@@ -2,8 +2,7 @@ import styled from 'styled-components';
const Wrapper = styled.div`
label {
font-size: ${(props) => props.theme.font.size.sm};
color: ${(props) => props.theme.colors.text.subtext1};
font-size: ${(props) => props.theme.font.size.base};
}
.single-line-editor-wrapper {

View File

@@ -47,19 +47,18 @@ const DigestAuth = ({ collection }) => {
return (
<StyledWrapper className="mt-2 w-full">
<label className="block mb-1">Username</label>
<div className="single-line-editor-wrapper mb-3">
<label className="block font-medium mb-2">Username</label>
<div className="single-line-editor-wrapper mb-2">
<SingleLineEditor
value={digestAuth.username || ''}
theme={storedTheme}
onSave={handleSave}
onChange={(val) => handleUsernameChange(val)}
collection={collection}
isCompact
/>
</div>
<label className="block mb-1">Password</label>
<label className="block font-medium mb-2">Password</label>
<div className="single-line-editor-wrapper flex items-center">
<SingleLineEditor
value={digestAuth.password || ''}
@@ -68,7 +67,6 @@ const DigestAuth = ({ collection }) => {
onChange={(val) => handlePasswordChange(val)}
collection={collection}
isSecret={true}
isCompact
/>
{showWarning && <SensitiveFieldWarning fieldName="digest-password" warningMessage={warningMessage} />}
</div>

View File

@@ -2,8 +2,7 @@ import styled from 'styled-components';
const Wrapper = styled.div`
label {
font-size: ${(props) => props.theme.font.size.sm};
color: ${(props) => props.theme.colors.text.subtext1};
font-size: ${(props) => props.theme.font.size.base};
}
.single-line-editor-wrapper {

View File

@@ -64,20 +64,19 @@ const NTLMAuth = ({ collection }) => {
return (
<StyledWrapper className="mt-2 w-full">
<label className="block mb-1">Username</label>
<div className="single-line-editor-wrapper mb-3">
<label className="block font-medium mb-2">Username</label>
<div className="single-line-editor-wrapper mb-2">
<SingleLineEditor
value={ntlmAuth.username || ''}
theme={storedTheme}
onSave={handleSave}
onChange={(val) => handleUsernameChange(val)}
collection={collection}
isCompact
/>
</div>
<label className="block mb-1">Password</label>
<div className="single-line-editor-wrapper mb-3 flex items-center">
<label className="block font-medium mb-2">Password</label>
<div className="single-line-editor-wrapper flex items-center">
<SingleLineEditor
value={ntlmAuth.password || ''}
theme={storedTheme}
@@ -85,12 +84,11 @@ const NTLMAuth = ({ collection }) => {
onChange={(val) => handlePasswordChange(val)}
collection={collection}
isSecret={true}
isCompact
/>
{showWarning && <SensitiveFieldWarning fieldName="ntlm-password" warningMessage={warningMessage} />}
</div>
<label className="block mb-1">Domain</label>
<label className="block font-medium mb-2">Domain</label>
<div className="single-line-editor-wrapper">
<SingleLineEditor
value={ntlmAuth.domain || ''}
@@ -98,7 +96,6 @@ const NTLMAuth = ({ collection }) => {
onSave={handleSave}
onChange={(val) => handleDomainChange(val)}
collection={collection}
isCompact
/>
</div>
</StyledWrapper>

View File

@@ -2,8 +2,7 @@ import styled from 'styled-components';
const Wrapper = styled.div`
label {
font-size: ${(props) => props.theme.font.size.sm};
color: ${(props) => props.theme.colors.text.subtext1};
font-size: ${(props) => props.theme.font.size.base};
}
.single-line-editor-wrapper {

View File

@@ -47,19 +47,18 @@ const WsseAuth = ({ collection }) => {
return (
<StyledWrapper className="mt-2 w-full">
<label className="block mb-1">Username</label>
<div className="single-line-editor-wrapper mb-3">
<label className="block font-medium mb-2">Username</label>
<div className="single-line-editor-wrapper mb-2">
<SingleLineEditor
value={wsseAuth.username || ''}
theme={storedTheme}
onSave={handleSave}
onChange={(val) => handleUserChange(val)}
collection={collection}
isCompact
/>
</div>
<label className="block mb-1">Password</label>
<label className="block font-medium mb-2">Password</label>
<div className="single-line-editor-wrapper flex items-center">
<SingleLineEditor
value={wsseAuth.password || ''}
@@ -68,7 +67,6 @@ const WsseAuth = ({ collection }) => {
onChange={(val) => handlePasswordChange(val)}
collection={collection}
isSecret={true}
isCompact
/>
{showWarning && <SensitiveFieldWarning fieldName="wsse-password" warningMessage={warningMessage} />}
</div>

View File

@@ -30,11 +30,11 @@ const StyledWrapper = styled.div`
box-shadow: none;
transition: border-color ease-in-out 0.1s;
border-radius: 3px;
background-color: ${(props) => props.theme.input.bg};
border: 1px solid ${(props) => props.theme.input.border};
background-color: ${(props) => props.theme.modal.input.bg};
border: 1px solid ${(props) => props.theme.modal.input.border};
&:focus {
border: solid 1px ${(props) => props.theme.input.focusBorder} !important;
border: solid 1px ${(props) => props.theme.modal.input.focusBorder} !important;
outline: none !important;
}
}

View File

@@ -9,8 +9,6 @@ import Markdown from 'components/MarkDown';
import CodeEditor from 'components/CodeEditor';
import StyledWrapper from './StyledWrapper';
import { IconEdit, IconX, IconFileText } from '@tabler/icons';
import Button from 'ui/Button/index';
import ActionIcon from 'ui/ActionIcon/index';
const Docs = ({ collection }) => {
const dispatch = useDispatch();
@@ -57,17 +55,17 @@ const Docs = ({ collection }) => {
<div className="flex flex-row gap-2 items-center justify-center">
{isEditing ? (
<>
<Button type="button" color="secondary" onClick={handleDiscardChanges}>
Cancel
</Button>
<Button type="button" onClick={onSave}>
<div className="editing-mode" role="tab" onClick={handleDiscardChanges}>
<IconX className="cursor-pointer" size={20} strokeWidth={1.5} />
</div>
<button type="submit" className="submit btn btn-sm btn-secondary" onClick={onSave}>
Save
</Button>
</button>
</>
) : (
<ActionIcon className="editing-mode" onClick={toggleViewMode}>
<IconEdit className="cursor-pointer" size={16} strokeWidth={1.5} />
</ActionIcon>
<div className="editing-mode" role="tab" onClick={toggleViewMode}>
<IconEdit className="cursor-pointer" size={20} strokeWidth={1.5} />
</div>
)}
</div>
</div>

View File

@@ -1,53 +0,0 @@
import styled from 'styled-components';
import { rgba } from 'polished';
const StyledWrapper = styled.div`
.icon-box {
&.location {
background-color: ${(props) => rgba(props.theme.textLink, 0.08)};
border: 1px solid ${(props) => rgba(props.theme.textLink, 0.09)};
svg {
color: ${(props) => props.theme.textLink};
}
}
&.environments {
background-color: ${(props) => rgba(props.theme.colors.text.green, 0.08)};
border: 1px solid ${(props) => rgba(props.theme.colors.text.green, 0.09)};
svg {
color: ${(props) => props.theme.colors.text.green};
}
}
&.requests {
background-color: ${(props) => rgba(props.theme.colors.text.purple, 0.08)};
border: 1px solid ${(props) => rgba(props.theme.colors.text.purple, 0.09)};
svg {
color: ${(props) => props.theme.colors.text.purple};
}
}
&.share {
background-color: ${(props) => rgba(props.theme.textLink, 0.08)};
border: 1px solid ${(props) => rgba(props.theme.textLink, 0.09)};
svg {
color: ${(props) => props.theme.textLink};
}
}
&.generate-docs {
background-color: ${(props) => rgba(props.theme.accents.primary, 0.08)};
border: 1px solid ${(props) => rgba(props.theme.accents.primary, 0.09)};
svg {
color: ${(props) => props.theme.accents.primary};
}
}
}
`;
export default StyledWrapper;

View File

@@ -1,13 +1,11 @@
import React from 'react';
import { getTotalRequestCountInCollection } from 'utils/collections/';
import { IconFolder, IconWorld, IconApi, IconShare, IconBook } from '@tabler/icons';
import { IconFolder, IconWorld, IconApi, IconShare } from '@tabler/icons';
import { areItemsLoading, getItemsLoadStats } from 'utils/collections/index';
import { useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import ShareCollection from 'components/ShareCollection/index';
import GenerateDocumentation from 'components/Sidebar/Collections/Collection/GenerateDocumentation';
import { addTab } from 'providers/ReduxStore/slices/tabs';
import StyledWrapper from './StyledWrapper';
const Info = ({ collection }) => {
const dispatch = useDispatch();
@@ -16,7 +14,6 @@ const Info = ({ collection }) => {
const isCollectionLoading = areItemsLoading(collection);
const { loading: itemsLoadingCount, total: totalItems } = getItemsLoadStats(collection);
const [showShareCollectionModal, toggleShowShareCollectionModal] = useState(false);
const [showGenerateDocumentationModal, setShowGenerateDocumentationModal] = useState(false);
const globalEnvironments = useSelector((state) => state.globalEnvironments.globalEnvironments);
@@ -28,17 +25,17 @@ const Info = ({ collection }) => {
};
return (
<StyledWrapper className="w-full flex flex-col h-fit">
<div className="w-full flex flex-col h-fit">
<div className="rounded-lg py-6">
<div className="grid gap-5">
{/* Location Row */}
<div className="flex items-start">
<div className="icon-box location flex-shrink-0 p-3 rounded-lg">
<IconFolder className="w-5 h-5" stroke={1.5} />
<div className="flex-shrink-0 p-3 bg-blue-50 dark:bg-blue-900/20 rounded-lg">
<IconFolder className="w-5 h-5 text-blue-500" stroke={1.5} />
</div>
<div className="ml-4">
<div className="font-medium">Location</div>
<div className="mt-1 text-muted break-all">
<div className="mt-1 text-muted break-all text-xs">
{collection.pathname}
</div>
</div>
@@ -46,15 +43,15 @@ const Info = ({ collection }) => {
{/* Environments Row */}
<div className="flex items-start">
<div className="icon-box environments flex-shrink-0 p-3 rounded-lg">
<IconWorld className="w-5 h-5" stroke={1.5} />
<div className="flex-shrink-0 p-3 bg-green-50 dark:bg-green-900/20 rounded-lg">
<IconWorld className="w-5 h-5 text-green-500" stroke={1.5} />
</div>
<div className="ml-4">
<div className="font-medium">Environments</div>
<div className="font-medium text-sm">Environments</div>
<div className="mt-1 flex flex-col gap-1">
<button
type="button"
className="text-link cursor-pointer hover:underline text-left bg-transparent"
className="text-sm text-link cursor-pointer hover:underline text-left bg-transparent"
onClick={() => {
dispatch(
addTab({
@@ -69,7 +66,7 @@ const Info = ({ collection }) => {
</button>
<button
type="button"
className="text-link cursor-pointer hover:underline text-left bg-transparent"
className="text-sm text-link cursor-pointer hover:underline text-left bg-transparent"
onClick={() => {
dispatch(
addTab({
@@ -88,12 +85,12 @@ const Info = ({ collection }) => {
{/* Requests Row */}
<div className="flex items-start">
<div className="icon-box requests flex-shrink-0 p-3 rounded-lg">
<IconApi className="w-5 h-5" stroke={1.5} />
<div className="flex-shrink-0 p-3 bg-purple-50 dark:bg-purple-900/20 rounded-lg">
<IconApi className="w-5 h-5 text-purple-500" stroke={1.5} />
</div>
<div className="ml-4">
<div className="font-medium">Requests</div>
<div className="mt-1 text-muted">
<div className="mt-1 text-muted text-xs">
{
isCollectionLoading ? `${totalItems - itemsLoadingCount} out of ${totalItems} requests in the collection loaded` : `${totalRequestsInCollection} request${totalRequestsInCollection !== 1 ? 's' : ''} in collection`
}
@@ -102,33 +99,20 @@ const Info = ({ collection }) => {
</div>
<div className="flex items-start group cursor-pointer" onClick={handleToggleShowShareCollectionModal(true)}>
<div className="icon-box share flex-shrink-0 p-3 rounded-lg">
<IconShare className="w-5 h-5" stroke={1.5} />
<div className="flex-shrink-0 p-3 bg-indigo-50 dark:bg-indigo-900/20 rounded-lg">
<IconShare className="w-5 h-5 text-indigo-500" stroke={1.5} />
</div>
<div className="ml-4 h-full flex flex-col justify-start">
<div className="font-medium h-fit my-auto">Share</div>
<div className="group-hover:underline text-link">
<div className="group-hover:underline text-link text-xs">
Share Collection
</div>
</div>
</div>
{showShareCollectionModal && <ShareCollection collectionUid={collection.uid} onClose={handleToggleShowShareCollectionModal(false)} />}
<div className="flex items-start group cursor-pointer" onClick={() => setShowGenerateDocumentationModal(true)}>
<div className="icon-box generate-docs flex-shrink-0 p-3 rounded-lg">
<IconBook className="w-5 h-5" stroke={1.5} />
</div>
<div className="ml-4 h-full flex flex-col justify-start">
<div className="font-medium h-fit my-auto">Documentation</div>
<div className="group-hover:underline text-link">
Generate Docs
</div>
</div>
</div>
{showGenerateDocumentationModal && <GenerateDocumentation collectionUid={collection.uid} onClose={() => setShowGenerateDocumentationModal(false)} />}
</div>
</div>
</StyledWrapper>
</div>
);
};

View File

@@ -1,29 +1,22 @@
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.table.border};
border-left: 1px solid ${(props) => props.theme.table.border};
border-right: 1px solid ${(props) => props.theme.table.border};
border-top: 1px solid ${(props) => props.theme.requestTabPanel.cardTable.border};
border-left: 1px solid ${(props) => props.theme.requestTabPanel.cardTable.border};
border-right: 1px solid ${(props) => props.theme.requestTabPanel.cardTable.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 {
thead {
color: ${(props) => props.theme.table.thead.color} !important;
background: ${(props) => props.theme.sidebar.bg};
background-color: ${(props) => props.theme.requestTabPanel.cardTable.table.thead.bg};
color: ${(props) => props.theme.requestTabPanel.cardTable.table.thead.color};
}
}
}

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">
<IconAlertTriangle size={16} className="warning-icon" />
<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" />
<span className="font-medium">Following requests were not loaded</span>
</div>
<table className="w-full border-collapse">

View File

@@ -16,11 +16,11 @@ const StyledWrapper = styled.div`
box-shadow: none;
transition: border-color ease-in-out 0.1s;
border-radius: 3px;
background-color: ${(props) => props.theme.input.bg};
border: 1px solid ${(props) => props.theme.input.border};
background-color: ${(props) => props.theme.modal.input.bg};
border: 1px solid ${(props) => props.theme.modal.input.border};
&:focus {
border: solid 1px ${(props) => props.theme.input.focusBorder} !important;
border: solid 1px ${(props) => props.theme.modal.input.focusBorder} !important;
outline: none !important;
}
}

View File

@@ -36,7 +36,7 @@ const PresetsSettings = ({ collection }) => {
return (
<StyledWrapper className="h-full w-full">
<div className="text-xs mb-4 text-muted">
<div className="text-xs mb-4 mt-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

@@ -8,151 +8,6 @@ const StyledWrapper = styled.div`
color: ${(props) => props.theme.colors.text.danger};
}
}
/* Section labels */
label {
color: ${(props) => props.theme.text};
}
/* Tooltip icon */
.tooltip-icon {
color: ${(props) => props.theme.colors.text.muted};
cursor: pointer;
}
/* Error messages */
.error-message {
color: ${(props) => props.theme.colors.text.danger};
background-color: ${(props) => props.theme.bg};
border-radius: ${(props) => props.theme.border.radius.base};
}
/* Tables */
table {
width: 100%;
border-collapse: collapse;
thead {
th {
text-align: left;
font-size: ${(props) => props.theme.font.size.xs};
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.05em;
color: ${(props) => props.theme.table.thead.color};
border: 1px solid ${(props) => props.theme.table.border};
padding: 0.5rem 0.75rem;
&.text-right {
text-align: right;
}
}
}
tbody {
td {
border: 1px solid ${(props) => props.theme.table.border};
padding: 0.5rem 0.75rem;
&.text-center {
text-align: center;
}
&.text-right {
text-align: right;
}
}
}
}
/* File/Directory icons */
.file-icon,
.folder-icon {
color: ${(props) => props.theme.colors.text.muted};
}
/* File/Directory names */
.file-name,
.directory-name {
font-weight: 500;
color: ${(props) => props.theme.text};
}
/* Path text */
.path-text {
font-size: ${(props) => props.theme.font.size.xs};
color: ${(props) => props.theme.colors.text.muted};
font-family: monospace;
}
/* Empty state */
.empty-state {
.empty-icon {
color: ${(props) => props.theme.colors.text.muted};
}
.empty-text {
color: ${(props) => props.theme.colors.text.muted};
}
}
/* Invalid file indicator */
.invalid-indicator {
color: ${(props) => props.theme.colors.text.danger};
}
/* Action buttons */
.action-button {
padding: 0.25rem;
border-radius: ${(props) => props.theme.border.radius.base};
transition: all 0.2s;
&.replace-button {
color: ${(props) => props.theme.colors.text.danger};
&:hover {
color: ${(props) => props.theme.colors.text.danger};
background-color: ${(props) => props.theme.colors.bg.danger}20;
}
}
&.remove-button {
color: ${(props) => props.theme.colors.text.muted};
&:hover {
color: ${(props) => props.theme.text};
background-color: ${(props) => props.theme.dropdown.hoverBg};
}
}
}
/* Checkbox */
input[type='checkbox'] {
cursor: pointer;
accent-color: ${(props) => props.theme.colors.accent};
border-color: ${(props) => props.theme.table.border};
&:focus {
outline: none;
border-color: ${(props) => props.theme.primary.solid};
}
}
/* Add button */
.btn-add-param {
color: ${(props) => props.theme.textLink};
padding-right: 0.5rem;
padding-top: 0.75rem;
padding-bottom: 0.75rem;
margin-top: 0.5rem;
user-select: none;
cursor: pointer;
transition: color 0.2s;
&:hover {
color: ${(props) => props.theme.primary.solid};
}
}
`;
export default StyledWrapper;

View File

@@ -113,12 +113,12 @@ const ProtobufSettings = ({ collection }) => {
<div className="mb-6" data-testid="protobuf-proto-files-section">
<div className="flex items-center justify-between mb-3">
<div className="flex items-center">
<label className="flex items-center" htmlFor="protoFiles">
<label className="font-medium flex items-center" htmlFor="protoFiles">
Proto Files (
{protoFiles.length}
)
<span id="proto-files-tooltip" className="ml-2">
<IconAlertCircle size={16} className="tooltip-icon" />
<IconAlertCircle size={16} className="text-gray-500 cursor-pointer" />
</span>
<Tooltip
anchorId="proto-files-tooltip"
@@ -131,7 +131,7 @@ const ProtobufSettings = ({ collection }) => {
<div>
{protoFiles.some((file) => !file.exists) && (
<div className="error-message text-xs mb-2 flex items-center p-2" data-testid="protobuf-invalid-files-message">
<div className="text-xs text-red-600 dark:text-red-400 mb-2 flex items-center p-2 rounded" data-testid="protobuf-invalid-files-message">
<IconAlertCircle size={14} className="mr-1" />
Some proto files cannot be found. Use the replace option to update their locations.
</div>
@@ -140,13 +140,13 @@ const ProtobufSettings = ({ collection }) => {
<table className="w-full border-collapse" data-testid="protobuf-proto-files-table">
<thead>
<tr>
<th>
<th className="text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider border border-gray-200 dark:border-gray-700 px-3 py-2">
File
</th>
<th>
<th className="text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider border border-gray-200 dark:border-gray-700 px-3 py-2">
Path
</th>
<th className="text-right">
<th className="text-right text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider border border-gray-200 dark:border-gray-700 px-3 py-2">
Actions
</th>
</tr>
@@ -154,10 +154,10 @@ const ProtobufSettings = ({ collection }) => {
<tbody>
{protoFiles.length === 0 ? (
<tr>
<td colSpan="3" className="text-center">
<div className="empty-state flex flex-col items-center">
<IconFile size={24} className="empty-icon mb-2" />
<span className="empty-text">No proto files added</span>
<td colSpan="3" className="border border-gray-200 dark:border-gray-700 px-3 py-8 text-center">
<div className="flex flex-col items-center">
<IconFile size={24} className="text-gray-400 mb-2" />
<span className="text-gray-500 dark:text-gray-400">No proto files added</span>
</div>
</td>
</tr>
@@ -167,27 +167,27 @@ const ProtobufSettings = ({ collection }) => {
return (
<tr key={index}>
<td>
<td className="border border-gray-200 dark:border-gray-700 px-3 py-2">
<div className="flex items-center">
<IconFile size={16} className="file-icon mr-2" />
<span className="file-name" data-testid="protobuf-proto-file-name">
<IconFile size={16} className="text-gray-500 dark:text-gray-400 mr-2" />
<span className="font-medium text-gray-900 dark:text-gray-100" data-testid="protobuf-proto-file-name">
{getBasename(collection.pathname, file.path)}
</span>
{!isValid && <IconAlertCircle size={12} className="invalid-indicator ml-2" />}
{!isValid && <IconAlertCircle size={12} className="text-red-600 dark:text-red-400 ml-2" />}
</div>
</td>
<td>
<div className="path-text">
<td className="border border-gray-200 dark:border-gray-700 px-3 py-2">
<div className="text-xs text-gray-600 dark:text-gray-400 font-mono">
{file.path}
</div>
</td>
<td className="text-right">
<td className="border border-gray-200 dark:border-gray-700 px-3 py-2 text-right">
<div className="flex items-center justify-end space-x-1">
{!isValid && (
<button
type="button"
onClick={() => handleReplaceProtoFile(index)}
className="action-button replace-button"
className="text-red-600 hover:text-red-900 dark:text-red-400 dark:hover:text-red-300 p-1 rounded"
title="Replace file"
>
<IconFileImport size={14} />
@@ -196,7 +196,7 @@ const ProtobufSettings = ({ collection }) => {
<button
type="button"
onClick={() => handleRemoveProtoFile(index)}
className="action-button remove-button"
className="text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-300 p-1 rounded"
title="Remove file"
data-testid="protobuf-remove-file-button"
>
@@ -220,12 +220,12 @@ const ProtobufSettings = ({ collection }) => {
<div className="mb-6" data-testid="protobuf-import-paths-section">
<div className="flex items-center justify-between mb-3">
<div className="flex items-center">
<label className="flex items-center" htmlFor="importPaths">
<label className="font-medium flex items-center" htmlFor="importPaths">
Import Paths (
{importPaths.length}
)
<span id="import-paths-tooltip" className="ml-2">
<IconAlertCircle size={16} className="tooltip-icon" />
<IconAlertCircle size={16} className="text-gray-500 cursor-pointer" />
</span>
<Tooltip
anchorId="import-paths-tooltip"
@@ -238,7 +238,7 @@ const ProtobufSettings = ({ collection }) => {
<div>
{importPaths.some((path) => !path.exists) && (
<div className="error-message text-xs mb-2 flex items-center p-2" data-testid="protobuf-invalid-import-paths-message">
<div className="text-xs text-red-600 dark:text-red-400 mb-2 flex items-center p-2 rounded" data-testid="protobuf-invalid-import-paths-message">
<IconAlertCircle size={14} className="mr-1" />
Some import paths cannot be found at their specified locations.
</div>
@@ -247,15 +247,15 @@ const ProtobufSettings = ({ collection }) => {
<table className="w-full border-collapse" data-testid="protobuf-import-paths-table">
<thead>
<tr>
<th>
<th className="text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider border border-gray-200 dark:border-gray-700 px-3 py-2">
</th>
<th>
<th className="text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider border border-gray-200 dark:border-gray-700 px-3 py-2">
Directory
</th>
<th>
<th className="text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider border border-gray-200 dark:border-gray-700 px-3 py-2">
Path
</th>
<th className="text-right">
<th className="text-right text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider border border-gray-200 dark:border-gray-700 px-3 py-2">
Actions
</th>
</tr>
@@ -263,10 +263,10 @@ const ProtobufSettings = ({ collection }) => {
<tbody>
{importPaths.length === 0 ? (
<tr>
<td colSpan="4" className="text-center">
<div className="empty-state flex flex-col items-center">
<IconFolder size={24} className="empty-icon mb-2" />
<span className="empty-text">No import paths added</span>
<td colSpan="4" className="border border-gray-200 dark:border-gray-700 px-3 py-8 text-center">
<div className="flex flex-col items-center">
<IconFolder size={24} className="text-gray-400 mb-2" />
<span className="text-gray-500 dark:text-gray-400">No import paths added</span>
</div>
</td>
</tr>
@@ -276,37 +276,37 @@ const ProtobufSettings = ({ collection }) => {
return (
<tr key={index}>
<td>
<td className="border border-gray-200 dark:border-gray-700 px-3 py-2">
<input
type="checkbox"
checked={importPath.enabled}
onChange={() => handleToggleImportPath(index)}
className="h-4 w-4"
className="h-4 w-4 text-gray-600 focus:ring-gray-500 border-gray-300 dark:border-gray-600 rounded"
title={importPath.enabled ? 'Disable this import path' : 'Enable this import path'}
data-testid="protobuf-import-path-checkbox"
/>
</td>
<td>
<td className="border border-gray-200 dark:border-gray-700 px-3 py-2">
<div className="flex items-center">
<IconFolder size={16} className="folder-icon mr-2" />
<span className="directory-name">
<IconFolder size={16} className="text-gray-500 dark:text-gray-400 mr-2" />
<span className="font-medium text-gray-900 dark:text-gray-100">
{getBasename(collection.pathname, importPath.path)}
</span>
{!isValid && <IconAlertCircle size={12} className="invalid-indicator ml-2" />}
{!isValid && <IconAlertCircle size={12} className="text-red-600 dark:text-red-400 ml-2" />}
</div>
</td>
<td>
<div className="path-text">
<td className="border border-gray-200 dark:border-gray-700 px-3 py-2">
<div className="text-xs text-gray-600 dark:text-gray-400 font-mono">
{importPath.path}
</div>
</td>
<td className="text-right">
<td className="border border-gray-200 dark:border-gray-700 px-3 py-2 text-right">
<div className="flex items-center justify-end space-x-1">
{!isValid && (
<button
type="button"
onClick={() => handleReplaceImportPath(index)}
className="action-button replace-button"
className="text-red-600 hover:text-red-900 dark:text-red-400 dark:hover:text-red-300 p-1 rounded"
title="Replace directory"
>
<IconFileImport size={14} />
@@ -315,7 +315,7 @@ const ProtobufSettings = ({ collection }) => {
<button
type="button"
onClick={() => handleRemoveImportPath(index)}
className="action-button remove-button"
className="text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-300 p-1 rounded"
title="Remove import path"
data-testid="protobuf-remove-import-path-button"
>

View File

@@ -14,11 +14,11 @@ const StyledWrapper = styled.div`
box-shadow: none;
transition: border-color ease-in-out 0.1s;
border-radius: 3px;
background-color: ${(props) => props.theme.input.bg};
border: 1px solid ${(props) => props.theme.input.border};
background-color: ${(props) => props.theme.modal.input.bg};
border: 1px solid ${(props) => props.theme.modal.input.border};
&:focus {
border: solid 1px ${(props) => props.theme.input.focusBorder} !important;
border: solid 1px ${(props) => props.theme.modal.input.focusBorder} !important;
outline: none !important;
}
}

View File

@@ -12,19 +12,7 @@ import Button from 'ui/Button';
const ProxySettings = ({ collection }) => {
const dispatch = useDispatch();
const initialProxyConfig = {
inherit: true,
config: {
protocol: 'http',
hostname: '',
port: '',
auth: {
username: '',
password: ''
},
bypassProxy: ''
}
};
const initialProxyConfig = { enabled: 'global', protocol: 'http', hostname: '', port: '', auth: { enabled: false, username: '', password: '' }, bypassProxy: '' };
// Get proxy from draft.brunoConfig if it exists, otherwise from brunoConfig
const currentProxyConfig = collection.draft?.brunoConfig
@@ -94,57 +82,34 @@ const ProxySettings = ({ collection }) => {
const handleEnabledChange = (e) => {
const value = e.target.value;
// Map UI values to new format
if (value === 'inherit') {
updateProxy({ disabled: false, inherit: true });
} else if (value === 'true') {
updateProxy({ disabled: false, inherit: false });
} else {
updateProxy({ disabled: true, inherit: false });
}
// Convert string to boolean or keep as 'global'
const enabled = value === 'true' ? true : value === 'false' ? false : 'global';
updateProxy({ enabled });
};
const handleProtocolChange = (e) => {
updateProxy({
config: {
...currentProxyConfig.config,
protocol: e.target.value
}
});
updateProxy({ protocol: e.target.value });
};
const handleHostnameChange = (e) => {
const hostname = e.target.value;
if (validateHostnameOnChange(hostname)) {
updateProxy({
config: {
...currentProxyConfig.config,
hostname
}
});
updateProxy({ hostname });
}
};
const handlePortChange = (e) => {
const port = e.target.value ? Number(e.target.value) : '';
if (validatePortOnChange(port)) {
updateProxy({
config: {
...currentProxyConfig.config,
port
}
});
updateProxy({ port });
}
};
const handleAuthEnabledChange = (e) => {
updateProxy({
config: {
...currentProxyConfig.config,
auth: {
...currentProxyConfig.config.auth,
disabled: !e.target.checked
}
auth: {
...currentProxyConfig.auth,
enabled: e.target.checked
}
});
};
@@ -153,12 +118,9 @@ const ProxySettings = ({ collection }) => {
const username = e.target.value;
if (validateAuthUsernameOnChange(username)) {
updateProxy({
config: {
...currentProxyConfig.config,
auth: {
...currentProxyConfig.config.auth,
username
}
auth: {
...currentProxyConfig.auth,
username
}
});
}
@@ -168,12 +130,9 @@ const ProxySettings = ({ collection }) => {
const password = e.target.value;
if (validateAuthPasswordOnChange(password)) {
updateProxy({
config: {
...currentProxyConfig.config,
auth: {
...currentProxyConfig.config.auth,
password
}
auth: {
...currentProxyConfig.auth,
password
}
});
}
@@ -182,19 +141,11 @@ const ProxySettings = ({ collection }) => {
const handleBypassProxyChange = (e) => {
const bypassProxy = e.target.value;
if (validateBypassProxyOnChange(bypassProxy)) {
updateProxy({
config: {
...currentProxyConfig.config,
bypassProxy
}
});
updateProxy({ bypassProxy });
}
};
// Map new format to UI values
const disabled = currentProxyConfig.disabled || false;
const inherit = currentProxyConfig.inherit !== undefined ? currentProxyConfig.inherit : true;
const enabledValue = disabled ? 'false' : (inherit ? 'inherit' : 'true');
const enabledValue = currentProxyConfig.enabled === true ? 'true' : currentProxyConfig.enabled === false ? 'false' : 'global';
return (
<StyledWrapper className="h-full w-full">
@@ -206,9 +157,9 @@ const ProxySettings = ({ collection }) => {
<InfoTip infotipId="request-var">
<div>
<ul>
<li><span style={{ width: '50px', display: 'inline-block' }}>inherit</span> - inherit from global preferences</li>
<li><span style={{ width: '50px', display: 'inline-block' }}>enabled</span> - use collection-specific proxy config</li>
<li><span style={{ width: '50px', display: 'inline-block' }}>disabled</span> - disable proxy for this collection</li>
<li><span style={{ width: '50px', display: 'inline-block' }}>global</span> - use global proxy config</li>
<li><span style={{ width: '50px', display: 'inline-block' }}>enabled</span> - use collection proxy config</li>
<li><span style={{ width: '50px', display: 'inline-block' }}>disable</span> - disable proxy</li>
</ul>
</div>
</InfoTip>
@@ -218,12 +169,12 @@ const ProxySettings = ({ collection }) => {
<input
type="radio"
name="enabled"
value="inherit"
checked={enabledValue === 'inherit'}
value="global"
checked={enabledValue === 'global'}
onChange={handleEnabledChange}
className="mr-1"
/>
inherit
global
</label>
<label className="flex items-center ml-4">
<input
@@ -249,168 +200,164 @@ const ProxySettings = ({ collection }) => {
</label>
</div>
</div>
{enabledValue === 'true' && (
<>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="protocol">
Protocol
</label>
<div className="flex items-center">
<label className="flex items-center">
<input
type="radio"
name="protocol"
value="http"
checked={(currentProxyConfig.config?.protocol || 'http') === 'http'}
onChange={handleProtocolChange}
className="mr-1"
/>
HTTP
</label>
<label className="flex items-center ml-4">
<input
type="radio"
name="protocol"
value="https"
checked={(currentProxyConfig.config?.protocol || 'http') === 'https'}
onChange={handleProtocolChange}
className="mr-1"
/>
HTTPS
</label>
<label className="flex items-center ml-4">
<input
type="radio"
name="protocol"
value="socks4"
checked={(currentProxyConfig.config?.protocol || 'http') === 'socks4'}
onChange={handleProtocolChange}
className="mr-1"
/>
SOCKS4
</label>
<label className="flex items-center ml-4">
<input
type="radio"
name="protocol"
value="socks5"
checked={(currentProxyConfig.config?.protocol || 'http') === 'socks5'}
onChange={handleProtocolChange}
className="mr-1"
/>
SOCKS5
</label>
</div>
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="hostname">
Hostname
</label>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="protocol">
Protocol
</label>
<div className="flex items-center">
<label className="flex items-center">
<input
id="hostname"
type="text"
name="hostname"
className="block textbox"
type="radio"
name="protocol"
value="http"
checked={(currentProxyConfig.protocol || 'http') === 'http'}
onChange={handleProtocolChange}
className="mr-1"
/>
HTTP
</label>
<label className="flex items-center ml-4">
<input
type="radio"
name="protocol"
value="https"
checked={(currentProxyConfig.protocol || 'http') === 'https'}
onChange={handleProtocolChange}
className="mr-1"
/>
HTTPS
</label>
<label className="flex items-center ml-4">
<input
type="radio"
name="protocol"
value="socks4"
checked={(currentProxyConfig.protocol || 'http') === 'socks4'}
onChange={handleProtocolChange}
className="mr-1"
/>
SOCKS4
</label>
<label className="flex items-center ml-4">
<input
type="radio"
name="protocol"
value="socks5"
checked={(currentProxyConfig.protocol || 'http') === 'socks5'}
onChange={handleProtocolChange}
className="mr-1"
/>
SOCKS5
</label>
</div>
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="hostname">
Hostname
</label>
<input
id="hostname"
type="text"
name="hostname"
className="block textbox"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
onChange={handleHostnameChange}
value={currentProxyConfig.hostname || ''}
/>
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="port">
Port
</label>
<input
id="port"
type="number"
name="port"
className="block textbox"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
onChange={handlePortChange}
value={currentProxyConfig.port || ''}
/>
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="auth.enabled">
Auth
</label>
<input
type="checkbox"
name="auth.enabled"
checked={currentProxyConfig.auth?.enabled || false}
onChange={handleAuthEnabledChange}
/>
</div>
<div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="auth.username">
Username
</label>
<input
id="auth.username"
type="text"
name="auth.username"
className="block textbox"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
value={currentProxyConfig.auth?.username || ''}
onChange={handleAuthUsernameChange}
/>
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="auth.password">
Password
</label>
<div className="textbox flex flex-row items-center w-[13.2rem] h-[1.70rem] relative">
<input
id="auth.password"
type={passwordVisible ? 'text' : 'password'}
name="auth.password"
className="outline-none bg-transparent w-[10.5rem]"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
onChange={handleHostnameChange}
value={currentProxyConfig.config?.hostname || ''}
value={currentProxyConfig.auth?.password || ''}
onChange={handleAuthPasswordChange}
/>
<button
type="button"
className="btn btn-sm absolute right-0"
onClick={() => setPasswordVisible(!passwordVisible)}
>
{passwordVisible ? <IconEyeOff size={18} strokeWidth={1.5} /> : <IconEye size={18} strokeWidth={1.5} />}
</button>
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="port">
Port
</label>
<input
id="port"
type="number"
name="port"
className="block textbox"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
onChange={handlePortChange}
value={currentProxyConfig.config?.port || ''}
/>
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="auth.disabled">
Auth
</label>
<input
type="checkbox"
name="auth.disabled"
checked={!currentProxyConfig.config?.auth?.disabled}
onChange={handleAuthEnabledChange}
/>
</div>
<div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="auth.username">
Username
</label>
<input
id="auth.username"
type="text"
name="auth.username"
className="block textbox"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
value={currentProxyConfig.config?.auth?.username || ''}
onChange={handleAuthUsernameChange}
/>
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="auth.password">
Password
</label>
<div className="textbox flex flex-row items-center w-[13.2rem] h-[1.70rem] relative">
<input
id="auth.password"
type={passwordVisible ? 'text' : 'password'}
name="auth.password"
className="outline-none bg-transparent w-[10.5rem]"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
value={currentProxyConfig.config?.auth?.password || ''}
onChange={handleAuthPasswordChange}
/>
<button
type="button"
className="btn btn-sm absolute right-0"
onClick={() => setPasswordVisible(!passwordVisible)}
>
{passwordVisible ? <IconEyeOff size={18} strokeWidth={1.5} /> : <IconEye size={18} strokeWidth={1.5} />}
</button>
</div>
</div>
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="bypassProxy">
Proxy Bypass
</label>
<input
id="bypassProxy"
type="text"
name="bypassProxy"
className="block textbox"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
onChange={handleBypassProxyChange}
value={currentProxyConfig.config?.bypassProxy || ''}
/>
</div>
</>
)}
</div>
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="bypassProxy">
Proxy Bypass
</label>
<input
id="bypassProxy"
type="text"
name="bypassProxy"
className="block textbox"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
onChange={handleBypassProxyChange}
value={currentProxyConfig.bypassProxy || ''}
/>
</div>
<div className="mt-6">
<Button type="submit" size="sm" onClick={handleSave}>
Save

View File

@@ -8,7 +8,7 @@ const StyledWrapper = styled.div`
}
div.title {
color: ${(props) => props.theme.colors.text.subtext0};
color: var(--color-tab-inactive);
}
`;

View File

@@ -56,7 +56,7 @@ const Script = ({ collection }) => {
};
return (
<StyledWrapper className="w-full flex flex-col h-full">
<StyledWrapper className="w-full flex flex-col h-full pt-4">
<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

@@ -7,7 +7,7 @@ const StyledWrapper = styled.div`
border: none;
border-bottom: solid 2px transparent;
margin-right: ${(props) => props.theme.tabs.marginRight};
color: ${(props) => props.theme.colors.text.subtext0};
color: var(--color-tab-inactive);
cursor: pointer;
&:focus,
@@ -19,10 +19,6 @@ 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;
@@ -44,11 +40,6 @@ const StyledWrapper = styled.div`
.muted {
color: ${(props) => props.theme.colors.text.muted};
}
input[type='radio'] {
cursor: pointer;
accent-color: ${(props) => props.theme.primary.solid};
}
`;
export default StyledWrapper;

View File

@@ -4,7 +4,7 @@ const StyledWrapper = styled.div`
max-width: 800px;
div.title {
color: ${(props) => props.theme.colors.text.subtext0};
color: var(--color-tab-inactive);
}
`;

View File

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

View File

@@ -1,26 +1,5 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
/* Info icon */
.info-icon {
color: ${(props) => props.theme.colors.text.muted};
}
/* Required field asterisk */
.required-asterisk {
color: ${(props) => props.theme.colors.text.danger};
}
/* Error messages */
.error-message {
color: ${(props) => props.theme.colors.text.danger};
}
/* Checkbox */
input[type='checkbox'] {
cursor: pointer;
accent-color: ${(props) => props.theme.primary.solid};
}
`;
const StyledWrapper = styled.div``;
export default StyledWrapper;

View File

@@ -11,7 +11,6 @@ import moment from 'moment';
import 'moment-timezone';
import { Tooltip } from 'react-tooltip';
import { isEmpty } from 'lodash';
import StyledWrapper from './StyledWrapper';
const removeEmptyValues = (obj) => {
return Object.fromEntries(Object.entries(obj).filter(([_, value]) => value !== null && value !== undefined));
@@ -226,147 +225,145 @@ const ModifyCookieModal = ({ onClose, domain, cookie }) => {
</div>
)}
>
<StyledWrapper>
<form onSubmit={(e) => e.preventDefault()} className="px-2">
{isRawMode ? (
<div>
<div className="flex items-center gap-2 mb-1">
<label className="block">Set-Cookie String</label>
<IconInfoCircle id="cookie-raw-info" size={16} strokeWidth={1.5} className="info-icon" />
<Tooltip
anchorId="cookie-raw-info"
className="tooltip-mod"
html="Key, Path, and Domain are immutable properties and cannot be modified for existing cookies"
/>
</div>
<textarea
value={cookieString}
onChange={(e) => setCookieString(e.target.value)}
className="block textbox w-full h-24"
placeholder="key=value; key2=value2"
<form onSubmit={(e) => e.preventDefault()} className="px-2">
{isRawMode ? (
<div>
<div className="flex items-center gap-2 mb-1">
<label className="block">Set-Cookie String</label>
<IconInfoCircle id="cookie-raw-info" size={16} strokeWidth={1.5} className="text-gray-400" />
<Tooltip
anchorId="cookie-raw-info"
className="tooltip-mod"
html="Key, Path, and Domain are immutable properties and cannot be modified for existing cookies"
/>
</div>
) : (
<div className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block mb-1">
Domain<span className="required-asterisk">*</span>{' '}
</label>
<input
type="text"
name="domain"
// Auto-focus if its add-new i.e. when domain prop is empty
autoFocus={!domain && !formik.values.domain}
value={formik.values.domain}
onChange={formik.handleChange}
className="block textbox non-passphrase-input w-full disabled:opacity-50"
disabled={!!cookie}
/>
{formik.touched.domain && formik.errors.domain && (
<div className="error-message mt-1">{formik.errors.domain}</div>
)}
</div>
<div>
<label className="block mb-1">Path</label>
<input
type="text"
name="path"
value={formik.values.path}
onChange={formik.handleChange}
className="block textbox non-passphrase-input w-full disabled:opacity-50"
disabled={!!cookie}
/>
{formik.touched.path && formik.errors.path && (
<div className="error-message mt-1">{formik.errors.path}</div>
)}
</div>
<div>
<label className="block mb-1">
Key<span className="required-asterisk">*</span>{' '}
</label>
<input
type="text"
name="key"
// Auto focus when add-for-domain i.e. if domain is already prefilled
autoFocus={!!domain && !formik.values.key}
value={formik.values.key}
onChange={formik.handleChange}
className="block textbox non-passphrase-input w-full disabled:opacity-50"
disabled={!!cookie}
/>
{formik.touched.key && formik.errors.key && (
<div className="error-message mt-1">{formik.errors.key}</div>
)}
</div>
<div>
<label className="block mb-1">
Value<span className="required-asterisk">*</span>{' '}
</label>
<input
type="text"
name="value"
// Auto-focus when its in edit mode i.e. cookie prop is present
autoFocus={!!cookie}
value={formik.values.value}
onChange={formik.handleChange}
className="block textbox non-passphrase-input w-full"
/>
{formik.touched.value && formik.errors.value && (
<div className="error-message mt-1">{formik.errors.value}</div>
)}
</div>
<textarea
value={cookieString}
onChange={(e) => setCookieString(e.target.value)}
className="block textbox w-full h-24"
placeholder="key=value; key2=value2"
/>
</div>
) : (
<div className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block mb-1">
Domain<span className="text-red-600">*</span>{' '}
</label>
<input
type="text"
name="domain"
// Auto-focus if its add-new i.e. when domain prop is empty
autoFocus={!domain && !formik.values.domain}
value={formik.values.domain}
onChange={formik.handleChange}
className="block textbox non-passphrase-input w-full disabled:opacity-50"
disabled={!!cookie}
/>
{formik.touched.domain && formik.errors.domain && (
<div className="text-red-500 mt-1">{formik.errors.domain}</div>
)}
</div>
<div>
<label className="block mb-1">Path</label>
<input
type="text"
name="path"
value={formik.values.path}
onChange={formik.handleChange}
className="block textbox non-passphrase-input w-full disabled:opacity-50"
disabled={!!cookie}
/>
{formik.touched.path && formik.errors.path && (
<div className="text-red-500 mt-1">{formik.errors.path}</div>
)}
</div>
<div>
<label className="block mb-1">
Key<span className="text-red-600">*</span>{' '}
</label>
<input
type="text"
name="key"
// Auto focus when add-for-domain i.e. if domain is already prefilled
autoFocus={!!domain && !formik.values.key}
value={formik.values.key}
onChange={formik.handleChange}
className="block textbox non-passphrase-input w-full disabled:opacity-50"
disabled={!!cookie}
/>
{formik.touched.key && formik.errors.key && (
<div className="text-red-500 mt-1">{formik.errors.key}</div>
)}
</div>
{/* Date Picker */}
<div className="w-full flex items-end">
<div>
<label className="block mb-1">Expiration ({moment.tz.guess()})</label>
<input
type="datetime-local"
name="expires"
value={formik.values.expires}
onChange={(e) => {
formik.handleChange(e);
}}
className="block textbox non-passphrase-input w-full"
min={moment().format(moment.HTML5_FMT.DATETIME_LOCAL)}
/>
{formik.touched.expires && formik.errors.expires && (
<div className="error-message mt-1">{formik.errors.expires}</div>
)}
</div>
{/* Checkboxes */}
<div className="flex space-x-4 ml-auto">
<label className="flex items-center">
<input
type="checkbox"
name="secure"
checked={formik.values.secure}
onChange={formik.handleChange}
className="mr-2"
/>
<span>Secure</span>
</label>
<label className="flex items-center">
<input
type="checkbox"
name="httpOnly"
checked={formik.values.httpOnly}
onChange={formik.handleChange}
className="mr-2"
/>
<span>HTTP Only</span>
</label>
</div>
<div>
<label className="block mb-1">
Value<span className="text-red-600">*</span>{' '}
</label>
<input
type="text"
name="value"
// Auto-focus when its in edit mode i.e. cookie prop is present
autoFocus={!!cookie}
value={formik.values.value}
onChange={formik.handleChange}
className="block textbox non-passphrase-input w-full"
/>
{formik.touched.value && formik.errors.value && (
<div className="text-red-500 mt-1">{formik.errors.value}</div>
)}
</div>
</div>
)}
</form>
</StyledWrapper>
{/* Date Picker */}
<div className="w-full flex items-end">
<div>
<label className="block mb-1">Expiration ({moment.tz.guess()})</label>
<input
type="datetime-local"
name="expires"
value={formik.values.expires}
onChange={(e) => {
formik.handleChange(e);
}}
className="block textbox non-passphrase-input w-full"
min={moment().format(moment.HTML5_FMT.DATETIME_LOCAL)}
/>
{formik.touched.expires && formik.errors.expires && (
<div className="text-red-500 mt-1">{formik.errors.expires}</div>
)}
</div>
{/* Checkboxes */}
<div className="flex space-x-4 ml-auto">
<label className="flex items-center">
<input
type="checkbox"
name="secure"
checked={formik.values.secure}
onChange={formik.handleChange}
className="mr-2"
/>
<span>Secure</span>
</label>
<label className="flex items-center">
<input
type="checkbox"
name="httpOnly"
checked={formik.values.httpOnly}
onChange={formik.handleChange}
className="mr-2"
/>
<span>HTTP Only</span>
</label>
</div>
</div>
</div>
)}
</form>
</Modal>
);
};

View File

@@ -28,11 +28,11 @@ const Wrapper = styled.div`
box-shadow: none;
transition: border-color ease-in-out 0.1s;
border-radius: 3px;
background-color: ${(props) => props.theme.input.bg};
border: 1px solid ${(props) => props.theme.input.border};
background-color: ${(props) => props.theme.modal.input.bg};
border: 1px solid ${(props) => props.theme.modal.input.border};
&:focus {
border: solid 1px ${(props) => props.theme.input.focusBorder} !important;
border: solid 1px ${(props) => props.theme.modal.input.focusBorder} !important;
outline: none !important;
}
}
@@ -70,92 +70,6 @@ const Wrapper = styled.div`
background-size: 100% 30px, 100% 30px, 100% 10px, 100% 10px;
background-attachment: local, local, scroll, scroll;
}
/* Warning icon */
.warning-icon {
color: ${(props) => props.theme.colors.text.warning};
}
/* Empty state */
.empty-icon {
color: ${(props) => props.theme.colors.text.muted};
}
.empty-text {
color: ${(props) => props.theme.colors.text.muted};
}
/* Domain count text */
.domain-count {
color: ${(props) => props.theme.colors.text.muted};
}
/* Action buttons */
.action-button {
color: ${(props) => props.theme.colors.text.muted};
transition: color 0.2s;
cursor: pointer;
&:hover {
color: ${(props) => props.theme.text};
}
}
.action-button-danger {
color: ${(props) => props.theme.text};
transition: color 0.2s;
cursor: pointer;
&:hover {
color: ${(props) => props.theme.colors.text.danger};
}
}
/* Table styles */
table {
thead {
tr {
border-bottom: 1px solid ${(props) => props.theme.table.border};
color: ${(props) => props.theme.table.thead.color};
th {
color: ${(props) => props.theme.table.thead.color};
}
}
}
tbody {
tr {
border-bottom: 1px solid ${(props) => props.theme.table.border};
&:last-child {
border-bottom: none;
}
}
}
}
/* Edit button */
.edit-button {
color: ${(props) => props.theme.text};
transition: color 0.2s;
cursor: pointer;
&:hover {
color: ${(props) => props.theme.colors.text.muted};
}
}
/* Delete button */
.delete-button {
color: ${(props) => props.theme.text};
transition: color 0.2s;
cursor: pointer;
&:hover {
color: ${(props) => props.theme.colors.text.danger};
}
}
`;
export default Wrapper;

View File

@@ -14,7 +14,7 @@ import Button from 'ui/Button';
const ClearDomainCookiesModal = ({ onClose, domain, onClear }) => (
<Modal onClose={onClose} handleCancel={onClose} title="Clear Domain Cookies" hideFooter={true}>
<div className="flex items-center font-normal">
<IconAlertTriangle size={32} strokeWidth={1.5} className="warning-icon" />
<IconAlertTriangle size={32} strokeWidth={1.5} className="text-yellow-600" />
<h1 className="ml-2 text-lg font-medium">Hold on..</h1>
</div>
<div className="font-normal mt-4">
@@ -23,12 +23,12 @@ const ClearDomainCookiesModal = ({ onClose, domain, onClear }) => (
<div className="flex justify-between mt-6">
<div>
<Button color="secondary" variant="ghost" onClick={onClose}>
<Button size="sm" color="secondary" variant="ghost" onClick={onClose}>
Close
</Button>
</div>
<div>
<Button color="danger" onClick={onClear}>
<Button size="sm" color="danger" onClick={onClear}>
Clear All
</Button>
</div>
@@ -39,7 +39,7 @@ const ClearDomainCookiesModal = ({ onClose, domain, onClear }) => (
const DeleteCookieModal = ({ onClose, cookieName, onDelete }) => (
<Modal onClose={onClose} handleCancel={onClose} title="Delete Cookie" hideFooter={true}>
<div className="flex items-center font-normal">
<IconAlertTriangle size={32} strokeWidth={1.5} className="warning-icon" />
<IconAlertTriangle size={32} strokeWidth={1.5} className="text-yellow-600" />
<h1 className="ml-2 text-lg font-medium">Hold on..</h1>
</div>
<div className="font-normal mt-4">
@@ -48,12 +48,12 @@ const DeleteCookieModal = ({ onClose, cookieName, onDelete }) => (
<div className="flex justify-between mt-6">
<div>
<Button color="secondary" variant="ghost" onClick={onClose}>
<Button size="sm" color="secondary" variant="ghost" onClick={onClose}>
Close
</Button>
</div>
<div>
<Button color="danger" onClick={onDelete}>
<Button size="sm" color="danger" onClick={onDelete}>
Delete
</Button>
</div>
@@ -159,28 +159,27 @@ const CollectionProperties = ({ onClose }) => {
{!cookies || !cookies.length ? (
// No cookies found
<div className="flex items-center justify-center flex-col">
<IconCookieOff size={48} strokeWidth={1.5} className="empty-icon" />
<IconCookieOff size={48} strokeWidth={1.5} className="text-gray-500" />
<h2 className="text-lg font-medium mt-4">No cookies found</h2>
<p className="empty-text mt-2">Add cookies to get started</p>
<Button
<p className="text-gray-500 mt-2">Add cookies to get started</p>
<button
type="submit"
size="sm"
className="mt-8"
icon={<IconCirclePlus strokeWidth={1.5} size={16} />}
className="submit btn btn-sm btn-secondary flex items-center gap-1 mt-8"
onClick={(e) => {
e.stopPropagation();
handleAddCookie();
}}
>
Add Cookie
</Button>
<IconCirclePlus strokeWidth={1.5} size={16} />
<span>Add Cookie</span>
</button>
</div>
) : cookies.length && !filteredCookies.length ? (
// No search results
<div className="flex items-center justify-center flex-col">
<IconSearch size={48} />
<h2 className="text-lg font-medium mt-4">No search results</h2>
<p className="empty-text mt-2">Try a different search term</p>
<p className="text-gray-500 mt-2">Try a different search term</p>
</div>
) : (
// Show cookies list
@@ -191,14 +190,14 @@ const CollectionProperties = ({ onClose }) => {
<Accordion.Header index={i} className="flex items-center">
<div className="flex items-center">
<span>{domainWithCookies.domain}</span>
<span className="domain-count ml-2 text-xs">
<span className="ml-2 text-xs dark:text-gray-300 text-gray-500">
({domainWithCookies.cookies.length}{' '}
{domainWithCookies.cookies.length === 1 ? 'cookie' : 'cookies'})
</span>
<div className="ml-auto flex items-center gap-2">
<button
type="submit"
className="action-button flex items-center gap-1"
className="flex items-center gap-1 text-gray-500 hover:text-gray-950 dark:text-white dark:hover:text-gray-300"
onClick={(e) => {
e.stopPropagation();
handleAddCookie(domainWithCookies.domain);
@@ -211,7 +210,7 @@ const CollectionProperties = ({ onClose }) => {
e.stopPropagation();
handleClearDomainCookies(domainWithCookies.domain);
}}
className="action-button-danger mr-2"
className="text-gray-950 dark:text-white dark:hover:hover:text-red-600 hover:text-red-600 mr-2"
>
<IconTrash strokeWidth={1.5} size={16} />
</button>
@@ -222,7 +221,7 @@ const CollectionProperties = ({ onClose }) => {
<div className="flex items-center justify-between">
<table className="w-full">
<thead>
<tr className="text-left">
<tr className="text-left border-b border-gray-200 dark:border-neutral-600 text-gray-700 dark:text-gray-300">
<th className="py-2 px-4 font-medium w-32">Name</th>
<th className="py-2 px-4 font-medium w-52">Value</th>
<th className="py-2 px-4 font-medium">Path</th>
@@ -234,7 +233,7 @@ const CollectionProperties = ({ onClose }) => {
</thead>
<tbody>
{domainWithCookies.cookies.map((cookie) => (
<tr key={cookie.key}>
<tr key={cookie.key} className="border-b border-gray-200 dark:border-neutral-600 last:border-none">
<td className="py-2 px-4 truncate">
<span id={`cookie-key-${cookie.key}`}>{cookie.key}</span>
<Tooltip
@@ -275,7 +274,8 @@ const CollectionProperties = ({ onClose }) => {
e.stopPropagation();
handleEditCookie(domainWithCookies.domain, cookie);
}}
className="edit-button"
className="text-gray-700 hover:text-gray-950
dark:text-white dark:hover:text-gray-300"
>
<IconEdit strokeWidth={1.5} size={16} />
</button>
@@ -284,7 +284,7 @@ const CollectionProperties = ({ onClose }) => {
e.stopPropagation();
handleDeleteCookie(domainWithCookies.domain, cookie.path, cookie.key);
}}
className="delete-button"
className="text-gray-950 dark:text-white dark:hover:hover:text-red-600 hover:text-red-600"
>
<IconTrash strokeWidth={1.5} size={16} />
</button>

View File

@@ -38,6 +38,20 @@ 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,6 +32,12 @@ const StyledWrapper = styled.div`
}
}
.network-controls {
display: flex;
align-items: center;
gap: 8px;
}
.network-content {
flex: 1;
overflow: hidden;
@@ -75,10 +81,11 @@ const StyledWrapper = styled.div`
display: grid;
grid-template-columns: 80px 80px 150px 1fr 100px 80px 80px;
gap: 12px;
padding: 4px 16px;
padding: 8px 16px;
background: ${(props) => props.theme.console.headerBg};
border-bottom: 1px solid ${(props) => props.theme.console.border};
font-size: 10px;
font-size: ${(props) => props.theme.font.size.xs};
font-weight: 500;
color: ${(props) => props.theme.console.titleColor};
text-transform: uppercase;
letter-spacing: 0.5px;
@@ -96,7 +103,8 @@ const StyledWrapper = styled.div`
display: grid;
grid-template-columns: 80px 80px 150px 1fr 100px 80px 80px;
gap: 12px;
padding: 2px 16px;
padding: 6px 16px;
border-bottom: 1px solid ${(props) => props.theme.console.border};
cursor: pointer;
transition: background-color 0.1s ease;
font-size: ${(props) => props.theme.font.size.sm};
@@ -107,8 +115,7 @@ const StyledWrapper = styled.div`
}
&.selected {
padding-left: 13px;
background: ${(props) => props.theme.console.logHoverBg};
background: ${(props) => props.theme.console.buttonHoverBg};
border-left: 3px solid ${(props) => props.theme.console.checkboxColor};
}
}
@@ -116,19 +123,25 @@ const StyledWrapper = styled.div`
.method-badge {
display: inline-flex;
align-items: center;
justify-content: start;
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;
}
.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;
@@ -161,6 +174,120 @@ 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,33 +1,128 @@
import React, { useMemo } from 'react';
import React, { useState, useRef, useEffect, 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 methodLower = method?.toLowerCase() || 'get';
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';
}
};
return (
<span className={`method-badge ${methodLower}`}>
<span
className="method-badge"
style={{ backgroundColor: getMethodColor(method) }}
>
{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">
<span
className="status-badge"
style={{ color: getStatusColor(statusCode) }}
>
{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;
@@ -146,6 +241,22 @@ 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: 2px 8px;
padding: 8px 16px;
background: ${(props) => props.theme.console.headerBg};
border-bottom: 1px solid ${(props) => props.theme.console.border};
flex-shrink: 0;
@@ -27,6 +27,7 @@ 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};
@@ -65,7 +66,7 @@ const StyledWrapper = styled.div`
display: flex;
align-items: center;
gap: 6px;
padding: 4px 8px;
padding: 8px 16px;
background: transparent;
border: none;
border-bottom: 2px solid transparent;
@@ -91,7 +92,7 @@ const StyledWrapper = styled.div`
flex: 1;
overflow-y: auto;
overflow-x: hidden;
padding: 8px;
padding: 16px;
min-height: 0;
height: 0;
}
@@ -169,7 +170,8 @@ const StyledWrapper = styled.div`
z-index: 10;
td {
padding: 4px 8px;
padding: 8px 12px;
font-weight: 500;
color: ${(props) => props.theme.console.titleColor};
text-transform: uppercase;
font-size: ${(props) => props.theme.font.size.xs};
@@ -196,7 +198,7 @@ const StyledWrapper = styled.div`
}
td {
padding: 2px 8px;
padding: 8px 12px;
vertical-align: top;
word-break: break-word;
}
@@ -254,7 +256,7 @@ const StyledWrapper = styled.div`
}
.response-body-container {
border-radius: ${(props) => props.theme.border.radius.sm};
border-radius: 4px;
overflow: hidden;
height: 400px;
display: flex;
@@ -268,7 +270,7 @@ const StyledWrapper = styled.div`
}
div[role="tablist"] {
padding: 4px 8px;
padding: 8px 12px;
border-bottom: 1px solid ${(props) => props.theme.console.border};
display: flex !important;
gap: 8px !important;
@@ -303,7 +305,7 @@ const StyledWrapper = styled.div`
}
}
.network-logs-wrapper {
.network-logs-container {
border: 1px solid ${(props) => props.theme.console.border};
border-radius: 4px;
overflow: hidden;
@@ -311,17 +313,17 @@ const StyledWrapper = styled.div`
min-height: 200px;
max-height: 400px;
.network-logs-container {
.network-logs {
background: ${(props) => props.theme.console.contentBg} !important;
color: ${(props) => props.theme.console.messageColor} !important;
height: 100% !important;
max-height: 400px !important;
padding: 0.5rem !important;
.network-logs-pre {
pre {
color: ${(props) => props.theme.console.messageColor} !important;
font-size: ${(props) => props.theme.font.size.xs} !important;
line-height: 1.4 !important;
padding: 12px !important;
}
}
}

View File

@@ -141,7 +141,7 @@ const NetworkTab = ({ response }) => {
<div className="tab-content">
<div className="section">
<h4>Network Logs</h4>
<div className="network-logs-wrapper">
<div className="network-logs-container">
{timeline.length > 0 ? (
<Network logs={timeline} />
) : (

View File

@@ -13,7 +13,7 @@ const StyledWrapper = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 8px;
padding: 0 16px;
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: 4px 8px;
padding: 6px 12px;
background: transparent;
border: none;
border-bottom: 2px solid transparent;
@@ -38,6 +38,8 @@ 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};
@@ -45,9 +47,9 @@ const StyledWrapper = styled.div`
}
&.active {
color: ${(props) => props.theme.primary.strong};
border-bottom-color: ${(props) => props.theme.primary.strong};
background: ${(props) => props.theme.background.mantle};
color: ${(props) => props.theme.console.checkboxColor};
border-bottom-color: ${(props) => props.theme.console.checkboxColor};
background: ${(props) => props.theme.console.contentBg};
}
}
@@ -142,6 +144,9 @@ 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 {
@@ -154,21 +159,23 @@ const StyledWrapper = styled.div`
display: flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
width: 28px;
height: 28px;
background: transparent;
border: none;
border-radius: 4px;
color: ${(props) => props.theme.text};
color: ${(props) => props.theme.console.buttonColor};
cursor: pointer;
transition: all 0.2s ease;
&:hover {
background: ${(props) => props.theme.background.surface0};
background: ${(props) => props.theme.console.buttonHoverBg};
color: ${(props) => props.theme.console.buttonHoverColor};
}
&.close-button:hover {
background: ${(props) => props.theme.background.surface0};
background: #e81123;
color: white;
}
}
@@ -180,17 +187,19 @@ const StyledWrapper = styled.div`
display: flex;
align-items: center;
gap: 6px;
padding: 2px 8px;
padding: 6px 8px;
background: transparent;
border: 1px solid ${(props) => props.theme.border.border0};
border-radius: ${(props) => props.theme.border.radius.sm};
color: ${(props) => props.theme.text};
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.background.surface0};
background: ${(props) => props.theme.console.buttonHoverBg};
color: ${(props) => props.theme.console.buttonHoverColor};
border-color: ${(props) => props.theme.console.border};
}
.filter-summary {
@@ -223,7 +232,7 @@ const StyledWrapper = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
padding: 4px 12px;
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};
@@ -254,7 +263,7 @@ const StyledWrapper = styled.div`
.filter-option {
display: flex;
align-items: center;
padding: 4px 12px;
padding: 6px 12px;
cursor: pointer;
transition: background-color 0.2s ease;
@@ -316,6 +325,20 @@ 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,9 +5,10 @@ import ToolHint from 'components/ToolHint/index';
const StyledSessionList = styled.div`
.session-list-item {
padding: 2px 6px;
padding: 10px 12px;
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;
@@ -23,8 +24,7 @@ const StyledSessionList = styled.div`
&.active {
background: ${(props) => props.theme.sidebarActive || 'rgba(59, 142, 234, 0.12)'};
border-left: 2px solid ${(props) => props.theme.primary.subtle};
padding-left: 4px;
border-left: 2px solid ${(props) => props.theme.brandColor || '#3b8eea'};
}
&:last-child {

View File

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

View File

@@ -182,6 +182,19 @@ 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)) {
@@ -228,6 +241,9 @@ 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,8 +1,6 @@
import React, { useCallback, useEffect, useState, useMemo } from 'react';
import React, { useCallback, useEffect, useState } 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;
@@ -12,9 +10,6 @@ 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();
@@ -73,15 +68,15 @@ const Devtools = ({ mainSectionRef }) => {
<div
onMouseDown={handleDevtoolsResizeStart}
style={{
height: '2px',
height: '4px',
cursor: 'row-resize',
backgroundColor: isResizingDevtools ? dragHandleColor : 'transparent',
backgroundColor: isResizingDevtools ? '#0078d4' : 'transparent',
transition: 'background-color 0.2s ease',
zIndex: 20,
position: 'relative'
}}
onMouseEnter={(e) => e.target.style.backgroundColor = dragHandleColor}
onMouseLeave={(e) => e.target.style.backgroundColor = isResizingDevtools ? dragHandleColor : 'transparent'}
onMouseEnter={(e) => e.target.style.backgroundColor = '#0078d4'}
onMouseLeave={(e) => e.target.style.backgroundColor = isResizingDevtools ? '#0078d4' : 'transparent'}
/>
<div style={{ height: `${devtoolsHeight}px`, overflow: 'hidden', position: 'relative' }}>
<Console />

View File

@@ -1,20 +1,12 @@
import styled from 'styled-components';
import { rgba } from 'polished';
const Wrapper = styled.div`
min-width: 160px;
font-size: ${(props) => props.theme.font.size.sm};
font-size: ${(props) => props.theme.font.size.base};
color: ${(props) => props.theme.dropdown.color};
background-color: ${(props) => props.theme.dropdown.bg};
${(props) =>
props.theme.dropdown.shadow && props.theme.dropdown.shadow !== 'none'
? `box-shadow: ${props.theme.dropdown.shadow};`
: ''}
box-shadow: ${(props) => props.theme.shadow.sm};
border-radius: ${(props) => props.theme.border.radius.base};
${(props) =>
props.theme.dropdown.border && props.theme.dropdown.border !== 'none'
? `border: 1px solid ${props.theme.dropdown.border};`
: ''}
max-height: 90vh;
overflow-y: auto;
max-width: unset !important;
@@ -53,7 +45,7 @@ const Wrapper = styled.div`
cursor: pointer;
border-radius: 6px;
margin: 0.0625rem 0;
font-size: ${(props) => props.theme.font.size.sm};
font-size: 0.8125rem;
&.active {
color: ${(props) => props.theme.colors.text.yellow} !important;
@@ -144,21 +136,17 @@ const Wrapper = styled.div`
/* Active/selected state - applied to the currently selected item */
&.dropdown-item-active {
color: ${({ theme }) => theme.dropdown.selectedColor} !important;
background-color: ${({ theme }) => rgba(theme.dropdown.selectedColor, 0.07)} !important;
color: ${({ theme }) => theme.colors.text.yellow};
background-color: ${({ theme }) => theme.dropdown.activeBg};
font-weight: 500;
.dropdown-icon {
color: ${({ theme }) => theme.dropdown.selectedColor} !important;
}
&:hover {
color: ${({ theme }) => theme.dropdown.selectedColor} !important;
background-color: ${({ theme }) => rgba(theme.dropdown.selectedColor, 0.07)} !important;
color: ${({ theme }) => theme.colors.text.yellow};
}
}
/* Combined state - when active item is also focused */
&.dropdown-item-active.dropdown-item-focused {
background-color: ${({ theme }) => rgba(theme.dropdown.selectedColor, 0.07)} !important;
background-color: ${({ theme }) => theme.dropdown.activeHoverBg};
}
/* Focus visible for accessibility */

View File

@@ -9,7 +9,7 @@ const StyledWrapper = styled.div`
.table-container {
overflow-y: auto;
border-radius: ${(props) => props.theme.border.radius.base};
border: solid 1px ${(props) => props.theme.border.border0};
border: ${(props) => props.theme.workspace.environments.indentBorder};
}
table {
@@ -31,8 +31,8 @@ const StyledWrapper = styled.div`
padding: 5px 10px !important;
border-top: none !important;
border-left: none !important;
border-bottom: solid 1px ${(props) => props.theme.border.border0};
border-right: solid 1px ${(props) => props.theme.border.border0};
border-bottom: ${(props) => props.theme.workspace.environments.indentBorder};
border-right: ${(props) => props.theme.workspace.environments.indentBorder};
vertical-align: middle;
&:last-child {
@@ -58,8 +58,8 @@ const StyledWrapper = styled.div`
padding: 1px 10px !important;
border-top: none !important;
border-left: none !important;
border-bottom: solid 1px ${(props) => props.theme.border.border0};
border-right: solid 1px ${(props) => props.theme.border.border0};
border-bottom: ${(props) => props.theme.workspace.environments.indentBorder};
border-right: ${(props) => props.theme.workspace.environments.indentBorder};
vertical-align: middle;
&:last-child {
@@ -83,6 +83,7 @@ const StyledWrapper = styled.div`
}
.tooltip-mod {
font-size: 11px !important;
max-width: 200px !important;
}

View File

@@ -12,7 +12,6 @@ const EditableTable = ({
getRowError,
showCheckbox = true,
showDelete = true,
disableCheckbox = false,
checkboxLabel = '',
checkboxKey = 'enabled',
reorderable = false,
@@ -289,7 +288,6 @@ const EditableTable = ({
className="mousetrap"
data-testid="column-checkbox"
checked={row[checkboxKey] ?? true}
disabled={disableCheckbox}
onChange={(e) => handleCheckboxChange(row.uid, e.target.checked)}
/>
)}

View File

@@ -6,7 +6,6 @@ import { browseDirectory } from 'providers/ReduxStore/slices/collections/actions
import { useDispatch } from 'react-redux';
import toast from 'react-hot-toast';
import StyledWrapper from './StyledWrapper';
import Button from 'ui/Button';
const ExportEnvironmentModal = ({ onClose, environments = [], environmentType }) => {
const dispatch = useDispatch();
@@ -243,26 +242,22 @@ const ExportEnvironmentModal = ({ onClose, environments = [], environmentType })
{/* Export Actions */}
<div className="flex justify-end gap-2 mt-4 pt-3 border-t border-gray-200 dark:border-gray-700">
<Button
<button
type="button"
size="sm"
color="secondary"
variant="ghost"
className="btn btn-sm btn-cancel mt-2 flex items-center"
onClick={onClose}
disabled={isExporting}
className="mt-2 mr-2"
>
Cancel
</Button>
<Button
</button>
<button
type="button"
size="sm"
className="btn btn-sm btn-secondary mt-2 flex items-center"
onClick={handleExport}
disabled={isExporting || selectedCount === 0}
className="mt-2"
>
{isExporting ? 'Exporting...' : `Export ${selectedCount || ''} Environment${selectedCount !== 1 ? 's' : ''}`}
</Button>
</button>
</div>
</div>
</Modal>

View File

@@ -26,15 +26,15 @@ const ConfirmCloseEnvironment = ({ onCancel, onCloseWithoutSave, onSaveAndClose,
<div className="flex justify-between mt-6">
<div>
<Button color="danger" onClick={onCloseWithoutSave}>
<Button size="sm" color="danger" onClick={onCloseWithoutSave}>
Don't Save
</Button>
</div>
<div className="flex gap-2">
<div>
<Button size="sm" color="secondary" variant="ghost" onClick={onCancel}>
Cancel
</Button>
<Button onClick={onSaveAndClose}>
<Button size="sm" onClick={onSaveAndClose}>
Save
</Button>
</div>

View File

@@ -33,7 +33,7 @@ const EnvironmentListContent = ({
{environments.map((env) => (
<div
key={env.uid}
className={`dropdown-item ${env.uid === activeEnvironmentUid ? 'dropdown-item-active' : ''}`}
className={`dropdown-item ${env.uid === activeEnvironmentUid ? 'active' : ''}`}
onClick={() => onEnvironmentSelect(env)}
data-tooltip-content={env.name}
data-tooltip-hidden={env.name?.length < 90}

View File

@@ -65,6 +65,35 @@ const Wrapper = styled.div`
overflow: hidden;
}
.tippy-box .tippy-content {
padding: 0;
display: flex;
flex-direction: column;
height: 100%;
.dropdown-item {
display: flex;
align-items: center;
padding: 0.35rem 0.6rem;
cursor: pointer;
font-size: ${(props) => props.theme.font.size.base};
color: ${(props) => props.theme.dropdown.primaryText};
&:hover:not(:disabled) {
background-color: ${(props) => props.theme.dropdown.hoverBg};
}
&.active {
background-color: ${(props) => props.theme.dropdown.selectedBg};
color: ${(props) => props.theme.dropdown.selectedColor};
}
&.no-environment {
color: ${(props) => props.theme.dropdown.mutedText};
}
}
}
.configure-button {
position: absolute;
bottom: 0;
@@ -80,7 +109,7 @@ const Wrapper = styled.div`
}
button {
color: ${(props) => props.theme.text};
color: ${(props) => props.theme.dropdown.primaryText};
display: flex;
align-items: center;
justify-content: center;
@@ -90,8 +119,8 @@ const Wrapper = styled.div`
}
.tab-button {
color: ${(props) => props.theme.colors.text.subtext0};
font-size: ${(props) => props.theme.font.size.sm};
color: var(--color-tab-inactive);
font-size: ${(props) => props.theme.font.size.base};
.tab-content-wrapper {
position: relative;
@@ -141,7 +170,7 @@ const Wrapper = styled.div`
min-height: 12.5rem;
h3 {
color: ${(props) => props.theme.text};
color: ${(props) => props.theme.dropdown.primaryText};
font-size: 1rem;
font-weight: 500;
margin-bottom: 0.5rem;
@@ -149,7 +178,7 @@ const Wrapper = styled.div`
}
p {
color: ${(props) => props.theme.text};
color: ${(props) => props.theme.dropdown.primaryText};
opacity: 0.75;
font-size: ${(props) => props.theme.font.size.xs};
line-height: 1.5;
@@ -165,9 +194,9 @@ const Wrapper = styled.div`
}
.space-y-2 > button {
border: 0.0625rem solid ${(props) => props.theme.text};
border: 0.0625rem solid ${(props) => props.theme.dropdown.primaryText};
background: transparent;
color: ${(props) => props.theme.text};
color: ${(props) => props.theme.dropdown.primaryText};
padding: 0.5rem 1rem;
border-radius: 0.375rem;
width: 100%;
@@ -195,7 +224,7 @@ const Wrapper = styled.div`
align-items: center;
justify-content: center;
padding: 2rem 1rem;
color: ${(props) => props.theme.text};
color: ${(props) => props.theme.dropdown.primaryText};
font-size: ${(props) => props.theme.font.size.base};
line-height: 1.5;
text-align: center;
@@ -203,7 +232,7 @@ const Wrapper = styled.div`
svg {
margin: 0 auto 1rem auto;
color: ${(props) => props.theme.text};
color: ${(props) => props.theme.dropdown.primaryText};
opacity: 0.5;
}
}

View File

@@ -186,7 +186,7 @@ const EnvironmentSelector = ({ collection }) => {
<div className="environment-selector flex align-center cursor-pointer">
<Dropdown onCreate={onDropdownCreate} icon={<Icon />} placement="bottom-end">
{/* Tab Headers */}
<div className="tab-header flex pt-3 pb-2 px-3">
<div className="tab-header flex p-[0.75rem]">
{tabs.map((tab) => (
<button
key={tab.id}

View File

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

View File

@@ -26,7 +26,6 @@ const DeleteEnvironment = ({ onClose, environment, collection }) => {
confirmText="Delete"
handleConfirm={onConfirm}
handleCancel={onClose}
confirmButtonColor="danger"
>
Are you sure you want to delete <span className="font-medium">{environment.name}</span> ?
</Modal>

View File

@@ -9,7 +9,7 @@ const Wrapper = styled.div`
.table-container {
overflow-y: auto;
border-radius: 8px;
border: solid 1px ${(props) => props.theme.border.border0};
border: ${(props) => props.theme.workspace.environments.indentBorder};
}
table {
@@ -46,8 +46,8 @@ const Wrapper = styled.div`
td {
padding: 5px 10px !important;
border-bottom: solid 1px ${(props) => props.theme.border.border0};
border-right: solid 1px ${(props) => props.theme.border.border0};
border-bottom: ${(props) => props.theme.workspace.environments.indentBorder};
border-right: ${(props) => props.theme.workspace.environments.indentBorder};
&:last-child {
border-right: none;
@@ -64,8 +64,8 @@ const Wrapper = styled.div`
}
td {
border-bottom: solid 1px ${(props) => props.theme.border.border0};
border-right: solid 1px ${(props) => props.theme.border.border0};
border-bottom: ${(props) => props.theme.workspace.environments.indentBorder};
border-right: ${(props) => props.theme.workspace.environments.indentBorder};
&:last-child {
border-right: none;
@@ -75,7 +75,28 @@ const Wrapper = styled.div`
}
}
.btn-add-param {
font-size: 12px;
color: ${(props) => props.theme.textLink};
font-weight: 500;
padding: 7px 14px;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 6px;
border-radius: 6px;
border: ${(props) => props.theme.sidebar.collection.item.indentBorder};
background: transparent;
transition: all 0.15s ease;
&:hover {
background: ${(props) => props.theme.listItem.hoverBg};
border-color: ${(props) => props.theme.textLink};
}
}
.tooltip-mod {
font-size: 11px !important;
max-width: 200px !important;
}
@@ -154,7 +175,7 @@ const Wrapper = styled.div`
border-radius: ${(props) => props.theme.border.radius.base};
background: transparent;
color: ${(props) => props.theme.text};
border: 1px solid ${(props) => props.theme.border.border1};
border: ${(props) => props.theme.sidebar.collection.item.indentBorder};
cursor: pointer;
transition: all 0.15s ease;

View File

@@ -1,5 +1,4 @@
import React, { useCallback, useRef, useMemo, useEffect } from 'react';
import { TableVirtuoso } from 'react-virtuoso';
import cloneDeep from 'lodash/cloneDeep';
import { get } from 'lodash';
import { IconTrash, IconAlertCircle, IconInfoCircle } from '@tabler/icons';
@@ -19,28 +18,14 @@ import { getGlobalEnvironmentVariables, flattenItems, isItemARequest } from 'uti
import SensitiveFieldWarning from 'components/SensitiveFieldWarning';
import { sensitiveFields } from './constants';
const TableRow = React.memo(({ children, item }) => <tr key={item.uid} data-testid={`env-var-row-${item.name}`}>{children}</tr>, (prevProps, nextProps) => {
const prevUid = prevProps?.item?.uid;
const nextUid = nextProps?.item?.uid;
return prevUid === nextUid && prevProps.children === nextProps.children;
});
const MIN_H = 35 * 2; // 2 rows worth of height
const EnvironmentVariables = ({ environment, setIsModified, collection }) => {
const dispatch = useDispatch();
const { storedTheme } = useTheme();
const { globalEnvironments, activeGlobalEnvironmentUid } = useSelector((state) => state.globalEnvironments);
const [tableHeight, setTableHeight] = React.useState(MIN_H);
const environmentsDraft = collection?.environmentsDraft;
const hasDraftForThisEnv = environmentsDraft?.environmentUid === environment.uid;
const handleTotalHeightChanged = React.useCallback((h) => {
setTableHeight(h);
}, []);
// Track environment changes for draft restoration
const prevEnvUidRef = React.useRef(null);
const mountedRef = React.useRef(false);
@@ -399,114 +384,111 @@ const EnvironmentVariables = ({ environment, setIsModified, collection }) => {
return (
<StyledWrapper>
<TableVirtuoso
className="table-container"
style={{ height: tableHeight }}
components={{ TableRow }}
data={formik.values}
totalListHeightChanged={handleTotalHeightChanged}
fixedHeaderContent={() => (
<tr>
<td className="text-center"></td>
<td>Name</td>
<td>Value</td>
<td className="text-center">Secret</td>
<td></td>
</tr>
)}
fixedItemHeight={35}
computeItemKey={(index, variable) => variable.uid}
itemContent={(index, variable) => {
const isLastRow = index === formik.values.length - 1;
const isEmptyRow = !variable.name || variable.name.trim() === '';
const isLastEmptyRow = isLastRow && isEmptyRow;
<div className="table-container">
<table>
<thead>
<tr>
<td className="text-center"></td>
<td>Name</td>
<td>Value</td>
<td className="text-center">Secret</td>
<td></td>
</tr>
</thead>
<tbody>
{formik.values.map((variable, index) => {
const isLastRow = index === formik.values.length - 1;
const isEmptyRow = !variable.name || variable.name.trim() === '';
const isLastEmptyRow = isLastRow && isEmptyRow;
return (
<>
<td className="text-center">
{!isLastEmptyRow && (
<input
type="checkbox"
className="mousetrap"
name={`${index}.enabled`}
checked={variable.enabled}
onChange={formik.handleChange}
/>
)}
</td>
<td>
<div className="flex items-center">
<input
type="text"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
className="mousetrap"
id={`${index}.name`}
name={`${index}.name`}
value={variable.name}
placeholder={isLastEmptyRow ? 'Name' : ''}
onChange={(e) => handleNameChange(index, e)}
onBlur={() => handleNameBlur(index)}
onKeyDown={(e) => handleNameKeyDown(index, e)}
/>
<ErrorMessage name={`${index}.name`} index={index} />
</div>
</td>
<td className="flex flex-row flex-nowrap items-center">
<div className="overflow-hidden grow w-full relative">
<MultiLineEditor
theme={storedTheme}
collection={_collection}
name={`${index}.value`}
value={variable.value}
placeholder={isLastEmptyRow ? 'Value' : ''}
isSecret={variable.secret}
readOnly={typeof variable.value !== 'string'}
onChange={(newValue) => formik.setFieldValue(`${index}.value`, newValue, true)}
onSave={handleSave}
/>
</div>
{typeof variable.value !== 'string' && (
<span className="ml-2 flex items-center">
<IconInfoCircle id={`${variable.uid}-disabled-info-icon`} className="text-muted" size={16} />
<Tooltip
anchorId={`${variable.uid}-disabled-info-icon`}
content="Non-string values set via scripts are read-only and can only be updated through scripts."
place="top"
/>
</span>
)}
{!variable.secret && hasSensitiveUsage(variable.name) && (
<SensitiveFieldWarning
fieldName={variable.name}
warningMessage="This variable is used in sensitive fields. Mark it as a secret for security"
/>
)}
</td>
<td className="text-center">
{!isLastEmptyRow && (
<input
type="checkbox"
className="mousetrap"
name={`${index}.secret`}
checked={variable.secret}
onChange={formik.handleChange}
/>
)}
</td>
<td>
{!isLastEmptyRow && (
<button onClick={() => handleRemoveVar(variable.uid)}>
<IconTrash strokeWidth={1.5} size={18} />
</button>
)}
</td>
</>
);
}}
/>
return (
<tr key={variable.uid} data-testid={`env-var-row-${variable.name}`}>
<td className="text-center">
{!isLastEmptyRow && (
<input
type="checkbox"
className="mousetrap"
name={`${index}.enabled`}
checked={variable.enabled}
onChange={formik.handleChange}
/>
)}
</td>
<td>
<div className="flex items-center">
<input
type="text"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
className="mousetrap"
id={`${index}.name`}
name={`${index}.name`}
value={variable.name}
placeholder={isLastEmptyRow ? 'Name' : ''}
onChange={(e) => handleNameChange(index, e)}
onBlur={() => handleNameBlur(index)}
onKeyDown={(e) => handleNameKeyDown(index, e)}
/>
<ErrorMessage name={`${index}.name`} index={index} />
</div>
</td>
<td className="flex flex-row flex-nowrap items-center">
<div className="overflow-hidden grow w-full relative">
<MultiLineEditor
theme={storedTheme}
collection={_collection}
name={`${index}.value`}
value={variable.value}
placeholder={isLastEmptyRow ? 'Value' : ''}
isSecret={variable.secret}
readOnly={typeof variable.value !== 'string'}
onChange={(newValue) => formik.setFieldValue(`${index}.value`, newValue, true)}
onSave={handleSave}
/>
</div>
{typeof variable.value !== 'string' && (
<span className="ml-2 flex items-center">
<IconInfoCircle id={`${variable.uid}-disabled-info-icon`} className="text-muted" size={16} />
<Tooltip
anchorId={`${variable.uid}-disabled-info-icon`}
content="Non-string values set via scripts are read-only and can only be updated through scripts."
place="top"
/>
</span>
)}
{!variable.secret && hasSensitiveUsage(variable.name) && (
<SensitiveFieldWarning
fieldName={variable.name}
warningMessage="This variable is used in sensitive fields. Mark it as a secret for security"
/>
)}
</td>
<td className="text-center">
{!isLastEmptyRow && (
<input
type="checkbox"
className="mousetrap"
name={`${index}.secret`}
checked={variable.secret}
onChange={formik.handleChange}
/>
)}
</td>
<td>
{!isLastEmptyRow && (
<button onClick={() => handleRemoveVar(variable.uid)}>
<IconTrash strokeWidth={1.5} size={18} />
</button>
)}
</td>
</tr>
);
})}
</tbody>
</table>
</div>
<div className="button-container">
<div className="flex items-center">

View File

@@ -1,10 +1,10 @@
import styled from 'styled-components';
import { rgba } from 'polished';
const StyledWrapper = styled.div`
display: flex;
height: 100%;
overflow: hidden;
background-color: ${(props) => props.theme.bg};
position: relative;
.environments-container {
@@ -22,12 +22,14 @@ const StyledWrapper = styled.div`
z-index: 10;
background: ${(props) => props.theme.bg};
padding: 12px;
border-bottom: 1px solid ${(props) => props.theme.sidebar.collection.item.indentBorder};
}
/* Left Sidebar */
.sidebar {
width: 240px;
min-width: 240px;
border-right: 1px solid ${(props) => props.theme.sidebar.collection.item.indentBorder};
display: flex;
flex-direction: column;
}
@@ -84,7 +86,7 @@ const StyledWrapper = styled.div`
padding: 6px 8px 6px 28px;
font-size: 12px;
background: transparent;
border: 1px solid ${(props) => props.theme.border.border1};
border: ${(props) => props.theme.sidebar.collection.item.indentBorder};
border-radius: 5px;
color: ${(props) => props.theme.text};
transition: all 0.15s ease;
@@ -172,7 +174,7 @@ const StyledWrapper = styled.div`
}
&.active {
background: ${(props) => props.theme.background.surface0};
background: ${(props) => props.theme.workspace.environments.activeBg};
color: ${(props) => props.theme.text};
}
@@ -191,12 +193,9 @@ const StyledWrapper = styled.div`
display: flex;
align-items: center;
flex: 1;
min-width: 0;
overflow: hidden;
.environment-name-input {
flex: 1;
min-width: 0;
background: transparent;
border: none;
outline: none;
@@ -213,14 +212,12 @@ const StyledWrapper = styled.div`
display: flex;
gap: 2px;
margin-left: 4px;
flex-shrink: 0;
}
}
&.creating {
.environment-name-input {
flex: 1;
min-width: 0;
background: transparent;
border: none;
outline: none;
@@ -237,36 +234,36 @@ const StyledWrapper = styled.div`
display: flex;
gap: 2px;
margin-left: 4px;
flex-shrink: 0;
}
}
.inline-action-btn {
display: flex;
align-items: center;
justify-content: center;
width: 22px;
height: 22px;
padding: 0;
background: transparent;
border: none;
border-radius: 4px;
cursor: pointer;
transition: all 0.15s ease;
&.save {
color: ${(props) => props.theme.colors.text.green};
.inline-action-btn {
display: flex;
align-items: center;
justify-content: center;
width: 22px;
height: 22px;
padding: 0;
background: transparent;
border: none;
border-radius: 4px;
cursor: pointer;
transition: all 0.15s ease;
&:hover {
background: ${(props) => rgba(props.theme.colors.text.green, 0.1)};
&.save {
color: ${(props) => props.theme.textLink};
&:hover {
background: ${(props) => props.theme.listItem.hoverBg};
}
}
}
&.cancel {
color: ${(props) => props.theme.colors.text.danger};
&:hover {
background: ${(props) => rgba(props.theme.colors.text.danger, 0.1)};
&.cancel {
color: ${(props) => props.theme.colors.text.muted};
&:hover {
background: ${(props) => props.theme.listItem.hoverBg};
color: ${(props) => props.theme.text};
}
}
}
}

View File

@@ -11,7 +11,7 @@ const StyledWrapper = styled.div`
display: flex;
flex-direction: column;
align-items: center;
padding-top: 10%;
justify-content: center;
flex: 1;
color: ${(props) => props.theme.colors.text.muted};
@@ -32,6 +32,22 @@ const StyledWrapper = styled.div`
gap: 8px;
}
}
.shared-button {
padding: 5px 10px;
font-size: 12px;
border-radius: 5px;
border: 1px solid ${(props) => props.theme.sidebar.collection.item.indentBorder};
background: ${(props) => props.theme.sidebar.bg};
color: ${(props) => props.theme.text};
cursor: pointer;
transition: all 0.1s ease;
&:hover {
background: ${(props) => props.theme.listItem.hoverBg};
border-color: ${(props) => props.theme.textLink};
}
}
`;
export default StyledWrapper;

View File

@@ -5,34 +5,30 @@ import StyledWrapper from './StyledWrapper';
import { IconFileAlert } from '@tabler/icons';
import ImportEnvironmentModal from 'components/Environments/Common/ImportEnvironmentModal';
import ExportEnvironmentModal from 'components/Environments/Common/ExportEnvironmentModal';
import Button from 'ui/Button';
const DefaultTab = ({ setTab }) => (
<div className="empty-state">
<IconFileAlert size={48} strokeWidth={1.5} />
<div className="title">No Environments</div>
<div className="actions">
<Button size="sm" color="secondary" onClick={() => setTab('create')}>
<button className="shared-button" onClick={() => setTab('create')}>
Create Environment
</Button>
<Button size="sm" color="secondary" onClick={() => setTab('import')}>
</button>
<button className="shared-button" onClick={() => setTab('import')}>
Import Environment
</Button>
</button>
</div>
</div>
);
const EnvironmentSettings = ({ collection }) => {
const [isModified, setIsModified] = useState(false);
const environments = collection?.environments || [];
const [selectedEnvironment, setSelectedEnvironment] = useState(() => {
if (!environments.length) return null;
return environments.find((env) => env.uid === collection?.activeEnvironmentUid) || environments[0];
});
const [selectedEnvironment, setSelectedEnvironment] = useState(null);
const [tab, setTab] = useState('default');
const [showExportModal, setShowExportModal] = useState(false);
const environments = collection?.environments || [];
if (!environments || !environments.length) {
return (
<StyledWrapper>

View File

@@ -1,110 +0,0 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
display: flex;
align-items: center;
width: 100%;
overflow: hidden;
min-width: 0;
.file-picker-btn {
display: flex;
align-items: center;
justify-content: center;
padding: 4px 8px;
color: ${(props) => props.theme.colors.text.muted};
background: transparent;
border: none;
cursor: pointer;
border-radius: 4px;
transition: color 0.15s ease;
font-size: 12px;
white-space: nowrap;
overflow: hidden;
min-width: 0;
max-width: 100%;
&:hover {
color: ${(props) => props.theme.text} !important;
}
&.read-only {
cursor: default;
opacity: 0.6;
}
&.icon-only {
padding: 4px;
flex-shrink: 0;
}
&.icon-right {
width: 100%;
justify-content: space-between;
}
span {
line-height: 1;
overflow: hidden;
text-overflow: ellipsis;
min-width: 0;
}
.label {
font-style: italic;
}
svg {
flex-shrink: 0;
}
}
.file-picker-selected {
display: flex;
align-items: center;
padding: 4px 0;
width: 100%;
cursor: pointer;
&.read-only {
cursor: default;
}
.file-icon {
flex-shrink: 0;
color: ${(props) => props.theme.colors.text.muted};
margin-right: 4px;
}
.file-name {
flex: 1;
font-size: 12px;
color: ${(props) => props.theme.text};
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
min-width: 0;
}
.clear-btn {
display: flex;
align-items: center;
justify-content: center;
padding: 4px;
margin-left: 4px;
color: ${(props) => props.theme.colors.text.muted};
background: transparent;
border: none;
cursor: pointer;
border-radius: 4px;
transition: color 0.15s ease;
flex-shrink: 0;
&:hover {
color: ${(props) => props.theme.text};
}
}
}
`;
export default StyledWrapper;

Some files were not shown because too many files have changed in this diff Show More