Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[New Nav Feature] Final docs examples and patterns #3117

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 }}>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a bunch of style tags in here. I know it's just docs, but likely this stuff will just be copy pasted. Should we make some selectors for these things since they'll need to make it anyway?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's actually good....? Just because they're necessary for the scrolling to work properly...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I guess I just meant, none of these looked dynamic. Should we make some actual css selectors rather than style tags, since they seem necessary for the component to work.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok so here's what's going on with the style tags. Really the only ones are:

  1. flex-shrink: 0 on EuiFlexItems
  2. maxHeight on the collapsible nav group

Remove both of those and the nav still works it's just all one big scroll. This implementation is a custom one where we (Kibana) wants the pinned section to scroll independently of the rest. They're not necessary for the Collapsible nav to work.

If we add a class for no. 1, It would be some arbitrary .eui-flexShrinkNone which I really don't think we want to go down this utility class path. Or it would be a specific .euiCollapsibleNavGroup__wrapper that simply adds the that one property in which case most consumers won't know why they need it or what it's doing.

We can't add a class for no. 2, because the value is completely dependent on the consumer and honestly just a best guess by me right now.

So really then it's back to, do we really want to add some random class for flex-shrink: 0 when ,honestly, shrink should just be a prop on EuiFlexItem like grow. But I tried adding this and it got a bit complicated so I abandoned.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking it was more something like euiCollapsableNav__something that just applied that stuff. Either way, your call. Just felt weird copy/pasting style tags.

{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