0
votes

[EDIT: updated to focus the issue on the overlaying problem; also edits have been made to the CodePens] [EDIT2: updated to include details about the method suggested by Mohamed Mansour]

I'm having difficulties designing the layout for an Office UI Fabric React UI. This is the layout I currently have:

enter image description here

The goal is to keep the top CommandBar and headers (the text labels and search box) anchored to the top, with the DetailsList and filing button anchored to the bottom and taking up as much space as possible after the header area.

I've successfully used ScrollablePane and two Sticky components to anchor the CommandBar and button in the desired places. However, the DetailsList scrolls through the top Sticky containing the CommandBar and header, and the button is slightly overlayed on top of the DetailsList grid:

enter image description here

I've tried using the Fabric Grid component to layout all sections but it doesn't make a difference (if it would help at all). The best layout has the projects section in between two Sticky elements; the layout is horrible if the project section is added to either of the Header or Footer Sticky elements. How can I ensure that the DetailsList doesn't scroll under the top and bottom components?

Here are two code pens that illustrate the issue:

Without Grid: https://codepen.io/elegault/pen/aQayvY?editors=0010 (the fileHeader uses a grid for its elements, but all other elements are not in a grid)

const columns = [{
  key: 'projectNameColumn',
  name: 'Project',
  fieldName: 'name',
  minWidth: 100,
  maxWidth: 200,
  isResizable: true,
  ariaLabel: 'Operations for Project'
}];
const items = [{
    id: '0',
    name: 'ABC Construction'
  },
  {
    id: '1',
    name: 'Air Bee and Bee'
  },
  {
    id: '2',
    name: 'Architectural Salvage'
  },
  {
    id: '3',
    name: 'Arkham Airport'
  },
  {
    id: '4',
    name: 'Arkham Assembly Hall'
  },
  {
    id: '5',
    name: 'Arkham Library'
  },
  {
    id: '6',
    name: 'zArkham Renovation'
  },
  {
    id: '7',
    name: 'Foo'
  },
  {
    id: '8',
    name: 'Foo1'
  },
  {
    id: '9',
    name: 'Foo2'
  },
  {
    id: '10',
    name: 'Foo3'
  },
  {
    id: '11',
    name: 'Foo4'
  },
  {
    id: '12',
    name: 'Foo5'
  },
  {
    id: '13',
    name: 'Foo6'
  },
  {
    id: '14',
    name: 'Foo7'
  },
  {
    id: '15',
    name: 'Foo8'
  },
  {
    id: '16',
    name: 'Foo9'
  },
  {
    id: '17',
    name: 'Foo10'
  },
];
class Content extends React.Component {
  public render() {
    const fileHeader = <
      div className = 'ms-Grid' >
      <
      div className = 'ms-Grid-row' >
      <
      div className = 'ms-Grid-col ms-sm2 ms-md2 ms-lg2' >
      <
      img width = '32'
    height = '32'
    alt = 'logo'
    title = 'logo' / >
      <
      /div> <
      div className = 'ms-Grid-col ms-sm10 ms-md10 ms-lg10' >
      <
      Fabric.Label className = 'ms-font-l ms-fontWeight-bold ms-fontColor-blue' > [TITLE] < /Fabric.Label> <
      /div> <
      /div> <
      div className = 'ms-Grid-row' >
      <
      div className = 'ms-Grid-col ms-sm2 ms-md2 ms-lg2' >
      <
      /div> <
      div className = 'ms-Grid-col ms-sm10 ms-md10 ms-lg10' >
      <
      Fabric.Label
    className = 'ms-font-m ms-fontWeight-bold ms-fontColor-neutralPrimary' > SELECTED PROJECT: < /Fabric.Label> <
      /div> <
      /div> <
      /div>;

    const commandBar = < div >
      <
      Fabric.CommandBar
    isSearchBoxVisible = {
      false
    }
    items = {
      [{
        key: 'openWebApp',
        name: 'Open Web App',
        icon: 'OpenInNewWindow', // Or Link
        ['data-automation-id']: 'openWebAppButton',
        title: 'Open web app',
        href: 'http://www.codepen.io',
        target: '_blank',
      }]
    }
    farItems = {
      [{
        key: 'menuOptions',
        name: 'Options',
        icon: 'Settings',
        iconOnly: 'true',
        ['data-automation-id']: 'settingsButton',
        subMenuProps: {
          items: [{
              key: 'privacyPolicy',
              name: 'Privacy Policy',
              icon: 'PageLock',
              href: 'http://www.codepen.io',
              target: '_blank',

            },
            {
              key: 'termsOfUse',
              name: 'Terms & Conditions',
              icon: 'TextDocument',
              href: 'http://www.codepen.io',
              target: '_blank'
            }
          ]
        }
      }]
    }
    /> <
    /div>;

    const projects = < Fabric.MarqueeSelection selection = {
      null
    }
    data - is - scrollable = {
        false
      } >
      <
      Fabric.DetailsList
    items = {
      items
    }
    columns = {
      columns
    }
    /> <
    /Fabric.MarqueeSelection>;
    const selection = < div > [project name] < /div>;
    const search = < Fabric.TextField label = 'Search projects:' / > ;
    const fileButton = <
      div >
      <
      Fabric.DefaultButton primary = {
        true
      } > File To Project < /Fabric.DefaultButton> <
      /div>;

    return ( <
      Fabric.Fabric >
      <
      div style = {
        {
          height: '500px',
          position: 'relative',
          maxHeight: 'inherit'
        }
      } >
      <
      Fabric.ScrollablePane scrollbarVisibility = {
        Fabric.ScrollbarVisibility.auto
      } >
      <
      Fabric.Sticky stickyPosition = {
        Fabric.StickyPositionType.Header
      } > {
        commandBar
      } {
        fileHeader
      } {
        selection
      } {
        search
      } <
      /Fabric.Sticky>     {
        projects
      } <
      Fabric.Sticky stickyPosition = {
        Fabric.StickyPositionType.Footer
      } > {
        fileButton
      } <
      /Fabric.Sticky>       <
      /Fabric.ScrollablePane> <
      /div> <
      /Fabric.Fabric>
    );
  }
}

