PCF DetailsList Layout with Fluent UI and Sticky
Views (98)
One of the challenges with PCF controls is getting them to reflow to the available space that they are stretched to fill the available space. Doing this using standard HTML involves using the flexbox. The really nice aspect of the Fluent UI react library is that it comes with an abstraction of the flexbox called the ‘Stack’.
The aim of this post is to layout a dataset PCF as follows:
- Left Panel - A fixed width vertical stack panel that fills 100% of the available space
- Top Bar - A fixed height top bar that can contain a command bar etc.
- Footer - A centre aligned footer that can contain status messages etc.
- Grid - a DetailsList with a sticky headers that occupies 100% of the middle area.
The main challenges of this exercise are:
- Expanding the areas to use 100% of the container space - this is done using a combination of
verticalFill
andheight:100%
- Ensure that the
DetailsList
header row is always visible when scrolling - this is done using theonRenderDetailsHeader
event of theDetailsList
in combination withSticky
andScrollablePane
- Ensure that the view selector and other command bar overlay appear on top of the stick header.
This requires a bit of a ‘hack’ in that we have to apply a z-order css rule to the Model Driven overlays for the ViewSelector and Command Bar flyoutRootNode. If this is not applied then flyout menus will show behind the Stick header:
Here is the React component for the layout:
/* eslint-disable @typescript-eslint/no-non-null-assertion */ /* eslint-disable @typescript-eslint/explicit-function-return-type */ import * as React from "react"; import { Stack, ScrollablePane, DetailsList, TooltipHost, IRenderFunction, IDetailsColumnRenderTooltipProps, IDetailsHeaderProps, StickyPositionType, Sticky, ScrollbarVisibility, } from "office-ui-fabric-react"; export class DatasetLayout extends React.Component { private onRenderDetailsHeader: IRenderFunction<IDetailsHeaderProps> = (props, defaultRender) => { if (!props) { return null; } const onRenderColumnHeaderTooltip: IRenderFunction<IDetailsColumnRenderTooltipProps> = tooltipHostProps => ( <TooltipHost {...tooltipHostProps} /> ); return ( <Sticky stickyPosition={StickyPositionType.Header} isScrollSynced> {defaultRender!({ ...props, onRenderColumnHeaderTooltip, })} </Sticky> ); }; private columns = [ { key: "name", name: "Name", isResizable: true, minWidth: 100, onRender: (item: string) => { return <span>{item}</span>; }, }, ]; render() { return ( <> <Stack horizontal styles={{ root: { height: "100%" } }}> <Stack.Item> {/*Left column*/} <Stack verticalFill> <Stack.Item verticalFill styles={{ root: { textAlign: "left", width: "150px", paddingLeft: "8px", paddingRight: "8px", overflowY: "auto", overflowX: "hidden", height: "100%", background: "#DBADB1", }, }} > <Stack> <Stack.Item>Left Item 1</Stack.Item> <Stack.Item>Left Item 2</Stack.Item> </Stack> </Stack.Item> </Stack> </Stack.Item> <Stack.Item styles={{ root: { width: "100%" } }}> {/*Right column*/} <Stack grow styles={{ root: { width: "100%", height: "100%", }, }} > <Stack.Item verticalFill> <Stack grow styles={{ root: { height: "100%", width: "100%", background: "#65A3DB", }, }} > <Stack.Item>Top Bar</Stack.Item> <Stack.Item verticalFill styles={{ root: { height: "100%", overflowY: "auto", overflowX: "auto", }, }} > <div style={{ position: "relative", height: "100%" }}> <ScrollablePane scrollbarVisibility={ScrollbarVisibility.auto}> <DetailsList onRenderDetailsHeader={this.onRenderDetailsHeader} compact={true} items={[...Array(200)].map((_, i) => `Item ${i + 1}`)} columns={this.columns} ></DetailsList> </ScrollablePane> </div> </Stack.Item> <Stack.Item align="center">Footer</Stack.Item> </Stack> </Stack.Item> </Stack> </Stack.Item> </Stack> </> ); } }
Here is the css:
div[id^="ViewSelector"]{ z-index: 20; } #__flyoutRootNode .flexbox { z-index: 20; }
Hope this helps!
This was originally posted here.
*This post is locked for comments