From 3081c06964de3441c3050c7122766392b871d0dc Mon Sep 17 00:00:00 2001 From: naman-bruno Date: Tue, 23 Dec 2025 23:29:03 +0530 Subject: [PATCH] update: modal styles (#6487) * update modals styles * chore: color and style improvements * fix: tests * fixes: tests --------- Co-authored-by: Anoop M D --- .../src/components/Modal/StyledWrapper.js | 48 ++++++++++++------- .../bruno-app/src/components/Modal/index.js | 2 +- .../src/components/Preferences/Beta/index.js | 39 ++++++++++----- .../Preferences/Display/Font/index.js | 41 ++++++++++------ .../components/Preferences/General/index.js | 37 +++++++++----- .../Preferences/ProxySettings/index.js | 35 ++++++++------ .../components/Preferences/StyledWrapper.js | 40 ++++++++++++---- .../RemoveCollection/StyledWrapper.js | 24 ++++++++++ .../Collection/RemoveCollection/index.js | 43 +++++++++++------ .../RemoveCollectionsModal/index.js | 2 +- packages/bruno-app/src/themes/dark.js | 3 ++ packages/bruno-app/src/themes/light.js | 9 ++-- .../file-types/file-input-acceptance.spec.ts | 2 +- .../file-types/invalid-file-handling.spec.ts | 2 +- .../insomnia/malformed-structure.spec.ts | 2 +- tests/import/openapi/malformed-yaml.spec.ts | 2 +- tests/import/openapi/missing-info.spec.ts | 2 +- tests/import/postman/invalid-json.spec.ts | 2 +- .../postman/invalid-missing-info.spec.ts | 2 +- tests/import/postman/invalid-schema.spec.ts | 2 +- tests/onboarding/sample-collection.spec.ts | 4 +- tests/preferences/autosave/autosave.spec.ts | 21 ++++---- .../default-collection-location.spec.js | 26 +++++----- tests/preferences/support-links.spec.js | 2 +- tests/request/encoding/curl-encoding.spec.ts | 10 ++-- tests/utils/page/actions.ts | 8 ++-- 26 files changed, 269 insertions(+), 141 deletions(-) create mode 100644 packages/bruno-app/src/components/Sidebar/Collections/Collection/RemoveCollection/StyledWrapper.js diff --git a/packages/bruno-app/src/components/Modal/StyledWrapper.js b/packages/bruno-app/src/components/Modal/StyledWrapper.js index 7f157dd74..5e6d44f44 100644 --- a/packages/bruno-app/src/components/Modal/StyledWrapper.js +++ b/packages/bruno-app/src/components/Modal/StyledWrapper.js @@ -28,8 +28,8 @@ const Wrapper = styled.div` .bruno-modal-card { animation-duration: 0.85s; animation-delay: 0.1s; - background: var(--color-background-top); - border-radius: var(--border-radius); + background: ${(props) => props.theme.modal.body.bg}; + border-radius: ${(props) => props.theme.border.radius.base}; position: relative; z-index: 11; max-width: calc(100% - var(--spacing-base-unit)); @@ -68,25 +68,37 @@ const Wrapper = styled.div` display: flex; justify-content: space-between; align-items: center; - text-transform: uppercase; color: ${(props) => props.theme.modal.title.color}; background-color: ${(props) => props.theme.modal.title.bg}; - font-size: ${(props) => props.theme.font.size.sm}; - padding: 12px; + font-size: ${(props) => props.theme.font.size.md}; + padding: 0.5rem 1rem; font-weight: 500; - border-top-left-radius: 4px; - border-top-right-radius: 4px; + border-top-left-radius: ${(props) => props.theme.border.radius.base}; + border-top-right-radius: ${(props) => props.theme.border.radius.base}; + + .bruno-modal-header-title { + display: flex; + align-items: center; + gap: 8px; + } .close { - font-size: 1.3rem; + display: flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + margin-right: -0.5rem; + font-size: 1.125rem; line-height: 1; - color: ${(props) => props.theme.modal.iconColor}; - text-shadow: 0 1px 0 #fff; - opacity: 0.5; - margin-top: -2px; + color: ${(props) => props.theme.modal.title.color}; + border-radius: ${(props) => props.theme.border.radius.sm}; + opacity: 0.7; + transition: opacity 0.2s ease, background-color 0.2s ease; &:hover { - opacity: 0.8; + opacity: 1; + background-color: ${(props) => props.theme.modal.closeButton.hoverBg}; } } } @@ -104,7 +116,7 @@ const Wrapper = styled.div` outline: none; box-shadow: none; transition: border-color ease-in-out 0.1s; - border-radius: 3px; + border-radius: ${(props) => props.theme.border.radius.sm}; background-color: ${(props) => props.theme.modal.input.bg}; border: 1px solid ${(props) => props.theme.modal.input.border}; @@ -144,14 +156,14 @@ const Wrapper = styled.div` .bruno-modal-footer { background-color: ${(props) => props.theme.modal.body.bg}; - border-bottom-left-radius: 3px; - border-bottom-right-radius: 3px; + border-bottom-left-radius: ${(props) => props.theme.border.radius.base}; + border-bottom-right-radius: ${(props) => props.theme.border.radius.base}; } &.modal-footer-none { .bruno-modal-content { - border-bottom-left-radius: 4px; - border-bottom-right-radius: 4px; + border-bottom-left-radius: ${(props) => props.theme.border.radius.base}; + border-bottom-right-radius: ${(props) => props.theme.border.radius.base}; } } `; diff --git a/packages/bruno-app/src/components/Modal/index.js b/packages/bruno-app/src/components/Modal/index.js index 7839c2dbf..c9ebbb3dc 100644 --- a/packages/bruno-app/src/components/Modal/index.js +++ b/packages/bruno-app/src/components/Modal/index.js @@ -10,7 +10,7 @@ const ModalHeader = ({ title, handleCancel, customHeader, hideClose }) => ( {customHeader ? customHeader : <>{title ?
{title}
: null}} {handleCancel && !hideClose ? ( // TODO: Remove data-test-id and use data-testid instead across the codebase. -
handleCancel() : null} data-test-id="modal-close-button" data-testid="modal-close-button"> +
handleCancel() : null} data-testid="modal-close-button"> ×
) : null} diff --git a/packages/bruno-app/src/components/Preferences/Beta/index.js b/packages/bruno-app/src/components/Preferences/Beta/index.js index 0387dc486..f9722c124 100644 --- a/packages/bruno-app/src/components/Preferences/Beta/index.js +++ b/packages/bruno-app/src/components/Preferences/Beta/index.js @@ -1,9 +1,10 @@ -import React from 'react'; +import React, { useEffect, useCallback } from 'react'; import { useFormik } from 'formik'; import { useSelector, useDispatch } from 'react-redux'; import { savePreferences } from 'providers/ReduxStore/slices/app'; import StyledWrapper from './StyledWrapper'; import * as Yup from 'yup'; +import debounce from 'lodash/debounce'; import toast from 'react-hot-toast'; import { IconFlask } from '@tabler/icons'; import get from 'lodash/get'; @@ -56,19 +57,37 @@ const Beta = ({ close }) => { } }); - const handleSave = (newBetaPreferences) => { + const handleSave = useCallback((newBetaPreferences) => { dispatch( savePreferences({ ...preferences, beta: newBetaPreferences }) ) - .then(() => { - toast.success('Beta preferences saved successfully'); - close(); - }) .catch((err) => console.log(err) && toast.error('Failed to update beta preferences')); - }; + }, [dispatch, preferences]); + + const debouncedSave = useCallback( + debounce((values) => { + betaSchema.validate(values, { abortEarly: true }) + .then((validatedValues) => { + handleSave(validatedValues); + }) + .catch((error) => { + }); + }, 500), + [handleSave, betaSchema] + ); + + // Auto-save when form values change + useEffect(() => { + if (formik.dirty && formik.isValid) { + debouncedSave(formik.values); + } + return () => { + debouncedSave.cancel(); + }; + }, [formik.values, formik.dirty, formik.isValid, debouncedSave]); const hasAnyBetaFeatures = BETA_FEATURES.length > 0; @@ -113,12 +132,6 @@ const Beta = ({ close }) => {

