Skip to content

Commit 8de4c60

Browse files
snidecchaos
andauthored
[KibanaPageTemplate] Adds collapsibility to solutionNav (#103192) (#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]>
1 parent 1e22a0b commit 8de4c60

File tree

18 files changed

+1152
-2379
lines changed

18 files changed

+1152
-2379
lines changed

src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap

Lines changed: 130 additions & 734 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap

Lines changed: 655 additions & 1216 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/core/public/chrome/ui/header/collapsible_nav.test.tsx

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ function mockProps() {
5656
navigateToApp: () => Promise.resolve(),
5757
navigateToUrl: () => Promise.resolve(),
5858
customNavLink$: new BehaviorSubject(undefined),
59+
button: <button />,
5960
};
6061
}
6162

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

8383
component.setProps({ isOpen: true });
8484
expect(component).toMatchSnapshot();
85-
86-
component.setProps({ isLocked: true });
87-
expect(component).toMatchSnapshot();
88-
89-
// limit the find to buttons because jest also renders data-test-subj on a JSX wrapper element
90-
component.find('button[data-test-subj="collapsible-nav-lock"]').simulate('click');
91-
expect(onLock.callCount).toEqual(1);
9285
});
9386

9487
it('renders links grouped by category', () => {

src/core/public/chrome/ui/header/collapsible_nav.tsx

Lines changed: 34 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@ import {
1515
EuiListGroup,
1616
EuiListGroupItem,
1717
EuiShowFor,
18-
EuiText,
18+
EuiCollapsibleNavProps,
19+
EuiButton,
1920
} from '@elastic/eui';
2021
import { i18n } from '@kbn/i18n';
22+
import { FormattedMessage } from '@kbn/i18n/react';
2123
import { groupBy, sortBy } from 'lodash';
2224
import React, { Fragment, useRef } from 'react';
2325
import useObservable from 'react-use/lib/useObservable';
@@ -78,6 +80,7 @@ interface Props {
7880
navigateToApp: InternalApplicationStart['navigateToApp'];
7981
navigateToUrl: InternalApplicationStart['navigateToUrl'];
8082
customNavLink$: Rx.Observable<ChromeNavLink | undefined>;
83+
button: EuiCollapsibleNavProps['button'];
8184
}
8285

8386
export function CollapsibleNav({
@@ -91,6 +94,7 @@ export function CollapsibleNav({
9194
closeNav,
9295
navigateToApp,
9396
navigateToUrl,
97+
button,
9498
...observables
9599
}: Props) {
96100
const navLinks = useObservable(observables.navLinks$, []).filter((link) => !link.hidden);
@@ -121,8 +125,10 @@ export function CollapsibleNav({
121125
defaultMessage: 'Primary',
122126
})}
123127
isOpen={isNavOpen}
124-
isDocked={isLocked}
125128
onClose={closeNav}
129+
button={button}
130+
ownFocus={false}
131+
size={240}
126132
>
127133
{customNavLink && (
128134
<Fragment>
@@ -192,16 +198,18 @@ export function CollapsibleNav({
192198
</EuiFlexItem>
193199

194200
{/* Recently viewed */}
195-
<EuiCollapsibleNavGroup
196-
key="recentlyViewed"
197-
background="light"
198-
title={i18n.translate('core.ui.recentlyViewed', { defaultMessage: 'Recently viewed' })}
199-
isCollapsible={true}
200-
initialIsOpen={getIsCategoryOpen('recentlyViewed', storage)}
201-
onToggle={(isCategoryOpen) => setIsCategoryOpen('recentlyViewed', isCategoryOpen, storage)}
202-
data-test-subj="collapsibleNavGroup-recentlyViewed"
203-
>
204-
{recentlyAccessed.length > 0 ? (
201+
{recentlyAccessed.length > 0 && (
202+
<EuiCollapsibleNavGroup
203+
key="recentlyViewed"
204+
background="light"
205+
title={i18n.translate('core.ui.recentlyViewed', { defaultMessage: 'Recently viewed' })}
206+
isCollapsible={true}
207+
initialIsOpen={getIsCategoryOpen('recentlyViewed', storage)}
208+
onToggle={(isCategoryOpen) =>
209+
setIsCategoryOpen('recentlyViewed', isCategoryOpen, storage)
210+
}
211+
data-test-subj="collapsibleNavGroup-recentlyViewed"
212+
>
205213
<EuiListGroup
206214
aria-label={i18n.translate('core.ui.recentlyViewedAriaLabel', {
207215
defaultMessage: 'Recently viewed links',
@@ -233,16 +241,8 @@ export function CollapsibleNav({
233241
size="s"
234242
className="kbnCollapsibleNav__recentsListGroup"
235243
/>
236-
) : (
237-
<EuiText size="s" color="subdued" style={{ padding: '0 8px 8px' }}>
238-
<p>
239-
{i18n.translate('core.ui.EmptyRecentlyViewed', {
240-
defaultMessage: 'No recently viewed items',
241-
})}
242-
</p>
243-
</EuiText>
244-
)}
245-
</EuiCollapsibleNavGroup>
244+
</EuiCollapsibleNavGroup>
245+
)}
246246

247247
<EuiHorizontalRule margin="none" />
248248

@@ -255,6 +255,7 @@ export function CollapsibleNav({
255255
<EuiCollapsibleNavGroup
256256
key={category.id}
257257
iconType={category.euiIconType}
258+
iconSize="m"
258259
title={category.label}
259260
isCollapsible={true}
260261
initialIsOpen={getIsCategoryOpen(category.id, storage)}
@@ -286,7 +287,7 @@ export function CollapsibleNav({
286287
))}
287288

288289
{/* Docking button only for larger screens that can support it*/}
289-
<EuiShowFor sizes={['l', 'xl']}>
290+
<EuiShowFor sizes={'none'}>
290291
<EuiCollapsibleNavGroup>
291292
<EuiListGroup flush>
292293
<EuiListGroupItem
@@ -324,6 +325,16 @@ export function CollapsibleNav({
324325
</EuiCollapsibleNavGroup>
325326
</EuiShowFor>
326327
</EuiFlexItem>
328+
{/* Quick addition of that "ADD DATA" button everyone wants :) Feel free to remove though. */}
329+
<EuiFlexItem grow={false}>
330+
{/* Span fakes the nav group into not being the first item and therefore adding a top border */}
331+
<span />
332+
<EuiCollapsibleNavGroup>
333+
<EuiButton fill fullWidth iconType="plusInCircleFilled">
334+
<FormattedMessage id="core.ui.primaryNavSection.addDataBtn" defaultMessage="Add Data" />
335+
</EuiButton>
336+
</EuiCollapsibleNavGroup>
337+
</EuiFlexItem>
327338
</EuiCollapsibleNav>
328339
);
329340
}

src/core/public/chrome/ui/header/header.tsx

Lines changed: 35 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -160,19 +160,41 @@ export function Header({
160160
<EuiHeader position="fixed" className="header__secondBar">
161161
<EuiHeaderSection grow={false}>
162162
<EuiHeaderSectionItem border="right" className="header__toggleNavButtonSection">
163-
<EuiHeaderSectionItemButton
164-
data-test-subj="toggleNavButton"
165-
aria-label={i18n.translate('core.ui.primaryNav.toggleNavAriaLabel', {
166-
defaultMessage: 'Toggle primary navigation',
167-
})}
168-
onClick={() => setIsNavOpen(!isNavOpen)}
169-
aria-expanded={isNavOpen}
170-
aria-pressed={isNavOpen}
171-
aria-controls={navId}
172-
ref={toggleCollapsibleNavRef}
173-
>
174-
<EuiIcon type="menu" size="m" />
175-
</EuiHeaderSectionItemButton>
163+
<CollapsibleNav
164+
appId$={application.currentAppId$}
165+
id={navId}
166+
isLocked={isLocked}
167+
navLinks$={observables.navLinks$}
168+
recentlyAccessed$={observables.recentlyAccessed$}
169+
isNavOpen={isNavOpen}
170+
homeHref={homeHref}
171+
basePath={basePath}
172+
navigateToApp={application.navigateToApp}
173+
navigateToUrl={application.navigateToUrl}
174+
onIsLockedUpdate={onIsLockedUpdate}
175+
closeNav={() => {
176+
setIsNavOpen(false);
177+
if (toggleCollapsibleNavRef.current) {
178+
toggleCollapsibleNavRef.current.focus();
179+
}
180+
}}
181+
customNavLink$={observables.customNavLink$}
182+
button={
183+
<EuiHeaderSectionItemButton
184+
data-test-subj="toggleNavButton"
185+
aria-label={i18n.translate('core.ui.primaryNav.toggleNavAriaLabel', {
186+
defaultMessage: 'Toggle primary navigation',
187+
})}
188+
onClick={() => setIsNavOpen(!isNavOpen)}
189+
aria-expanded={isNavOpen}
190+
aria-pressed={isNavOpen}
191+
aria-controls={navId}
192+
ref={toggleCollapsibleNavRef}
193+
>
194+
<EuiIcon type="menu" size="m" />
195+
</EuiHeaderSectionItemButton>
196+
}
197+
/>
176198
</EuiHeaderSectionItem>
177199

178200
<HeaderNavControls side="left" navControls$={observables.navControlsLeft$} />
@@ -205,27 +227,6 @@ export function Header({
205227
</EuiHeaderSection>
206228
</EuiHeader>
207229
</div>
208-
209-
<CollapsibleNav
210-
appId$={application.currentAppId$}
211-
id={navId}
212-
isLocked={isLocked}
213-
navLinks$={observables.navLinks$}
214-
recentlyAccessed$={observables.recentlyAccessed$}
215-
isNavOpen={isNavOpen}
216-
homeHref={homeHref}
217-
basePath={basePath}
218-
navigateToApp={application.navigateToApp}
219-
navigateToUrl={application.navigateToUrl}
220-
onIsLockedUpdate={onIsLockedUpdate}
221-
closeNav={() => {
222-
setIsNavOpen(false);
223-
if (toggleCollapsibleNavRef.current) {
224-
toggleCollapsibleNavRef.current.focus();
225-
}
226-
}}
227-
customNavLink$={observables.customNavLink$}
228-
/>
229230
</header>
230231
</>
231232
);

src/plugins/kibana_react/public/page_template/__snapshots__/page_template.test.tsx.snap

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,11 @@
1-
$euiSideNavEmphasizedBackgroundColor: transparentize($euiColorLightShade, .7);
2-
31
.kbnPageTemplate__pageSideBar {
4-
padding: $euiSizeL;
5-
background:
6-
linear-gradient(160deg, $euiSideNavEmphasizedBackgroundColor 0, $euiSideNavEmphasizedBackgroundColor $euiSizeXL, rgba(#FFF, 0) 0),
7-
linear-gradient(175deg, $euiSideNavEmphasizedBackgroundColor 0, $euiSideNavEmphasizedBackgroundColor $euiSize, rgba(#FFF, 0) 0);
8-
}
2+
overflow: hidden;
3+
4+
@include euiCanAnimate {
5+
transition: min-width $euiAnimSpeedFast $euiAnimSlightResistance;
6+
}
97

10-
@include euiBreakpoint('xs','s') {
11-
.kbnPageTemplate__pageSideBar {
12-
width: auto;
13-
padding: 0;
8+
&.kbnPageTemplate__pageSideBar--shrink {
9+
min-width: $euiSizeXXL + $euiSize;
1410
}
1511
}

0 commit comments

Comments
 (0)