From 67c1d39e60dd80a2f0f2a96b1f9803a4b1644473 Mon Sep 17 00:00:00 2001 From: Pragadesh-45 <54320162+Pragadesh-45@users.noreply.github.com> Date: Wed, 21 Jan 2026 19:22:28 +0530 Subject: [PATCH] feat: preferences as tab (#6786) * feat: preferences as tab refactor: remove preferences tab from permanent tabs and update tab label handling fix: comment Co-authored-by: Sid * refactor: replace Checkbox component with native input elements in Preferences and ProxySettings --------- Co-authored-by: Sid --- .../Preferences/Display/Font/index.js | 2 +- .../Preferences/General/StyledWrapper.js | 16 +++ .../components/Preferences/General/index.js | 2 +- .../Preferences/Keybindings/StyledWrapper.js | 9 +- .../Preferences/ProxySettings/index.js | 2 + .../components/Preferences/StyledWrapper.js | 28 ++++- .../src/components/Preferences/index.js | 109 ++++++++++-------- .../src/components/RequestTabPanel/index.js | 7 +- .../RequestTabs/CollectionToolBar/index.js | 4 + .../RequestTabs/RequestTab/SpecialTab.js | 8 ++ .../RequestTabs/RequestTab/index.js | 2 +- .../src/components/RequestTabs/index.js | 6 +- .../src/components/StatusBar/index.js | 46 +++++--- .../src/components/Table/StyledWrapper.js | 2 +- .../src/components/WorkspaceHome/index.js | 3 + .../WorkspaceTabs/WorkspaceTab/index.js | 5 +- .../src/components/WorkspaceTabs/index.js | 2 +- packages/bruno-app/src/pages/Bruno/index.js | 2 +- .../src/providers/App/useIpcEvents.js | 37 +++++- .../bruno-app/src/providers/Hotkeys/index.js | 19 ++- .../src/providers/ReduxStore/slices/app.js | 13 +-- .../src/providers/ReduxStore/slices/tabs.js | 3 +- .../ReduxStore/slices/workspaceTabs.js | 7 +- .../bruno-app/src/utils/collections/index.js | 3 + 24 files changed, 232 insertions(+), 105 deletions(-) diff --git a/packages/bruno-app/src/components/Preferences/Display/Font/index.js b/packages/bruno-app/src/components/Preferences/Display/Font/index.js index 6759db71e..2f5c50df3 100644 --- a/packages/bruno-app/src/components/Preferences/Display/Font/index.js +++ b/packages/bruno-app/src/components/Preferences/Display/Font/index.js @@ -6,7 +6,7 @@ import { savePreferences } from 'providers/ReduxStore/slices/app'; import StyledWrapper from './StyledWrapper'; import toast from 'react-hot-toast'; -const Font = ({ close }) => { +const Font = () => { const dispatch = useDispatch(); const preferences = useSelector((state) => state.app.preferences); const isInitialMount = useRef(true); diff --git a/packages/bruno-app/src/components/Preferences/General/StyledWrapper.js b/packages/bruno-app/src/components/Preferences/General/StyledWrapper.js index d45eda5b6..e8cb7d515 100644 --- a/packages/bruno-app/src/components/Preferences/General/StyledWrapper.js +++ b/packages/bruno-app/src/components/Preferences/General/StyledWrapper.js @@ -2,6 +2,22 @@ import styled from 'styled-components'; const StyledWrapper = styled.div` color: ${(props) => props.theme.text}; + + .text-link { + color: ${(props) => props.theme.colors.text.link}; + text-decoration: none; + font-size: 0.8125rem; + + &:hover { + text-decoration: underline; + } + } + + form.bruno-form { + label { + font-size: 0.8125rem; + } + } `; export default StyledWrapper; diff --git a/packages/bruno-app/src/components/Preferences/General/index.js b/packages/bruno-app/src/components/Preferences/General/index.js index d14df4c1f..01b743917 100644 --- a/packages/bruno-app/src/components/Preferences/General/index.js +++ b/packages/bruno-app/src/components/Preferences/General/index.js @@ -11,7 +11,7 @@ import toast from 'react-hot-toast'; import path from 'utils/common/path'; import { IconTrash } from '@tabler/icons'; -const General = ({ close }) => { +const General = () => { const preferences = useSelector((state) => state.app.preferences); const dispatch = useDispatch(); const inputFileCaCertificateRef = useRef(); diff --git a/packages/bruno-app/src/components/Preferences/Keybindings/StyledWrapper.js b/packages/bruno-app/src/components/Preferences/Keybindings/StyledWrapper.js index 36830aacc..7d2cbb399 100644 --- a/packages/bruno-app/src/components/Preferences/Keybindings/StyledWrapper.js +++ b/packages/bruno-app/src/components/Preferences/Keybindings/StyledWrapper.js @@ -2,12 +2,12 @@ import styled from 'styled-components'; const StyledWrapper = styled.div` table { - width: 100%; + width: 80%; border-collapse: collapse; thead, td { - border: 2px solid ${(props) => props.theme.table.border}; + border: 1px solid ${(props) => props.theme.table.border}; } thead { @@ -17,7 +17,7 @@ const StyledWrapper = styled.div` } td { - padding: 4px 8px; + padding: 6px 10px; font-size: ${(props) => props.theme.font.size.sm}; } @@ -25,6 +25,7 @@ const StyledWrapper = styled.div` font-weight: 500; padding: 10px; text-align: left; + border: 1px solid ${(props) => props.theme.table.border}; } } @@ -35,11 +36,13 @@ const StyledWrapper = styled.div` .key-button { display: inline-block; color: ${(props) => props.theme.table.input.color}; + opacity: 0.7; border-radius: 4px; padding: 1px 5px; font-family: monospace; margin-right: 8px; border: 1px solid #ccc; + border-bottom: 1.44px solid ${(props) => props.theme.table.input.border}; } `; diff --git a/packages/bruno-app/src/components/Preferences/ProxySettings/index.js b/packages/bruno-app/src/components/Preferences/ProxySettings/index.js index 10bfc74a6..2601093ca 100644 --- a/packages/bruno-app/src/components/Preferences/ProxySettings/index.js +++ b/packages/bruno-app/src/components/Preferences/ProxySettings/index.js @@ -291,12 +291,14 @@ const ProxySettings = ({ close }) => { Auth { formik.setFieldValue('config.auth.disabled', !e.target.checked); }} + className="mousetrap mr-0" />
diff --git a/packages/bruno-app/src/components/Preferences/StyledWrapper.js b/packages/bruno-app/src/components/Preferences/StyledWrapper.js index ddffa99e4..f186fb9ea 100644 --- a/packages/bruno-app/src/components/Preferences/StyledWrapper.js +++ b/packages/bruno-app/src/components/Preferences/StyledWrapper.js @@ -2,7 +2,7 @@ import styled from 'styled-components'; const StyledWrapper = styled.div` div.tabs { - padding: 8px; + padding: 12px; min-width: 160px; div.tab { @@ -28,10 +28,10 @@ const StyledWrapper = styled.div` &.active { color: ${(props) => props.theme.text} !important; - background: ${(props) => props.theme.modal.title.bg}; + background: ${(props) => props.theme.tabs.secondary.active.bg}; &:hover { - background: ${(props) => props.theme.modal.title.bg} !important; + background: ${(props) => props.theme.tabs.secondary.active.bg} !important; } } } @@ -39,9 +39,9 @@ const StyledWrapper = styled.div` section.tab-panel { min-height: 70vh; - max-height: 70vh; overflow-y: auto; - width: clamp(300px, 45vw, 550px); + flex-grow: 1; + padding: 12px; } input[type="checkbox"], @@ -50,6 +50,24 @@ const StyledWrapper = styled.div` cursor: pointer; } + .textbox { + line-height: 1.5; + padding: 0.45rem; + border-radius: ${(props) => props.theme.border.radius.sm}; + background-color: ${(props) => props.theme.input.bg}; + border: 1px solid ${(props) => props.theme.input.border}; + color: ${(props) => props.theme.text}; + + &:focus { + border: solid 1px ${(props) => props.theme.input.focusBorder} !important; + outline: none !important; + } + + &:disabled { + opacity: 0.5; + cursor: not-allowed; + } + } .section-header { font-size: ${(props) => props.theme.font.size.sm}; color: ${(props) => props.theme.colors.text.muted}; diff --git a/packages/bruno-app/src/components/Preferences/index.js b/packages/bruno-app/src/components/Preferences/index.js index c5c07629b..547ffd09a 100644 --- a/packages/bruno-app/src/components/Preferences/index.js +++ b/packages/bruno-app/src/components/Preferences/index.js @@ -1,7 +1,16 @@ -import Modal from 'components/Modal/index'; import classnames from 'classnames'; -import React, { useState } from 'react'; -import { IconSettings, IconPalette, IconBrowser, IconUserCircle, IconKeyboard, IconZoomQuestion, IconSquareLetterB } from '@tabler/icons'; +import React from 'react'; +import { useSelector, useDispatch } from 'react-redux'; +import { updateActivePreferencesTab } from 'providers/ReduxStore/slices/app'; +import { + IconSettings, + IconPalette, + IconBrowser, + IconUserCircle, + IconKeyboard, + IconZoomQuestion, + IconSquareLetterB +} from '@tabler/icons'; import Support from './Support'; import General from './General'; @@ -13,8 +22,13 @@ import Beta from './Beta'; import StyledWrapper from './StyledWrapper'; -const Preferences = ({ onClose }) => { - const [tab, setTab] = useState('general'); +const Preferences = () => { + const dispatch = useDispatch(); + const tab = useSelector((state) => state.app.activePreferencesTab); + + const setTab = (tab) => { + dispatch(updateActivePreferencesTab({ tab })); + }; const getTabClassname = (tabName) => { return classnames(`tab select-none ${tabName}`, { @@ -25,27 +39,27 @@ const Preferences = ({ onClose }) => { const getTabPanel = (tab) => { switch (tab) { case 'general': { - return ; + return ; } case 'themes': { - return ; + return ; } case 'proxy': { - return ; + return ; } case 'display': { - return ; + return ; } case 'keybindings': { - return ; + return ; } case 'beta': { - return ; + return ; } case 'support': { @@ -55,42 +69,47 @@ const Preferences = ({ onClose }) => { }; return ( - - -
-
-
setTab('general')}> - - General -
-
setTab('themes')}> - - Themes -
-
setTab('display')}> - - Display -
-
setTab('proxy')}> - - Proxy -
-
setTab('keybindings')}> - - Keybindings -
-
setTab('support')}> - - Support -
-
setTab('beta')}> - - Beta -
+ +
+
+
setTab('general')}> + + General +
+
setTab('themes')}> + + Themes +
+
setTab('display')}> + + Display +
+
setTab('proxy')}> + + Proxy +
+
setTab('keybindings')}> + + Keybindings +
+
setTab('support')}> + + Support +
+
setTab('beta')}> + + Beta
-
{getTabPanel(tab)}
- +
+ {getTabPanel(tab)} +
+
); }; diff --git a/packages/bruno-app/src/components/RequestTabPanel/index.js b/packages/bruno-app/src/components/RequestTabPanel/index.js index 88aca8b20..ad53e7af2 100644 --- a/packages/bruno-app/src/components/RequestTabPanel/index.js +++ b/packages/bruno-app/src/components/RequestTabPanel/index.js @@ -33,6 +33,7 @@ import WSResponsePane from 'components/ResponsePane/WsResponsePane'; import { useTabPaneBoundaries } from 'hooks/useTabPaneBoundaries/index'; import ResponseExample from 'components/ResponseExample'; import WorkspaceHome from 'components/WorkspaceHome'; +import Preferences from 'components/Preferences'; import EnvironmentSettings from 'components/Environments/EnvironmentSettings'; import GlobalEnvironmentSettings from 'components/Environments/GlobalEnvironmentSettings'; @@ -171,13 +172,17 @@ const RequestTabPanel = () => { }, [isConsoleOpen, isVerticalLayout]); if (!activeTabUid || !focusedTab) { - return ; + return
An error occurred!
; } if (focusedTab.type === 'global-environment-settings') { return ; } + if (focusedTab.type === 'preferences') { + return ; + } + if (!focusedTab.uid || !focusedTab.collectionUid) { return
An error occurred!
; } diff --git a/packages/bruno-app/src/components/RequestTabs/CollectionToolBar/index.js b/packages/bruno-app/src/components/RequestTabs/CollectionToolBar/index.js index d2afd5275..3c53cdbef 100644 --- a/packages/bruno-app/src/components/RequestTabs/CollectionToolBar/index.js +++ b/packages/bruno-app/src/components/RequestTabs/CollectionToolBar/index.js @@ -12,6 +12,10 @@ import ActionIcon from 'ui/ActionIcon'; const CollectionToolBar = ({ collection }) => { const dispatch = useDispatch(); + if (!collection) { + return null; + } + const handleRun = () => { dispatch( addTab({ diff --git a/packages/bruno-app/src/components/RequestTabs/RequestTab/SpecialTab.js b/packages/bruno-app/src/components/RequestTabs/RequestTab/SpecialTab.js index 89cc5949a..dad87bf1b 100644 --- a/packages/bruno-app/src/components/RequestTabs/RequestTab/SpecialTab.js +++ b/packages/bruno-app/src/components/RequestTabs/RequestTab/SpecialTab.js @@ -61,6 +61,14 @@ const SpecialTab = ({ handleCloseClick, type, tabName, handleDoubleClick, hasDra ); } + case 'preferences': { + return ( + <> + + Preferences + + ); + } } }; diff --git a/packages/bruno-app/src/components/RequestTabs/RequestTab/index.js b/packages/bruno-app/src/components/RequestTabs/RequestTab/index.js index 0714af2db..f001484e3 100644 --- a/packages/bruno-app/src/components/RequestTabs/RequestTab/index.js +++ b/packages/bruno-app/src/components/RequestTabs/RequestTab/index.js @@ -172,7 +172,7 @@ const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUi setShowConfirmGlobalEnvironmentClose(true); }; - if (['collection-settings', 'collection-overview', 'folder-settings', 'variables', 'collection-runner', 'environment-settings', 'global-environment-settings'].includes(tab.type)) { + if (['collection-settings', 'collection-overview', 'folder-settings', 'variables', 'collection-runner', 'environment-settings', 'global-environment-settings', 'preferences'].includes(tab.type)) { return ( { }, []); const activeTab = find(tabs, (t) => t.uid === activeTabUid); - const activeCollection = find(collections, (c) => c.uid === activeTab?.collectionUid); + const activeCollection = find(collections, (c) => c?.uid === activeTab?.collectionUid); const collectionRequestTabs = filter(tabs, (t) => t.collectionUid === activeTab?.collectionUid); useEffect(() => { @@ -52,7 +52,7 @@ const RequestTabs = () => { const checkOverflow = () => { if (tabsRef.current && scrollContainerRef.current) { - const hasOverflow = tabsRef.current.scrollWidth > scrollContainerRef.current.clientWidth; + const hasOverflow = tabsRef.current.scrollWidth > scrollContainerRef.current.clientWidth + 1; setShowChevrons(hasOverflow); } }; @@ -111,7 +111,7 @@ const RequestTabs = () => { )} {collectionRequestTabs && collectionRequestTabs.length ? ( <> - + {activeCollection && }
diff --git a/packages/bruno-app/src/components/StatusBar/index.js b/packages/bruno-app/src/components/StatusBar/index.js index 87f1657b9..636c71ffc 100644 --- a/packages/bruno-app/src/components/StatusBar/index.js +++ b/packages/bruno-app/src/components/StatusBar/index.js @@ -1,22 +1,29 @@ import React, { useState } from 'react'; import { useSelector, useDispatch } from 'react-redux'; +import find from 'lodash/find'; import { IconSettings, IconCookie, IconTool, IconSearch, IconPalette, IconBrandGithub } from '@tabler/icons'; import Mousetrap from 'mousetrap'; import { getKeyBindingsForActionAllOS } from 'providers/Hotkeys/keyMappings'; import ToolHint from 'components/ToolHint'; -import Preferences from 'components/Preferences'; import Cookies from 'components/Cookies'; import Notifications from 'components/Notifications'; import Portal from 'components/Portal'; import ThemeDropdown from './ThemeDropdown'; -import { showPreferences } from 'providers/ReduxStore/slices/app'; import { openConsole } from 'providers/ReduxStore/slices/logs'; +import { setActiveWorkspaceTab } from 'providers/ReduxStore/slices/workspaceTabs'; +import { addTab } from 'providers/ReduxStore/slices/tabs'; import { useApp } from 'providers/App'; import StyledWrapper from './StyledWrapper'; const StatusBar = () => { const dispatch = useDispatch(); - const preferencesOpen = useSelector((state) => state.app.showPreferences); + const activeWorkspaceUid = useSelector((state) => state.workspaces.activeWorkspaceUid); + const showHomePage = useSelector((state) => state.app.showHomePage); + const showManageWorkspacePage = useSelector((state) => state.app.showManageWorkspacePage); + const showApiSpecPage = useSelector((state) => state.app.showApiSpecPage); + const tabs = useSelector((state) => state.tabs.tabs); + const activeTabUid = useSelector((state) => state.tabs.activeTabUid); + const activeTab = find(tabs, (t) => t.uid === activeTabUid); const logs = useSelector((state) => state.logs.logs); const [cookiesOpen, setCookiesOpen] = useState(false); const { version } = useApp(); @@ -27,6 +34,22 @@ const StatusBar = () => { dispatch(openConsole()); }; + const handlePreferencesClick = () => { + if (showHomePage || showManageWorkspacePage || showApiSpecPage || !activeTabUid) { + if (activeWorkspaceUid) { + dispatch(setActiveWorkspaceTab({ workspaceUid: activeWorkspaceUid, type: 'preferences' })); + } + } else { + dispatch( + addTab({ + type: 'preferences', + uid: activeTab?.collectionUid ? `${activeTab.collectionUid}-preferences` : 'preferences', + collectionUid: activeTab?.collectionUid + }) + ); + } + }; + const openGlobalSearch = () => { const bindings = getKeyBindingsForActionAllOS('globalSearch') || []; bindings.forEach((binding) => { @@ -36,21 +59,6 @@ const StatusBar = () => { return ( - {preferencesOpen && ( - - { - dispatch(showPreferences(false)); - document.querySelector('[data-trigger="preferences"]').focus(); - }} - aria-modal="true" - role="dialog" - aria-labelledby="preferences-title" - aria-describedby="preferences-description" - /> - - )} - {cookiesOpen && ( {