Page layout
A collection of components which let you compose an application's page layout.Deprecated
This package is deprecated. Use our new navigation system.
Basic
PageLayout wraps an entire app view and helps split the viewport into sections where you can
render components in slots, such as TopNavigation, Main, LeftSidebar, and more.
import { jsx } from '@emotion/react';
import {
Banner,
Content,
LeftSidebarWithoutResize,
Main,
PageLayout,
RightPanel,
TopNavigation,
} from '@atlaskit/page-layout';
import { token } from '@atlaskit/tokens';
import { SlotLabel, SlotWrapper } from '../common';
const BasicGrid = (): jsx.JSX.Element => {
return (
<PageLayout>
{
<Banner testId="banner" id="banner" skipLinkTitle="Banner" height={60} isFixed={false}>
<SlotWrapper borderColor={token('color.border.accent.yellow')}>
<SlotLabel>Banner</SlotLabel>
</SlotWrapper>
</Banner>
}
{
<TopNavigation
testId="topNavigation"
id="app-navigation"
skipLinkTitle="App Navigation"
height={60}
isFixed={false}
>
<SlotWrapper borderColor={token('color.border.accent.blue')}>
<SlotLabel>App Navigation</SlotLabel>
</SlotWrapper>
</TopNavigation>
}
<Content testId="content">
{
<LeftSidebarWithoutResize
testId="leftSidebar"
id="space-navigation"
skipLinkTitle="Project Navigation"
isFixed={false}
width={125}
>
<SlotWrapper borderColor={token('color.border.accent.green')} hasExtraPadding>
<SlotLabel isSmall>Space Navigation</SlotLabel>
</SlotWrapper>
</LeftSidebarWithoutResize>
}
{
<Main testId="main" id="main" skipLinkTitle="Main Content">
<SlotWrapper borderColor={token('color.border')} minHeight={400}>
<SlotLabel isSmall>Main Content</SlotLabel>
<p>Visit the first focusable element on the page to see the skip links menu</p>
</SlotWrapper>
</Main>
}
</Content>
{
<RightPanel
testId="rightPanel"
id="help-panel"
skipLinkTitle="Help Panel"
isFixed={false}
width={125}
>
<SlotWrapper borderColor={token('color.border.accent.orange')}>
<SlotLabel>Help Panel</SlotLabel>
</SlotWrapper>
</RightPanel>
}
</PageLayout>
);
};
export default BasicGrid;This is a customisable and more interactive example to demonstrate the page layout slots.
import { Fragment, useCallback, useState } from 'react';
import { jsx } from '@emotion/react';
import {
Banner,
Content,
LeftPanel,
LeftSidebarWithoutResize,
Main,
PageLayout,
RightPanel,
RightSidebar,
TopNavigation,
} from '@atlaskit/page-layout';
import { token } from '@atlaskit/tokens';
import {
ScrollableContent,
SlotLabel,
SlotWrapper,
Toggle,
ToggleBox,
toKebabCase,
} from '../common';
type SlotName =
| 'Banner'
| 'TopNavigation'
| 'LeftPanel'
| 'LeftSidebar'
| 'Main'
| 'RightSidebar'
| 'RightPanel';
const initialState = {
isBannerShown: true,
isTopNavigationShown: true,
isLeftPanelShown: true,
isLeftSidebarShown: true,
isMainShown: true,
isRightSidebarShown: true,
isRightPanelShown: true,
isBannerFixed: true,
isTopNavigationFixed: true,
isLeftPanelFixed: false,
isLeftPanelScrollable: false,
isLeftSidebarFixed: true,
isLeftSidebarScrollable: false,
isMainScrollable: false,
isMainExtraWide: false,
isRightSidebarFixed: false,
isRightSidebarScrollable: false,
isRightPanelFixed: false,
isRightPanelScrollable: false,
};
const BasicGrid = (): jsx.JSX.Element => {
const [gridState, setGridState] = useState(initialState);
const ToggleFixed = useCallback(
({ slotName }: { slotName: SlotName }) => {
const gridKey = `is${slotName}Fixed` as keyof typeof gridState;
return (
<Toggle
id={`${slotName}--fixed`}
isChecked={gridState[gridKey]}
onChange={() => setGridState({ ...gridState, [gridKey]: !gridState[gridKey] })}
>
Toggle fixed
</Toggle>
);
},
[gridState],
);
const ToggleScrollable = useCallback(
({ slotName }: { slotName: SlotName }) => {
const gridKey = `is${slotName}Scrollable` as keyof typeof gridState;
return (
<Fragment>
<Toggle
id={`${slotName}--scrollable`}
isChecked={gridState[gridKey]}
onChange={() => setGridState({ ...gridState, [gridKey]: !gridState[gridKey] })}
>
Toggle scrollable content
</Toggle>
{gridState[gridKey] && <ScrollableContent />}
</Fragment>
);
},
[gridState],
);
const ToggleShown = useCallback(
({ slotName }: { slotName: SlotName }) => {
const gridKey = `is${slotName}Shown` as keyof typeof gridState;
return (
<Toggle
id={`toggle-${toKebabCase(slotName)}`}
onChange={() => setGridState({ ...gridState, [gridKey]: !gridState[gridKey] })}
isChecked={!gridState[gridKey]}
>{`${gridState[gridKey] ? 'Hide' : 'Show'} ${slotName}`}</Toggle>
);
},
[gridState],
);
const ToggleExtraWide = useCallback(
() => (
<Fragment>
<Toggle
id={`toggle--extra-wide`}
onChange={() =>
setGridState({
...gridState,
isMainExtraWide: !gridState.isMainExtraWide,
})
}
isChecked={gridState.isMainExtraWide}
>
Toggle extra-wide content
</Toggle>
{gridState.isMainExtraWide && (
<img
src="https://picsum.photos/seed/picsum/1600"
alt="wide placeholder"
title="wide placeholder image"
/>
)}
</Fragment>
),
[gridState],
);
return (
<PageLayout>
{gridState.isBannerShown && (
<Banner
testId="banner"
id="banner"
skipLinkTitle="Banner"
height={100}
isFixed={gridState.isBannerFixed}
>
<SlotWrapper borderColor={token('color.border.accent.yellow')}>
<SlotLabel>Banner</SlotLabel>
<b>Visit the first focusable element on the page to see the skip links menu</b>
<ToggleFixed slotName="Banner" />
</SlotWrapper>
</Banner>
)}
{gridState.isTopNavigationShown && (
<TopNavigation
testId="topNavigation"
id="top-navigation"
skipLinkTitle="Top Navigation"
height={60}
isFixed={gridState.isTopNavigationFixed}
>
<SlotWrapper borderColor={token('color.border.accent.blue')}>
<SlotLabel>TopNavigation</SlotLabel>
<ToggleFixed slotName="TopNavigation" />
</SlotWrapper>
</TopNavigation>
)}
{gridState.isLeftPanelShown && (
<LeftPanel
testId="leftPanel"
id="left-panel"
skipLinkTitle="Left Panel"
isFixed={gridState.isLeftPanelFixed}
width={200}
>
<SlotWrapper borderColor={token('color.border.accent.orange')}>
<SlotLabel>LeftPanel</SlotLabel>
<ToggleFixed slotName="LeftPanel" />
<ToggleScrollable slotName="LeftPanel" />
</SlotWrapper>
</LeftPanel>
)}
<Content testId="content">
{gridState.isLeftSidebarShown && (
<LeftSidebarWithoutResize
testId="leftSidebar"
id="left-sidebar"
skipLinkTitle="Project Navigation"
isFixed={gridState.isLeftSidebarFixed}
width={250}
>
<SlotWrapper borderColor={token('color.border.accent.green')} hasExtraPadding>
<SlotLabel>LeftSidebar</SlotLabel>
<ToggleFixed slotName="LeftSidebar" />
<ToggleScrollable slotName="LeftSidebar" />
</SlotWrapper>
</LeftSidebarWithoutResize>
)}
{gridState.isMainShown && (
<Main testId="main" id="main" skipLinkTitle="Main Content">
<SlotWrapper borderColor={token('color.border')}>
<SlotLabel>Main</SlotLabel>
<ToggleExtraWide />
<ToggleScrollable slotName="Main" />
</SlotWrapper>
</Main>
)}
{gridState.isRightSidebarShown && (
<RightSidebar
testId="rightSidebar"
id="right-sidebar"
skipLinkTitle="Right Sidebar"
isFixed={gridState.isRightSidebarFixed}
width={200}
>
<SlotWrapper borderColor={token('color.border.accent.green')}>
<SlotLabel>RightSidebar</SlotLabel>
<ToggleFixed slotName="RightSidebar" />
<ToggleScrollable slotName="RightSidebar" />
</SlotWrapper>
</RightSidebar>
)}
</Content>
{gridState.isRightPanelShown && (
<RightPanel
testId="rightPanel"
id="right-panel"
skipLinkTitle="Right Panel"
isFixed={gridState.isRightPanelFixed}
width={200}
>
<SlotWrapper borderColor={token('color.border.accent.orange')}>
<SlotLabel>RightPanel</SlotLabel>
<ToggleFixed slotName="RightPanel" />
<ToggleScrollable slotName="RightPanel" />
</SlotWrapper>
</RightPanel>
)}
<ToggleBox>
<ToggleShown slotName="Banner" />
<ToggleShown slotName="TopNavigation" />
<ToggleShown slotName="LeftPanel" />
<ToggleShown slotName="LeftSidebar" />
<ToggleShown slotName="Main" />
<ToggleShown slotName="RightSidebar" />
<ToggleShown slotName="RightPanel" />
</ToggleBox>
</PageLayout>
);
};
export default BasicGrid;Integrated
PageLayout is designed to work in tandem with the
Atlassian navigation and
side navigation components. This is an example where these
components are used together.
import { useState } from 'react';
import { jsx } from '@emotion/react';
import {
AtlassianNavigation,
Create,
Help,
PrimaryButton,
ProductHome,
} from '@atlaskit/atlassian-navigation';
import noop from '@atlaskit/ds-lib/noop';
import { ConfluenceIcon, ConfluenceLogo } from '@atlaskit/logo';
import ButtonItem from '@atlaskit/menu/button-item';
import MenuGroup from '@atlaskit/menu/menu-group';
import Section from '@atlaskit/menu/section';
import { Content, LeftSidebar, Main, PageLayout, TopNavigation } from '@atlaskit/page-layout';
import { Popup } from '@atlaskit/popup/popup';
import { Header } from '@atlaskit/side-navigation/header';
import { NavigationHeader } from '@atlaskit/side-navigation/navigation-header';
import { NestableNavigationContent } from '@atlaskit/side-navigation/nestable-navigation-content';
import { NestingItem } from '@atlaskit/side-navigation/nesting-item';
import { SideNavigation } from '@atlaskit/side-navigation/side-navigation';
import { SlotLabel, SlotWrapper } from '../common';
export default function ProductLayout(): jsx.JSX.Element {
return (
<PageLayout>
<TopNavigation
isFixed={true}
id="confluence-navigation"
skipLinkTitle="Confluence Navigation"
>
<TopNavigationContents />
</TopNavigation>
<Content testId="content">
<LeftSidebar
isFixed={false}
width={450}
id="project-navigation"
skipLinkTitle="Project Navigation"
testId="left-sidebar"
resizeGrabAreaLabel="Resize Current project sidebar"
resizeButtonLabel="Current project sidebar"
valueTextLabel="Width"
>
<SideNavigationContent />
</LeftSidebar>
<Main id="main-content" skipLinkTitle="Main Content">
<SlotWrapper>
<SlotLabel>Main Content</SlotLabel>
</SlotWrapper>
</Main>
</Content>
</PageLayout>
);
}
function TopNavigationContents() {
return (
<AtlassianNavigation
label="site"
moreLabel="More"
primaryItems={[
<PrimaryButton isHighlighted>Item 1</PrimaryButton>,
<PrimaryButton>Item 2</PrimaryButton>,
<PrimaryButton>Item 3</PrimaryButton>,
<PrimaryButton>Item 4</PrimaryButton>,
]}
renderProductHome={ProductHomeExample}
renderCreate={DefaultCreate}
renderHelp={HelpPopup}
/>
);
}
const SideNavigationContent = () => {
return (
<SideNavigation label="Project navigation" testId="side-navigation">
<NavigationHeader>
<Header description="Sidebar header description">Sidebar Header</Header>
</NavigationHeader>
<NestableNavigationContent initialStack={[]}>
<Section>
<NestingItem id="1" title="Nested Item">
<Section title="Group 1">
<ButtonItem>Item 1</ButtonItem>
<ButtonItem>Item 2</ButtonItem>
</Section>
</NestingItem>
</Section>
</NestableNavigationContent>
</SideNavigation>
);
};
/*
* Components for composing top and side navigation
*/
export const DefaultCreate = (): jsx.JSX.Element => (
<Create buttonTooltip="Create" iconButtonTooltip="Create" onClick={noop} text="Create" />
);
const ProductHomeExample = () => (
<ProductHome onClick={console.log} icon={ConfluenceIcon} logo={ConfluenceLogo} siteTitle="App" />
);
export const HelpPopup = (): jsx.JSX.Element => {
const [isOpen, setIsOpen] = useState(false);
const onClick = () => {
setIsOpen(!isOpen);
};
const onClose = () => {
setIsOpen(false);
};
return (
<Popup
shouldRenderToParent
placement="bottom-start"
content={HelpPopupContent}
isOpen={isOpen}
onClose={onClose}
trigger={(triggerProps) => (
<Help isSelected={isOpen} onClick={onClick} tooltip="Help" {...triggerProps} />
)}
/>
);
};
const HelpPopupContent = () => (
<MenuGroup>
<Section title={'Menu Heading'}>
<ButtonItem>Item 1</ButtonItem>
<ButtonItem>Item 2</ButtonItem>
<ButtonItem>Item 3</ButtonItem>
<ButtonItem>Item 4</ButtonItem>
</Section>
<Section title="Menu Heading with separator" hasSeparator>
<ButtonItem>Item 5</ButtonItem>
<ButtonItem>Item 6</ButtonItem>
</Section>
</MenuGroup>
);The left sidebar houses all the navigation components for the current space a user is in.
PageLayout lets people choose to resize, collapse or expand the sidebar. This lets people get more
screen space to do work when needed.
The left sidebar:
- can be resized using a mouse or a keyboard
- can be expanded/collapsed using a mouse or a keyboard
- has a 'Flyout' feature when hovering over collapsed sidebar to quickly see navigation items
- uses appropriate accessibility APIs to let users with assistive technologies interact with the left sidebar
In this example, the left sidebar can be resized and even collapsed to give more screen space to the main content:
Use the useLeftSidebarFlyoutLock hook to prevent the sidebar flyout from collapsing in some
situations, such as when a pop-up menu has been opened inside the sidebar.
If you need a left sidebar slot without any resize functionality, use the lighter-weight
LeftSidebarWithoutResize component instead of the LeftSidebar component.
Programatically toggling with a keyboard shortcut
A common pattern with our LeftSidebar is to show, hide, and/or toggle the state based on a keyboard shortcut or some external input. For this, we provide utilities that control sidebar state using React Context.
These are the supported options exposed via context:
toggleLeftSidebar()to toggle visibility of the left sidebarcollapseLeftSidebar()to hide the left sidebarexpandLeftSidebar()to show the left sidebar visibleisLeftSidebarCollapsedwhether or not the left sidebar is currently hidden
The context is only available within the LeftSidebar component, so ensure your logic exists as a
child for these to be available, example:
Skip links
Skip links are hidden links that appear on focus and allow people to skip content on the page. We recommend implementing skip links for pages with complex navigation layouts as they allow people navigating by keyboard to skip to different sections of the page.
Page layout automatically generates a global skip link menu based on the sections included inside
PageLayout. To add a section to the skip link menu, give it an ID to allow focus to be placed on
the element, and a skipLinkTitle for the text used to describe the section. Screen readers will read
the skipLinkTitle with the text 'skip to' prepended for context.
Behavior
The skip links menu:
- appears on keyboard focus and is the first focusable item on the page
- can be closed by pressing escape, which brings focus to the first element after the skip link menu
- lists all PageLayout sections that have
skipLinkTitleandidprops set - allows registering of custom skip links through the
CustomSkipLinkcomoponent - uses a focus ring when a link is selected to highlight the selection
To modify the "Skip to:" text, set the skipLinksLabel prop in PageLayout.
On first tab into the example below, you should see the skip link menu appear:
import { jsx } from '@emotion/react';
import {
Banner,
Content,
LeftSidebarWithoutResize,
Main,
PageLayout,
RightPanel,
TopNavigation,
} from '@atlaskit/page-layout';
import { token } from '@atlaskit/tokens';
import { SlotLabel, SlotWrapper } from '../common';
const BasicGrid = (): jsx.JSX.Element => {
return (
<PageLayout>
{
<Banner testId="banner" id="banner" skipLinkTitle="Banner" height={60} isFixed={false}>
<SlotWrapper borderColor={token('color.border.accent.yellow')}>
<SlotLabel>Banner</SlotLabel>
</SlotWrapper>
</Banner>
}
{
<TopNavigation
testId="topNavigation"
id="app-navigation"
skipLinkTitle="App Navigation"
height={60}
isFixed={false}
>
<SlotWrapper borderColor={token('color.border.accent.blue')}>
<SlotLabel>App Navigation</SlotLabel>
</SlotWrapper>
</TopNavigation>
}
<Content testId="content">
{
<LeftSidebarWithoutResize
testId="leftSidebar"
id="space-navigation"
skipLinkTitle="Project Navigation"
isFixed={false}
width={125}
>
<SlotWrapper borderColor={token('color.border.accent.green')} hasExtraPadding>
<SlotLabel isSmall>Space Navigation</SlotLabel>
</SlotWrapper>
</LeftSidebarWithoutResize>
}
{
<Main testId="main" id="main" skipLinkTitle="Main Content">
<SlotWrapper borderColor={token('color.border')} minHeight={400}>
<SlotLabel isSmall>Main Content</SlotLabel>
<p>Visit the first focusable element on the page to see the skip links menu</p>
</SlotWrapper>
</Main>
}
</Content>
{
<RightPanel
testId="rightPanel"
id="help-panel"
skipLinkTitle="Help Panel"
isFixed={false}
width={125}
>
<SlotWrapper borderColor={token('color.border.accent.orange')}>
<SlotLabel>Help Panel</SlotLabel>
</SlotWrapper>
</RightPanel>
}
</PageLayout>
);
};
export default BasicGrid;Custom skip links
Sometimes it may be necessary to add a skip link to a section of the page which is not one of the
slots provided by PageLayout. This is where the useCustomSkipLink hook comes in handy. Here's an
example of using the useCustomSkipLink to set up skip links to elements that are not direct
children of a PageLayout slot.
You can choose the position the link will show up in the menu by using the optional listIndex
prop. Positions are zero-indexed.
Note: Although useCustomSkipLink can link to DOM elements outside of PageLayout using the
HTML id, it needs to be called from within PageLayout, since it relies on the context provider
that wraps PageLayout.
import { useState } from 'react';
import { jsx } from '@emotion/react';
import {
Banner,
Content,
LeftSidebarWithoutResize,
Main,
PageLayout,
RightPanel,
TopNavigation,
useCustomSkipLink,
} from '@atlaskit/page-layout';
import { token } from '@atlaskit/tokens';
import { SlotLabel, SlotWrapper } from '../common';
const baseId = 'list-example';
const ListExample = () => {
const skipLinkCount = document.querySelectorAll('[data-skip-link-wrapper] a').length;
const [id, setId] = useState(baseId);
const [position, setPosition] = useState(2);
useCustomSkipLink(id, 'List example', position);
const moveDown = () => {
setPosition((pos) => Math.min(pos + 1, skipLinkCount));
};
const moveUp = () => {
setPosition((pos) => Math.max(pos - 1, 0));
};
const changeId = () => {
setId(`${baseId}${Date.now()}`);
};
return (
<ol id={id}>
<li>
This list is an example of an element that you can skipped to that is <em>nested inside</em>{' '}
the PageLayout component using the useCustomSkipLink hook.
</li>
<li>
Current position of <i>List example</i> skip link: <b>{position}</b>
<button onClick={moveUp} type="button">
Move up
</button>
<button onClick={moveDown} type="button">
Move down
</button>
</li>
<li>
Current id: <b>{id}</b>
<button onClick={changeId} type="button">
Update the id of this section
</button>
</li>
</ol>
);
};
// Registering custom skip links
// whose targets live outside PageLayout
const RegisterCustomSkipLinks = () => {
useCustomSkipLink('external-footer', 'External Footer', 7);
useCustomSkipLink('intro-section', 'Intro section', 0);
return null;
};
const BasicGrid = (): jsx.JSX.Element => {
return (
<div>
<section id="intro-section">
<p>
This section isn't part of PageLayout, but you can still use skip links to jump to it with
the `useCustomSkipLink` hook that is exported from PageLayout.
</p>
</section>
<PageLayout>
<RegisterCustomSkipLinks />
<Banner testId="banner" id="banner" skipLinkTitle="Banner" height={60} isFixed={false}>
<SlotWrapper borderColor={token('color.border.accent.yellow')}>
<SlotLabel>Banner</SlotLabel>
</SlotWrapper>
</Banner>
<TopNavigation
testId="topNavigation"
id="app-navigation"
skipLinkTitle="App Navigation"
height={60}
isFixed={false}
>
<SlotWrapper borderColor={token('color.border.accent.blue')}>
<SlotLabel>App Navigation</SlotLabel>
</SlotWrapper>
</TopNavigation>
<Content testId="content">
<LeftSidebarWithoutResize
testId="leftSidebar"
id="space-navigation"
skipLinkTitle="Project Navigation"
isFixed={false}
width={125}
>
<SlotWrapper borderColor={token('color.border.accent.green')} hasExtraPadding>
<SlotLabel isSmall>Space Navigation</SlotLabel>
</SlotWrapper>
</LeftSidebarWithoutResize>
<Main testId="main" id="main" skipLinkTitle="Main Content">
<SlotWrapper borderColor={token('color.border')} minHeight={400}>
<SlotLabel isSmall>Main Content</SlotLabel>
<p>Visit the first focusable element on the page to see the skip links menu</p>
<ListExample />
</SlotWrapper>
</Main>
</Content>
<RightPanel
testId="rightPanel"
id="help-panel"
skipLinkTitle="Help Panel"
isFixed={false}
width={125}
>
<SlotWrapper borderColor={token('color.border.accent.orange')}>
<SlotLabel>Help Panel</SlotLabel>
<p>It's also possible to</p>
</SlotWrapper>
</RightPanel>
</PageLayout>
<footer id="external-footer">
This footer isn't part of PageLayout, but you can still use skip links to jump to it with
the `useCustomSkipLink` hook that is exported from PageLayout.
</footer>
</div>
);
};
export default BasicGrid;Server side rendering
Here is an example of a server-rendered page.
import { Fragment, useCallback, useState } from 'react';
import { css, jsx } from '@emotion/react';
import {
Banner,
BANNER_HEIGHT,
Content,
LEFT_PANEL_WIDTH,
LEFT_SIDEBAR_WIDTH,
LeftPanel,
LeftSidebar,
Main,
PageLayout,
RIGHT_PANEL_WIDTH,
RIGHT_SIDEBAR_WIDTH,
RightPanel,
RightSidebar,
TOP_NAVIGATION_HEIGHT,
TopNavigation,
} from '@atlaskit/page-layout';
import { token } from '@atlaskit/tokens';
import { ScrollableContent, SlotLabel, SlotWrapper, Toggle, ToggleBox } from '../common';
type SlotName =
| 'Banner'
| 'TopNavigation'
| 'LeftPanel'
| 'LeftSidebar'
| 'Main'
| 'RightSidebar'
| 'RightPanel';
const serverRenderedStyles = css({
height: 'auto',
position: 'absolute',
backgroundColor: token('color.background.neutral.subtle'),
insetBlockEnd: 0,
insetBlockStart: `calc(${TOP_NAVIGATION_HEIGHT} + ${BANNER_HEIGHT})`,
insetInlineEnd: `calc(${RIGHT_PANEL_WIDTH} + ${RIGHT_SIDEBAR_WIDTH})`,
insetInlineStart: `calc(${LEFT_PANEL_WIDTH} + ${LEFT_SIDEBAR_WIDTH})`,
transition: 'left 300ms',
});
const draggingStyles = css({
[`[data-is-sidebar-dragging] &`]: {
transition: 'none',
},
});
const ServerRenderedPage = () => {
return (
<SlotWrapper borderColor={token('color.border')} css={[serverRenderedStyles, draggingStyles]}>
Server rendered page. Added as a sibling to Grid componenet
</SlotWrapper>
);
};
const initialState = {
isBannerShown: true,
isTopNavigationShown: true,
isLeftPanelShown: true,
isLeftSidebarShown: true,
isRightSidebarShown: true,
isRightPanelShown: true,
isBannerFixed: false,
isTopNavigationFixed: false,
isLeftPanelFixed: false,
isLeftPanelScrollable: false,
isLeftSidebarFixed: false,
isLeftSidebarScrollable: false,
isRightSidebarFixed: false,
isRightSidebarScrollable: false,
isRightPanelFixed: false,
isRightPanelScrollable: false,
};
const BasicGrid = (): jsx.JSX.Element => {
const [gridState, setGridState] = useState(initialState);
const ToggleFixed = useCallback(
({ slotName }: { slotName: SlotName }) => {
const gridKey = `is${slotName}Fixed` as keyof typeof gridState;
return (
<Toggle
id={`${slotName}--fixed`}
isChecked={gridState[gridKey]}
onChange={() => setGridState({ ...gridState, [gridKey]: !gridState[gridKey] })}
>
Toggle fixed
</Toggle>
);
},
[gridState],
);
const ToggleScrollable = useCallback(
({ slotName }: { slotName: SlotName }) => {
const gridKey = `is${slotName}Scrollable` as keyof typeof gridState;
return (
<Fragment>
<Toggle
id={`${slotName}--scrollable`}
isChecked={gridState[gridKey]}
onChange={() => setGridState({ ...gridState, [gridKey]: !gridState[gridKey] })}
>
Toggle scrollable content
</Toggle>
{gridState[gridKey] && <ScrollableContent />}
</Fragment>
);
},
[gridState],
);
const ToggleShown = useCallback(
({ slotName }: { slotName: SlotName }) => {
const gridKey = `is${slotName}Shown` as keyof typeof gridState;
return (
<Toggle
id={`${slotName}--shown`}
onChange={() => setGridState({ ...gridState, [gridKey]: !gridState[gridKey] })}
isChecked={!gridState[gridKey]}
>{`${gridState[gridKey] ? 'Hide' : 'Show'} ${slotName}`}</Toggle>
);
},
[gridState],
);
return (
<Fragment>
<PageLayout>
{gridState.isBannerShown && (
<Banner isFixed={gridState.isBannerFixed}>
<SlotWrapper borderColor={token('color.border.accent.yellow')}>
<SlotLabel>Banner</SlotLabel>
<ToggleFixed slotName="Banner" />
</SlotWrapper>
</Banner>
)}
{gridState.isTopNavigationShown && (
<TopNavigation isFixed={gridState.isTopNavigationFixed}>
<SlotWrapper borderColor={token('color.border.accent.blue')}>
<SlotLabel>TopNavigation</SlotLabel>
<ToggleFixed slotName="TopNavigation" />
</SlotWrapper>
</TopNavigation>
)}
{gridState.isLeftPanelShown && (
<LeftPanel isFixed={gridState.isLeftPanelFixed}>
<SlotWrapper borderColor={token('color.border.accent.orange')}>
<SlotLabel>LeftPanel</SlotLabel>
<ToggleFixed slotName="LeftPanel" />
<ToggleScrollable slotName="LeftPanel" />
</SlotWrapper>
</LeftPanel>
)}
<Content>
{gridState.isLeftSidebarShown && (
<LeftSidebar isFixed={gridState.isLeftSidebarFixed}>
<SlotWrapper borderColor={token('color.border.accent.green')}>
<SlotLabel>LeftSidebar</SlotLabel>
<ToggleFixed slotName="LeftSidebar" />
<ToggleScrollable slotName="LeftSidebar" />
</SlotWrapper>
</LeftSidebar>
)}
<Main>{''}</Main>
{gridState.isRightSidebarShown && (
<RightSidebar isFixed={gridState.isRightSidebarFixed} width={200}>
<SlotWrapper borderColor={token('color.border.accent.green')}>
<SlotLabel>RightSidebar</SlotLabel>
<ToggleFixed slotName="RightSidebar" />
<ToggleScrollable slotName="RightSidebar" />
</SlotWrapper>
</RightSidebar>
)}
</Content>
{gridState.isRightPanelShown && (
<RightPanel isFixed={gridState.isRightPanelFixed} width={200}>
<SlotWrapper borderColor={token('color.border.accent.orange')}>
<SlotLabel>RightPanel</SlotLabel>
<ToggleFixed slotName="RightPanel" />
<ToggleScrollable slotName="RightPanel" />
</SlotWrapper>
</RightPanel>
)}
<ToggleBox>
<ToggleShown slotName="Banner" />
<ToggleShown slotName="TopNavigation" />
<ToggleShown slotName="LeftPanel" />
<ToggleShown slotName="LeftSidebar" />
<ToggleShown slotName="RightSidebar" />
<ToggleShown slotName="RightPanel" />
</ToggleBox>
</PageLayout>
<ServerRenderedPage />
</Fragment>
);
};
export default BasicGrid;Using CSS variables
@atlaskit/page-layout exports a set of variables that can be used to setup the grid on non-react
pages. The following variables are exported:
LEFT_PANEL_WIDTHRIGHT_PANEL_WIDTHLEFT_SIDEBAR_WIDTHRIGHT_SIDEBAR_WIDTHTOP_NAVIGATION_HEIGHTBANNER_HEIGHTLEFT_SIDEBAR_FLYOUT_WIDTH
Always use these variables instead of accessing the CSS variable names directly because these variables have sensible fallback values baked into them. Accessing the variables directly runs the risk of setting the intended styles to "unset" which can cause unintended styling issues.
See the server rendered example for a more complete example of how to use these variables.