reactjsoffice-fabric

Fluent UI DetailsList - Is there a way to add filters to each column


I am using Fluent UI DetailsList. My table looks like below: enter image description here

I need filters below every column (text or drop-down) as shown below: enter image description here

Please let me know if this is possible? Or maybe a way to display custom header (using html) ?


Solution

  • This actually turned out to be easier than I thought it'd be...

    If you're ok with clicking the column header to reveal the choices (vs having the dropdown directly under the title) then this can be achieved using the ContextualMenu component in conjunction with DetailsList. I got it working by tweaking from the variable row height example in the official docs: https://developer.microsoft.com/en-us/fluentui#/controls/web/detailslist/variablerowheights.

    Add a ContextualMenu underneath your DetailsList:

    <DetailsList
        items={items}
        columns={columns}
    />
    {this.state.contextualMenuProps && <ContextualMenu {...this.state.contextualMenuProps} />}
    

    Inside your column definition, set the hasDropdown action so the user gets a UI indicator that they can/should click the header, and call a contextMenu method (note I'm using onColumnContextMenu as well as onColumnClick so it doesn't matter if they left or right click the header:

    {
        key: 'dept',
        name: 'Department',
        fieldName: 'dept',
        minWidth: 125,
        maxWidth: 200,
        onColumnContextMenu: (column, ev) => {
            this.onColumnContextMenu(column, ev);
        },
        onColumnClick: (ev, column) => {
            this.onColumnContextMenu(column, ev);
        },
        columnActionsMode: ColumnActionsMode.hasDropdown,
    }
    

    When the onColumnContextMenu method gets invoked, we need to build the context menu properties that will get consumed by the ContextualMenu component. Note the dismissal method as well, which clears out the state so the menu is hidden.

    private onContextualMenuDismissed = (): void => {
        this.setState({
            contextualMenuProps: undefined,
        });
    }
    
    private onColumnContextMenu = (column: IColumn, ev: React.MouseEvent<HTMLElement>): void => {
        if (column.columnActionsMode !== ColumnActionsMode.disabled) {
            this.setState({
                contextualMenuProps: this.getContextualMenuProps(ev, column),
            });
        }
    };
    

    Finally, inside of getContextualMenuProps you need to determine what the options should be for the user to click. In this example, I'm simply giving sort options (you'll need to add an onClick handler to actually do something when the user clicks the item), but I'll use the column to determine what those items should actually be and paint the filters into the items collection so the user can select one to filter.

    private getContextualMenuProps = (ev: React.MouseEvent<HTMLElement>, column: IColumn): IContextualMenuProps => {
        const items: IContextualMenuItem[] = [
            {
                key: 'aToZ',
                name: 'A to Z',
                iconProps: { iconName: 'SortUp' },
                canCheck: true,
                checked: column.isSorted && !column.isSortedDescending,
            },
            {
                key: 'zToA',
                name: 'Z to A',
                iconProps: { iconName: 'SortDown' },
                canCheck: true,
                checked: column.isSorted && column.isSortedDescending,
            }
        ];
    
        return {
            items: items,
            target: ev.currentTarget as HTMLElement,
            directionalHint: DirectionalHint.bottomLeftEdge,
            gapSpace: 10,
            isBeakVisible: true,
            onDismiss: this.onContextualMenuDismissed,
        }
    }
    

    Note the target on the ContextualMenuProps object, which is what tells the ContextualMenu where to lock itself onto (in this case, the column header that you clicked to instantiate the menu.