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> ); };