ReactDOM.render( <
  Content / > ,
  document.getElementById('content')
);

With full Grid: https://codepen.io/elegault/pen/wQxoRR

const columns = [
    {
        key: 'projectNameColumn',
        name: 'Project',
        fieldName: 'name',
        minWidth: 100,
        maxWidth: 200,
        isResizable: true,
        ariaLabel: 'Operations for Project'
    }
];
const items = [
    {id: '0', name: 'ABC Construction'},
    {id: '1', name: 'Air Bee and Bee'},
    {id: '2', name: 'Architectural Salvage'},
    {id: '3', name: 'Arkham Airport'},
    {id: '4', name: 'Arkham Assembly Hall'},
    {id: '5', name: 'Arkham Library'},
    {id: '6', name: 'zArkham Renovation'},
    {id: '7', name: 'Foo'},
    {id: '8', name: 'Foo1'},
    {id: '9', name: 'Foo2'},
    {id: '10', name: 'Foo3'},
    {id: '11', name: 'Foo4'},
    {id: '12', name: 'Foo5'},
    {id: '13', name: 'Foo6'},
    {id: '14', name: 'Foo7'},
    {id: '15', name: 'Foo8'},
    {id: '16', name: 'Foo9'},
    {id: '17', name: 'Foo10'},
];
class Content extends React.Component {
    public render() {        
		      
		const commandBar = <div>
            <Fabric.CommandBar
                isSearchBoxVisible={false}
                items={[
                    {
                        key: 'openWebApp',
                        name: 'Open Web App',
                        icon: 'OpenInNewWindow', // Or Link                        
                        title: 'Open web app',
                        href: 'http://www.codepen.io',
                        target: '_blank',
                    }
                ]}
                farItems={[
                    {
                        key: 'menuOptions',
                        name: 'Options',
                        icon: 'Settings',
                        iconOnly: 'true',
                        ['data-automation-id']: 'settingsButton',
                        subMenuProps: {
                            items: [
                                {
                                    key: 'privacyPolicy',
                                    name: 'Privacy Policy',
                                    icon: 'PageLock',
                                    href: 'http://www.codepen.io',
                                    target: '_blank',

                                },
                                {
                                    key: 'termsOfUse',
                                    name: 'Terms & Conditions',
                                    icon: 'TextDocument',
                                    href: 'http://www.codepen.io',
                                    target: '_blank'
                                }
                            ]
                        }
                    }
                ]}
            />
        </div>;
      
        const projects = <Fabric.MarqueeSelection selection={null} data-is-scrollable={false}>
            <Fabric.DetailsList
                items={items}
                columns={columns}              
            />
        </Fabric.MarqueeSelection>;
        const selection = <div>[project name]</div>;
        const search = <Fabric.TextField label='Search projects:'/>;
        const fileButton = 
              <div>
                <Fabric.DefaultButton primary={true}>File To Project</Fabric.DefaultButton>
              </div>;
		
		    const fileHeader =             
            <div className='ms-Grid'>
                <div className='ms-Grid-row'>
                    <div className='ms-Grid-col ms-sm2 ms-md2 ms-lg2'>
                        <img width='32' height='32' alt='logo' title='logo'/>
                    </div>
                    <div className='ms-Grid-col ms-sm10 ms-md10 ms-lg10'>
                        <Fabric.Label className='ms-font-l ms-fontWeight-bold ms-fontColor-blue'>[TITLE]</Fabric.Label>
                    </div>
                </div>
                <div className='ms-Grid-row'>
                    <div className='ms-Grid-col ms-sm2 ms-md2 ms-lg2'>
                    </div>
                    <div className='ms-Grid-col ms-sm10 ms-md10 ms-lg10'>
                        <Fabric.Label
                            className='ms-font-m ms-fontWeight-bold ms-fontColor-neutralPrimary'>SELECTED PROJECT:</Fabric.Label>
                    </div>
                </div>
            </div>;
			
        return (
			<Fabric.Fabric>                
				<div style={{height:'500px', position:'relative', maxHeight:'inherit'}}>										
          <Fabric.ScrollablePane scrollbarVisibility={Fabric.ScrollbarVisibility.auto}>
						<div className='ms-Grid'>
							<Fabric.Sticky stickyPosition={Fabric.StickyPositionType.Header}>
								<div className='ms-Grid-row'>
									<div className='ms-Grid-col ms-sm10 ms-md10 ms-lg10'>
										{commandBar}
									</div>
								</div>								
								<div className='ms-Grid-row'>
									<div className='ms-Grid-col ms-sm10 ms-md10 ms-lg10'>
										{fileHeader}
									</div>
								</div>									
								<div className='ms-Grid-row'>
									<div className='ms-Grid-col ms-sm10 ms-md10 ms-lg10'>
										{selection}
									</div>
								</div>							
								<div className='ms-Grid-row'>
									<div className='ms-Grid-col ms-sm10 ms-md10 ms-lg10'>
										{search}            
									</div>
								</div>																		
							</Fabric.Sticky>    
							<div className='ms-Grid-row'>
								<div className='ms-Grid-col ms-sm10 ms-md10 ms-lg10'>
									{projects}            
								</div>
							</div>								                      
							<Fabric.Sticky stickyPosition={Fabric.StickyPositionType.Footer}>						
								<div className='ms-Grid-row'>
									<div className='ms-Grid-col ms-sm10 ms-md10 ms-lg10'>
										{fileButton}            
									</div>
								</div>								
							</Fabric.Sticky>      
						</div>
          </Fabric.ScrollablePane>
				</div>
      </Fabric.Fabric>
        );			
		
    }
}

