From b2e93a9852040277064a0f0b380a9a86d0fc89c4 Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Fri, 6 Oct 2023 18:10:31 -0500 Subject: [PATCH] auto complete filter variant feature --- .../components/prop-tables/rowInstanceAPIs.ts | 50 ++++- .../prop-tables/tableInstanceAPIs.ts | 56 +++++ .../pages/migrating-to-v2.mdx | 5 +- .../material-react-table/build-locales.mjs | 4 +- .../src/inputs/MRT_FilterTextField.tsx | 193 +++++++++++------- packages/material-react-table/src/types.ts | 14 ++ .../stories/features/Filtering.stories.tsx | 4 +- 7 files changed, 242 insertions(+), 84 deletions(-) diff --git a/apps/material-react-table-docs/components/prop-tables/rowInstanceAPIs.ts b/apps/material-react-table-docs/components/prop-tables/rowInstanceAPIs.ts index c14f41c06..8d1c251e8 100644 --- a/apps/material-react-table-docs/components/prop-tables/rowInstanceAPIs.ts +++ b/apps/material-react-table-docs/components/prop-tables/rowInstanceAPIs.ts @@ -51,6 +51,13 @@ export const rowInstanceAPIs: RowInstanceAPI[] = [ link: '', linkText: '', }, + { + rowInstanceAPI: 'getCanPin', + type: '', + description: '', + link: '', + linkText: '', + }, { rowInstanceAPI: 'getCanSelect', type: '', @@ -72,6 +79,20 @@ export const rowInstanceAPIs: RowInstanceAPI[] = [ link: '', linkText: '', }, + { + rowInstanceAPI: 'getGroupingValue', + type: '', + description: '', + link: '', + linkText: '', + }, + { + rowInstanceAPI: 'getIsAllParentsExpanded', + type: '', + description: '', + link: '', + linkText: '', + }, { rowInstanceAPI: 'getIsAllSubRowsSelected', type: '', @@ -93,6 +114,13 @@ export const rowInstanceAPIs: RowInstanceAPI[] = [ link: '', linkText: '', }, + { + rowInstanceAPI: 'getIsPinned', + type: '', + description: '', + link: '', + linkText: '', + }, { rowInstanceAPI: 'getIsSelected', type: '', @@ -135,6 +163,13 @@ export const rowInstanceAPIs: RowInstanceAPI[] = [ link: '', linkText: '', }, + { + rowInstanceAPI: 'getPinnedIndex', + type: '', + description: '', + link: '', + linkText: '', + }, { rowInstanceAPI: 'getRightVisibleCells', type: '', @@ -177,7 +212,13 @@ export const rowInstanceAPIs: RowInstanceAPI[] = [ link: '', linkText: '', }, - { rowInstanceAPI: 'id', type: '', description: '', link: '', linkText: '' }, + { + rowInstanceAPI: 'id', + type: '', + description: '', + link: '', + linkText: '', + }, { rowInstanceAPI: 'index', type: '', @@ -206,6 +247,13 @@ export const rowInstanceAPIs: RowInstanceAPI[] = [ link: '', linkText: '', }, + { + rowInstanceAPI: 'pin', + type: '', + description: '', + link: '', + linkText: '', + }, { rowInstanceAPI: 'renderValue', type: '', diff --git a/apps/material-react-table-docs/components/prop-tables/tableInstanceAPIs.ts b/apps/material-react-table-docs/components/prop-tables/tableInstanceAPIs.ts index 4bc2bbcbe..00823142f 100644 --- a/apps/material-react-table-docs/components/prop-tables/tableInstanceAPIs.ts +++ b/apps/material-react-table-docs/components/prop-tables/tableInstanceAPIs.ts @@ -30,6 +30,13 @@ export const tableInstanceAPIs: TableInstanceAPI[] = [ link: '', linkText: '', }, + { + tableInstanceAPI: 'getBottomRows', + type: '', + description: '', + link: '', + linkText: '', + }, { tableInstanceAPI: 'getCanNextPage', type: '', @@ -86,6 +93,13 @@ export const tableInstanceAPIs: TableInstanceAPI[] = [ link: '', linkText: '', }, + { + tableInstanceAPI: 'getCenterRows', + type: '', + description: '', + link: '', + linkText: '', + }, { tableInstanceAPI: 'getCenterTotalSize', type: '', @@ -268,6 +282,13 @@ export const tableInstanceAPIs: TableInstanceAPI[] = [ link: '', linkText: '', }, + { + tableInstanceAPI: 'getIsSomeRowsPinned', + type: '', + description: '', + link: '', + linkText: '', + }, { tableInstanceAPI: 'getIsSomeRowsSelected', type: '', @@ -506,6 +527,13 @@ export const tableInstanceAPIs: TableInstanceAPI[] = [ link: '', linkText: '', }, + { + tableInstanceAPI: 'getTopRows', + type: '', + description: '', + link: '', + linkText: '', + }, { tableInstanceAPI: 'getTotalSize', type: '', @@ -653,6 +681,13 @@ export const tableInstanceAPIs: TableInstanceAPI[] = [ link: '', linkText: '', }, + { + tableInstanceAPI: 'resetRowPinning', + type: '', + description: '', + link: '', + linkText: '', + }, { tableInstanceAPI: 'resetRowSelection', type: '', @@ -716,6 +751,13 @@ export const tableInstanceAPIs: TableInstanceAPI[] = [ link: '', linkText: '', }, + { + tableInstanceAPI: 'setCreatingRow', + type: '', + description: '', + link: '', + linkText: '', + }, { tableInstanceAPI: 'setDensity', type: '', @@ -835,6 +877,13 @@ export const tableInstanceAPIs: TableInstanceAPI[] = [ link: '', linkText: '', }, + { + tableInstanceAPI: 'setRowPinning', + type: '', + description: '', + link: '', + linkText: '', + }, { tableInstanceAPI: 'setRowSelection', type: '', @@ -863,6 +912,13 @@ export const tableInstanceAPIs: TableInstanceAPI[] = [ link: '', linkText: '', }, + { + tableInstanceAPI: 'setShowToolbarDropZone', + type: '', + description: '', + link: '', + linkText: '', + }, { tableInstanceAPI: 'setSorting', type: '', diff --git a/apps/material-react-table-docs/pages/migrating-to-v2.mdx b/apps/material-react-table-docs/pages/migrating-to-v2.mdx index 3f5a0ac63..f5ee787c8 100644 --- a/apps/material-react-table-docs/pages/migrating-to-v2.mdx +++ b/apps/material-react-table-docs/pages/migrating-to-v2.mdx @@ -19,11 +19,12 @@ import { InstallCommand } from '../components/mdx/InstallCommand'; 2. Greatly improved Editing and Creating features. 3. New Row Pinning Features 4. New Column Filtering `'popover'` display mode to give a more "excel-like" filtering experience. -5. New Date Filter variants. +5. New Date and Date Range Filter variants +6. New Autocomplete Filter variant ### Breaking Changes -- `@mui/x-date-pickers` is now a required peer dependency. Install it with +- `@mui/x-date-pickers v >=6.15.0` is now a required peer dependency. Install it with: diff --git a/packages/material-react-table/build-locales.mjs b/packages/material-react-table/build-locales.mjs index 95e188c74..1e25f347f 100644 --- a/packages/material-react-table/build-locales.mjs +++ b/packages/material-react-table/build-locales.mjs @@ -1,6 +1,6 @@ +import typescript from '@rollup/plugin-typescript'; import { rollup } from 'rollup'; import copy from 'rollup-plugin-copy'; -import typescript from '@rollup/plugin-typescript'; const supportedLocales = [ 'ar', @@ -50,10 +50,10 @@ async function build(locale) { copy({ targets: [ ...['cjs', 'esm'].map((format) => ({ - src: `./dist/esm/types/locales/${locale}.d.ts`, dest: './locales', rename: () => format === 'esm' ? `${locale}.${format}.d.ts` : `${locale}.d.ts`, + src: `./dist/esm/types/locales/${locale}.d.ts`, })), ], }), diff --git a/packages/material-react-table/src/inputs/MRT_FilterTextField.tsx b/packages/material-react-table/src/inputs/MRT_FilterTextField.tsx index 0b855697e..3e08caea7 100644 --- a/packages/material-react-table/src/inputs/MRT_FilterTextField.tsx +++ b/packages/material-react-table/src/inputs/MRT_FilterTextField.tsx @@ -7,6 +7,7 @@ import { useRef, useState, } from 'react'; +import Autocomplete from '@mui/material/Autocomplete'; import Box from '@mui/material/Box'; import Checkbox from '@mui/material/Checkbox'; import Chip from '@mui/material/Chip'; @@ -42,6 +43,7 @@ export const MRT_FilterTextField = >({ icons: { CloseIcon, FilterListIcon }, localization, manualFiltering, + muiFilterAutocompleteProps, muiFilterDatePickerProps, muiFilterTextFieldProps, }, @@ -60,6 +62,14 @@ export const MRT_FilterTextField = >({ }), }; + const autocompleteProps = { + ...parseFromValuesOrFunc(muiFilterAutocompleteProps, { column, table }), + ...parseFromValuesOrFunc(columnDef.muiFilterAutocompleteProps, { + column, + table, + }), + }; + const datePickerProps: DatePickerProps = { ...parseFromValuesOrFunc(muiFilterDatePickerProps, { column, table }), ...parseFromValuesOrFunc(columnDef.muiFilterDatePickerProps, { @@ -69,12 +79,14 @@ export const MRT_FilterTextField = >({ }; const isDateFilter = filterVariant?.startsWith('date'); + const isAutocompleteFilter = filterVariant === 'autocomplete'; const isRangeFilter = filterVariant?.includes('range') || rangeFilterIndex !== undefined; const isSelectFilter = filterVariant === 'select'; const isMultiSelectFilter = filterVariant === 'multi-select'; const isTextboxFilter = - filterVariant === 'text' || (!isSelectFilter && !isMultiSelectFilter); + ['autocomplete', 'text'].includes(filterVariant!) || + (!isSelectFilter && !isMultiSelectFilter); const currentFilterOption = columnDef._filterFn; const filterChipLabel = ['empty', 'notEmpty'].includes(currentFilterOption) ? //@ts-ignore @@ -104,22 +116,6 @@ export const MRT_FilterTextField = >({ const facetedUniqueValues = column.getFacetedUniqueValues(); - const filterSelectOptions = useMemo( - () => - columnDef.filterSelectOptions ?? - ((isSelectFilter || isMultiSelectFilter) && facetedUniqueValues - ? Array.from(facetedUniqueValues.keys()) - .filter((value) => value !== null && value !== undefined) - .sort((a, b) => a.localeCompare(b)) - : undefined), - [ - columnDef.filterSelectOptions, - facetedUniqueValues, - isMultiSelectFilter, - isSelectFilter, - ], - ); - const [anchorEl, setAnchorEl] = useState(null); const [filterValue, setFilterValue] = useState(() => isMultiSelectFilter @@ -162,6 +158,7 @@ export const MRT_FilterTextField = >({ ? event.target.valueAsNumber : event.target.value; handleChange(newValue); + textFieldProps?.onChange?.(event); }; const handleClear = () => { @@ -216,9 +213,29 @@ export const MRT_FilterTextField = >({ ); } + const dropdownOptions = useMemo( + () => + columnDef.filterSelectOptions ?? + ((isSelectFilter || isMultiSelectFilter || isAutocompleteFilter) && + facetedUniqueValues + ? Array.from(facetedUniqueValues.keys()) + .filter((value) => value !== null && value !== undefined) + .sort((a, b) => a.localeCompare(b)) + : undefined), + [ + columnDef.filterSelectOptions, + facetedUniqueValues, + isMultiSelectFilter, + isSelectFilter, + ], + ); + const endAdornment = - !isDateFilter && !filterChipLabel ? ( - + !isAutocompleteFilter && !isDateFilter && !filterChipLabel ? ( + >({ onClick={handleClear} size="small" sx={{ - height: '1.75rem', - width: '1.75rem', + height: '2rem', + transform: 'scale(0.9)', + width: '2rem' }} > @@ -285,6 +303,7 @@ export const MRT_FilterTextField = >({ ) : null, inputProps: { + autoComplete: 'new-password', // disable autocomplete and autofill disabled: !!filterChipLabel, sx: { textOverflow: 'ellipsis', @@ -305,10 +324,9 @@ export const MRT_FilterTextField = >({ filterChipLabel || isSelectFilter || isMultiSelectFilter ? undefined : filterPlaceholder, + variant: 'standard', + ...textFieldProps, sx: (theme) => ({ - '& .MuiSelect-icon': { - mr: '1.5rem', - }, minWidth: isDateFilter ? '160px' : isRangeFilter @@ -321,11 +339,8 @@ export const MRT_FilterTextField = >({ width: 'calc(100% + 4px)', ...(parseFromValuesOrFunc(textFieldProps?.sx, theme) as any), }), - variant: 'standard', }; - console.log(column.id, filterValue) - return ( <> {isDateFilter ? ( @@ -347,6 +362,30 @@ export const MRT_FilterTextField = >({ }, }} /> + ) : isAutocompleteFilter ? ( + option} + onChange={(_e, newValue) => handleChange(newValue)} + options={dropdownOptions ?? []} + {...autocompleteProps} + renderInput={(builtinTextFieldProps) => ( + + )} + value={filterValue} + /> ) : ( >({ ) : ( {(selected as string[])?.map((value) => { - const selectedValue = filterSelectOptions?.find( - (option) => - option instanceof Object - ? option.value === value - : option === value, + const selectedValue = dropdownOptions?.find((option) => + option instanceof Object + ? option.value === value + : option === value, ); return ( >({ }} onChange={handleTextFieldChange} select={isSelectFilter || isMultiSelectFilter} - value={filterValue ?? ''} {...commonTextFieldProps} - {...textFieldProps} + value={filterValue ?? ''} > - {(isSelectFilter || isMultiSelectFilter) && ( - , + ...[ + textFieldProps.children ?? + dropdownOptions?.map( + (option: { text: string; value: string } | string, index) => { + if (!option) return ''; + let value: string; + let text: string; + if (typeof option !== 'object') { + value = option; + text = option; + } else { + value = option.value; + text = option.text; + } + return ( + + {isMultiSelectFilter && ( + + )} + {text}{' '} + {!columnDef.filterSelectOptions && + `(${facetedUniqueValues.get(value)})`} + + ); + }, + ), + ], + ]} )} > = Omit< filterFn?: MRT_FilterFn; filterSelectOptions?: ({ text: string; value: any } | string)[]; filterVariant?: + | 'autocomplete' | 'checkbox' | 'date' | 'date-range' @@ -461,6 +463,12 @@ export type MRT_ColumnDef> = Omit< table: MRT_TableInstance; }) => TextFieldProps) | TextFieldProps; + muiFilterAutocompleteProps?: + | ((props: { + column: MRT_Column; + table: MRT_TableInstance; + }) => AutocompleteProps) + | AutocompleteProps; muiFilterCheckboxProps?: | ((props: { column: MRT_Column; @@ -792,6 +800,12 @@ export type MRT_TableOptions> = Omit< table: MRT_TableInstance; }) => IconButtonProps) | IconButtonProps; + muiFilterAutocompleteProps?: + | ((props: { + column: MRT_Column; + table: MRT_TableInstance; + }) => AutocompleteProps) + | AutocompleteProps; muiFilterCheckboxProps?: | ((props: { column: MRT_Column; diff --git a/packages/material-react-table/stories/features/Filtering.stories.tsx b/packages/material-react-table/stories/features/Filtering.stories.tsx index 45a61b832..3d93774fc 100644 --- a/packages/material-react-table/stories/features/Filtering.stories.tsx +++ b/packages/material-react-table/stories/features/Filtering.stories.tsx @@ -194,7 +194,7 @@ export const FilterFnAndFilterVariantsFaceted = () => ( }, { accessorKey: 'lastName', - filterVariant: 'select', + filterVariant: 'autocomplete', header: 'Last Name', }, { @@ -272,7 +272,7 @@ export const FilteringChangeModeEnabledFaceted = () => ( }, { accessorKey: 'lastName', - filterVariant: 'select', + filterVariant: 'autocomplete', header: 'Last Name', }, {