Skip to content

Commit

Permalink
[New Nav Feature] Final docs examples and patterns (#3117)
Browse files Browse the repository at this point in the history
* Fixed the passing of `size` from EuiListGroup to items
* Fix padding of `EuiCollapsibleNavGroup` if extra action is passed
* Reset line-height of heading in button
* Fix `title` type for EuiCollapsibleNavGroup
* Starting full pattern example
* Adjusted EuiFlyout position based on fixed EuiHeader
* Utility CSS helper for simple overflow scroll without shadows
* Adding GuideFullScreen component
* Adding content and storing states
* Fixing incompatible type with `href`
* Fix EuiHorizontalSizing when in flex groups
* Cleanup
* Quick fix to nav heading
* Using subdued text color
* Ghost button in dark section for now
* Some browser fixes
* Fixes for mobile
  - Including the addition of the EuiCollapsibleNavToggle component
* render prop pattern
* clean up
* Adding accessibility (?)
* One more a11y piece
* Addressing some a11y concerns
- Focus state for accordions without arrow toggles (underline)
- Added link name in pin/unpin titles
* More a11y fixes
  • Loading branch information
cchaos authored and cchaos committed Mar 26, 2020
1 parent dbe9792 commit b432d11
Show file tree
Hide file tree
Showing 32 changed files with 794 additions and 56 deletions.
8 changes: 8 additions & 0 deletions src-docs/src/components/guide_components.scss
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,14 @@ $guideZLevelHighest: $euiZLevel9 + 1000;
height: 1px;
}

.guideFullScreenOverlay {
position: fixed;
width: 100%;
height: 100%;
left: 0;
top: 0;
}

@import '../views/guidelines/index';
@import 'guide_section/index';
@import 'guide_rule/index';
Expand Down
32 changes: 32 additions & 0 deletions src-docs/src/services/full_screen/full_screen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React, {
useState,
Fragment,
FunctionComponent,
ReactElement,
ReactNode,
} from 'react';

import { EuiFocusTrap } from '../../../../src/components/focus_trap';
import { EuiButton } from '../../../../src/components/button';