ReactDOM.render( 
  <Content />,
  document.getElementById('content')
);

Also, when attempting to use Flex classes or defined heights in parent divs (as per Mohamed Mansour's suggestion) in a project outside of the codepens that uses nested custom React components, the entire DetailsList fails to render. For example, this DOM explorer shows how even setting a defined height and using flex in multiple 'main' divs still fails to render DetailsList:

enter image description here

1

1 Answers

3
votes

Lets clear some terminology, these are from the docs:

  • ScrollablePane: Uses absolute positioning, the parent has to have relative and a height (either from flex or static). So if you want it to stretch in your own way, you need to satisfy these constraints.
  • Sticky: These are ideally for headers and footers, but will still be within the scrollbar region. Usually used for multiple section.
  • Overlapping Contents: This is perfectly normal, your header and footer do not have a solid background, set it to white, and it will work as expected.

To achieve what you wanted, for the scrollbar to just appear between the header and footer, you could add some normal CSS. Run the snippet below to see it. Basically just a flex layout.

I have updated your example as well: https://codepen.io/mohamedhmansour/pen/rQqqvP

let { DetailsList, Fabric, ScrollablePane, ScrollbarVisibility } = window.Fabric;

function Content() {
  const items = [...Array(500).keys()].map(i => ({key: i, text: `Item ${i}`}));
  
  return (
    <ScrollablePane  scrollbarVisibility={ScrollbarVisibility.auto}> 
      <DetailsList items={items} />
    </ScrollablePane>
  );
}

ReactDOM.render( 
  <Fabric className='wrapper'>
    <div className='header'>Header</div>
    <div className='main'>
      <Content />
    </div>
    <div className='footer'>Footer</div>
  </Fabric>,
  document.getElementById('content')
);
.wrapper {
  display: flex;
  flex-direction: column;
  width: 100%;
  position: absolute;
  top: 0;
  bottom: 0;
}

.main {
  flex: 1;
  position: relative;
}


.header, .footer {
  background: black;
  color: white;
  padding: 10px;
}
<script src="//unpkg.com/react@16/umd/react.development.js"></script>
<script src="//unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

<!-- could point to local version -->
<script src="//unpkg.com/office-ui-fabric-react/dist/office-ui-fabric-react.min.js"></script>

<div id='content'></div>