From 593ac96b216d8ef31b2e5fb548b760b577e3114f Mon Sep 17 00:00:00 2001 From: Aman Mahajan Date: Tue, 18 May 2021 18:49:54 -0500 Subject: [PATCH 01/15] Remove filter props --- src/DataGrid.tsx | 30 +----- src/FilterRow.tsx | 42 -------- src/hooks/useViewportColumns.ts | 13 +-- src/index.ts | 2 - src/style/header.ts | 22 +--- src/types.ts | 15 +-- stories/demos/HeaderFilters.tsx | 182 +++++++++++++++++++------------- 7 files changed, 117 insertions(+), 189 deletions(-) delete mode 100644 src/FilterRow.tsx diff --git a/src/DataGrid.tsx b/src/DataGrid.tsx index 3ec11f61de..dc7dc698b8 100644 --- a/src/DataGrid.tsx +++ b/src/DataGrid.tsx @@ -20,7 +20,6 @@ import { RowSelectionChangeProvider } from './hooks'; import HeaderRow from './HeaderRow'; -import FilterRow from './FilterRow'; import Row from './Row'; import GroupRowRenderer from './GroupRow'; import SummaryRow from './SummaryRow'; @@ -40,7 +39,6 @@ import { import type { CalculatedColumn, Column, - Filters, Position, RowRendererProps, RowsChangeData, @@ -116,8 +114,6 @@ export interface DataGridProps extends Sha rowHeight?: number | ((args: RowHeightArgs) => number) | null; /** The height of the header row in pixels */ headerRowHeight?: number | null; - /** The height of the header filter row in pixels */ - headerFiltersHeight?: number | null; /** The height of each summary row in pixels */ summaryRowHeight?: number | null; @@ -134,8 +130,6 @@ export interface DataGridProps extends Sha sortDirection?: SortDirection | null; /** Function called whenever grid is sorted*/ onSort?: ((columnKey: string, direction: SortDirection) => void) | null; - filters?: Readonly | null; - onFiltersChange?: ((filters: Filters) => void) | null; defaultColumnOptions?: DefaultColumnOptions | null; groupBy?: readonly string[] | null; rowGrouper?: ((rows: readonly R[], columnKey: string) => Record) | null; @@ -165,8 +159,6 @@ export interface DataGridProps extends Sha /** * Toggles and modes */ - /** Toggles whether filters row is displayed or not */ - enableFilterRow?: boolean | null; cellNavigationMode?: CellNavigationMode | null; enableVirtualization?: boolean | null; @@ -196,7 +188,6 @@ function DataGrid( // Dimensions props rowHeight, headerRowHeight, - headerFiltersHeight, summaryRowHeight: rawSummaryRowHeight, // Feature props selectedRows, @@ -204,8 +195,6 @@ function DataGrid( sortColumn, sortDirection, onSort, - filters, - onFiltersChange, defaultColumnOptions, groupBy: rawGroupBy, rowGrouper, @@ -222,7 +211,6 @@ function DataGrid( onFill, onPaste, // Toggles and modes - enableFilterRow, cellNavigationMode: rawCellNavigationMode, enableVirtualization, // Miscellaneous @@ -242,10 +230,8 @@ function DataGrid( */ rowHeight ??= 35; headerRowHeight ??= typeof rowHeight === 'number' ? rowHeight : 35; - headerFiltersHeight ??= 45; const summaryRowHeight = rawSummaryRowHeight ?? (typeof rowHeight === 'number' ? rowHeight : 35); const RowRenderer = rowRenderer ?? Row; - enableFilterRow ??= false; const cellNavigationMode = rawCellNavigationMode ?? 'NONE'; enableVirtualization ??= true; const editorPortalTarget = rawEditorPortalTarget ?? body; @@ -283,10 +269,9 @@ function DataGrid( * computed values */ const [gridRef, gridWidth, gridHeight] = useGridDimensions(); - const headerRowsCount = enableFilterRow ? 2 : 1; + const headerRowsCount = 1; const summaryRowsCount = summaryRows?.length ?? 0; - const totalHeaderHeight = headerRowHeight + (enableFilterRow ? headerFiltersHeight : 0); - const clientHeight = gridHeight - totalHeaderHeight - summaryRowsCount * summaryRowHeight; + const clientHeight = gridHeight - headerRowHeight - summaryRowsCount * summaryRowHeight; const isSelectable = selectedRows != null && onSelectedRowsChange != null; const allRowsSelected = useMemo((): boolean => { @@ -353,7 +338,6 @@ function DataGrid( rowOverscanEndIdx, rows, summaryRows, - enableFilterRow, isGroupRow }); @@ -965,7 +949,7 @@ function DataGrid( let startRowIndex = 0; for (let rowIdx = rowOverscanStartIdx; rowIdx <= rowOverscanEndIdx; rowIdx++) { const row = rows[rowIdx]; - const top = getRowTop(rowIdx) + totalHeaderHeight; + const top = getRowTop(rowIdx) + headerRowHeight!; if (isGroupRow(row)) { ({ startRowIndex } = row); const isGroupRowSelected = @@ -1067,7 +1051,6 @@ function DataGrid( { ...style, '--header-row-height': `${headerRowHeight}px`, - '--filter-row-height': `${headerFiltersHeight}px`, '--row-width': `${totalColumnWidth}px`, '--summary-row-height': `${summaryRowHeight}px`, ...layoutCssVars @@ -1088,13 +1071,6 @@ function DataGrid( onSort={onSort} lastFrozenColumnIndex={lastFrozenColumnIndex} /> - {enableFilterRow && ( - - columns={viewportColumns} - filters={filters} - onFiltersChange={onFiltersChange} - /> - )} {rows.length === 0 && EmptyRowsRenderer ? ( ) : ( diff --git a/src/FilterRow.tsx b/src/FilterRow.tsx deleted file mode 100644 index 2510cc62eb..0000000000 --- a/src/FilterRow.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { memo } from 'react'; - -import { getCellStyle, getCellClassname } from './utils'; -import type { CalculatedColumn, Filters } from './types'; -import type { DataGridProps } from './DataGrid'; -import { filterRowClassname } from './style'; - -type SharedDataGridProps = Pick, 'filters' | 'onFiltersChange'>; - -interface FilterRowProps extends SharedDataGridProps { - columns: readonly CalculatedColumn[]; -} - -function FilterRow({ columns, filters, onFiltersChange }: FilterRowProps) { - function onChange(key: string, value: unknown) { - const newFilters: Filters = { ...filters }; - newFilters[key] = value; - onFiltersChange?.(newFilters); - } - - return ( -
- {columns.map((column) => { - const { key } = column; - - return ( -
- {column.filterRenderer && ( - onChange(key, value)} - /> - )} -
- ); - })} -
- ); -} - -export default memo(FilterRow) as (props: FilterRowProps) => JSX.Element; diff --git a/src/hooks/useViewportColumns.ts b/src/hooks/useViewportColumns.ts index 51da6316a9..da73ef83e8 100644 --- a/src/hooks/useViewportColumns.ts +++ b/src/hooks/useViewportColumns.ts @@ -13,7 +13,6 @@ interface ViewportColumnsArgs { lastFrozenColumnIndex: number; rowOverscanStartIdx: number; rowOverscanEndIdx: number; - enableFilterRow: boolean; isGroupRow: (row: R | GroupRow) => row is GroupRow; } @@ -27,7 +26,6 @@ export function useViewportColumns({ lastFrozenColumnIndex, rowOverscanStartIdx, rowOverscanEndIdx, - enableFilterRow, isGroupRow }: ViewportColumnsArgs) { // find the column that spans over a column within the visible columns range and adjust colOverscanStartIdx @@ -52,14 +50,6 @@ export function useViewportColumns({ break; } - // check filter row - if ( - enableFilterRow && - updateStartIdx(colIdx, getColSpan(column, lastFrozenColumnIndex, { type: 'FILTER' })) - ) { - break; - } - // check viewport rows for (let rowIdx = rowOverscanStartIdx; rowIdx <= rowOverscanEndIdx; rowIdx++) { const row = rows[rowIdx]; @@ -95,8 +85,7 @@ export function useViewportColumns({ colOverscanStartIdx, lastFrozenColumnIndex, colSpanColumns, - isGroupRow, - enableFilterRow + isGroupRow ]); return useMemo((): readonly CalculatedColumn[] => { diff --git a/src/index.ts b/src/index.ts index 7ccf4c9298..51bed63cc9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -17,8 +17,6 @@ export type { HeaderRendererProps, CellRendererProps, RowRendererProps, - FilterRendererProps, - Filters, RowsChangeData, SelectRowEvent, FillEvent, diff --git a/src/style/header.ts b/src/style/header.ts index a2d632a19a..9e886da577 100644 --- a/src/style/header.ts +++ b/src/style/header.ts @@ -1,32 +1,20 @@ import { css } from '@linaria/core'; -const headerRowAndFilterRow = css` +const headerRow = css` contain: strict; contain: size layout style paint; display: grid; grid-template-columns: var(--template-columns); + grid-template-rows: var(--header-row-height); + height: var(--header-row-height); // needed on Firefox + line-height: var(--header-row-height); width: var(--row-width); position: sticky; background-color: var(--header-background-color); font-weight: bold; z-index: 3; -`; - -const headerRow = css` - grid-template-rows: var(--header-row-height); - height: var(--header-row-height); // needed on Firefox - line-height: var(--header-row-height); top: 0; touch-action: none; `; -export const headerRowClassname = `rdg-header-row ${headerRowAndFilterRow} ${headerRow}`; - -const filterRow = css` - grid-template-rows: var(--filter-row-height); - height: var(--filter-row-height); // needed on Firefox - line-height: var(--filter-row-height); - top: var(--header-row-height); -`; - -export const filterRowClassname = `rdg-filter-row ${headerRowAndFilterRow} ${filterRow}`; +export const headerRowClassname = `rdg-header-row ${headerRow}`; diff --git a/src/types.ts b/src/types.ts index 0d4646dfd9..8e61d63fd6 100644 --- a/src/types.ts +++ b/src/types.ts @@ -52,8 +52,6 @@ export interface Column { } | null; /** Header renderer for each header cell */ headerRenderer?: React.ComponentType> | null; - /** Component to be used to filter the data of the column */ - filterRenderer?: React.ComponentType> | null; } export interface CalculatedColumn extends Column { @@ -186,14 +184,6 @@ export interface RowRendererProps selectCell: SelectCellFn; } -export interface FilterRendererProps { - column: CalculatedColumn; - value: TFilterValue; - onChange: (value: TFilterValue) => void; -} - -export type Filters = Record; - export interface RowsChangeData { indexes: number[]; column: CalculatedColumn; @@ -242,9 +232,6 @@ export interface GroupRow { export type CellNavigationMode = 'NONE' | 'CHANGE_ROW' | 'LOOP_OVER_ROW'; export type SortDirection = 'ASC' | 'DESC' | 'NONE'; -export type ColSpanArgs = - | { type: 'HEADER' | 'FILTER' } - | { type: 'ROW'; row: R } - | { type: 'SUMMARY'; row: SR }; +export type ColSpanArgs = { type: 'ROW'; row: R } | { type: 'SUMMARY'; row: SR }; export type RowHeightArgs = { type: 'ROW'; row: R } | { type: 'GROUP'; row: GroupRow }; diff --git a/stories/demos/HeaderFilters.tsx b/stories/demos/HeaderFilters.tsx index 057db4a3f0..70b2376179 100644 --- a/stories/demos/HeaderFilters.tsx +++ b/stories/demos/HeaderFilters.tsx @@ -4,7 +4,7 @@ import faker from 'faker'; import { css } from '@linaria/core'; import DataGrid from '../../src'; -import type { Column, Filters } from '../../src'; +import type { Column } from '../../src'; import { NumericFilter } from './components/Filters'; const rootClassname = css` @@ -23,9 +23,16 @@ const toolbarClassname = css` `; const filterContainerClassname = css` - display: flex; - height: inherit; - align-items: center; + line-height: 35px; + padding: 0; + + > div { + padding: 0 8px; + + &::first-child { + border-bottom: 1px solid var(--border-color); + } + } `; const filterClassname = css` @@ -60,7 +67,7 @@ function createRows() { export function HeaderFilters() { const [rows] = useState(createRows); - const [filters, setFilters] = useState({ + const [filters, setFilters] = useState({ task: '', priority: 'Critical', issueType: 'All', @@ -84,83 +91,114 @@ export function HeaderFilters() { { key: 'task', name: 'Title', - filterRenderer: (p) => ( -
- p.onChange(e.target.value)} - /> -
+ headerCellClass: filterContainerClassname, + headerRenderer: ({ column }) => ( + <> +
{column.name}
+
+ + setFilters({ + ...filters, + task: e.target.value + }) + } + /> +
+ ) }, { key: 'priority', name: 'Priority', - filterRenderer: (p) => ( -
- -
+ headerCellClass: filterContainerClassname, + headerRenderer: ({ column }) => ( + <> +
{column.name}
+
+ +
+ ) }, { key: 'issueType', name: 'Issue Type', - filterRenderer: (p) => ( -
- -
+ headerCellClass: filterContainerClassname, + headerRenderer: ({ column }) => ( + <> +
{column.name}
+
+ +
+ ) }, { key: 'developer', name: 'Developer', - filterRenderer: (p) => ( -
- ({ + ...provided, + padding: 10 + }), + control: (provided) => ({ + ...provided, + height: 30, + minHeight: 30, + padding: 0, + lineHeight: 'normal' + }), + container: (provided) => ({ + ...provided, + width: '100%' + }) + }} + menuPortalTarget={document.body} + /> +
+ ) }, { @@ -169,7 +207,7 @@ export function HeaderFilters() { filterRenderer: NumericFilter } ]; - }, [rows]); + }, [filters, rows]); const filteredRows = useMemo(() => { return rows.filter((r) => { @@ -207,13 +245,7 @@ export function HeaderFilters() { Clear Filters - + ); } From 62b2e41b5f899944edc3e5690931b95a2c2b8ab1 Mon Sep 17 00:00:00 2001 From: Aman Mahajan Date: Tue, 18 May 2021 19:04:09 -0500 Subject: [PATCH 02/15] Fix ColSpanArgs type --- src/types.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/types.ts b/src/types.ts index 8e61d63fd6..fabde7f730 100644 --- a/src/types.ts +++ b/src/types.ts @@ -232,6 +232,9 @@ export interface GroupRow { export type CellNavigationMode = 'NONE' | 'CHANGE_ROW' | 'LOOP_OVER_ROW'; export type SortDirection = 'ASC' | 'DESC' | 'NONE'; -export type ColSpanArgs = { type: 'ROW'; row: R } | { type: 'SUMMARY'; row: SR }; +export type ColSpanArgs = + | { type: 'HEADER' } + | { type: 'ROW'; row: R } + | { type: 'SUMMARY'; row: SR }; export type RowHeightArgs = { type: 'ROW'; row: R } | { type: 'GROUP'; row: GroupRow }; From daf8ceb43ed8797f698a07667d4afc35f068c77a Mon Sep 17 00:00:00 2001 From: Aman Mahajan Date: Wed, 19 May 2021 07:48:28 -0500 Subject: [PATCH 03/15] Remove Numeric filter, fix focus --- stories/demos/HeaderFilters.tsx | 150 +++++++++++------- .../components/Filters/NumericFilter.tsx | 133 ---------------- stories/demos/components/Filters/index.ts | 1 - 3 files changed, 90 insertions(+), 194 deletions(-) delete mode 100644 stories/demos/components/Filters/NumericFilter.tsx delete mode 100644 stories/demos/components/Filters/index.ts diff --git a/stories/demos/HeaderFilters.tsx b/stories/demos/HeaderFilters.tsx index 70b2376179..631099bcf9 100644 --- a/stories/demos/HeaderFilters.tsx +++ b/stories/demos/HeaderFilters.tsx @@ -1,11 +1,10 @@ -import { useMemo, useState } from 'react'; -import Select from 'react-select'; +import { createContext, useContext, useMemo, useState } from 'react'; import faker from 'faker'; import { css } from '@linaria/core'; import DataGrid from '../../src'; import type { Column } from '../../src'; -import { NumericFilter } from './components/Filters'; +import type { HeaderRendererProps, Omit } from '../../src/types'; const rootClassname = css` display: flex; @@ -29,7 +28,7 @@ const filterContainerClassname = css` > div { padding: 0 8px; - &::first-child { + &:first-child { border-bottom: 1px solid var(--border-color); } } @@ -50,6 +49,14 @@ interface Row { complete: number; } +interface Filter extends Omit { + complete?: number; +} + +// Context is needed to read filter values otherwise columns are +// re-created when filters are changed and filter looses focus +const FilterContext = createContext(undefined); + function createRows() { const rows: Row[] = []; for (let i = 1; i < 500; i++) { @@ -67,12 +74,12 @@ function createRows() { export function HeaderFilters() { const [rows] = useState(createRows); - const [filters, setFilters] = useState({ + const [filters, setFilters] = useState({ task: '', priority: 'Critical', issueType: 'All', developer: '', - complete: '' + complete: undefined }); const [enableFilterRow, setEnableFilterRow] = useState(true); @@ -92,10 +99,9 @@ export function HeaderFilters() { key: 'task', name: 'Title', headerCellClass: filterContainerClassname, - headerRenderer: ({ column }) => ( - <> -
{column.name}
-
+ headerRenderer: (p) => ( + + {(filters) => ( -
- + )} + ) }, { key: 'priority', name: 'Priority', headerCellClass: filterContainerClassname, - headerRenderer: ({ column }) => ( - <> -
{column.name}
-
+ headerRenderer: (p) => ( + + {(filters) => ( -
- + )} + ) }, { key: 'issueType', name: 'Issue Type', headerCellClass: filterContainerClassname, - headerRenderer: ({ column }) => ( - <> -
{column.name}
-
+ headerRenderer: (p) => ( + + {(filters) => ( -
- + )} + ) }, { key: 'developer', name: 'Developer', headerCellClass: filterContainerClassname, - headerRenderer: ({ column }) => ( - <> -
{column.name}
-
- + setFilters({ + ...filters, + developer: e.target.value + }) + } + list="developers" + /> + + {developerOptions.map(({ label, value }) => ( + + ))} + + + )} + ) }, { key: 'complete', name: '% Complete', - filterRenderer: NumericFilter + headerCellClass: filterContainerClassname, + headerRenderer: (p) => ( + + {(filters) => ( + + setFilters({ + ...filters, + complete: Number.isFinite(e.target.valueAsNumber) + ? e.target.valueAsNumber + : undefined + }) + } + /> + )} + + ) } ]; - }, [filters, rows]); + }, [rows]); const filteredRows = useMemo(() => { return rows.filter((r) => { @@ -215,8 +232,8 @@ export function HeaderFilters() { (filters.task ? r.task.includes(filters.task) : true) && (filters.priority !== 'All' ? r.priority === filters.priority : true) && (filters.issueType !== 'All' ? r.issueType === filters.issueType : true) && - (filters.developer ? r.developer === filters.developer.value : true) && - (filters.complete ? filters.complete.filterValues(r, filters.complete, 'complete') : true) + (filters.developer ? r.developer.startsWith(filters.developer) : true) && + (filters.complete !== undefined ? r.complete >= filters.complete : true) ); }); }, [rows, filters]); @@ -227,7 +244,7 @@ export function HeaderFilters() { priority: 'All', issueType: 'All', developer: '', - complete: '' + complete: undefined }); } @@ -245,9 +262,22 @@ export function HeaderFilters() { Clear Filters
- + + + ); } -HeaderFilters.storyName = 'Header Filters'; +function FilterRenderer({ + column, + children +}: HeaderRendererProps & { children: (filters: Filter) => React.ReactElement }) { + const filters = useContext(FilterContext)!; + return ( + <> +
{column.name}
+
{children(filters)}
+ + ); +} diff --git a/stories/demos/components/Filters/NumericFilter.tsx b/stories/demos/components/Filters/NumericFilter.tsx deleted file mode 100644 index ffafb4e232..0000000000 --- a/stories/demos/components/Filters/NumericFilter.tsx +++ /dev/null @@ -1,133 +0,0 @@ -import type { Column, FilterRendererProps } from '../../../../src'; - -enum RuleType { - number = 1, - range = 2, - greaterThan = 3, - lessThan = 4 -} - -type Rule = - | { type: RuleType.range; begin: number; end: number } - | { type: RuleType.greaterThan | RuleType.lessThan | RuleType.number; value: number }; - -interface ChangeEvent { - filterTerm: Rule[] | null; - column: Column; - rawValue: string; - filterValues: typeof filterValues; -} - -export function NumericFilter({ - value, - column, - onChange -}: FilterRendererProps, SR>) { - /** Validates the input */ - function handleKeyDown(event: React.KeyboardEvent) { - const result = /[><,0-9-]/.test(event.key); - if (!result) { - event.preventDefault(); - } - } - - function handleChange(event: React.ChangeEvent) { - const { value } = event.target; - const filters = getRules(value); - onChange({ - filterTerm: filters.length > 0 ? filters : null, - column, - rawValue: value, - filterValues - }); - } - - const tooltipText = 'Input Methods: Range (x-y), Greater Than (>x), Less Than ( - - - ? - - - ); -} - -function filterValues( - row: R, - columnFilter: { filterTerm: { [key in string]: Rule } }, - columnKey: keyof R -) { - // implement default filter logic - const value = parseInt(row[columnKey] as unknown as string, 10); - for (const ruleKey in columnFilter.filterTerm) { - const rule = columnFilter.filterTerm[ruleKey]; - - switch (rule.type) { - case RuleType.number: - if (rule.value === value) { - return true; - } - break; - case RuleType.greaterThan: - if (rule.value <= value) { - return true; - } - break; - case RuleType.lessThan: - if (rule.value >= value) { - return true; - } - break; - case RuleType.range: - if (rule.begin <= value && rule.end >= value) { - return true; - } - break; - default: - break; - } - } - - return false; -} - -function getRules(value: string): Rule[] { - if (value === '') { - return []; - } - - // handle each value with comma - return value.split(',').map((str): Rule => { - // handle dash - const dashIdx = str.indexOf('-'); - if (dashIdx > 0) { - const begin = parseInt(str.slice(0, dashIdx), 10); - const end = parseInt(str.slice(dashIdx + 1), 10); - return { type: RuleType.range, begin, end }; - } - - // handle greater then - if (str.includes('>')) { - const begin = parseInt(str.slice(str.indexOf('>') + 1), 10); - return { type: RuleType.greaterThan, value: begin }; - } - - // handle less then - if (str.includes('<')) { - const end = parseInt(str.slice(str.indexOf('<') + 1), 10); - return { type: RuleType.lessThan, value: end }; - } - - // handle normal values - const numericValue = parseInt(str, 10); - return { type: RuleType.number, value: numericValue }; - }); -} diff --git a/stories/demos/components/Filters/index.ts b/stories/demos/components/Filters/index.ts deleted file mode 100644 index 9eaf3e9fcf..0000000000 --- a/stories/demos/components/Filters/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './NumericFilter'; From ed909a0a7fd7ee909a299e019d5cf1af69391b0f Mon Sep 17 00:00:00 2001 From: Aman Mahajan Date: Wed, 19 May 2021 07:56:00 -0500 Subject: [PATCH 04/15] Fix toggle filter --- stories/demos/HeaderFilters.tsx | 239 +++++++++++++++++--------------- 1 file changed, 128 insertions(+), 111 deletions(-) diff --git a/stories/demos/HeaderFilters.tsx b/stories/demos/HeaderFilters.tsx index 631099bcf9..c474770a2b 100644 --- a/stories/demos/HeaderFilters.tsx +++ b/stories/demos/HeaderFilters.tsx @@ -84,6 +84,7 @@ export function HeaderFilters() { const [enableFilterRow, setEnableFilterRow] = useState(true); const columns = useMemo((): readonly Column[] => { + const headerCellClass = enableFilterRow ? filterContainerClassname : undefined; const developerOptions = Array.from(new Set(rows.map((r) => r.developer))).map((d) => ({ label: d, value: d @@ -98,133 +99,143 @@ export function HeaderFilters() { { key: 'task', name: 'Title', - headerCellClass: filterContainerClassname, - headerRenderer: (p) => ( - - {(filters) => ( - - setFilters({ - ...filters, - task: e.target.value - }) - } - /> - )} - - ) + headerCellClass, + headerRenderer: enableFilterRow + ? (p) => ( + + {(filters) => ( + + setFilters({ + ...filters, + task: e.target.value + }) + } + /> + )} + + ) + : undefined }, { key: 'priority', name: 'Priority', - headerCellClass: filterContainerClassname, - headerRenderer: (p) => ( - - {(filters) => ( - - )} - - ) + headerCellClass, + headerRenderer: enableFilterRow + ? (p) => ( + + {(filters) => ( + + )} + + ) + : undefined }, { key: 'issueType', name: 'Issue Type', - headerCellClass: filterContainerClassname, - headerRenderer: (p) => ( - - {(filters) => ( - - )} - - ) + headerCellClass, + headerRenderer: enableFilterRow + ? (p) => ( + + {(filters) => ( + + )} + + ) + : undefined }, { key: 'developer', name: 'Developer', - headerCellClass: filterContainerClassname, - headerRenderer: (p) => ( - - {(filters) => ( - <> - - setFilters({ - ...filters, - developer: e.target.value - }) - } - list="developers" - /> - - {developerOptions.map(({ label, value }) => ( - - ))} - - - )} - - ) + headerCellClass, + headerRenderer: enableFilterRow + ? (p) => ( + + {(filters) => ( + <> + + setFilters({ + ...filters, + developer: e.target.value + }) + } + list="developers" + /> + + {developerOptions.map(({ label, value }) => ( + + ))} + + + )} + + ) + : undefined }, { key: 'complete', name: '% Complete', - headerCellClass: filterContainerClassname, - headerRenderer: (p) => ( - - {(filters) => ( - - setFilters({ - ...filters, - complete: Number.isFinite(e.target.valueAsNumber) - ? e.target.valueAsNumber - : undefined - }) - } - /> - )} - - ) + headerCellClass, + headerRenderer: enableFilterRow + ? (p) => ( + + {(filters) => ( + + setFilters({ + ...filters, + complete: Number.isFinite(e.target.valueAsNumber) + ? e.target.valueAsNumber + : undefined + }) + } + /> + )} + + ) + : undefined } ]; - }, [rows]); + }, [enableFilterRow, rows]); const filteredRows = useMemo(() => { return rows.filter((r) => { @@ -232,7 +243,9 @@ export function HeaderFilters() { (filters.task ? r.task.includes(filters.task) : true) && (filters.priority !== 'All' ? r.priority === filters.priority : true) && (filters.issueType !== 'All' ? r.issueType === filters.issueType : true) && - (filters.developer ? r.developer.startsWith(filters.developer) : true) && + (filters.developer + ? r.developer.toLowerCase().startsWith(filters.developer.toLowerCase()) + : true) && (filters.complete !== undefined ? r.complete >= filters.complete : true) ); }); @@ -263,7 +276,11 @@ export function HeaderFilters() { - + ); From 723f4bc4eb2982bb1afda8b8cfeeae13e2c4289f Mon Sep 17 00:00:00 2001 From: Aman Mahajan Date: Wed, 19 May 2021 08:36:09 -0500 Subject: [PATCH 05/15] Update src/DataGrid.tsx Co-authored-by: Nicolas Stepien <567105+nstepien@users.noreply.github.com> --- src/DataGrid.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DataGrid.tsx b/src/DataGrid.tsx index dc7dc698b8..82c74f74bd 100644 --- a/src/DataGrid.tsx +++ b/src/DataGrid.tsx @@ -187,7 +187,7 @@ function DataGrid( onRowsChange, // Dimensions props rowHeight, - headerRowHeight, + headerRowHeight: rawHeaderRowHeight, summaryRowHeight: rawSummaryRowHeight, // Feature props selectedRows, From 4f2a8e602dc265101611d1e4cc89867684c36d88 Mon Sep 17 00:00:00 2001 From: Aman Mahajan Date: Wed, 19 May 2021 08:36:16 -0500 Subject: [PATCH 06/15] Update src/DataGrid.tsx Co-authored-by: Nicolas Stepien <567105+nstepien@users.noreply.github.com> --- src/DataGrid.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DataGrid.tsx b/src/DataGrid.tsx index 82c74f74bd..1310e17be3 100644 --- a/src/DataGrid.tsx +++ b/src/DataGrid.tsx @@ -229,7 +229,7 @@ function DataGrid( * defaults */ rowHeight ??= 35; - headerRowHeight ??= typeof rowHeight === 'number' ? rowHeight : 35; + const headerRowHeight = rawHeaderRowHeight ?? (typeof rowHeight === 'number' ? rowHeight : 35); const summaryRowHeight = rawSummaryRowHeight ?? (typeof rowHeight === 'number' ? rowHeight : 35); const RowRenderer = rowRenderer ?? Row; const cellNavigationMode = rawCellNavigationMode ?? 'NONE'; From e8bdb29f93c6d5a8f9f92b3ae6c42a358f841a2a Mon Sep 17 00:00:00 2001 From: Aman Mahajan Date: Wed, 19 May 2021 08:36:22 -0500 Subject: [PATCH 07/15] Update src/DataGrid.tsx Co-authored-by: Nicolas Stepien <567105+nstepien@users.noreply.github.com> --- src/DataGrid.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DataGrid.tsx b/src/DataGrid.tsx index 1310e17be3..7adcfbd802 100644 --- a/src/DataGrid.tsx +++ b/src/DataGrid.tsx @@ -949,7 +949,7 @@ function DataGrid( let startRowIndex = 0; for (let rowIdx = rowOverscanStartIdx; rowIdx <= rowOverscanEndIdx; rowIdx++) { const row = rows[rowIdx]; - const top = getRowTop(rowIdx) + headerRowHeight!; + const top = getRowTop(rowIdx) + headerRowHeight; if (isGroupRow(row)) { ({ startRowIndex } = row); const isGroupRowSelected = From a37320b0b3a34d0387174ce7f3b0bc7696b547e0 Mon Sep 17 00:00:00 2001 From: Aman Mahajan Date: Wed, 19 May 2021 08:36:30 -0500 Subject: [PATCH 08/15] Update stories/demos/HeaderFilters.tsx Co-authored-by: Nicolas Stepien <567105+nstepien@users.noreply.github.com> --- stories/demos/HeaderFilters.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stories/demos/HeaderFilters.tsx b/stories/demos/HeaderFilters.tsx index c474770a2b..44257271bf 100644 --- a/stories/demos/HeaderFilters.tsx +++ b/stories/demos/HeaderFilters.tsx @@ -54,7 +54,7 @@ interface Filter extends Omit { } // Context is needed to read filter values otherwise columns are -// re-created when filters are changed and filter looses focus +// re-created when filters are changed and filter loses focus const FilterContext = createContext(undefined); function createRows() { From 1ffb617016d580cdb89f2a2e49d908ea26d43625 Mon Sep 17 00:00:00 2001 From: Aman Mahajan Date: Wed, 19 May 2021 08:36:42 -0500 Subject: [PATCH 09/15] Update stories/demos/HeaderFilters.tsx Co-authored-by: Nicolas Stepien <567105+nstepien@users.noreply.github.com> --- stories/demos/HeaderFilters.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stories/demos/HeaderFilters.tsx b/stories/demos/HeaderFilters.tsx index 44257271bf..1281c7b2b8 100644 --- a/stories/demos/HeaderFilters.tsx +++ b/stories/demos/HeaderFilters.tsx @@ -199,7 +199,7 @@ export function HeaderFilters() { /> {developerOptions.map(({ label, value }) => ( - + ))} From 76ef74880c20e38a2700add1c20b631ccae99f5e Mon Sep 17 00:00:00 2001 From: Aman Mahajan Date: Wed, 19 May 2021 08:51:26 -0500 Subject: [PATCH 10/15] Address comments --- src/style/header.ts | 2 +- stories/demos/HeaderFilters.tsx | 263 ++++++++++++++++---------------- 2 files changed, 131 insertions(+), 134 deletions(-) diff --git a/src/style/header.ts b/src/style/header.ts index 9e886da577..37d6298c8f 100644 --- a/src/style/header.ts +++ b/src/style/header.ts @@ -10,10 +10,10 @@ const headerRow = css` line-height: var(--header-row-height); width: var(--row-width); position: sticky; + top: 0; background-color: var(--header-background-color); font-weight: bold; z-index: 3; - top: 0; touch-action: none; `; diff --git a/stories/demos/HeaderFilters.tsx b/stories/demos/HeaderFilters.tsx index 1281c7b2b8..87135afbc6 100644 --- a/stories/demos/HeaderFilters.tsx +++ b/stories/demos/HeaderFilters.tsx @@ -22,14 +22,16 @@ const toolbarClassname = css` `; const filterContainerClassname = css` - line-height: 35px; - padding: 0; + .rdg-header-row .rdg-cell:not(:first-child) { + line-height: 35px; + padding: 0; - > div { - padding: 0 8px; + > div { + padding: 0 8px; - &:first-child { - border-bottom: 1px solid var(--border-color); + &:first-child { + border-bottom: 1px solid var(--border-color); + } } } `; @@ -83,13 +85,16 @@ export function HeaderFilters() { }); const [enableFilterRow, setEnableFilterRow] = useState(true); - const columns = useMemo((): readonly Column[] => { - const headerCellClass = enableFilterRow ? filterContainerClassname : undefined; - const developerOptions = Array.from(new Set(rows.map((r) => r.developer))).map((d) => ({ - label: d, - value: d - })); + const developerOptions = useMemo( + () => + Array.from(new Set(rows.map((r) => r.developer))).map((d) => ({ + label: d, + value: d + })), + [rows] + ); + const columns = useMemo((): readonly Column[] => { return [ { key: 'id', @@ -99,143 +104,123 @@ export function HeaderFilters() { { key: 'task', name: 'Title', - headerCellClass, - headerRenderer: enableFilterRow - ? (p) => ( - - {(filters) => ( - - setFilters({ - ...filters, - task: e.target.value - }) - } - /> - )} - - ) - : undefined + headerRenderer: (p) => ( + + {(filters) => ( + + setFilters({ + ...filters, + task: e.target.value + }) + } + /> + )} + + ) }, { key: 'priority', name: 'Priority', - headerCellClass, - headerRenderer: enableFilterRow - ? (p) => ( - - {(filters) => ( - - )} - - ) - : undefined + headerRenderer: (p) => ( + + {(filters) => ( + + )} + + ) }, { key: 'issueType', name: 'Issue Type', - headerCellClass, - headerRenderer: enableFilterRow - ? (p) => ( - - {(filters) => ( - - )} - - ) - : undefined + headerRenderer: (p) => ( + + {(filters) => ( + + )} + + ) }, { key: 'developer', name: 'Developer', - headerCellClass, - headerRenderer: enableFilterRow - ? (p) => ( - - {(filters) => ( - <> - - setFilters({ - ...filters, - developer: e.target.value - }) - } - list="developers" - /> - - {developerOptions.map(({ label, value }) => ( - - ))} - - - )} - - ) - : undefined + headerRenderer: (p) => ( + + {(filters) => ( + <> + + setFilters({ + ...filters, + developer: e.target.value + }) + } + list="developers" + /> + + )} + + ) }, { key: 'complete', name: '% Complete', - headerCellClass, - headerRenderer: enableFilterRow - ? (p) => ( - - {(filters) => ( - - setFilters({ - ...filters, - complete: Number.isFinite(e.target.valueAsNumber) - ? e.target.valueAsNumber - : undefined - }) - } - /> - )} - - ) - : undefined + headerRenderer: (p) => ( + + {(filters) => ( + + setFilters({ + ...filters, + complete: Number.isFinite(e.target.valueAsNumber) + ? e.target.valueAsNumber + : undefined + }) + } + /> + )} + + ) } ]; - }, [enableFilterRow, rows]); + }, [enableFilterRow]); const filteredRows = useMemo(() => { return rows.filter((r) => { @@ -277,10 +262,18 @@ export function HeaderFilters() { + + {developerOptions.map(({ label, value }) => ( + + ))} + ); @@ -288,13 +281,17 @@ export function HeaderFilters() { function FilterRenderer({ column, + enableFilterRow, children -}: HeaderRendererProps & { children: (filters: Filter) => React.ReactElement }) { +}: HeaderRendererProps & { + enableFilterRow: boolean; + children: (filters: Filter) => React.ReactElement; +}) { const filters = useContext(FilterContext)!; return ( <>
{column.name}
-
{children(filters)}
+ {enableFilterRow &&
{children(filters)}
} ); } From 969574486d7d08be2a508754737bc2924d77c1dc Mon Sep 17 00:00:00 2001 From: Aman Mahajan Date: Wed, 19 May 2021 08:57:13 -0500 Subject: [PATCH 11/15] Add filters.enabled flag --- stories/demos/HeaderFilters.tsx | 63 +++++++++++++++++---------------- 1 file changed, 33 insertions(+), 30 deletions(-) diff --git a/stories/demos/HeaderFilters.tsx b/stories/demos/HeaderFilters.tsx index 87135afbc6..e930cb8300 100644 --- a/stories/demos/HeaderFilters.tsx +++ b/stories/demos/HeaderFilters.tsx @@ -53,27 +53,13 @@ interface Row { interface Filter extends Omit { complete?: number; + enabled: boolean; } // Context is needed to read filter values otherwise columns are // re-created when filters are changed and filter loses focus const FilterContext = createContext(undefined); -function createRows() { - const rows: Row[] = []; - for (let i = 1; i < 500; i++) { - rows.push({ - id: i, - task: `Task ${i}`, - complete: Math.min(100, Math.round(Math.random() * 110)), - priority: ['Critical', 'High', 'Medium', 'Low'][Math.floor(Math.random() * 4)], - issueType: ['Bug', 'Improvement', 'Epic', 'Story'][Math.floor(Math.random() * 4)], - developer: faker.name.findName() - }); - } - return rows; -} - export function HeaderFilters() { const [rows] = useState(createRows); const [filters, setFilters] = useState({ @@ -81,9 +67,9 @@ export function HeaderFilters() { priority: 'Critical', issueType: 'All', developer: '', - complete: undefined + complete: undefined, + enabled: true }); - const [enableFilterRow, setEnableFilterRow] = useState(true); const developerOptions = useMemo( () => @@ -105,7 +91,7 @@ export function HeaderFilters() { key: 'task', name: 'Title', headerRenderer: (p) => ( - + {(filters) => ( ( - + {(filters) => ( ( - + {(filters) => ( <> ( - + {(filters) => ( { return rows.filter((r) => { @@ -242,12 +228,16 @@ export function HeaderFilters() { priority: 'All', issueType: 'All', developer: '', - complete: undefined + complete: undefined, + enabled: true }); } function toggleFilters() { - setEnableFilterRow(!enableFilterRow); + setFilters({ + ...filters, + enabled: !filters.enabled + }); } return ( @@ -262,10 +252,10 @@ export function HeaderFilters() { {developerOptions.map(({ label, value }) => ( @@ -281,17 +271,30 @@ export function HeaderFilters() { function FilterRenderer({ column, - enableFilterRow, children }: HeaderRendererProps & { - enableFilterRow: boolean; children: (filters: Filter) => React.ReactElement; }) { const filters = useContext(FilterContext)!; return ( <>
{column.name}
- {enableFilterRow &&
{children(filters)}
} + {filters.enabled &&
{children(filters)}
} ); } + +function createRows() { + const rows: Row[] = []; + for (let i = 1; i < 500; i++) { + rows.push({ + id: i, + task: `Task ${i}`, + complete: Math.min(100, Math.round(Math.random() * 110)), + priority: ['Critical', 'High', 'Medium', 'Low'][Math.floor(Math.random() * 4)], + issueType: ['Bug', 'Improvement', 'Epic', 'Story'][Math.floor(Math.random() * 4)], + developer: faker.name.findName() + }); + } + return rows; +} From 6c030a610200b49ba797bab1e56d18727efce39e Mon Sep 17 00:00:00 2001 From: Aman Mahajan Date: Wed, 19 May 2021 09:19:27 -0500 Subject: [PATCH 12/15] Update stories/demos/HeaderFilters.tsx Co-authored-by: Nicolas Stepien <567105+nstepien@users.noreply.github.com> --- stories/demos/HeaderFilters.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stories/demos/HeaderFilters.tsx b/stories/demos/HeaderFilters.tsx index e930cb8300..ea435071ac 100644 --- a/stories/demos/HeaderFilters.tsx +++ b/stories/demos/HeaderFilters.tsx @@ -234,10 +234,10 @@ export function HeaderFilters() { } function toggleFilters() { - setFilters({ + setFilters(filters => ({ ...filters, enabled: !filters.enabled - }); + })); } return ( From 4bdc40877bc57a8c8218b50eee6c5422e0ed4b1a Mon Sep 17 00:00:00 2001 From: Aman Mahajan Date: Wed, 19 May 2021 09:21:37 -0500 Subject: [PATCH 13/15] Move datalist outside the provider. --- stories/demos/HeaderFilters.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/stories/demos/HeaderFilters.tsx b/stories/demos/HeaderFilters.tsx index ea435071ac..2c10d55eea 100644 --- a/stories/demos/HeaderFilters.tsx +++ b/stories/demos/HeaderFilters.tsx @@ -234,7 +234,7 @@ export function HeaderFilters() { } function toggleFilters() { - setFilters(filters => ({ + setFilters((filters) => ({ ...filters, enabled: !filters.enabled })); @@ -257,14 +257,14 @@ export function HeaderFilters() { rows={filteredRows} headerRowHeight={filters.enabled ? 70 : undefined} /> - - {developerOptions.map(({ label, value }) => ( - - ))} -
+ + {developerOptions.map(({ label, value }) => ( + + ))} + ); } From 32e436fea5bf7da6143f3aa246187c97bfc18dc2 Mon Sep 17 00:00:00 2001 From: Aman Mahajan Date: Wed, 19 May 2021 09:25:32 -0500 Subject: [PATCH 14/15] Use a static class name to filterable columns --- stories/demos/HeaderFilters.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/stories/demos/HeaderFilters.tsx b/stories/demos/HeaderFilters.tsx index 2c10d55eea..b485140f69 100644 --- a/stories/demos/HeaderFilters.tsx +++ b/stories/demos/HeaderFilters.tsx @@ -21,8 +21,10 @@ const toolbarClassname = css` text-align: end; `; +const filterColumnClassName = 'filter-cell'; + const filterContainerClassname = css` - .rdg-header-row .rdg-cell:not(:first-child) { + .rdg-header-row .${filterColumnClassName} { line-height: 35px; padding: 0; @@ -90,6 +92,7 @@ export function HeaderFilters() { { key: 'task', name: 'Title', + headerCellClass: filterColumnClassName, headerRenderer: (p) => ( {(filters) => ( @@ -110,6 +113,7 @@ export function HeaderFilters() { { key: 'priority', name: 'Priority', + headerCellClass: filterColumnClassName, headerRenderer: (p) => ( {(filters) => ( @@ -136,6 +140,7 @@ export function HeaderFilters() { { key: 'issueType', name: 'Issue Type', + headerCellClass: filterColumnClassName, headerRenderer: (p) => ( {(filters) => ( @@ -162,6 +167,7 @@ export function HeaderFilters() { { key: 'developer', name: 'Developer', + headerCellClass: filterColumnClassName, headerRenderer: (p) => ( {(filters) => ( @@ -185,6 +191,7 @@ export function HeaderFilters() { { key: 'complete', name: '% Complete', + headerCellClass: filterColumnClassName, headerRenderer: (p) => ( {(filters) => ( From a394e9cfe2faa9cfda72748a91b0a1ade4d73f52 Mon Sep 17 00:00:00 2001 From: Aman Mahajan Date: Wed, 19 May 2021 09:32:25 -0500 Subject: [PATCH 15/15] Update stories/demos/HeaderFilters.tsx Co-authored-by: Nicolas Stepien <567105+nstepien@users.noreply.github.com> --- stories/demos/HeaderFilters.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stories/demos/HeaderFilters.tsx b/stories/demos/HeaderFilters.tsx index b485140f69..a5f2d2f92b 100644 --- a/stories/demos/HeaderFilters.tsx +++ b/stories/demos/HeaderFilters.tsx @@ -24,7 +24,7 @@ const toolbarClassname = css` const filterColumnClassName = 'filter-cell'; const filterContainerClassname = css` - .rdg-header-row .${filterColumnClassName} { + .${filterColumnClassName} { line-height: 35px; padding: 0;