export const GuideFullScreen: FunctionComponent<{
children: (setFullScreen: (isFullScreen: boolean) => void) => ReactElement;
buttonText?: ReactNode;
isFullScreen?: boolean;
}> = ({
children,
isFullScreen = false,
buttonText = 'Show fullscreen demo',
}) => {
const [fullScreen, setFullScreen] = useState(isFullScreen);

return (
<Fragment>
<EuiButton onClick={() => setFullScreen(true)} iconType="fullScreen">
{buttonText}
</EuiButton>

{fullScreen && <EuiFocusTrap>{children(setFullScreen)}</EuiFocusTrap>}
</Fragment>
);
};
11 changes: 10 additions & 1 deletion src-docs/src/views/collapsible_nav/collapsible_nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,18 @@ export default () => {

return (
<>
<EuiButton onClick={() => setNavIsOpen(!navIsOpen)}>Toggle nav</EuiButton>
<EuiButton
onClick={() => setNavIsOpen(!navIsOpen)}
aria-label="Toggle main navigation"
aria-controls="guideCollapsibleNavExampleNav"
aria-expanded={navIsOpen}
aria-pressed={navIsOpen}>
Toggle nav
</EuiButton>
{navIsOpen && (
<EuiCollapsibleNav
id="guideCollapsibleNavExampleNav"
aria-label="Example of main navigation flyout"
docked={navIsDocked}
onClose={() => setNavIsOpen(false)}>
<div style={{ padding: 16 }}>
Expand Down
280 changes: 280 additions & 0 deletions src-docs/src/views/collapsible_nav/collapsible_nav_all.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
import React, { useState } from 'react';
import _ from 'lodash';

import {
EuiCollapsibleNav,
EuiCollapsibleNavToggle,
EuiCollapsibleNavGroup,
} from '../../../../src/components/collapsible_nav';
import {
EuiHeaderSectionItemButton,
EuiHeaderLogo,
EuiHeader,
} from '../../../../src/components/header';
import { EuiIcon } from '../../../../src/components/icon';
import { EuiButtonEmpty } from '../../../../src/components/button';
import { EuiPage } from '../../../../src/components/page';
import {
EuiPinnableListGroup,
EuiListGroupItem,
EuiPinnableListGroupItemProps,
} from '../../../../src/components/list_group';
import { EuiFlexItem } from '../../../../src/components/flex';
import { EuiHorizontalRule } from '../../../../src/components/horizontal_rule';
import { GuideFullScreen } from '../../services/full_screen/full_screen';

import {
DeploymentsGroup,
KibanaNavLinks,
SecurityGroup,
} from './collapsible_nav_list';
import { EuiShowFor } from '../../../../src/components/responsive';

const TopLinks = [
{ label: 'Home', iconType: 'home', isActive: true, 'aria-current': true },
];
const KibanaLinks: EuiPinnableListGroupItemProps[] = KibanaNavLinks.map(
link => {
return {
...link,
href: '#/navigation/collapsible-nav',
};
}
);
const LearnLinks: EuiPinnableListGroupItemProps[] = [
{ label: 'Docs', href: '#/navigation/collapsible-nav' },
{ label: 'Blogs', href: '#/navigation/collapsible-nav' },
{ label: 'Webinars', href: '#/navigation/collapsible-nav' },
{ label: 'Elastic.co', href: 'https://elastic.co' },
];

export default () => {
const [navIsOpen, setNavIsOpen] = useState(
JSON.parse(String(localStorage.getItem('navIsDocked'))) || false
);
const [navIsDocked, setNavIsDocked] = useState(
JSON.parse(String(localStorage.getItem('navIsDocked'))) || false
);

/**
* Accordion toggling
*/
const [openGroups, setOpenGroups] = useState(
JSON.parse(String(localStorage.getItem('openNavGroups'))) || [
'Kibana',
'Learn',
]
);

// Save which groups are open and which are not with state and local store
const toggleAccordion = (isOpen: boolean, title?: string) => {
if (!title) return;
const itExists = openGroups.includes(title);
if (isOpen) {
if (itExists) return;
openGroups.push(title);
} else {
const index = openGroups.indexOf(title);
if (index > -1) {
openGroups.splice(index, 1);
}
}
setOpenGroups([...openGroups]);
localStorage.setItem('openNavGroups', JSON.stringify(openGroups));
};

/**
* Pinning
*/
const [pinnedItems, setPinnedItems] = useState<
EuiPinnableListGroupItemProps[]
>(JSON.parse(String(localStorage.getItem('pinnedItems'))) || []);

const addPin = (item: any) => {
if (!item || _.find(pinnedItems, { label: item.label })) {
return;
}
item.pinned = true;
const newPinnedItems = pinnedItems ? pinnedItems.concat(item) : [item];
setPinnedItems(newPinnedItems);
localStorage.setItem('pinnedItems', JSON.stringify(newPinnedItems));
};

const removePin = (item: any) => {
const pinIndex = _.findIndex(pinnedItems, { label: item.label });
if (pinIndex > -1) {
item.pinned = false;
const newPinnedItems = pinnedItems;
newPinnedItems.splice(pinIndex, 1);
setPinnedItems([...newPinnedItems]);
localStorage.setItem('pinnedItems', JSON.stringify(newPinnedItems));
}
};

function alterLinksWithCurrentState(
links: EuiPinnableListGroupItemProps[],
showPinned = false
): EuiPinnableListGroupItemProps[] {
return links.map(link => {
const { pinned, ...rest } = link;
return {
pinned: showPinned ? pinned : false,
...rest,
};
});
}

function addLinkNameToPinTitle(listItem: EuiPinnableListGroupItemProps) {
return `Pin ${listItem.label} to top`;
}

function addLinkNameToUnpinTitle(listItem: EuiPinnableListGroupItemProps) {
return `Unpin ${listItem.label}`;
}

const leftSectionItems = [
<EuiCollapsibleNavToggle navIsDocked={navIsDocked}>
<EuiHeaderSectionItemButton
aria-label="Toggle main navigation"
aria-controls="guideCollapsibleNavAllExampleNav"
aria-expanded={navIsOpen}
aria-pressed={navIsOpen}
onClick={() => setNavIsOpen(!navIsOpen)}>
<EuiIcon type={'menu'} size="m" aria-hidden="true" />
</EuiHeaderSectionItemButton>
</EuiCollapsibleNavToggle>,
<EuiHeaderLogo iconType="logoElastic">Elastic</EuiHeaderLogo>,
];

return (
<GuideFullScreen>
{setIsFullScreen => (
<React.Fragment>
<EuiHeader
position="fixed"
sections={[
{
items: leftSectionItems,
borders: 'right',
},
{
items: [
<EuiButtonEmpty
iconType="minimize"
onClick={() => setIsFullScreen(false)}>
Exit full screen
</EuiButtonEmpty>,
],
},
]}
/>

{navIsOpen && (
<EuiCollapsibleNav
id="guideCollapsibleNavAllExampleNav"
aria-label="Main navigation"
docked={navIsDocked}
onClose={() => setNavIsOpen(false)}>
{/* Dark deployments section */}
<EuiFlexItem grow={false} style={{ flexShrink: 0 }}>
{DeploymentsGroup}
</EuiFlexItem>

{/* Shaded pinned section always with a home item */}
<EuiFlexItem grow={false} style={{ flexShrink: 0 }}>
<EuiCollapsibleNavGroup
background="light"
className="eui-yScroll"
style={{ maxHeight: '40vh' }}>
<EuiPinnableListGroup
aria-label="Pinned links" // A11y : Since this group doesn't have a visible `title` it should be provided an accessible description
listItems={alterLinksWithCurrentState(TopLinks).concat(
alterLinksWithCurrentState(pinnedItems, true)
)}
unpinTitle={addLinkNameToUnpinTitle}
onPinClick={removePin}
maxWidth="none"
color="subdued"
gutterSize="none"
size="s"
/>
</EuiCollapsibleNavGroup>
</EuiFlexItem>

<EuiHorizontalRule margin="none" />

{/* BOTTOM */}
<EuiFlexItem className="eui-yScroll">
{/* Kibana section */}
<EuiCollapsibleNavGroup
title="Kibana"
iconType="logoKibana"
isCollapsible={true}
initialIsOpen={openGroups.includes('Kibana')}
onToggle={(isOpen: boolean) =>
toggleAccordion(isOpen, 'Kibana')
}>
<EuiPinnableListGroup
aria-label="Kibana" // A11y : EuiCollapsibleNavGroup can't correctly pass the `title` as the `aria-label` to the right HTML element, so it must be added manually
listItems={alterLinksWithCurrentState(KibanaLinks)}
pinTitle={addLinkNameToPinTitle}
onPinClick={addPin}
maxWidth="none"
color="subdued"
gutterSize="none"
size="s"
/>
</EuiCollapsibleNavGroup>

{/* Security callout */}
{SecurityGroup}

{/* Learn section */}
<EuiCollapsibleNavGroup
title="Learn"
iconType="training"
isCollapsible={true}
initialIsOpen={openGroups.includes('Learn')}
onToggle={(isOpen: boolean) =>
toggleAccordion(isOpen, 'Learn')
}>
<EuiPinnableListGroup
aria-label="Learn" // A11y : EuiCollapsibleNavGroup can't correctly pass the `title` as the `aria-label` to the right HTML element, so it must be added manually
listItems={alterLinksWithCurrentState(LearnLinks)}
pinTitle={addLinkNameToPinTitle}
onPinClick={addPin}
maxWidth="none"
color="subdued"
gutterSize="none"
size="s"
/>
</EuiCollapsibleNavGroup>

{/* Docking button only for larger screens that can support it*/}
<EuiShowFor sizes={['l', 'xl']}>
<EuiCollapsibleNavGroup>
<EuiListGroupItem
size="xs"
color="subdued"
label={`${navIsDocked ? 'Undock' : 'Dock'} navigation`}
onClick={() => {
setNavIsDocked(!navIsDocked);
localStorage.setItem(
'navIsDocked',
JSON.stringify(!navIsDocked)
);
}}
iconType={navIsDocked ? 'lock' : 'lockOpen'}
/>
</EuiCollapsibleNavGroup>
</EuiShowFor>
</EuiFlexItem>
</EuiCollapsibleNav>
)}

<EuiPage className="guideFullScreenOverlay" />
</React.Fragment>
)}
</GuideFullScreen>
);
};
Loading

0 comments on commit b432d11

Please sign in to comment.