From b9856f8c6499ba35d701cd3c4a4a8499e5b37e47 Mon Sep 17 00:00:00 2001 From: sanish chirayath Date: Thu, 1 Jan 2026 23:47:58 +0530 Subject: [PATCH] Feat/update file picker (#6614) * styling: file-picker editor component * use filepicker component within filebody and response example filebody * edit example to use button components * fix: hide delete, disable checkbox in preview mode * make label italic * chore: change example cta buttons to filled style --------- Co-authored-by: Bijin A B --- .../src/components/EditableTable/index.js | 2 + .../FilePickerEditor/StyledWrapper.js | 101 ++++++++++++++ .../src/components/FilePickerEditor/index.js | 125 +++++++++++++----- .../RequestPane/FileBody/StyledWrapper.js | 1 + .../components/RequestPane/FileBody/index.js | 1 + .../ResponseExampleFileBody/index.js | 2 + .../index.js | 2 + .../ResponseExampleHeaders/index.js | 2 + .../index.js | 2 + .../ResponseExampleParams/index.js | 2 + .../ResponseExampleResponseHeaders/index.js | 1 + .../ResponseExampleTopBar/index.js | 36 ++--- 12 files changed, 225 insertions(+), 52 deletions(-) create mode 100644 packages/bruno-app/src/components/FilePickerEditor/StyledWrapper.js diff --git a/packages/bruno-app/src/components/EditableTable/index.js b/packages/bruno-app/src/components/EditableTable/index.js index 3e3bd29f5..804c5b51a 100644 --- a/packages/bruno-app/src/components/EditableTable/index.js +++ b/packages/bruno-app/src/components/EditableTable/index.js @@ -12,6 +12,7 @@ const EditableTable = ({ getRowError, showCheckbox = true, showDelete = true, + disableCheckbox = false, checkboxLabel = '', checkboxKey = 'enabled', reorderable = false, @@ -288,6 +289,7 @@ const EditableTable = ({ className="mousetrap" data-testid="column-checkbox" checked={row[checkboxKey] ?? true} + disabled={disableCheckbox} onChange={(e) => handleCheckboxChange(row.uid, e.target.checked)} /> )} diff --git a/packages/bruno-app/src/components/FilePickerEditor/StyledWrapper.js b/packages/bruno-app/src/components/FilePickerEditor/StyledWrapper.js new file mode 100644 index 000000000..418b3ca9c --- /dev/null +++ b/packages/bruno-app/src/components/FilePickerEditor/StyledWrapper.js @@ -0,0 +1,101 @@ +import styled from 'styled-components'; + +const StyledWrapper = styled.div` + display: flex; + align-items: center; + width: 100%; + + .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; + + &:hover { + color: ${(props) => props.theme.text} !important; + } + + &.read-only { + cursor: default; + opacity: 0.6; + } + + &.icon-only { + padding: 4px; + } + + &.icon-right { + width: 100%; + justify-content: space-between; + } + + span { + line-height: 1; + } + + .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; diff --git a/packages/bruno-app/src/components/FilePickerEditor/index.js b/packages/bruno-app/src/components/FilePickerEditor/index.js index 758a9910e..65f9046b5 100644 --- a/packages/bruno-app/src/components/FilePickerEditor/index.js +++ b/packages/bruno-app/src/components/FilePickerEditor/index.js @@ -2,10 +2,33 @@ import React from 'react'; import path from 'utils/common/path'; import { useDispatch } from 'react-redux'; import { browseFiles } from 'providers/ReduxStore/slices/collections/actions'; -import { IconX } from '@tabler/icons'; +import { IconX, IconUpload, IconFile } from '@tabler/icons'; import { isWindowsOS } from 'utils/common/platform'; +import StyledWrapper from './StyledWrapper'; -const FilePickerEditor = ({ value, onChange, collection, isSingleFilePicker = false, readOnly = false }) => { +/** + * FilePickerEditor component for selecting files + * + * @param {Object} props + * @param {string|string[]} props.value - Selected file path(s) + * @param {Function} props.onChange - Callback when file selection changes + * @param {Object} props.collection - Collection object with pathname + * @param {boolean} props.isSingleFilePicker - If true, only allows single file selection + * @param {boolean} props.readOnly - If true, disables file selection + * @param {string} props.displayMode - Display mode: 'label', 'icon', or 'labelAndIcon' (default: 'label') + * @param {string} props.label - Custom label text (defaults to "Select File" or "Select Files") + * @param {React.ComponentType} props.icon - Custom icon component (defaults to IconUpload) + */ +const FilePickerEditor = ({ + value, + onChange, + collection, + isSingleFilePicker = false, + readOnly = false, + displayMode = 'label', + label, + icon: CustomIcon +}) => { const dispatch = useDispatch(); const filenames = (isSingleFilePicker ? [value] : value || []) .filter((v) => v != null && v != '') @@ -18,6 +41,8 @@ const FilePickerEditor = ({ value, onChange, collection, isSingleFilePicker = fa const title = filenames.map((v) => `- ${v}`).join('\n'); const browse = () => { + if (readOnly) return; + dispatch(browseFiles([], [!isSingleFilePicker ? 'multiSelections' : ''])) .then((filePaths) => { // If file is in the collection's directory, then we use relative path @@ -39,7 +64,8 @@ const FilePickerEditor = ({ value, onChange, collection, isSingleFilePicker = fa }); }; - const clear = () => { + const clear = (e) => { + e.stopPropagation(); onChange(isSingleFilePicker ? '' : []); }; @@ -50,40 +76,69 @@ const FilePickerEditor = ({ value, onChange, collection, isSingleFilePicker = fa return filenames.length + ' file(s) selected'; }; - const buttonClass = `btn btn-secondary px-1 ${readOnly ? 'view-mode' : 'edit-mode'}`; + const defaultLabel = isSingleFilePicker ? 'Select File' : 'Select Files'; + const displayLabel = label || defaultLabel; + const IconComponent = CustomIcon || IconUpload; - return filenames.length > 0 ? ( -
- {!readOnly && ( - - )} - {!readOnly && <> } - { + switch (displayMode) { + case 'icon': + return ; + case 'labelAndIcon': + return ( + <> + {displayLabel} + + + ); + case 'label': + default: + return {displayLabel}; + } + }; + + // When files are selected, show file info with clear button + if (filenames.length > 0) { + return ( + +
+ + + {renderButtonText(filenames)} + + {!readOnly && ( + + )} +
+
+ ); + } + + // When no files selected, show the picker button + return ( + +
- ) : ( - + {renderButtonContent()} + + ); }; diff --git a/packages/bruno-app/src/components/RequestPane/FileBody/StyledWrapper.js b/packages/bruno-app/src/components/RequestPane/FileBody/StyledWrapper.js index 1853f1c5e..fe69bee54 100644 --- a/packages/bruno-app/src/components/RequestPane/FileBody/StyledWrapper.js +++ b/packages/bruno-app/src/components/RequestPane/FileBody/StyledWrapper.js @@ -59,6 +59,7 @@ const Wrapper = styled.div` cursor: pointer; position: relative; top: 1px; + accent-color: ${(props) => props.theme.primary.solid}; } `; diff --git a/packages/bruno-app/src/components/RequestPane/FileBody/index.js b/packages/bruno-app/src/components/RequestPane/FileBody/index.js index 62f7dd90c..9394fca18 100644 --- a/packages/bruno-app/src/components/RequestPane/FileBody/index.js +++ b/packages/bruno-app/src/components/RequestPane/FileBody/index.js @@ -102,6 +102,7 @@ const FileBody = ({ item, collection }) => { 'filePath' )} collection={collection} + displayMode="labelAndIcon" /> diff --git a/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleFileBody/index.js b/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleFileBody/index.js index 1a231d097..62a8de94d 100644 --- a/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleFileBody/index.js +++ b/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleFileBody/index.js @@ -120,6 +120,7 @@ const ResponseExampleFileBody = ({ item, collection, exampleUid, editMode = fals onChange={(newPath) => handleFilePathChange(row, newPath, onChange)} collection={collection} readOnly={!editMode} + displayMode="labelAndIcon" /> ) }, @@ -187,6 +188,7 @@ const ResponseExampleFileBody = ({ item, collection, exampleUid, editMode = fals onReorder={handleParamDrag} showAddRow={editMode} showCheckbox={false} + showDelete={editMode} /> ); diff --git a/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleFormUrlEncodedParams/index.js b/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleFormUrlEncodedParams/index.js index e0b0ef23c..3df926e32 100644 --- a/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleFormUrlEncodedParams/index.js +++ b/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleFormUrlEncodedParams/index.js @@ -94,6 +94,8 @@ const ResponseExampleFormUrlEncodedParams = ({ item, collection, exampleUid, edi reorderable={editMode} onReorder={handleParamDrag} showAddRow={editMode} + showDelete={editMode} + disableCheckbox={!editMode} /> ); diff --git a/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleHeaders/index.js b/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleHeaders/index.js index da5c8209d..86faee75f 100644 --- a/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleHeaders/index.js +++ b/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleHeaders/index.js @@ -139,6 +139,8 @@ const ResponseExampleHeaders = ({ editMode, item, collection, exampleUid }) => { reorderable={editMode} onReorder={handleHeaderDrag} showAddRow={editMode} + showDelete={editMode} + disableCheckbox={!editMode} /> {editMode && (
diff --git a/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleMultipartFormParams/index.js b/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleMultipartFormParams/index.js index 6d680f9f0..6d6b769c5 100644 --- a/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleMultipartFormParams/index.js +++ b/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleMultipartFormParams/index.js @@ -265,6 +265,8 @@ const ResponseExampleMultipartFormParams = ({ item, collection, exampleUid, edit reorderable={editMode} onReorder={handleParamDrag} showAddRow={editMode} + showDelete={editMode} + disableCheckbox={!editMode} /> ); diff --git a/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleParams/index.js b/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleParams/index.js index a6518fb25..21470d9ad 100644 --- a/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleParams/index.js +++ b/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleParams/index.js @@ -192,6 +192,8 @@ const ResponseExampleParams = ({ editMode, item, collection, exampleUid }) => { reorderable={editMode} onReorder={handleQueryParamDrag} showAddRow={editMode} + showDelete={editMode} + disableCheckbox={!editMode} /> {editMode && (
diff --git a/packages/bruno-app/src/components/ResponseExample/ResponseExampleResponsePane/ResponseExampleResponseHeaders/index.js b/packages/bruno-app/src/components/ResponseExample/ResponseExampleResponsePane/ResponseExampleResponseHeaders/index.js index 499018b4b..bdc6fe4c7 100644 --- a/packages/bruno-app/src/components/ResponseExample/ResponseExampleResponsePane/ResponseExampleResponseHeaders/index.js +++ b/packages/bruno-app/src/components/ResponseExample/ResponseExampleResponsePane/ResponseExampleResponseHeaders/index.js @@ -178,6 +178,7 @@ const ResponseExampleResponseHeaders = ({ editMode, item, collection, exampleUid onReorder={handleHeaderDrag} showAddRow={editMode} showCheckbox={false} + showDelete={editMode} /> {editMode && (
diff --git a/packages/bruno-app/src/components/ResponseExample/ResponseExampleTopBar/index.js b/packages/bruno-app/src/components/ResponseExample/ResponseExampleTopBar/index.js index 79882e7b0..962d8f239 100644 --- a/packages/bruno-app/src/components/ResponseExample/ResponseExampleTopBar/index.js +++ b/packages/bruno-app/src/components/ResponseExample/ResponseExampleTopBar/index.js @@ -7,6 +7,7 @@ import { useTheme } from 'providers/Theme'; import TruncatedText from 'components/TruncatedText'; import { updateResponseExampleName, updateResponseExampleDescription } from 'providers/ReduxStore/slices/collections'; import get from 'lodash/get'; +import Button from 'ui/Button'; const ResponseExampleTopBar = ({ item, @@ -130,21 +131,22 @@ const ResponseExampleTopBar = ({
- - +
@@ -178,23 +180,23 @@ const ResponseExampleTopBar = ({
- - +