No beta features are currently available

)} - -
- -
); 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 69e25a3db..6759db71e 100644 --- a/packages/bruno-app/src/components/Preferences/Display/Font/index.js +++ b/packages/bruno-app/src/components/Preferences/Display/Font/index.js @@ -1,5 +1,6 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect, useCallback, useRef } from 'react'; import get from 'lodash/get'; +import debounce from 'lodash/debounce'; import { useSelector, useDispatch } from 'react-redux'; import { savePreferences } from 'providers/ReduxStore/slices/app'; import StyledWrapper from './StyledWrapper'; @@ -8,6 +9,7 @@ import toast from 'react-hot-toast'; const Font = ({ close }) => { const dispatch = useDispatch(); const preferences = useSelector((state) => state.app.preferences); + const isInitialMount = useRef(true); const [codeFont, setCodeFont] = useState(get(preferences, 'font.codeFont', 'default')); const [codeFontSize, setCodeFontSize] = useState(get(preferences, 'font.codeFontSize', '13')); @@ -22,22 +24,37 @@ const Font = ({ close }) => { setCodeFontSize(clampedSize); }; - const handleSave = () => { + const handleSave = useCallback((font, fontSize) => { dispatch( savePreferences({ ...preferences, font: { - codeFont, - codeFontSize + codeFont: font, + codeFontSize: fontSize } }) - ).then(() => { - toast.success('Preferences saved successfully'); - close(); - }).catch(() => { + ).catch(() => { toast.error('Failed to save preferences'); }); - }; + }, [dispatch, preferences]); + + const debouncedSave = useCallback( + debounce((font, fontSize) => { + handleSave(font, fontSize); + }, 500), + [handleSave] + ); + + useEffect(() => { + if (isInitialMount.current) { + isInitialMount.current = false; + return; + } + debouncedSave(codeFont, codeFontSize); + return () => { + debouncedSave.cancel(); + }; + }, [codeFont, codeFontSize, debouncedSave]); return ( @@ -68,12 +85,6 @@ const Font = ({ close }) => { /> - -
- -
); }; diff --git a/packages/bruno-app/src/components/Preferences/General/index.js b/packages/bruno-app/src/components/Preferences/General/index.js index 96b66d7aa..da7b7dc57 100644 --- a/packages/bruno-app/src/components/Preferences/General/index.js +++ b/packages/bruno-app/src/components/Preferences/General/index.js @@ -1,5 +1,6 @@ -import React, { useRef } from 'react'; +import React, { useRef, useEffect, useCallback } from 'react'; import get from 'lodash/get'; +import debounce from 'lodash/debounce'; import { useFormik } from 'formik'; import { useSelector, useDispatch } from 'react-redux'; import { savePreferences } from 'providers/ReduxStore/slices/app'; @@ -95,7 +96,7 @@ const General = ({ close }) => { } }); - const handleSave = (newPreferences) => { + const handleSave = useCallback((newPreferences) => { dispatch( savePreferences({ ...preferences, @@ -123,12 +124,29 @@ const General = ({ close }) => { defaultCollectionLocation: newPreferences.defaultCollectionLocation } })) - .then(() => { - toast.success('Preferences saved successfully'); - close(); - }) .catch((err) => console.log(err) && toast.error('Failed to update preferences')); - }; + }, [dispatch, preferences]); + + const debouncedSave = useCallback( + debounce((values) => { + preferencesSchema.validate(values, { abortEarly: true }) + .then((validatedValues) => { + handleSave(validatedValues); + }) + .catch((error) => { + }); + }, 500), + [handleSave] + ); + + useEffect(() => { + if (formik.dirty && formik.isValid) { + debouncedSave(formik.values); + } + return () => { + debouncedSave.cancel(); + }; + }, [formik.values, formik.dirty, formik.isValid, debouncedSave]); const addCaCertificate = (e) => { const filePath = window?.ipcRenderer?.getFilePath(e?.target?.files?.[0]); @@ -366,11 +384,6 @@ const General = ({ close }) => { {formik.touched.defaultCollectionLocation && formik.errors.defaultCollectionLocation ? (
{formik.errors.defaultCollectionLocation}
) : null} -
- -
); diff --git a/packages/bruno-app/src/components/Preferences/ProxySettings/index.js b/packages/bruno-app/src/components/Preferences/ProxySettings/index.js index 2b411adec..3539b42dc 100644 --- a/packages/bruno-app/src/components/Preferences/ProxySettings/index.js +++ b/packages/bruno-app/src/components/Preferences/ProxySettings/index.js @@ -1,6 +1,7 @@ -import React, { useEffect } from 'react'; +import React, { useEffect, useCallback } from 'react'; import { useFormik } from 'formik'; import * as Yup from 'yup'; +import debounce from 'lodash/debounce'; import toast from 'react-hot-toast'; import { savePreferences } from 'providers/ReduxStore/slices/app'; @@ -74,7 +75,7 @@ const ProxySettings = ({ close }) => { } }); - const onUpdate = (values) => { + const onUpdate = useCallback((values) => { proxySchema .validate(values, { abortEarly: true }) .then((validatedProxy) => { @@ -83,18 +84,20 @@ const ProxySettings = ({ close }) => { ...preferences, proxy: validatedProxy }) - ).then(() => { - toast.success('Preferences saved successfully'); - close(); - }).catch(() => { + ).catch(() => { toast.error('Failed to save preferences'); }); }) .catch((error) => { - let errMsg = error.message || 'Preferences validation error'; - toast.error(errMsg); }); - }; + }, [dispatch, preferences, proxySchema]); + + const debouncedSave = useCallback( + debounce((values) => { + onUpdate(values); + }, 500), + [onUpdate] + ); const [passwordVisible, setPasswordVisible] = useState(false); @@ -113,6 +116,15 @@ const ProxySettings = ({ close }) => { }); }, [preferences]); + useEffect(() => { + if (formik.dirty) { + debouncedSave(formik.values); + } + return () => { + debouncedSave.cancel(); + }; + }, [formik.values, formik.dirty, debouncedSave]); + return (
@@ -365,11 +377,6 @@ const ProxySettings = ({ close }) => { ) : null} -
- -
); diff --git a/packages/bruno-app/src/components/Preferences/StyledWrapper.js b/packages/bruno-app/src/components/Preferences/StyledWrapper.js index f43047925..caaf3b54e 100644 --- a/packages/bruno-app/src/components/Preferences/StyledWrapper.js +++ b/packages/bruno-app/src/components/Preferences/StyledWrapper.js @@ -2,14 +2,20 @@ import styled from 'styled-components'; const StyledWrapper = styled.div` div.tabs { + padding: 8px; + min-width: 160px; + div.tab { + display: flex; + align-items: center; + gap: 8px; width: 100%; - min-width: 120px; - padding: 7px 10px; + padding: 6px 10px; border: none; - border-bottom: solid 2px transparent; - color: var(--color-tab-inactive); + border-radius: ${(props) => props.theme.border.radius.sm}; + color: ${(props) => props.theme.colors.text.muted}; cursor: pointer; + transition: background-color 0.15s ease; &:focus, &:active, @@ -21,18 +27,36 @@ const StyledWrapper = styled.div` } &.active { - color: ${(props) => props.theme.sidebar.color} !important; - background: ${(props) => props.theme.sidebar.collection.item.bg}; + color: ${(props) => props.theme.text} !important; + background: ${(props) => props.theme.modal.title.bg}; &:hover { - background: ${(props) => props.theme.sidebar.collection.item.bg} !important; + background: ${(props) => props.theme.modal.title.bg} !important; } } } } section.tab-panel { - min-height: 300px; + min-height: 70vh; + max-height: 70vh; + overflow-y: auto; + max-width: 50vw; + } + + input[type="checkbox"], + input[type="radio"] { + accent-color: ${(props) => props.theme.workspace.accent}; + cursor: pointer; + } + + .section-header { + font-size: ${(props) => props.theme.font.size.sm}; + color: ${(props) => props.theme.colors.text.muted}; + font-weight: 500; + margin-bottom: 8px; + text-transform: uppercase; + letter-spacing: 0.5px; } `; diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/RemoveCollection/StyledWrapper.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/RemoveCollection/StyledWrapper.js new file mode 100644 index 000000000..a61236286 --- /dev/null +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/RemoveCollection/StyledWrapper.js @@ -0,0 +1,24 @@ +import styled from 'styled-components'; + +const StyledWrapper = styled.div` + .collection-info-card { + background-color: ${(props) => props.theme.modal.title.bg}; + border-radius: 4px; + padding: 12px; + } + .collection-name { + font-weight: 500; + color: ${(props) => props.theme.text}; + margin-bottom: 4px; + &:hover { + background: none; + } + } + .collection-path { + font-size: ${(props) => props.theme.font.size.sm}; + color: ${(props) => props.theme.colors.text.muted}; + word-break: break-all; + } +`; + +export default StyledWrapper; diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/RemoveCollection/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/RemoveCollection/index.js index 49fc910f4..24b353210 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/RemoveCollection/index.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/RemoveCollection/index.js @@ -2,11 +2,12 @@ import React, { useMemo } from 'react'; import toast from 'react-hot-toast'; import Modal from 'components/Modal'; import { useDispatch, useSelector } from 'react-redux'; -import { IconFiles } from '@tabler/icons'; +import { IconAlertCircle } from '@tabler/icons'; import { removeCollection } from 'providers/ReduxStore/slices/collections/actions'; import { findCollectionByUid, flattenItems, isItemARequest, hasRequestChanges } from 'utils/collections/index'; import filter from 'lodash/filter'; import ConfirmCollectionCloseDrafts from './ConfirmCollectionCloseDrafts'; +import StyledWrapper from './StyledWrapper'; const RemoveCollection = ({ onClose, collectionUid }) => { const dispatch = useDispatch(); @@ -42,21 +43,35 @@ const RemoveCollection = ({ onClose, collectionUid }) => { return ; } + const customHeader = ( +
+ + Remove Collection +
+ ); + // Otherwise, show the standard remove confirmation modal return ( - -
- - {collection.name} -
-
{collection.pathname}
-
- Are you sure you want to remove collection {collection.name} from this workspace? -
-
- The collection files will remain on disk and can be re-added to this or another workspace later. -
-
+ + +

Are you sure you want to close following collection in Bruno?

+
+
{collection.name}
+
{collection.pathname}
+
+

+ It will still be available in the filesystem at the above location and can be re-opened later. +

+
+
); }; diff --git a/packages/bruno-app/src/components/Sidebar/Collections/RemoveCollectionsModal/index.js b/packages/bruno-app/src/components/Sidebar/Collections/RemoveCollectionsModal/index.js index 5d6f32b53..d52d9e893 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/RemoveCollectionsModal/index.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/RemoveCollectionsModal/index.js @@ -255,7 +255,7 @@ const RemoveCollectionsModal = ({ collectionUids, onClose }) => { Collections will be removed from the current workspace but will still be available in the file system and can be re-opened later.
-