Add support for search and custom actions

Summary: Introduced search bar and support for custom buttons therein.

Reviewed By: nikoant

Differential Revision: D26338666

fbshipit-source-id: e53cd3c4381e11f5f90c05c92e39a6c8ac2eca65
This commit is contained in:
Michel Weststrate
2021-03-16 14:54:53 -07:00
committed by Facebook GitHub Bot
parent 44bb5b1beb
commit fb7c09c972
6 changed files with 147 additions and 13 deletions

View File

@@ -15,11 +15,14 @@ import {TableHead} from './TableHead';
import {Percentage} from '../utils/widthUtils';
import {DataSourceRenderer} from './DataSourceRenderer';
import {useDataTableManager, TableManager} from './useDataTableManager';
import {TableSearch} from './TableSearch';
interface DataTableProps<T = any> {
columns: DataTableColumn<T>[];
dataSource: DataSource<T, any, any>;
autoScroll?: boolean;
extraActions?: React.ReactElement;
// custom onSearch(text, row) option?
tableManagerRef?: RefObject<TableManager>;
_testHeight?: number; // exposed for unit testing only
}
@@ -58,6 +61,11 @@ export function DataTable<T extends object>(props: DataTableProps<T>) {
return (
<Layout.Top>
<Layout.Container>
<TableSearch
onSearch={tableManager.setSearchValue}
extraActions={props.extraActions}
/>
<TableHead
columns={tableManager.columns}
visibleColumns={tableManager.visibleColumns}
@@ -67,6 +75,7 @@ export function DataTable<T extends object>(props: DataTableProps<T>) {
sorting={tableManager.sorting}
onColumnSort={tableManager.sortColumn}
/>
</Layout.Container>
<DataSourceRenderer<any, RenderContext>
dataSource={props.dataSource}
autoScroll={props.autoScroll}

View File

@@ -13,7 +13,7 @@ import {
Percentage,
Width,
} from '../utils/widthUtils';
import {useRef} from 'react';
import {memo, useRef} from 'react';
import {Interactive, InteractiveProps} from '../Interactive';
import styled from '@emotion/styled';
import React from 'react';
@@ -181,7 +181,7 @@ function TableHeadColumn({
);
}
export function TableHead({
export const TableHead = memo(function TableHead({
columns,
visibleColumns,
...props
@@ -243,7 +243,7 @@ export function TableHead({
</Dropdown>
</TableHeadContainer>
);
}
});
const SettingsButton = styled(Button)({
padding: 4,
@@ -251,4 +251,5 @@ const SettingsButton = styled(Button)({
right: 0,
top: 0,
backgroundColor: theme.backgroundWash,
borderRadius: 0,
});

View File

@@ -12,6 +12,7 @@ import styled from '@emotion/styled';
import {theme} from 'flipper-plugin';
import type {RenderContext} from './DataTable';
import {Width} from '../utils/widthUtils';
import {pad} from 'lodash';
// heuristic for row estimation, should match any future styling updates
export const DEFAULT_ROW_HEIGHT = 24;
@@ -91,6 +92,13 @@ export const TableRow = memo(function TableRow(props: Props) {
value = value ? 'true' : 'false';
}
if (value instanceof Date) {
value =
value.toTimeString().split(' ')[0] +
'.' +
pad('' + value.getMilliseconds(), 3);
}
return (
<TableBodyColumnContainer
className="ant-table-cell"

View File

@@ -0,0 +1,33 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
import {Input} from 'antd';
import React, {memo} from 'react';
import {Layout} from '../Layout';
import {theme} from '../theme';
export const TableSearch = memo(function TableSearch({
onSearch,
extraActions,
}: {
onSearch(value: string): void;
extraActions?: React.ReactElement;
}) {
return (
<Layout.Horizontal
gap
style={{
backgroundColor: theme.backgroundWash,
padding: theme.space.small,
}}>
<Input.Search allowClear placeholder="Search..." onSearch={onSearch} />
{extraActions}
</Layout.Horizontal>
);
});

View File

@@ -12,6 +12,7 @@ import {DataTable, DataTableColumn} from '../DataTable';
import {render, act} from '@testing-library/react';
import {createDataSource} from '../../../state/datasource/DataSource';
import {TableManager} from '../useDataTableManager';
import {Button} from 'antd';
type Todo = {
title: string;
@@ -222,3 +223,57 @@ test('sorting', async () => {
]);
}
});
test('search', async () => {
const ds = createTestDataSource();
ds.clear();
ds.append({
title: 'item abc',
done: false,
});
ds.append({
title: 'item x',
done: false,
});
ds.append({
title: 'item b',
done: false,
});
const ref = createRef<TableManager>();
const rendering = render(
<DataTable
dataSource={ds}
columns={columns}
tableManagerRef={ref}
extraActions={<Button>Test Button</Button>}
_testHeight={400}
/>,
);
{
// button is visible
rendering.getByText('Test Button');
const elem = await rendering.findAllByText(/item/);
expect(elem.length).toBe(3);
expect(elem.map((e) => e.textContent)).toEqual([
'item abc',
'item x',
'item b',
]);
}
{
// filter
act(() => {
ref.current?.setSearchValue('b');
});
const elem = await rendering.findAllByText(/item/);
expect(elem.map((e) => e.textContent)).toEqual(['item abc', 'item b']);
}
{
// reset
act(() => {
ref.current?.reset();
});
const elem = await rendering.findAllByText(/item/);
expect(elem.length).toBe(3);
}
});

View File

@@ -10,7 +10,7 @@
import {DataTableColumn} from 'flipper-plugin/src/ui/datatable/DataTable';
import {Percentage} from '../utils/widthUtils';
import produce from 'immer';
import {useCallback, useMemo, useState} from 'react';
import {useCallback, useEffect, useMemo, useState} from 'react';
import {DataSource} from '../../state/datasource/DataSource';
export type OnColumnResize = (id: string, size: number | Percentage) => void;
@@ -33,11 +33,30 @@ export function useDataTableManager<T extends object>(
computeInitialColumns(defaultColumns),
);
const [sorting, setSorting] = useState<Sorting | undefined>(undefined);
const [searchValue, setSearchValue] = useState('');
const visibleColumns = useMemo(
() => columns.filter((column) => column.visible),
[columns],
);
// filter is computed by useMemo to support adding column filters etc here in the future
const currentFilter = useMemo(
function computeFilter() {
if (searchValue === '') {
// unset
return undefined;
}
const searchString = searchValue.toLowerCase();
return function dataTableFilter(item: object) {
return Object.values(item).some((v) =>
String(v).toLowerCase().includes(searchString),
);
};
},
[searchValue],
);
const reset = useCallback(() => {
setEffectiveColumns(computeInitialColumns(defaultColumns));
setSorting(undefined);
@@ -88,6 +107,13 @@ export function useDataTableManager<T extends object>(
);
}, []);
useEffect(
function applyFilter() {
dataSource.setFilter(currentFilter);
},
[currentFilter, dataSource],
);
return {
/** The default columns, but normalized */
columns,
@@ -103,6 +129,8 @@ export function useDataTableManager<T extends object>(
sortColumn,
/** Show / hide the given column */
toggleColumnVisibility,
/** Active search value */
setSearchValue,
};
}