Repository: provectus/kafka-ui
Issue: Add clear button to the search component
Labels: good first issue
scope/frontend
status/accepted
type/chore

### Describe the solution you'd like
Search input should be clearable


### Additional context
Component: src/components/common/Search/Search.tsx


Issue comments:
Can I work on this issue? can you please assign me this?
> Can I work on this issue? can you please assign me this?

sure :)
Hi @Haarolean I am very new to apache kafka can you please guide me hou to setup a local cluster so that I can test this UI and can work on this issue? I really want to contribue to this project but without setting up the cluster It is impossible to work so I need your help.
Hi @Haarolean  could you please help me?
@detronetdip please refer to:
[contributing guide](https://github.com/provectus/kafka-ui/blob/master/CONTRIBUTING.md)
[frontend build guide](https://github.com/provectus/kafka-ui/blob/master/kafka-ui-react-app/README.md)
You'll have to run the compose: `docker-compose -f ./documentation/compose/kafka-ui.yaml up` and set `VITE_DEV_PROXY` to `http://localhost:8080`.
@detronetdip any luck?
@Haarolean I am working
@Haarolean  I followed you instruction, and on opening `localhost:3000` it is showing a blank screen. I think I need your help again

![Screenshot from 2022-10-20 21-57-15](https://user-images.githubusercontent.com/64276906/197005446-1751edbf-8427-4a3f-bf70-fd66d43b2a6d.png)

> @Haarolean I followed you instruction, and on opening `localhost:3000` it is showing a blank screen. I think I need your help again
> 
> ![Screenshot from 2022-10-20 21-57-15](https://user-images.githubusercontent.com/64276906/197005446-1751edbf-8427-4a3f-bf70-fd66d43b2a6d.png)

I suppose you could check the red error in your browser console as a start.
@workshur by saying search input should be clearable, did you mean there will be a button to clear the input field? and please guide me which search input you are taking about.
> @workshur by saying search input should be clearable, did you mean there will be a button to clear the input field? and please guide me which search input you are taking about.

Yes it's about having a button to clear the inout.
We're talking about all the search inputs in the app, there's a component class mentioned in the issue.
@Haarolean  I have run `docker-compose -f ./documentation/compose/kafka-ui.yaml up` command and have set `VITE_DEV_PROXY` to `http://localhost:8080`  in the `.env` file inside `kafka-ui-react-app` folder and then I visit `localhost:8080` in my browser and the finally I can see the UI but.  Is this the correct way? If it is then should I stop the docker containers and restart it again to view changes? Actually I am unable to find how to properly setup the development environment  and how to view my changes. Please guide me.
@detronetdip as it is stated in [frontend module readme](https://github.com/provectus/kafka-ui/tree/master/kafka-ui-react-app) if you develop the frontend part, you should run it via `pnpm start` and visit the link produced by this command. The running containers are used as a backend which you don't have to rebuild unless you do modifications to the backend side as well.
![Screenshot from 2022-10-30 22-12-34](https://user-images.githubusercontent.com/64276906/198890576-6699ea81-9fc4-4620-ade6-830bb865b086.png)
@Haarolean  It is showing this error.
It’s your ad blocker. On 31 Oct 2022, at 00:44, Ayondip Jana ***@***.***> wrote:﻿

@Haarolean  It is showing this error.

—Reply to this email directly, view it on GitHub, or unsubscribe.You are receiving this because you were mentioned.Message ID: ***@***.***>
@Haarolean  I have disabled the ad-blocker and now it is throwing a branch of errors

![Screenshot from 2022-10-30 22-17-25](https://user-images.githubusercontent.com/64276906/198890802-8f8309c7-a54c-4c92-8c49-ac2caa96647e.png)

> @Haarolean I have disabled the ad-blocker and now it is throwing a branch of errors
> 
> ![Screenshot from 2022-10-30 22-17-25](https://user-images.githubusercontent.com/64276906/198890802-8f8309c7-a54c-4c92-8c49-ac2caa96647e.png)

I honestly have no foggiest what you're doing wrong from this obscure screenshots. There's a decent guide which does work without any problems in 90% of cases. Please ensure that backend is accessible via localhost:3000/api/xxx, the proxy is set properly, etc.
Ok let try again from the very beginning.
@detronetdip any luck?
Hi, I am looking forward to diving deep into the project, but I went into the same problem as @detronetdip did. After I followed the instruction on the readme page (stated below) and set the .env file as empty
<img width="811" alt="image" src="https://user-images.githubusercontent.com/43143420/226153059-4b62cfd7-af2a-41be-aa83-f171a498ff0f.png">
and I got a blank screen and the error in the browser console showed
<img width="423" alt="image" src="https://user-images.githubusercontent.com/43143420/226153082-27b368a3-d0db-4791-9e05-0af0fec6ab27.png">
<img width="507" alt="image" src="https://user-images.githubusercontent.com/43143420/226153100-acbe80d6-1b7d-4165-beb3-b298226023a5.png">
It seems that there are some cache problems with the front end when starting the project using pnpm. The backend works totally fine since I can access UI through the 8080 port. Do we actually need to configure the dev proxy even when using docker as the backend? If so, how to set it? The readme is not very clear with the API server. What is that and how to set it as a development environment? 
BTW, I am using WSL2 as my dev environment, hope this is not the cause of the problem
@Mano-Liaoyan hey, wanna jump into discord so we don't spam over here? We can figure out what's happening. The link is available at top of the readme, but here's another one: https://discord.gg/4DWzD7pGE5

EDIT: Solved: missing dev proxy in .env.local

Pull request review comments:
i don't think passing it as `children` here is very intuitive , we can pass it as a prop , because i think it will be hard to understand what is a `children`  of a `Input` component.
ok i will try to change 
why we are using `createRef` instead of `useRef` ?
yeh i will replace with useRef it's more effectively

Patch that resolved the issue:
diff --git a/kafka-ui-react-app/src/components/common/Input/Input.styled.ts b/kafka-ui-react-app/src/components/common/Input/Input.styled.ts
index 9495aaecbeb..f21962fe6b6 100644
--- a/kafka-ui-react-app/src/components/common/Input/Input.styled.ts
+++ b/kafka-ui-react-app/src/components/common/Input/Input.styled.ts
@@ -29,6 +29,16 @@ export const Wrapper = styled.div`
     width: 16px;
     fill: ${({ theme }) => theme.input.icon.color};
   }
+  svg:last-child {
+    position: absolute;
+    top: 8px;
+    line-height: 0;
+    z-index: 1;
+    left: unset;
+    right: 12px;
+    height: 16px;
+    width: 16px;
+  }
 `;
 
 export const Input = styled.input<InputProps>(
diff --git a/kafka-ui-react-app/src/components/common/Input/Input.tsx b/kafka-ui-react-app/src/components/common/Input/Input.tsx
index ae76bc47178..4d04b730e51 100644
--- a/kafka-ui-react-app/src/components/common/Input/Input.tsx
+++ b/kafka-ui-react-app/src/components/common/Input/Input.tsx
@@ -16,6 +16,7 @@ export interface InputProps
   withError?: boolean;
   label?: React.ReactNode;
   hint?: React.ReactNode;
+  clearIcon?: React.ReactNode;
 
   // Some may only accept integer, like `Number of Partitions`
   // some may accept decimal
@@ -99,19 +100,22 @@ function pasteNumberCheck(
   return value;
 }
 
-const Input: React.FC<InputProps> = ({
-  name,
-  hookFormOptions,
-  search,
-  inputSize = 'L',
-  type,
-  positiveOnly,
-  integerOnly,
-  withError = false,
-  label,
-  hint,
-  ...rest
-}) => {
+const Input = React.forwardRef<HTMLInputElement, InputProps>((props, ref) => {
+  const {
+    name,
+    hookFormOptions,
+    search,
+    inputSize = 'L',
+    type,
+    positiveOnly,
+    integerOnly,
+    withError = false,
+    label,
+    hint,
+    clearIcon,
+    ...rest
+  } = props;
+
   const methods = useFormContext();
 
   const fieldId = React.useId();
@@ -168,7 +172,6 @@ const Input: React.FC<InputProps> = ({
     // if the field is a part of react-hook-form form
     inputOptions = { ...rest, ...methods.register(name, hookFormOptions) };
   }
-
   return (
     <div>
       {label && <InputLabel htmlFor={rest.id || fieldId}>{label}</InputLabel>}
@@ -181,8 +184,11 @@ const Input: React.FC<InputProps> = ({
           type={type}
           onKeyPress={keyPressEventHandler}
           onPaste={pasteEventHandler}
+          ref={ref}
           {...inputOptions}
         />
+        {clearIcon}
+
         {withError && isHookFormField && (
           <S.FormError>
             <ErrorMessage name={name} />
@@ -192,6 +198,6 @@ const Input: React.FC<InputProps> = ({
       </S.Wrapper>
     </div>
   );
-};
+});
 
 export default Input;
diff --git a/kafka-ui-react-app/src/components/common/Search/Search.tsx b/kafka-ui-react-app/src/components/common/Search/Search.tsx
index 66c0e95030b..65116d645a6 100644
--- a/kafka-ui-react-app/src/components/common/Search/Search.tsx
+++ b/kafka-ui-react-app/src/components/common/Search/Search.tsx
@@ -1,7 +1,9 @@
-import React from 'react';
+import React, { useRef } from 'react';
 import { useDebouncedCallback } from 'use-debounce';
 import Input from 'components/common/Input/Input';
 import { useSearchParams } from 'react-router-dom';
+import CloseIcon from 'components/common/Icons/CloseIcon';
+import styled from 'styled-components';
 
 interface SearchProps {
   placeholder?: string;
@@ -10,6 +12,16 @@ interface SearchProps {
   value?: string;
 }
 
+const IconButtonWrapper = styled.span.attrs(() => ({
+  role: 'button',
+  tabIndex: '0',
+}))`
+  height: 16px !important;
+  display: inline-block;
+  &:hover {
+    cursor: pointer;
+  }
+`;
 const Search: React.FC<SearchProps> = ({
   placeholder = 'Search',
   disabled = false,
@@ -17,7 +29,11 @@ const Search: React.FC<SearchProps> = ({
   onChange,
 }) => {
   const [searchParams, setSearchParams] = useSearchParams();
+  const ref = useRef<HTMLInputElement>(null);
   const handleChange = useDebouncedCallback((e) => {
+    if (ref.current != null) {
+      ref.current.value = e.target.value;
+    }
     if (onChange) {
       onChange(e.target.value);
     } else {
@@ -28,6 +44,15 @@ const Search: React.FC<SearchProps> = ({
       setSearchParams(searchParams);
     }
   }, 500);
+  const clearSearchValue = () => {
+    if (searchParams.get('q')) {
+      searchParams.set('q', '');
+      setSearchParams(searchParams);
+    }
+    if (ref.current != null) {
+      ref.current.value = '';
+    }
+  };
 
   return (
     <Input
@@ -37,7 +62,13 @@ const Search: React.FC<SearchProps> = ({
       defaultValue={value || searchParams.get('q') || ''}
       inputSize="M"
       disabled={disabled}
+      ref={ref}
       search
+      clearIcon={
+        <IconButtonWrapper onClick={clearSearchValue}>
+          <CloseIcon />
+        </IconButtonWrapper>
+      }
     />
   );
 };
diff --git a/kafka-ui-react-app/src/components/common/Search/__tests__/Search.spec.tsx b/kafka-ui-react-app/src/components/common/Search/__tests__/Search.spec.tsx
index 808f229317e..2103d223367 100644
--- a/kafka-ui-react-app/src/components/common/Search/__tests__/Search.spec.tsx
+++ b/kafka-ui-react-app/src/components/common/Search/__tests__/Search.spec.tsx
@@ -41,4 +41,24 @@ describe('Search', () => {
     render(<Search />);
     expect(screen.queryByPlaceholderText('Search')).toBeInTheDocument();
   });
+
+  it('Clear button is visible', () => {
+    render(<Search placeholder={placeholder} />);
+
+    const clearButton = screen.getByRole('button');
+    expect(clearButton).toBeInTheDocument();
+  });
+
+  it('Clear button should clear text from input', async () => {
+    render(<Search placeholder={placeholder} />);
+
+    const searchField = screen.getAllByRole('textbox')[0];
+    await userEvent.type(searchField, 'some text');
+    expect(searchField).toHaveValue('some text');
+
+    const clearButton = screen.getByRole('button');
+    await userEvent.click(clearButton);
+
+    expect(searchField).toHaveValue('');
+  });
 });


Test patch: