From aa2fc19f597f43ab5c9db33f4ae3e89c0ac4c8e0 Mon Sep 17 00:00:00 2001 From: Raphael Mattos Date: Tue, 19 Sep 2023 11:50:06 -0300 Subject: [PATCH] feat(cxl-ui): add snap scrolling in category-accordion --- .../scss/themes/cxl-tabs-slider.scss | 89 +++++++++++++++++-- .../cxl-ui/src/components/cxl-tabs-slider.js | 53 ++++++++++- 2 files changed, 131 insertions(+), 11 deletions(-) diff --git a/packages/cxl-lumo-styles/scss/themes/cxl-tabs-slider.scss b/packages/cxl-lumo-styles/scss/themes/cxl-tabs-slider.scss index f0859f77a..e41cd4285 100644 --- a/packages/cxl-lumo-styles/scss/themes/cxl-tabs-slider.scss +++ b/packages/cxl-lumo-styles/scss/themes/cxl-tabs-slider.scss @@ -38,6 +38,85 @@ } } +// Featured course slider and course-slider themes should always snap scroll +// between cards. can also be set with new attribute "snap-scroll" +:host([theme~="cxl-featured-course-slider"]), +:host([theme~="cxl-course-slider"]), +:host([snap-scroll]) { + [part="tabs"] { + padding: 0; + margin: 0; + overflow-x: auto; + scrollbar-width: none; + -ms-overflow-style: none; + + scroll-snap-type: x mandatory; + -webkit-overflow-scrolling: touch; + scroll-behavior: smooth; + + ::slotted(*) { + scroll-snap-align: start; + scroll-snap-stop: always; + /* stylelint-disable-next-line declaration-no-important */ + pointer-events: all !important; + } + + &::-webkit-scrollbar { + display: none; + } + } +} + +// Featured course slider +:host([theme~="cxl-featured-course-slider"][orientation="horizontal"]) { + background-color: var(--cxl-color-light-gray); + + [part="back-button"], + [part="forward-button"] { + top: calc(50% - 2 * var(--lumo-space-xl)); + width: var(--lumo-size-s); + height: var(--lumo-size-s); + color: var(--lumo-primary-color); + border-radius: 100%; + + &::after { + font-size: var(--lumo-font-size-xl); + } + + @media #{mq.$medium} { + top: unset; + width: var(--lumo-space-xl); + height: var(--lumo-space-xl); + + &::after { + font-size: var(--lumo-font-size-xxl); + } + } + } + + [part="back-button"] { + margin-left: var(--lumo-space-s); + } + + [part="forward-button"] { + margin-right: var(--lumo-space-s); + } + + ::slotted(vaadin-tab) { + padding: 0 var(--lumo-space-m); + + @media #{mq.$medium} { + padding: 0; + } + } + + ::slotted(vaadin-tab:not(:first-child)) { + @media #{mq.$medium} { + padding-left: var(--lumo-size-xl); + } + } +} + /** * Theme "cxl-course-slider" */ @@ -48,6 +127,7 @@ ::slotted(vaadin-tab) { padding: 0 calc(var(--lumo-space-s) / 2) calc(var(--lumo-space-s) / 2); overflow: visible; + scroll-margin-inline-start: var(--lumo-space-xl); } ::slotted(vaadin-tab:first-of-type) { @@ -55,6 +135,7 @@ @media #{mq.$small} { margin-inline-start: calc(var(--lumo-space-l) - var(--lumo-space-xs)); + scroll-margin-inline-start: var(--lumo-space-l); } } @@ -71,15 +152,7 @@ [part="tabs"] { padding-top: var(--cxl-space-sm); - margin: 0; - overflow-x: auto; overflow-y: hidden; - scrollbar-width: none; - -ms-overflow-style: none; - - &::-webkit-scrollbar { - display: none; - } } [part="back-button"], diff --git a/packages/cxl-ui/src/components/cxl-tabs-slider.js b/packages/cxl-ui/src/components/cxl-tabs-slider.js index 8c9275395..3a99fc10c 100644 --- a/packages/cxl-ui/src/components/cxl-tabs-slider.js +++ b/packages/cxl-ui/src/components/cxl-tabs-slider.js @@ -5,9 +5,15 @@ import { customElement } from 'lit/decorators.js'; @customElement('cxl-tabs-slider') export class CXLTabsSliderElement extends Tabs { - _updateOverflow() { - this.centeredContainerWidthCheck(); - super._updateOverflow(); + /** + * @return {number} + * @protected + */ + get _scrollOffset() { + if (this.getAttribute('theme').split(' ').includes('cxl-category-accordion')) { + return 200; + } + return super._scrollOffset; } _onResize() { @@ -32,6 +38,7 @@ export class CXLTabsSliderElement extends Tabs { * @see https://github.com/vaadin/web-components/blob/v20.0.0-beta1/packages/vaadin-tabs/src/vaadin-tabs.js#L222-L240 * @see https://lit-and-friends.slack.com/archives/C6ULJ2F7S/p1620125717028800 */ + centeredContainerWidthCheck() { const themeAttr = 'theme'; const centeredVal = 'centered'; @@ -50,6 +57,46 @@ export class CXLTabsSliderElement extends Tabs { this.setAttribute(themeAttr, themes.join(' ')); } + /** Redefining Vaadin-Tabs method to fix bug in scroll position end detection */ + _updateOverflow() { + this.centeredContainerWidthCheck(); + + const scrollPosition = this._vertical + ? this._scrollerElement.scrollTop + : this.__getNormalizedScrollLeft(this._scrollerElement); + const scrollSize = this._vertical + ? this._scrollerElement.scrollHeight + : this._scrollerElement.scrollWidth; + const offset = this._vertical + ? this._scrollerElement.offsetHeight + : this._scrollerElement.offsetWidth; + + // Note that we are not comparing floored scrollPosition to be greater than zero here, which would make a natural + // sense, but to be greater than one intentionally. There is a known bug in Chromium browsers on Linux/Mac + // (https://bugs.chromium.org/p/chromium/issues/detail?id=1123301), which returns invalid value of scrollLeft when + // text direction is RTL. The value is off by one pixel in that case. Comparing scrollPosition to be greater than + // one on the following line is a workaround for that bug. Comparing scrollPosition to be greater than one means, + // that the left overflow and left arrow will be displayed "one pixel" later than normal. In other words, if the tab + // scroller element is scrolled just by 1px, the overflow is not yet showing. + let overflow = Math.floor(scrollPosition) > 1 ? 'start' : ''; + + if (Math.ceil(scrollPosition) + offset < Math.ceil(scrollSize - this._scrollOffset / 10)) { + overflow += ' end'; + } + + if (this.__direction === 1) { + overflow = overflow.replace(/start|end/gi, (matched) => + matched === 'start' ? 'end' : 'start' + ); + } + + if (overflow) { + this.setAttribute('overflow', overflow.trim()); + } else { + this.removeAttribute('overflow'); + } + } + static get is() { return 'cxl-tabs-slider'; }