Skip to content

Commit

Permalink
Merge pull request #724 from onewelcome/UCL-523
Browse files Browse the repository at this point in the history
UCL-523 DataGrid search
  • Loading branch information
pawel-napieracz authored Aug 5, 2024
2 parents 5f91f9b + 8be151d commit 40826ec
Show file tree
Hide file tree
Showing 13 changed files with 406 additions and 102 deletions.
123 changes: 90 additions & 33 deletions src/components/DataGrid/DataGrid.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@
* limitations under the License.
*/

import React, { useEffect, useRef } from "react";
import React, { useEffect, useRef, useState } from "react";
import { DataGrid, Props } from "./DataGrid";
import { getAllByRole, render, queryAllByRole, getByRole } from "@testing-library/react";
import { getAllByRole, render, queryAllByRole, getByRole, waitFor } from "@testing-library/react";
import { DataGridRow } from "./DataGridBody/DataGridRow/DataGridRow";
import { DataGridCell } from "./DataGridBody/DataGridCell/DataGridCell";
import { ContextMenu } from "../ContextMenu/ContextMenu";
Expand Down Expand Up @@ -465,18 +465,15 @@ const paramsWithFilters: Props<WithFiltersDataType> = {
}
],
filters: {
enable: true,
filtersProps: {
filterValues: [],
columnsMetadata: [
{ name: "name", headline: "Name", operators: ["is", "is not"] },
{ name: "type", headline: "Type", operators: ["is", "is not"] }
],
onFilterAdd: filter => console.log(filter),
onFilterEdit: filter => console.log(filter),
onFilterDelete: id => console.log(id),
onFiltersClear: () => console.log("clear")
}
filterValues: [],
columnsMetadata: [
{ name: "name", headline: "Name", operators: ["is", "is not"] },
{ name: "type", headline: "Type", operators: ["is", "is not"] }
],
onFilterAdd: filter => console.log(filter),
onFilterEdit: filter => console.log(filter),
onFilterDelete: id => console.log(id),
onFiltersClear: () => console.log("clear")
},
headers: [
{ name: "name", headline: "Name" },
Expand All @@ -494,7 +491,7 @@ const createDataGridWithFilters = (params?: (defaultParams: any) => Props<WithFi

const DataGridWithFilters = () => {
const { onFilterAdd, onFilterDelete, onFilterEdit, onFiltersClear, gridData, filters } =
useMockFilteringLogic(parameters.data || [], parameters.filters?.filtersProps.filterValues);
useMockFilteringLogic(parameters.data || [], parameters.filters?.filterValues);

return (
parameters.filters && (
Expand All @@ -503,14 +500,11 @@ const createDataGridWithFilters = (params?: (defaultParams: any) => Props<WithFi
data={gridData}
filters={{
...parameters.filters,
filtersProps: {
...parameters?.filters.filtersProps,
filterValues: filters,
onFilterAdd,
onFilterEdit,
onFilterDelete,
onFiltersClear
}
filterValues: filters,
onFilterAdd,
onFilterEdit,
onFilterDelete,
onFiltersClear
}}
data-testid="dataGrid"
/>
Expand Down Expand Up @@ -570,11 +564,8 @@ describe("DataGrid with filters", () => {
createDataGridWithFilters(prev => ({
...prev,
filters: {
enable: !!prev.filters?.enable,
filtersProps: {
...prev.filters.filtersProps,
filterValues: [{ id: "test", column: "name", operator: "is", value: ["Company 1"] }]
}
...prev.filters,
filterValues: [{ id: "test", column: "name", operator: "is", value: ["Company 1"] }]
}
}));

Expand Down Expand Up @@ -610,11 +601,8 @@ describe("DataGrid with filters", () => {
const { dataGrid, getByText, getAllByRole } = createDataGridWithFilters(prev => ({
...prev,
filters: {
enable: !!prev.filters?.enable,
filtersProps: {
...prev.filters.filtersProps,
filterValues: [{ id: "test", column: "name", operator: "is", value: ["Company 1"] }]
}
...prev.filters,
filterValues: [{ id: "test", column: "name", operator: "is", value: ["Company 1"] }]
}
}));

Expand All @@ -629,3 +617,72 @@ describe("DataGrid with filters", () => {
expect(getAllByRole("row")).toHaveLength(4);
});
});

const paramsWithSearch: Props<DataType> = {
children: ({ item }) => (
<DataGridRow key={item.firstName}>
<DataGridCell>{item.firstName}</DataGridCell>
<DataGridCell>{item.lastName}</DataGridCell>
<DataGridCell>{item.date}</DataGridCell>
</DataGridRow>
),
headers: [
{ name: "firstName", headline: "First name" },
{ name: "lastName", headline: "Last name" },
{ name: "date", headline: "Date" }
],
enableMultiSorting: true,
data: [
{ firstName: "Paweł", lastName: "Napieracz", date: "12.12.1990" },
{ firstName: "Michał", lastName: "Górski", date: "12.12.1994" },
{ firstName: "Daniel", lastName: "Velden", date: "12.12.199x" },
{ firstName: "Jasha", lastName: "Joachimsthal", date: "12.12.198x" }
],
isLoading: false
};

const createDataGridWithSearch = (params?: (defaultParams: any) => Props<DataType>) => {
let parameters = paramsWithSearch;
if (params) {
parameters = params(paramsWithSearch);
}

const DataGridWithSearch = () => {
const [searchValue, setSearchValue] = useState("");

return (
<DataGrid
{...parameters}
search={{
onSearch: setSearchValue,
debounceTime: 500,
initialSearchValue: searchValue
}}
data-testid="dataGrid"
/>
);
};

const queries = render(<DataGridWithSearch />);
const dataGrid = queries.getByTestId("dataGrid");

return {
...queries,
dataGrid
};
};

describe("DataGrid with search", () => {
it("should highlight matching values", async () => {
const { dataGrid, getByPlaceholderText, findByTestId } = createDataGridWithSearch();

expect(dataGrid).toBeInTheDocument();
expect(getByPlaceholderText("Search items")).toBeInTheDocument();

await userEvent.type(getByPlaceholderText("Search items"), "Daniel");

const highlight = await findByTestId("Daniel-mark");

expect(highlight).toBeInTheDocument();
});
});
17 changes: 11 additions & 6 deletions src/components/DataGrid/DataGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import { HeaderCell, OnSortFunction, Sort } from "./datagrid.interfaces";
import { Pagination, Props as PaginationProps } from "../Pagination/Pagination";
import { Spacing, useSpacing } from "../../hooks/useSpacing";
import { DataGridToolbar, DataGridToolbarProps } from "./DataGridFilters/DataGridToolbar";
import { DataGridToolbarWrapper } from "./DataGridFilters/DataGridToolbarWrapper";
import { DataGridSearchbar, DataGridSearchbarProps } from "./DataGridFilters/DataGridSearchbar";

export interface Props<T> extends Omit<ComponentPropsWithRef<"div">, "children"> {
children: ({ item, index }: { item: T; index: number }) => ReactElement;
Expand All @@ -44,10 +46,8 @@ export interface Props<T> extends Omit<ComponentPropsWithRef<"div">, "children">
paginationProps?: PaginationProps;
disableContextMenuColumn?: boolean;
enableExpandableRow?: boolean;
filters?: {
enable: boolean;
filtersProps: DataGridToolbarProps;
};
filters?: DataGridToolbarProps;
search?: DataGridSearchbarProps;
isLoading?: boolean;
enableMultiSorting?: boolean;
spacing?: Spacing;
Expand All @@ -65,6 +65,7 @@ const DataGridInner = <T extends {}>(
disableContextMenuColumn,
enableExpandableRow,
filters,
search,
isLoading,
enableMultiSorting,
emptyLabel,
Expand Down Expand Up @@ -132,8 +133,11 @@ const DataGridInner = <T extends {}>(
paddingBottom: styleWithSpacing?.paddingBottom
}}
>
{filters?.enable ? (
<DataGridToolbar {...filters.filtersProps} />
{filters || search ? (
<DataGridToolbarWrapper>
{search && <DataGridSearchbar {...search} />}
{filters && <DataGridToolbar {...filters} />}
</DataGridToolbarWrapper>
) : (
<DataGridActions
{...actions}
Expand Down Expand Up @@ -164,6 +168,7 @@ const DataGridInner = <T extends {}>(
disableContextMenuColumn={disableContextMenuColumn}
emptyLabel={emptyLabel}
spacing={styleWithSpacing}
searchValue={search?.initialSearchValue}
>
{children}
</DataGridBody>
Expand Down
3 changes: 3 additions & 0 deletions src/components/DataGrid/DataGridBody/DataGridBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export interface Props<T> extends Omit<ComponentPropsWithRef<"tbody">, "children
headers: HeaderCell[];
isLoading?: boolean;
disableContextMenuColumn?: boolean;
searchValue?: string;
emptyLabel?: string;
spacing?: React.CSSProperties;
}
Expand All @@ -42,6 +43,7 @@ const DataGridBodyInner = <T extends {}>(
disableContextMenuColumn,
emptyLabel,
spacing,
searchValue,
...rest
}: Props<T>,
ref: Ref<HTMLTableSectionElement>
Expand Down Expand Up @@ -77,6 +79,7 @@ const DataGridBodyInner = <T extends {}>(

return data?.map((item, index) => {
return React.cloneElement(children({ item, index }), {
searchValue: searchValue,
headers,
spacing,
disableContextMenuColumn
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,15 @@ describe("DataGridCell should render", () => {
expect(skeletonLoadingEl).toHaveAttribute("aria-busy", "true");
expect(skeletonLoadingEl).toHaveAttribute("aria-live", "polite");
});

it("should render highlighted text once match is found", () => {
const { dataGridCell } = createDataGridCell(params => ({
...params,
searchValue: "ce"
}));
const highlightEl = dataGridCell.querySelector("span")?.querySelector("mark");
expect(highlightEl).toBeDefined();
});
});

describe("ref should work", () => {
Expand Down
36 changes: 34 additions & 2 deletions src/components/DataGrid/DataGridBody/DataGridCell/DataGridCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,20 @@
* limitations under the License.
*/

import React, { ForwardRefRenderFunction, ComponentPropsWithRef, ReactElement } from "react";
import React, {
ForwardRefRenderFunction,
ComponentPropsWithRef,
ReactElement,
Fragment
} from "react";
import { Typography } from "../../../Typography/Typography";
import classes from "./DataGridCell.module.scss";

export interface Props extends ComponentPropsWithRef<"td"> {
children?: ReactElement | string | number;
isLoading?: boolean;
spacing?: React.CSSProperties;
searchValue?: string;
cellIndex?: number;
columnLength?: number;
disableContextMenuColumn?: boolean;
Expand All @@ -33,6 +39,7 @@ const DataGridCellComponent: ForwardRefRenderFunction<HTMLTableCellElement, Prop
className,
isLoading,
spacing,
searchValue,
cellIndex,
columnLength,
disableContextMenuColumn,
Expand All @@ -52,6 +59,31 @@ const DataGridCellComponent: ForwardRefRenderFunction<HTMLTableCellElement, Prop
cellStyle.paddingRight = spacing?.paddingRight;
}

//NOTE: we might want to migrate to Highlight API once it's supported by Firefox
const renderContent = () => {
if (typeof children === "string" && searchValue) {
if (!children.toLowerCase().includes(searchValue.toLowerCase())) {
return children;
}

const matchingSequence = new RegExp(searchValue, "i").exec(children);

const parts = children.split(matchingSequence?.[0] ?? "");
return parts.map((part, i) => {
if (i === parts.length - 1) return <Fragment key={`${part}-${i}`}>{part}</Fragment>;

return (
<Fragment key={`${part}-${i}`}>
{part}
<mark data-testid={`${matchingSequence}-mark`}>{matchingSequence}</mark>
</Fragment>
);
});
}

return children;
};

return (
<td
{...rest}
Expand All @@ -62,7 +94,7 @@ const DataGridCellComponent: ForwardRefRenderFunction<HTMLTableCellElement, Prop
{isLoading && <div className={classes["loading"]} aria-busy="true" aria-live="polite"></div>}
{!isLoading && (
<Typography className={classes["text"]} variant="body" tag="span">
{children}
{renderContent()}
</Typography>
)}
</td>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export interface Props extends ComponentPropsWithRef<"tr"> {
headers?: HeaderCell[];
isLoading?: boolean;
spacing?: React.CSSProperties;
searchValue?: string;
disableContextMenuColumn?: boolean;
expandableRowProps?: {
enableExpandableRow: boolean;
Expand All @@ -42,6 +43,7 @@ const DataGridRowComponent: ForwardRefRenderFunction<HTMLTableRowElement, Props>
children,
className,
headers,
searchValue,
isLoading,
spacing,
expandableRowProps,
Expand All @@ -61,6 +63,7 @@ const DataGridRowComponent: ForwardRefRenderFunction<HTMLTableRowElement, Props>
const visibleCells = React.Children.map(children as React.ReactElement[], (child, index) => {
if (child) {
const cellWithSpacing = React.cloneElement(child, {
searchValue,
spacing: spacing,
cellIndex: index,
columnLength: headers?.length,
Expand Down
Loading

0 comments on commit 40826ec

Please sign in to comment.