(
@@ -415,9 +437,9 @@ const EnvironmentVariables = ({ environment, setIsModified, collection }) => {
)}
fixedItemHeight={35}
- computeItemKey={(index, variable) => variable.uid}
- itemContent={(index, variable) => {
- const isLastRow = index === formik.values.length - 1;
+ computeItemKey={(index, item) => item.variable.uid}
+ itemContent={(index, { variable, index: actualIndex }) => {
+ const isLastRow = actualIndex === formik.values.length - 1;
const isEmptyRow = !variable.name || variable.name.trim() === '';
const isLastEmptyRow = isLastRow && isEmptyRow;
@@ -428,7 +450,7 @@ const EnvironmentVariables = ({ environment, setIsModified, collection }) => {
@@ -443,15 +465,15 @@ const EnvironmentVariables = ({ environment, setIsModified, collection }) => {
autoCapitalize="off"
spellCheck="false"
className="mousetrap"
- id={`${index}.name`}
- name={`${index}.name`}
+ id={`${actualIndex}.name`}
+ name={`${actualIndex}.name`}
value={variable.name}
placeholder={isLastEmptyRow ? 'Name' : ''}
- onChange={(e) => handleNameChange(index, e)}
- onBlur={() => handleNameBlur(index)}
- onKeyDown={(e) => handleNameKeyDown(index, e)}
+ onChange={(e) => handleNameChange(actualIndex, e)}
+ onBlur={() => handleNameBlur(actualIndex)}
+ onKeyDown={(e) => handleNameKeyDown(actualIndex, e)}
/>
-
+
@@ -459,12 +481,12 @@ const EnvironmentVariables = ({ environment, setIsModified, collection }) => {
formik.setFieldValue(`${index}.value`, newValue, true)}
+ onChange={(newValue) => formik.setFieldValue(`${actualIndex}.value`, newValue, true)}
onSave={handleSave}
/>
@@ -490,7 +512,7 @@ const EnvironmentVariables = ({ environment, setIsModified, collection }) => {
diff --git a/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/StyledWrapper.js b/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/StyledWrapper.js
index f879a90d7..b6b199f77 100644
--- a/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/StyledWrapper.js
+++ b/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/StyledWrapper.js
@@ -94,8 +94,63 @@ const StyledWrapper = styled.div`
.actions {
display: flex;
+ align-items: center;
gap: 2px;
+ .search-container {
+ position: relative;
+ top: 6px;
+ display: flex;
+ align-items: center;
+
+ .search-icon {
+ position: absolute;
+ color: ${(props) => props.theme.colors.text.muted};
+ pointer-events: none;
+ }
+
+ .search-input {
+ width: 200px;
+ padding: 5px 32px 5px 32px;
+ border: 1px solid ${(props) => props.theme.input.border};
+ border-radius: ${(props) => props.theme.border.radius.sm};
+ background: ${(props) => props.theme.input.bg};
+ color: ${(props) => props.theme.text};
+ font-size: ${(props) => props.theme.font.size.base};
+ outline: none;
+ transition: border-color 0.15s ease;
+
+ &:focus {
+ border-color: ${(props) => props.theme.input.focusBorder};
+ }
+
+ &::placeholder {
+ color: ${(props) => props.theme.input.placeholder.color};
+ opacity: ${(props) => props.theme.input.placeholder.opacity};
+ }
+ }
+
+ .clear-search {
+ position: absolute;
+ right: 8px;
+ padding: 4px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: ${(props) => props.theme.colors.text.muted};
+ background: transparent;
+ border: none;
+ cursor: pointer;
+ border-radius: ${(props) => props.theme.border.radius.sm};
+ transition: all 0.15s ease;
+
+ &:hover {
+ color: ${(props) => props.theme.text};
+ background: ${(props) => props.theme.sidebar.collection.item.hoverBg};
+ }
+ }
+ }
+
button {
display: inline-flex;
align-items: center;
diff --git a/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/index.js b/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/index.js
index eaf092f14..d7adcc81b 100644
--- a/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/index.js
+++ b/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/index.js
@@ -1,6 +1,7 @@
-import { IconCopy, IconEdit, IconTrash, IconCheck, IconX } from '@tabler/icons';
+import { IconCopy, IconEdit, IconTrash, IconCheck, IconX, IconSearch } from '@tabler/icons';
import { useState, useRef } from 'react';
import { useDispatch } from 'react-redux';
+import useDebounce from 'hooks/useDebounce';
import { renameEnvironment } from 'providers/ReduxStore/slices/collections/actions';
import { validateName, validateNameError } from 'utils/common/regex';
import toast from 'react-hot-toast';
@@ -18,6 +19,8 @@ const EnvironmentDetails = ({ environment, setIsModified, collection }) => {
const [isRenaming, setIsRenaming] = useState(false);
const [newName, setNewName] = useState('');
const [nameError, setNameError] = useState('');
+ const [searchQuery, setSearchQuery] = useState('');
+ const debouncedSearchQuery = useDebounce(searchQuery, 300);
const inputRef = useRef(null);
const validateEnvironmentName = (name) => {
@@ -162,6 +165,29 @@ const EnvironmentDetails = ({ environment, setIsModified, collection }) => {
{nameError && isRenaming && {nameError} }
+
+
+ setSearchQuery(e.target.value)}
+ className="search-input"
+ autoComplete="off"
+ autoCorrect="off"
+ autoCapitalize="off"
+ spellCheck="false"
+ />
+ {searchQuery && (
+
+ )}
+
@@ -175,7 +201,12 @@ const EnvironmentDetails = ({ environment, setIsModified, collection }) => {
-
+
);
diff --git a/packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js b/packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js
index 73a741948..a27a17f39 100644
--- a/packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js
+++ b/packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js
@@ -1,4 +1,4 @@
-import React, { useCallback, useRef } from 'react';
+import React, { useCallback, useRef, useMemo } from 'react';
import { TableVirtuoso } from 'react-virtuoso';
import cloneDeep from 'lodash/cloneDeep';
import { IconTrash, IconAlertCircle, IconInfoCircle } from '@tabler/icons';
@@ -22,13 +22,13 @@ import Button from 'ui/Button';
const MIN_H = 35 * 2;
-const TableRow = React.memo(({ children, item }) => {children} , (prevProps, nextProps) => {
- const prevUid = prevProps?.item?.uid;
- const nextUid = nextProps?.item?.uid;
+const TableRow = React.memo(({ children, item }) => {children} , (prevProps, nextProps) => {
+ const prevUid = prevProps?.item?.variable?.uid;
+ const nextUid = nextProps?.item?.variable?.uid;
return prevUid === nextUid && prevProps.children === nextProps.children;
});
-const EnvironmentVariables = ({ environment, setIsModified, originalEnvironmentVariables, collection }) => {
+const EnvironmentVariables = ({ environment, setIsModified, collection, searchQuery = '' }) => {
const dispatch = useDispatch();
const { storedTheme } = useTheme();
const { globalEnvironments, activeGlobalEnvironmentUid, globalEnvironmentDraft } = useSelector(
@@ -335,16 +335,38 @@ const EnvironmentVariables = ({ environment, setIsModified, originalEnvironmentV
};
}, []);
+ const filteredVariables = useMemo(() => {
+ const allVariables = formik.values.map((variable, index) => ({ variable, index }));
+ if (!searchQuery?.trim()) {
+ return allVariables;
+ }
+
+ const query = searchQuery.toLowerCase().trim();
+
+ return allVariables.filter(({ variable, index }) => {
+ const isLastRow = index === formik.values.length - 1;
+ const isEmptyRow = !variable.name || variable.name.trim() === '';
+ if (isLastRow && isEmptyRow) {
+ return true;
+ }
+
+ const nameMatch = variable.name ? variable.name.toLowerCase().includes(query) : false;
+ const valueMatch = typeof variable.value === 'string' ? variable.value.toLowerCase().includes(query) : false;
+
+ return !!(nameMatch || valueMatch);
+ });
+ }, [formik.values, searchQuery]);
+
return (
variable.uid}
+ computeItemKey={(index, item) => item.variable.uid}
fixedHeaderContent={() => (
|
@@ -354,8 +376,9 @@ const EnvironmentVariables = ({ environment, setIsModified, originalEnvironmentV
|
)}
- itemContent={(index, variable) => {
- const isLastRow = index === formik.values.length - 1;
+
+ itemContent={(index, { variable, index: actualIndex }) => {
+ const isLastRow = actualIndex === formik.values.length - 1;
const isEmptyRow = !variable.name || variable.name.trim() === '';
const isLastEmptyRow = isLastRow && isEmptyRow;
@@ -366,7 +389,7 @@ const EnvironmentVariables = ({ environment, setIsModified, originalEnvironmentV
@@ -381,15 +404,15 @@ const EnvironmentVariables = ({ environment, setIsModified, originalEnvironmentV
autoCapitalize="off"
spellCheck="false"
className="mousetrap"
- id={`${index}.name`}
- name={`${index}.name`}
+ id={`${actualIndex}.name`}
+ name={`${actualIndex}.name`}
value={variable.name}
placeholder={isLastEmptyRow ? 'Name' : ''}
- onChange={(e) => handleNameChange(index, e)}
- onBlur={() => handleNameBlur(index)}
- onKeyDown={(e) => handleNameKeyDown(index, e)}
+ onChange={(e) => handleNameChange(actualIndex, e)}
+ onBlur={() => handleNameBlur(actualIndex)}
+ onKeyDown={(e) => handleNameKeyDown(actualIndex, e)}
/>
-
+
|
@@ -397,12 +420,12 @@ const EnvironmentVariables = ({ environment, setIsModified, originalEnvironmentV
formik.setFieldValue(`${index}.value`, newValue, true)}
+ onChange={(newValue) => formik.setFieldValue(`${actualIndex}.value`, newValue, true)}
onSave={handleSave}
/>
@@ -422,7 +445,7 @@ const EnvironmentVariables = ({ environment, setIsModified, originalEnvironmentV
diff --git a/packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/StyledWrapper.js b/packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/StyledWrapper.js
index d6f9d20e3..697e489bd 100644
--- a/packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/StyledWrapper.js
+++ b/packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/StyledWrapper.js
@@ -94,8 +94,66 @@ const StyledWrapper = styled.div`
.actions {
display: flex;
+ align-items: center;
gap: 2px;
-
+
+ .search-container {
+ position: relative;
+ top: 6px;
+ display: flex;
+ align-items: center;
+ // margin-right: 8px;
+ // margin-top: 8px;
+
+ .search-icon {
+ position: absolute;
+ // left: 10px;
+ color: ${(props) => props.theme.colors.text.muted};
+ pointer-events: none;
+ }
+
+ .search-input {
+ width: 200px;
+ padding: 5px 32px 5px 32px;
+ border: 1px solid ${(props) => props.theme.input.border};
+ border-radius: ${(props) => props.theme.border.radius.sm};
+ background: ${(props) => props.theme.input.bg};
+ color: ${(props) => props.theme.text};
+ font-size: ${(props) => props.theme.font.size.base};
+ outline: none;
+ transition: border-color 0.15s ease;
+
+ &:focus {
+ border-color: ${(props) => props.theme.input.focusBorder};
+ }
+
+ &::placeholder {
+ color: ${(props) => props.theme.input.placeholder.color};
+ opacity: ${(props) => props.theme.input.placeholder.opacity};
+ }
+ }
+
+ .clear-search {
+ position: absolute;
+ right: 8px;
+ padding: 4px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: ${(props) => props.theme.colors.text.muted};
+ background: transparent;
+ border: none;
+ cursor: pointer;
+ border-radius: ${(props) => props.theme.border.radius.sm};
+ transition: all 0.15s ease;
+
+ &:hover {
+ color: ${(props) => props.theme.text};
+ background: ${(props) => props.theme.sidebar.collection.item.hoverBg};
+ }
+ }
+ }
+
button {
display: inline-flex;
align-items: center;
diff --git a/packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/index.js b/packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/index.js
index 362e33ac6..b197c7810 100644
--- a/packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/index.js
+++ b/packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/index.js
@@ -1,6 +1,7 @@
-import { IconCopy, IconEdit, IconTrash, IconCheck, IconX } from '@tabler/icons';
+import { IconCopy, IconEdit, IconTrash, IconCheck, IconX, IconSearch } from '@tabler/icons';
import { useState, useRef } from 'react';
import { useDispatch, useSelector } from 'react-redux';
+import useDebounce from 'hooks/useDebounce';
import { renameGlobalEnvironment } from 'providers/ReduxStore/slices/global-environments';
import { validateName, validateNameError } from 'utils/common/regex';
import toast from 'react-hot-toast';
@@ -18,6 +19,8 @@ const EnvironmentDetails = ({ environment, setIsModified, collection }) => {
const [isRenaming, setIsRenaming] = useState(false);
const [newName, setNewName] = useState('');
const [nameError, setNameError] = useState('');
+ const [searchQuery, setSearchQuery] = useState('');
+ const debouncedSearchQuery = useDebounce(searchQuery, 300);
const inputRef = useRef(null);
const validateEnvironmentName = (name) => {
@@ -164,6 +167,29 @@ const EnvironmentDetails = ({ environment, setIsModified, collection }) => {
{nameError && isRenaming && {nameError} }
+
+
+ setSearchQuery(e.target.value)}
+ className="search-input"
+ autoComplete="off"
+ autoCorrect="off"
+ autoCapitalize="off"
+ spellCheck="false"
+ />
+ {searchQuery && (
+
+ )}
+
@@ -177,7 +203,12 @@ const EnvironmentDetails = ({ environment, setIsModified, collection }) => {
-
+
);
|