-
Notifications
You must be signed in to change notification settings - Fork 843
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[New Nav Feature] Final docs examples and patterns (#3117)
* 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
Showing
32 changed files
with
794 additions
and
56 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
280 changes: 280 additions & 0 deletions
280
src-docs/src/views/collapsible_nav/collapsible_nav_all.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
}; |
Oops, something went wrong.