Skip to content

Commit

Permalink
feat(masthead): add horizontal scroller (#4717)
Browse files Browse the repository at this point in the history
### Related Ticket(s)

Refs #4457.

### Description

TBD

### Changelog

**New**

- The horizontal scroller in `<dds-top-nav>`.

**Changed**

- The breakpoint of left/top nav switch (from `md` to `800px`).
  • Loading branch information
asudoh authored Dec 29, 2020
1 parent cbcb9a2 commit 6d6bd55
Show file tree
Hide file tree
Showing 6 changed files with 530 additions and 50 deletions.
1 change: 1 addition & 0 deletions packages/styles/scss/components/masthead/_masthead-l1.scss
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ $search-transition-timing: 95ms;
height: $layout-04;
background-color: $ui-02;
transition-timing-function: $search-transition;
overflow: hidden;

@include carbon--breakpoint-down(800px) {
display: none;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
/**
* @license
*
* Copyright IBM Corp. 2020
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/

import { html, render } from 'lit-html';
import MockIntersectionObserver from '../../../../tests/utils/mock-intersection-observer';
import '../top-nav';

const template = ({ width = 215 }: { width?: number } = {}) => {
return html`
<style>
dds-top-nav {
display: flex;
width: ${width}px;
height: 1rem;
}
dds-top-nav::part(nav) {
/* Forces the nav being shown */
display: block;
/* Removes the padding for simpler test */
padding: 0;
}
dds-top-nav::part(prev-button),
dds-top-nav::part(next-button) {
display: block;
}
.dds-ce--test--scroll-content {
width: 100px;
flex-grow: 0;
flex-shrink: 0;
}
.dds-ce--test--scroll-content--odd {
width: 75px;
}
</style>
<dds-top-nav>
<div class="dds-ce--test--scroll-content dds-ce--test--scroll-content--even"></div>
<div class="dds-ce--test--scroll-content dds-ce--test--scroll-content--odd"></div>
<div class="dds-ce--test--scroll-content dds-ce--test--scroll-content--even"></div>
<div class="dds-ce--test--scroll-content dds-ce--test--scroll-content--odd"></div>
<div class="dds-ce--test--scroll-content dds-ce--test--scroll-content--even"></div>
<div class="dds-ce--test--scroll-content dds-ce--test--scroll-content--odd"></div>
<div class="dds-ce--test--scroll-content dds-ce--test--scroll-content--even"></div>
<div class="dds-ce--test--scroll-content dds-ce--test--scroll-content--odd"></div>
<div class="dds-ce--test--scroll-content dds-ce--test--scroll-content--even"></div>
<div class="dds-ce--test--scroll-content dds-ce--test--scroll-content--odd"></div>
</dds-top-nav>
`;
};

describe('dds-top-nav', function() {
let origIntersectionObserver;

beforeEach(function() {
origIntersectionObserver = window.IntersectionObserver;
window.IntersectionObserver = (MockIntersectionObserver as unknown) as typeof IntersectionObserver;
});

describe('Navigating to right', function() {
it('should go to the next page', async function() {
render(template(), document.body);
await Promise.resolve(); // Update cycle for the component
await Promise.resolve(); // The cycle where `slotchange` event is called
const topNav = document.querySelector('dds-top-nav');
const intersectionRightSentinelNode = topNav!.shadowRoot!.querySelector('.bx--sub-content-right');
MockIntersectionObserver.run(intersectionRightSentinelNode!, false);
await Promise.resolve();
(topNav!.shadowRoot!.querySelector('[part="next-button"]') as HTMLElement).click();
await Promise.resolve();
expect((topNav!.shadowRoot!.querySelector('.bx--header__nav-content') as HTMLElement).style.left).toBe('-175px');
});

it('should support snapping to menu item', async function() {
render(template({ width: 200 }), document.body);
await Promise.resolve(); // Update cycle for the component
await Promise.resolve(); // The cycle where `slotchange` event is called
const topNav = document.querySelector('dds-top-nav');
const intersectionRightSentinelNode = topNav!.shadowRoot!.querySelector('.bx--sub-content-right');
MockIntersectionObserver.run(intersectionRightSentinelNode!, false);
await Promise.resolve();
(topNav!.shadowRoot!.querySelector('[part="next-button"]') as HTMLElement).click();
await Promise.resolve();
expect((topNav!.shadowRoot!.querySelector('.bx--header__nav-content') as HTMLElement).style.left).toBe('-100px');
});

it('should cope with change in the hidden state of the go to next page button', async function() {
render(template(), document.body);
await Promise.resolve(); // Update cycle for the component
await Promise.resolve(); // The cycle where `slotchange` event is called
const topNav = document.querySelector('dds-top-nav');
const intersectionLeftSentinelNode = topNav!.shadowRoot!.querySelector('.bx--sub-content-left');
MockIntersectionObserver.run(intersectionLeftSentinelNode!, false);
const intersectionRightSentinelNode = topNav!.shadowRoot!.querySelector('.bx--sub-content-right');
MockIntersectionObserver.run(intersectionRightSentinelNode!, false);
await Promise.resolve();
(topNav as any)._currentScrollPosition = 565;
(topNav!.shadowRoot!.querySelector('[part="next-button"]') as HTMLElement).click();
await Promise.resolve();
expect((topNav!.shadowRoot!.querySelector('.bx--header__nav-content') as HTMLElement).style.left).toBe('-700px');
});

it('should snap to the right edge at the last page', async function() {
render(template(), document.body);
await Promise.resolve(); // Update cycle for the component
await Promise.resolve(); // The cycle where `slotchange` event is called
const topNav = document.querySelector('dds-top-nav');
const intersectionLeftSentinelNode = topNav!.shadowRoot!.querySelector('.bx--sub-content-left');
MockIntersectionObserver.run(intersectionLeftSentinelNode!, false);
const intersectionRightSentinelNode = topNav!.shadowRoot!.querySelector('.bx--sub-content-right');
MockIntersectionObserver.run(intersectionRightSentinelNode!, false);
await Promise.resolve();
(topNav as any)._currentScrollPosition = 690;
(topNav!.shadowRoot!.querySelector('[part="next-button"]') as HTMLElement).click();
await Promise.resolve();
expect((topNav!.shadowRoot!.querySelector('.bx--header__nav-content') as HTMLElement).style.left).toBe('-700px');
});
});

describe('Navigating to left', function() {
it('should go to the next page', async function() {
render(template({ width: 255 }), document.body);
await Promise.resolve(); // Update cycle for the component
await Promise.resolve(); // The cycle where `slotchange` event is called
const topNav = document.querySelector('dds-top-nav');
const intersectionLeftSentinelNode = topNav!.shadowRoot!.querySelector('.bx--sub-content-left');
MockIntersectionObserver.run(intersectionLeftSentinelNode!, false);
const intersectionRightSentinelNode = topNav!.shadowRoot!.querySelector('.bx--sub-content-right');
MockIntersectionObserver.run(intersectionRightSentinelNode!, false);
await Promise.resolve();
(topNav as any)._currentScrollPosition = 350;
(topNav!.shadowRoot!.querySelector('[part="prev-button"]') as HTMLElement).click();
await Promise.resolve();
expect((topNav!.shadowRoot!.querySelector('.bx--header__nav-content') as HTMLElement).style.left).toBe('-175px');
});

it('should support snapping to menu item', async function() {
render(template({ width: 250 }), document.body);
await Promise.resolve(); // Update cycle for the component
await Promise.resolve(); // The cycle where `slotchange` event is called
const topNav = document.querySelector('dds-top-nav');
const intersectionLeftSentinelNode = topNav!.shadowRoot!.querySelector('.bx--sub-content-left');
MockIntersectionObserver.run(intersectionLeftSentinelNode!, false);
const intersectionRightSentinelNode = topNav!.shadowRoot!.querySelector('.bx--sub-content-right');
MockIntersectionObserver.run(intersectionRightSentinelNode!, false);
await Promise.resolve();
(topNav as any)._currentScrollPosition = 350;
(topNav!.shadowRoot!.querySelector('[part="prev-button"]') as HTMLElement).click();
await Promise.resolve();
expect((topNav!.shadowRoot!.querySelector('.bx--header__nav-content') as HTMLElement).style.left).toBe('-275px');
});

it('should cope with change in the hidden state of the go to previous page button', async function() {
render(template(), document.body);
await Promise.resolve(); // Update cycle for the component
await Promise.resolve(); // The cycle where `slotchange` event is called
const topNav = document.querySelector('dds-top-nav');
const intersectionLeftSentinelNode = topNav!.shadowRoot!.querySelector('.bx--sub-content-left');
MockIntersectionObserver.run(intersectionLeftSentinelNode!, false);
const intersectionRightSentinelNode = topNav!.shadowRoot!.querySelector('.bx--sub-content-right');
MockIntersectionObserver.run(intersectionRightSentinelNode!, false);
await Promise.resolve();
(topNav as any)._currentScrollPosition = 175;
(topNav!.shadowRoot!.querySelector('[part="prev-button"]') as HTMLElement).click();
await Promise.resolve();
expect((topNav!.shadowRoot!.querySelector('.bx--header__nav-content') as HTMLElement).style.left).toBe('0px');
});
});

afterEach(async function() {
await render(undefined!, document.body);
window.IntersectionObserver = origIntersectionObserver;
});
});
53 changes: 43 additions & 10 deletions packages/web-components/src/components/masthead/masthead.scss
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@
width: mini-units(6);
height: mini-units(6);

@include carbon--breakpoint('md') {
display: block;
}

@include carbon--breakpoint(800px) {
display: none;
}

.#{$dds-prefix}-ce--header__menu-trigger__container {
display: contents;
}
Expand All @@ -45,12 +53,10 @@
@include tooltip--placement('definition', 'bottom', 'start');
}

// We have a style to remove extra left padding when the masthead logo is next to left nav trigger button:
// https://github.com/carbon-design-system/carbon-for-ibm-dotcom/blob/v1.8.0/packages/styles/scss/components/masthead/_masthead-leftnav.scss#L41-L47
// For `md`-`lg` breakpoint it's always the case.
// Ensures that IBM logo gets the padding of "wider screen" mode if we use nav pager
a {
@include carbon--breakpoint-between('md', 'lg') {
padding: 0 $carbon--spacing-05;
@include carbon--breakpoint(800px) {
padding: 0 $carbon--spacing-07;
}
}

Expand Down Expand Up @@ -124,21 +130,48 @@

:host(#{$dds-prefix}-top-nav),
:host(#{$dds-prefix}-top-nav-l1) {
display: contents;
display: none;
overflow: hidden;

@include carbon--breakpoint(800px) {
flex: 1;
display: flex;
align-items: stretch;
}

.#{$prefix}--header__nav {
@include carbon--breakpoint(800px) {
display: none;
}

@include carbon--breakpoint('lg') {
display: block;
position: absolute;
}
}

&[hide-divider] .#{$prefix}--header__nav {
padding-left: 0;
}

.#{$dds-prefix}-ce--header__nav-content-container {
flex: 1;
position: relative;
}

.#{$prefix}--header__nav-caret-left-container,
.#{$prefix}--header__nav-caret-right-container {
position: relative;
flex-grow: 0;
flex-shrink: 0;
z-index: 1;
}

.#{$prefix}--header__nav-caret-left,
.#{$prefix}--header__nav-caret-right {
position: relative;
}

.#{$dds-prefix}-ce--header__nav-caret-container--hidden {
position: absolute;
visibility: hidden;
}
}

:host(#{$dds-prefix}-top-nav-item) {
Expand Down
Loading

0 comments on commit 6d6bd55

Please sign in to comment.