Skip to content

Commit

Permalink
Add clear button to search bar (apache#43795)
Browse files Browse the repository at this point in the history
  • Loading branch information
bbovenzi authored Nov 7, 2024
1 parent 24b2369 commit af0837c
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 35 deletions.
51 changes: 51 additions & 0 deletions airflow/ui/src/components/SearchBar.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*!
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import { describe, it, expect, vi } from "vitest";

import { Wrapper } from "src/utils/Wrapper";

import { SearchBar } from "./SearchBar";

describe("Test SearchBar", () => {
it("Renders and clear button works", async () => {
render(<SearchBar defaultValue="" onChange={vi.fn()} />, {
wrapper: Wrapper,
});

const input = screen.getByTestId("search-dags");

expect(screen.getByText("Advanced Search")).toBeDefined();
expect(screen.queryByTestId("clear-search")).toBeNull();

fireEvent.change(input, { target: { value: "search" } });

await waitFor(() =>
expect((input as HTMLInputElement).value).toBe("search"),
);

const clearButton = screen.getByTestId("clear-search");

expect(clearButton).toBeDefined();

fireEvent.click(clearButton);

expect((input as HTMLInputElement).value).toBe("");
});
});
73 changes: 46 additions & 27 deletions airflow/ui/src/components/SearchBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,57 +16,76 @@
* specific language governing permissions and limitations
* under the License.
*/
import {
Button,
Input,
type ButtonProps,
type InputProps,
} from "@chakra-ui/react";
import type { ChangeEvent } from "react";
import { Button, Input, type ButtonProps } from "@chakra-ui/react";
import { useState, type ChangeEvent } from "react";
import { FiSearch } from "react-icons/fi";
import { useDebouncedCallback } from "use-debounce";

import { InputGroup, type InputGroupProps } from "./ui";
import { CloseButton, InputGroup, type InputGroupProps } from "./ui";

const debounceDelay = 200;

type Props = {
readonly buttonProps?: ButtonProps;
readonly defaultValue: string;
readonly groupProps?: InputGroupProps;
readonly onChange: (value: string) => void;
};

export const SearchBar = ({
buttonProps,
defaultValue,
groupProps,
inputProps,
}: {
readonly buttonProps?: ButtonProps;
readonly groupProps?: InputGroupProps;
readonly inputProps?: InputProps;
}) => {
onChange,
}: Props) => {
const handleSearchChange = useDebouncedCallback(
(event: ChangeEvent<HTMLInputElement>) => inputProps?.onChange?.(event),
(val: string) => onChange(val),
debounceDelay,
);

const [value, setValue] = useState(defaultValue);

const onSearchChange = (event: ChangeEvent<HTMLInputElement>) => {
setValue(event.target.value);
handleSearchChange(event.target.value);
};

return (
<InputGroup
{...groupProps}
endElement={
<Button
colorPalette="blue"
fontWeight="normal"
height="1.75rem"
variant="ghost"
width={140}
{...buttonProps}
>
Advanced Search
</Button>
<>
{Boolean(value) ? (
<CloseButton
aria-label="Clear search"
data-testid="clear-search"
onClick={() => {
setValue("");
onChange("");
}}
size="xs"
/>
) : undefined}
<Button
colorPalette="blue"
fontWeight="normal"
height="1.75rem"
variant="ghost"
width={140}
{...buttonProps}
>
Advanced Search
</Button>
</>
}
startElement={<FiSearch />}
>
<Input
data-testid="search-dags"
onChange={onSearchChange}
placeholder="Search Dags"
pr={150}
{...inputProps}
onChange={handleSearchChange}
value={value}
/>
</InputGroup>
);
Expand Down
12 changes: 4 additions & 8 deletions airflow/ui/src/pages/DagsList/DagsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import {
type SelectValueChangeDetails,
} from "@chakra-ui/react";
import type { ColumnDef } from "@tanstack/react-table";
import { type ChangeEvent, useCallback, useState } from "react";
import { useCallback, useState } from "react";
import { Link as RouterLink, useSearchParams } from "react-router-dom";
import { useLocalStorage } from "usehooks-ts";

Expand Down Expand Up @@ -170,9 +170,7 @@ export const DagsList = () => {
const [sort] = sorting;
const orderBy = sort ? `${sort.desc ? "-" : ""}${sort.id}` : undefined;

const handleSearchChange = ({
target: { value },
}: ChangeEvent<HTMLInputElement>) => {
const handleSearchChange = (value: string) => {
if (value) {
searchParams.set(NAME_PATTERN_PARAM, value);
} else {
Expand Down Expand Up @@ -217,10 +215,8 @@ export const DagsList = () => {
<VStack alignItems="none">
<SearchBar
buttonProps={{ disabled: true }}
inputProps={{
defaultValue: dagDisplayNamePattern,
onChange: handleSearchChange,
}}
defaultValue={dagDisplayNamePattern ?? ""}
onChange={handleSearchChange}
/>
<DagsFilters />
<HStack justifyContent="space-between">
Expand Down

0 comments on commit af0837c

Please sign in to comment.