Skip to content

Commit

Permalink
[KibanaPageTemplate] Adds collapsibility to solutionNav (#103192) (#…
Browse files Browse the repository at this point in the history
…104867)

Adds the ability to collapse the sidenav. This should work in all solutions. It also adds breakpoints that turn it into a flyout at lower screen widths.

Co-authored-by: Caroline Horn <[email protected]>
  • Loading branch information
snide and cchaos authored Jul 8, 2021
1 parent 1e22a0b commit 8de4c60
Show file tree
Hide file tree
Showing 18 changed files with 1,152 additions and 2,379 deletions.

Large diffs are not rendered by default.

1,871 changes: 655 additions & 1,216 deletions src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap

Large diffs are not rendered by default.

11 changes: 2 additions & 9 deletions src/core/public/chrome/ui/header/collapsible_nav.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ function mockProps() {
navigateToApp: () => Promise.resolve(),
navigateToUrl: () => Promise.resolve(),
customNavLink$: new BehaviorSubject(undefined),
button: <button />,
};
}

Expand All @@ -76,19 +77,11 @@ function clickGroup(component: ReactWrapper, group: string) {
describe('CollapsibleNav', () => {
// this test is mostly an "EUI works as expected" sanity check
it('renders the default nav', () => {
const onLock = sinon.spy();
const component = mount(<CollapsibleNav {...mockProps()} onIsLockedUpdate={onLock} />);
const component = mount(<CollapsibleNav {...mockProps()} />);
expect(component).toMatchSnapshot();

component.setProps({ isOpen: true });
expect(component).toMatchSnapshot();

component.setProps({ isLocked: true });
expect(component).toMatchSnapshot();

// limit the find to buttons because jest also renders data-test-subj on a JSX wrapper element
component.find('button[data-test-subj="collapsible-nav-lock"]').simulate('click');
expect(onLock.callCount).toEqual(1);
});

it('renders links grouped by category', () => {
Expand Down
57 changes: 34 additions & 23 deletions src/core/public/chrome/ui/header/collapsible_nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ import {
EuiListGroup,
EuiListGroupItem,
EuiShowFor,
EuiText,
EuiCollapsibleNavProps,
EuiButton,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { groupBy, sortBy } from 'lodash';
import React, { Fragment, useRef } from 'react';
import useObservable from 'react-use/lib/useObservable';
Expand Down Expand Up @@ -78,6 +80,7 @@ interface Props {
navigateToApp: InternalApplicationStart['navigateToApp'];
navigateToUrl: InternalApplicationStart['navigateToUrl'];
customNavLink$: Rx.Observable<ChromeNavLink | undefined>;
button: EuiCollapsibleNavProps['button'];
}

export function CollapsibleNav({
Expand All @@ -91,6 +94,7 @@ export function CollapsibleNav({
closeNav,
navigateToApp,
navigateToUrl,
button,
...observables
}: Props) {
const navLinks = useObservable(observables.navLinks$, []).filter((link) => !link.hidden);
Expand Down Expand Up @@ -121,8 +125,10 @@ export function CollapsibleNav({
defaultMessage: 'Primary',
})}
isOpen={isNavOpen}
isDocked={isLocked}
onClose={closeNav}
button={button}
ownFocus={false}
size={240}
>
{customNavLink && (
<Fragment>
Expand Down Expand Up @@ -192,16 +198,18 @@ export function CollapsibleNav({
</EuiFlexItem>

{/* Recently viewed */}
<EuiCollapsibleNavGroup
key="recentlyViewed"
background="light"
title={i18n.translate('core.ui.recentlyViewed', { defaultMessage: 'Recently viewed' })}
isCollapsible={true}
initialIsOpen={getIsCategoryOpen('recentlyViewed', storage)}
onToggle={(isCategoryOpen) => setIsCategoryOpen('recentlyViewed', isCategoryOpen, storage)}
data-test-subj="collapsibleNavGroup-recentlyViewed"
>
{recentlyAccessed.length > 0 ? (
{recentlyAccessed.length > 0 && (
<EuiCollapsibleNavGroup
key="recentlyViewed"
background="light"
title={i18n.translate('core.ui.recentlyViewed', { defaultMessage: 'Recently viewed' })}
isCollapsible={true}
initialIsOpen={getIsCategoryOpen('recentlyViewed', storage)}
onToggle={(isCategoryOpen) =>
setIsCategoryOpen('recentlyViewed', isCategoryOpen, storage)
}
data-test-subj="collapsibleNavGroup-recentlyViewed"
>
<EuiListGroup
aria-label={i18n.translate('core.ui.recentlyViewedAriaLabel', {
defaultMessage: 'Recently viewed links',
Expand Down Expand Up @@ -233,16 +241,8 @@ export function CollapsibleNav({
size="s"
className="kbnCollapsibleNav__recentsListGroup"
/>
) : (
<EuiText size="s" color="subdued" style={{ padding: '0 8px 8px' }}>
<p>
{i18n.translate('core.ui.EmptyRecentlyViewed', {
defaultMessage: 'No recently viewed items',
})}
</p>
</EuiText>
)}
</EuiCollapsibleNavGroup>
</EuiCollapsibleNavGroup>
)}

<EuiHorizontalRule margin="none" />

Expand All @@ -255,6 +255,7 @@ export function CollapsibleNav({
<EuiCollapsibleNavGroup
key={category.id}
iconType={category.euiIconType}
iconSize="m"
title={category.label}
isCollapsible={true}
initialIsOpen={getIsCategoryOpen(category.id, storage)}
Expand Down Expand Up @@ -286,7 +287,7 @@ export function CollapsibleNav({
))}

{/* Docking button only for larger screens that can support it*/}
<EuiShowFor sizes={['l', 'xl']}>
<EuiShowFor sizes={'none'}>
<EuiCollapsibleNavGroup>
<EuiListGroup flush>
<EuiListGroupItem
Expand Down Expand Up @@ -324,6 +325,16 @@ export function CollapsibleNav({
</EuiCollapsibleNavGroup>
</EuiShowFor>
</EuiFlexItem>
{/* Quick addition of that "ADD DATA" button everyone wants :) Feel free to remove though. */}
<EuiFlexItem grow={false}>
{/* Span fakes the nav group into not being the first item and therefore adding a top border */}
<span />
<EuiCollapsibleNavGroup>
<EuiButton fill fullWidth iconType="plusInCircleFilled">
<FormattedMessage id="core.ui.primaryNavSection.addDataBtn" defaultMessage="Add Data" />
</EuiButton>
</EuiCollapsibleNavGroup>
</EuiFlexItem>
</EuiCollapsibleNav>
);
}
69 changes: 35 additions & 34 deletions src/core/public/chrome/ui/header/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -160,19 +160,41 @@ export function Header({
<EuiHeader position="fixed" className="header__secondBar">
<EuiHeaderSection grow={false}>
<EuiHeaderSectionItem border="right" className="header__toggleNavButtonSection">
<EuiHeaderSectionItemButton
data-test-subj="toggleNavButton"
aria-label={i18n.translate('core.ui.primaryNav.toggleNavAriaLabel', {
defaultMessage: 'Toggle primary navigation',
})}
onClick={() => setIsNavOpen(!isNavOpen)}
aria-expanded={isNavOpen}
aria-pressed={isNavOpen}
aria-controls={navId}
ref={toggleCollapsibleNavRef}
>
<EuiIcon type="menu" size="m" />
</EuiHeaderSectionItemButton>
<CollapsibleNav
appId$={application.currentAppId$}
id={navId}
isLocked={isLocked}
navLinks$={observables.navLinks$}
recentlyAccessed$={observables.recentlyAccessed$}
isNavOpen={isNavOpen}
homeHref={homeHref}
basePath={basePath}
navigateToApp={application.navigateToApp}
navigateToUrl={application.navigateToUrl}
onIsLockedUpdate={onIsLockedUpdate}
closeNav={() => {
setIsNavOpen(false);
if (toggleCollapsibleNavRef.current) {
toggleCollapsibleNavRef.current.focus();
}
}}
customNavLink$={observables.customNavLink$}
button={
<EuiHeaderSectionItemButton
data-test-subj="toggleNavButton"
aria-label={i18n.translate('core.ui.primaryNav.toggleNavAriaLabel', {
defaultMessage: 'Toggle primary navigation',
})}
onClick={() => setIsNavOpen(!isNavOpen)}
aria-expanded={isNavOpen}
aria-pressed={isNavOpen}
aria-controls={navId}
ref={toggleCollapsibleNavRef}
>
<EuiIcon type="menu" size="m" />
</EuiHeaderSectionItemButton>
}
/>
</EuiHeaderSectionItem>

<HeaderNavControls side="left" navControls$={observables.navControlsLeft$} />
Expand Down Expand Up @@ -205,27 +227,6 @@ export function Header({
</EuiHeaderSection>
</EuiHeader>
</div>

<CollapsibleNav
appId$={application.currentAppId$}
id={navId}
isLocked={isLocked}
navLinks$={observables.navLinks$}
recentlyAccessed$={observables.recentlyAccessed$}
isNavOpen={isNavOpen}
homeHref={homeHref}
basePath={basePath}
navigateToApp={application.navigateToApp}
navigateToUrl={application.navigateToUrl}
onIsLockedUpdate={onIsLockedUpdate}
closeNav={() => {
setIsNavOpen(false);
if (toggleCollapsibleNavRef.current) {
toggleCollapsibleNavRef.current.focus();
}
}}
customNavLink$={observables.customNavLink$}
/>
</header>
</>
);
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 7 additions & 11 deletions src/plugins/kibana_react/public/page_template/page_template.scss
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
$euiSideNavEmphasizedBackgroundColor: transparentize($euiColorLightShade, .7);

.kbnPageTemplate__pageSideBar {
padding: $euiSizeL;
background:
linear-gradient(160deg, $euiSideNavEmphasizedBackgroundColor 0, $euiSideNavEmphasizedBackgroundColor $euiSizeXL, rgba(#FFF, 0) 0),
linear-gradient(175deg, $euiSideNavEmphasizedBackgroundColor 0, $euiSideNavEmphasizedBackgroundColor $euiSize, rgba(#FFF, 0) 0);
}
overflow: hidden;

@include euiCanAnimate {
transition: min-width $euiAnimSpeedFast $euiAnimSlightResistance;
}

@include euiBreakpoint('xs','s') {
.kbnPageTemplate__pageSideBar {
width: auto;
padding: 0;
&.kbnPageTemplate__pageSideBar--shrink {
min-width: $euiSizeXXL + $euiSize;
}
}
Loading

0 comments on commit 8de4c60

Please sign in to comment.