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
and height:100%
- Ensure that the
DetailsList
header row is always visible when scrolling - this is done using the onRenderDetailsHeader
event of the DetailsList
in combination with Sticky
and ScrollablePane
- 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>
</>
);
}
}
div[id^="ViewSelector"]{
z-index: 20;
}
#__flyoutRootNode .flexbox {
z-index: 20;
}
Hope this helps!
@ScottDurow