Commit 43b628ef authored by Yue Zhang's avatar Yue Zhang Committed by Chromium LUCI CQ

[ChromeCart] Make cart module scrollable

This CL makes cart module scrollable by adding side scroll buttons to
show previous/next items. The side scroll buttons will only be visible
when the cart number goes beyond what we can show.

Bug: 1160967
Change-Id: Ib06a3e89d8555a4194d38917f08dcec41994a0d8
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2612126
Commit-Queue: Yue Zhang <yuezhanggg@chromium.org>
Reviewed-by: default avatarTibor Goldschwendt <tiborg@chromium.org>
Cr-Commit-Position: refs/heads/master@{#844256}
parent 6e8d44eb
......@@ -7,20 +7,26 @@
}
#moduleContent {
box-sizing: border-box;
display: flex;
flex-direction: row;
float: left;
height: 100%;
padding-inline-end: 15px;
padding-inline-start: 15px;
width: 100%;
position: relative;
}
#moduleContent:hover .side-scroll-button {
visibility: visible;
}
#cartCarousel {
display: inline-block;
overflow-x: hidden;
white-space: nowrap;
z-index: 0;
}
.cart-item {
border: 1px solid var(--ntp-border-color);
border-radius: 4px;
display: flex;
display: inline-flex;
flex-direction: column;
height: 160px;
margin: 0 4px;
......@@ -113,7 +119,7 @@
right: unset;
}
cr-icon-button {
.cart-item cr-icon-button {
--cr-icon-button-fill-color: var(--ntp-icon-button-color);
--cr-icon-button-size: 16px;
--cr-icon-button-transition: none;
......@@ -125,9 +131,65 @@
transition: opacity 100ms ease-in-out;
}
cr-icon-button:hover {
.cart-item cr-icon-button:hover {
--cr-icon-button-fill-color: var(--ntp-icon-button-color-active);
}
.side-scroll-shadow {
background-color: var(--ntp-background-override-color);
display: flex;
height: 160px;
opacity: 0.38;
pointer-events: none;
position: absolute;
width: 24px;
z-index: 1;
}
#leftScrollShadow {
left: 0;
}
#rightScrollShadow {
right: 0;
}
.side-scroll-button {
--cr-icon-button-fill-color: var(--ntp-icon-button-color);
--cr-icon-button-icon-size: 16px;
--cr-icon-button-margin-start: 0;
--cr-icon-button-margin-end: 0;
--cr-icon-image: url(icons/chevron.svg);
background-color: var(--ntp-theme-shortcut-background-color);
border-radius: 50%;
position: absolute;
top: 50%;
transform: translateY(-50%);
visibility: hidden;
z-index: 2;
}
.side-scroll-button:hover {
--cr-icon-button-fill-color: var(--ntp-icon-button-color-active);
background-color: var( --ntp-active-background-color);
}
#leftScrollButton {
--cr-icon-image-transform: rotate(90deg);
left: 0;
margin-inline-start: 4px;
}
#rightScrollButton {
--cr-icon-image-transform: rotate(270deg);
margin-inline-end: 4px;
right: 0;
}
.probe {
display: inline-flex;
width: 15px;
}
</style>
<ntp-module-header
chip-text="$i18n{modulesCartHeaderNew}"
......@@ -135,42 +197,58 @@
$i18n{modulesCartTitle}
</ntp-module-header>
<div id="moduleContent">
<template id="cartItemRepeat" is="dom-repeat" items="[[cartItems]]">
<a class="cart-item" title="[[item.merchant]]"
href="[[item.cartUrl.url]]">
<cr-icon-button class="icon-more-vert"
title="$i18n{moreActions}" on-click="onMenuButtonClick_">
</cr-icon-button>
<img class="favicon-image" is="ntp-img"
auto-src="[[getFaviconUrl_(item.cartUrl.url)]]"></img>
<div class="cart-title">
<span class="merchant">[[item.merchant]]</span>
<template is="dom-if" if="[[item.productImageUrls.length]]">
<span class="item-count">
&nbsp•&nbsp[[item.productImageUrls.length]]
</span>
</template>
</div>
<div class="thumbnail-container">
<template is="dom-if"
if="[[item.productImageUrls.length]]">
<ul class="thumbnail-list">
<template is="dom-repeat"
items="[[getImagesToShow_(item.productImageUrls)]]">
<li>
<img class="thumbnail-img" is="ntp-img"
auto-src="[[item.url]]"></img>
</li>
</template>
</ul>
</template>
<template id="thumbnailFallback" is="dom-if"
if="[[!item.productImageUrls.length]]">
<img class="thumbnail-fallback"
src="chrome://new-tab-page/icons/cart_fallback.svg">
</template>
</div>
</a>
<template is="dom-if" if="[[showLeftScrollButton_]]">
<div id="leftScrollShadow" class="side-scroll-shadow"></div>
<cr-icon-button id="leftScrollButton"
class="side-scroll-button" on-click="onLeftScrollClick_">
</cr-icon-button>
</template>
<div id="cartCarousel">
<div id="leftProbe" class="probe"></div>
<template id="cartItemRepeat" is="dom-repeat" items="[[cartItems]]">
<a class="cart-item" title="[[item.merchant]]"
href="[[item.cartUrl.url]]">
<cr-icon-button class="icon-more-vert"
title="$i18n{moreActions}" on-click="onMenuButtonClick_">
</cr-icon-button>
<img class="favicon-image" is="ntp-img"
auto-src="[[getFaviconUrl_(item.cartUrl.url)]]"></img>
<div class="cart-title">
<span class="merchant">[[item.merchant]]</span>
<template is="dom-if" if="[[item.productImageUrls.length]]">
<span class="item-count">
&nbsp•&nbsp[[item.productImageUrls.length]]
</span>
</template>
</div>
<div class="thumbnail-container">
<template is="dom-if"
if="[[item.productImageUrls.length]]">
<ul class="thumbnail-list">
<template is="dom-repeat"
items="[[getImagesToShow_(item.productImageUrls)]]">
<li>
<img class="thumbnail-img" is="ntp-img"
auto-src="[[item.url]]"></img>
</li>
</template>
</ul>
</template>
<template id="thumbnailFallback" is="dom-if"
if="[[!item.productImageUrls.length]]">
<img class="thumbnail-fallback"
src="chrome://new-tab-page/icons/cart_fallback.svg">
</template>
</div>
</a>
</template>
<div id="rightProbe" class="probe"></div>
</div>
<cr-action-menu id="actionMenu"></cr-action-menu>
<template is="dom-if" if="[[showRightScrollButton_]]">
<div id="rightScrollShadow" class="side-scroll-shadow"> </div>
<cr-icon-button id="rightScrollButton"
class="side-scroll-button" on-click="onRightScrollClick_">
</cr-icon-button>
</template>
</div>
\ No newline at end of file
......@@ -32,9 +32,60 @@ class ChromeCartModuleElement extends PolymerElement {
return {
/** @type {!Array<!chromeCart.mojom.MerchantCart>} */
cartItems: Array,
/** @private {boolean} */
showLeftScrollButton_: Boolean,
/** @private {boolean} */
showRightScrollButton_: Boolean,
};
}
constructor() {
super();
/** @private {IntersectionObserver} */
this.intersectionObserver_ = null;
/** @type {string} */
this.scrollBehavior = 'smooth';
}
/** @override */
connectedCallback() {
super.connectedCallback();
const leftProbe = this.$.cartCarousel.querySelector('#leftProbe');
const rightProbe = this.$.cartCarousel.querySelector('#rightProbe');
this.intersectionObserver_ = new IntersectionObserver(entries => {
entries.forEach(({target, intersectionRatio}) => {
const show = intersectionRatio === 0;
if (target === leftProbe) {
this.showLeftScrollButton_ = show;
if (show) {
this.dispatchEvent(new Event('left-scroll-show'));
} else {
this.dispatchEvent(new Event('left-scroll-hide'));
}
} else if (target === rightProbe) {
this.showRightScrollButton_ = show;
if (show) {
this.dispatchEvent(new Event('right-scroll-show'));
} else {
this.dispatchEvent(new Event('right-scroll-hide'));
}
}
});
}, {root: this.$.cartCarousel});
this.shadowRoot.querySelectorAll('.probe').forEach(
el => this.intersectionObserver_.observe(el));
}
/** @override */
disconnectedCallback() {
super.disconnectedCallback();
this.intersectionObserver_.disconnect();
}
/**
* @param {string} url
* @return {string}
......@@ -86,6 +137,72 @@ class ChromeCartModuleElement extends PolymerElement {
e.target.parentElement.parentElement);
this.$.actionMenu.showAt(e.target);
}
/**
* Gets called when the right scroll button is clicked to show the next items
* on the right.
* @private
*/
onRightScrollClick_() {
const carts = this.$.cartCarousel.querySelectorAll('.cart-item');
let lastVisibleIndex = 0;
for (let i = 0; i < carts.length; i++) {
if (this.getVisibilityForIndex_(i)) {
lastVisibleIndex = i;
}
}
this.scrollToIndex_(lastVisibleIndex + 1);
}
/**
* Gets called when the left scroll button is clicked to show the previous
* items on the left.
* @private
*/
onLeftScrollClick_() {
const carts = this.$.cartCarousel.querySelectorAll('.cart-item');
let visibleRange = 0, firstVisibleIndex = 0;
for (let i = carts.length - 1; i >= 0; i--) {
if (this.getVisibilityForIndex_(i)) {
visibleRange += 1;
firstVisibleIndex = i;
}
}
this.scrollToIndex_(Math.max(0, firstVisibleIndex - visibleRange));
}
/**
* @param {!number} index The target index to scroll to.
* @private
*/
scrollToIndex_(index) {
const carts = this.$.cartCarousel.querySelectorAll('.cart-item');
// Calculate scroll shadow width as scroll offset.
const leftScrollShadow = this.shadowRoot.getElementById('leftScrollShadow');
const rightScrollShadow =
this.shadowRoot.getElementById('rightScrollShadow');
const scrollOffset = Math.max(
leftScrollShadow ? leftScrollShadow.offsetWidth : 0,
rightScrollShadow ? rightScrollShadow.offsetWidth : 0);
this.$.cartCarousel.scrollTo({
top: 0,
left: carts[index].offsetLeft - scrollOffset,
behavior: this.scrollBehavior,
});
}
/**
* @param {!number} index
* @return {!boolean} True if the item at index is completely visible.
* @private
*/
getVisibilityForIndex_(index) {
const cartCarousel = this.$.cartCarousel;
const cart = cartCarousel.querySelectorAll('.cart-item')[index];
return cart && (cart.offsetLeft > cartCarousel.scrollLeft) &&
(cartCarousel.scrollLeft + cartCarousel.clientWidth) >
(cart.offsetLeft + cart.offsetWidth);
}
}
customElements.define(ChromeCartModuleElement.is, ChromeCartModuleElement);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment