From fb7c09c9729e325b0b9e71802033fe135db0cb0c Mon Sep 17 00:00:00 2001 From: Michel Weststrate Date: Tue, 16 Mar 2021 14:54:53 -0700 Subject: [PATCH] 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 --- .../src/ui/datatable/DataTable.tsx | 27 ++++++--- .../src/ui/datatable/TableHead.tsx | 7 ++- .../src/ui/datatable/TableRow.tsx | 8 +++ .../src/ui/datatable/TableSearch.tsx | 33 +++++++++++ .../ui/datatable/__tests__/DataTable.node.tsx | 55 +++++++++++++++++++ .../src/ui/datatable/useDataTableManager.tsx | 30 +++++++++- 6 files changed, 147 insertions(+), 13 deletions(-) create mode 100644 desktop/flipper-plugin/src/ui/datatable/TableSearch.tsx diff --git a/desktop/flipper-plugin/src/ui/datatable/DataTable.tsx b/desktop/flipper-plugin/src/ui/datatable/DataTable.tsx index 09947f1dc..fc114ab10 100644 --- a/desktop/flipper-plugin/src/ui/datatable/DataTable.tsx +++ b/desktop/flipper-plugin/src/ui/datatable/DataTable.tsx @@ -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 { columns: DataTableColumn[]; dataSource: DataSource; autoScroll?: boolean; + extraActions?: React.ReactElement; + // custom onSearch(text, row) option? tableManagerRef?: RefObject; _testHeight?: number; // exposed for unit testing only } @@ -58,15 +61,21 @@ export function DataTable(props: DataTableProps) { return ( - + + + + dataSource={props.dataSource} autoScroll={props.autoScroll} diff --git a/desktop/flipper-plugin/src/ui/datatable/TableHead.tsx b/desktop/flipper-plugin/src/ui/datatable/TableHead.tsx index fb5f41a9e..33cd95fbc 100644 --- a/desktop/flipper-plugin/src/ui/datatable/TableHead.tsx +++ b/desktop/flipper-plugin/src/ui/datatable/TableHead.tsx @@ -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({ ); -} +}); const SettingsButton = styled(Button)({ padding: 4, @@ -251,4 +251,5 @@ const SettingsButton = styled(Button)({ right: 0, top: 0, backgroundColor: theme.backgroundWash, + borderRadius: 0, }); diff --git a/desktop/flipper-plugin/src/ui/datatable/TableRow.tsx b/desktop/flipper-plugin/src/ui/datatable/TableRow.tsx index e63b751dd..5a53a72b5 100644 --- a/desktop/flipper-plugin/src/ui/datatable/TableRow.tsx +++ b/desktop/flipper-plugin/src/ui/datatable/TableRow.tsx @@ -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 ( + + {extraActions} + + ); +}); diff --git a/desktop/flipper-plugin/src/ui/datatable/__tests__/DataTable.node.tsx b/desktop/flipper-plugin/src/ui/datatable/__tests__/DataTable.node.tsx index 174f7e6e9..c286383aa 100644 --- a/desktop/flipper-plugin/src/ui/datatable/__tests__/DataTable.node.tsx +++ b/desktop/flipper-plugin/src/ui/datatable/__tests__/DataTable.node.tsx @@ -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(); + const rendering = render( + Test 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); + } +}); diff --git a/desktop/flipper-plugin/src/ui/datatable/useDataTableManager.tsx b/desktop/flipper-plugin/src/ui/datatable/useDataTableManager.tsx index acb272776..40c236aa0 100644 --- a/desktop/flipper-plugin/src/ui/datatable/useDataTableManager.tsx +++ b/desktop/flipper-plugin/src/ui/datatable/useDataTableManager.tsx @@ -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( computeInitialColumns(defaultColumns), ); const [sorting, setSorting] = useState(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( ); }, []); + useEffect( + function applyFilter() { + dataSource.setFilter(currentFilter); + }, + [currentFilter, dataSource], + ); + return { /** The default columns, but normalized */ columns, @@ -103,6 +129,8 @@ export function useDataTableManager( sortColumn, /** Show / hide the given column */ toggleColumnVisibility, + /** Active search value */ + setSearchValue, }; }