Quantcast
Viewing all articles
Browse latest Browse all 79

Attaching a context menu to DetailsList column headers in Fluent UI

I wanted to show a menu when clicking the column headers of a DetailsList, similar to how SharePoint does it. Fluent UI 8 does not have any built in support for this, so I had to figure out a way to do it myself.

Here is a sample project showing my solution. When clicking the column headers you get a menu allowing you to sort the list based on that column. Not very existing, but it shows how to dynamically build and position the context menu, and how to handle when the user selects a menu option depending on the column.

Image may be NSFW.
Clik here to view.

Get the full project code on GitHub

Most of the files are just generated boilerplace code. The interesting part is App.tsx:

import React, { useEffect } from 'react';
import './App.css';
import { ContextualMenu, DetailsList, DirectionalHint, IColumn, IContextualMenuItem } from '@fluentui/react';

interface IFruit {
  [key:string]: string;
  name: string;
  color: string;
}

export const App: React.FunctionComponent = () => {

  const [menuDomTarget, setMenuDomTarget] = React.useState<HTMLElement | undefined>(undefined);
  const [menuItems, setMenuItems] = React.useState<IContextualMenuItem[]>([]);
  const [clickedColumn, setClickedColumn] = React.useState<IColumn | undefined>(undefined);

  const [listItems, setListItems] = React.useState<IFruit[]>([
      { name: "Strawberry", color: "Red" },
      { name: "Pear", color: "Green" },
      { name: "Apple", color: "Red" },
      { name: "Banana", color: "Yellow" },
      { name: "Grapes", color: "Green" },
      { name: "Pineapple", color: "Yellow" },
      { name: "Watermelon", color: "Green" },
  ]);

  const onColumnClick = (ev?: React.MouseEvent<HTMLElement>, column?: IColumn): void => {
    setMenuDomTarget(ev?.currentTarget);
    setClickedColumn(column);  
  };

  const [columns, setColumns] = React.useState<IColumn[]>([
    { key: 'name', name: "Name", fieldName: 'name', minWidth: 100, isRowHeader: true },
    { key: 'color', name: "Color", fieldName: 'color', minWidth: 100, }
  ]);

  const sortItems = (columnKey: string, sortDescending: boolean): void => {

    // Update columns to show sort order icon
    const newColumns: IColumn[] = columns.slice();
    const currColumn: IColumn = newColumns.filter(currCol => columnKey === currCol.key)[0];
    newColumns.forEach((newCol: IColumn) => {
      if (newCol === currColumn) {
        currColumn.isSortedDescending = sortDescending;
        currColumn.isSorted = true;
      } else {
        newCol.isSorted = false;
        newCol.isSortedDescending = true;
      }
    });
    setColumns(newColumns);

    // Sort the items accordingly
    const newItems = [...listItems];
    newItems.sort((a: IFruit, b: IFruit) => {
      if(sortDescending)
        return b[columnKey].localeCompare(a[columnKey])
      else
        return a[columnKey].localeCompare(b[columnKey])
    });
    setListItems(newItems);
  };

  useEffect(() => {
    if(clickedColumn) {
      // Dynamically generate the menu items based on the clicked column.
      const items: IContextualMenuItem[] = [
        {
          key: "sortAsc",
          text: "A to Z",
          iconProps: { iconName: "Ascending"},
          onClick: () => {sortItems(clickedColumn.key, false)},
        },
        {
          key: "sortDesc",
          text: "Z to A",
          iconProps: { iconName: "Descending"},
          onClick: () => {sortItems(clickedColumn.key, true)},
        }
      ];
      setMenuItems(items);
    }
  }, [clickedColumn, sortItems]);

  return (
    <div className="container">
      <DetailsList
        items={listItems}
        columns={columns}
        isHeaderVisible={true}
        onColumnHeaderClick={onColumnClick}
        className="details-list"
      />

      {menuDomTarget !== undefined && 
        <ContextualMenu
          items={menuItems}
          hidden={menuDomTarget === undefined}
          target={menuDomTarget}
          onItemClick={() => setMenuDomTarget(undefined)}
          onDismiss={() => setMenuDomTarget(undefined)}
          directionalHint={DirectionalHint.bottomLeftEdge}
          />
      }
    </div>
  );
};

 


Viewing all articles
Browse latest Browse all 79

Trending Articles