diff --git a/ui/js/page/discover/view.jsx b/ui/js/page/discover/view.jsx
index fc57a5ba8..2817fb946 100644
--- a/ui/js/page/discover/view.jsx
+++ b/ui/js/page/discover/view.jsx
@@ -1,38 +1,206 @@
import React from "react";
+import ReactDOM from "react-dom";
import lbryio from "lbryio.js";
import lbryuri from "lbryuri";
import FileCard from "component/fileCard";
-import { BusyMessage } from "component/common.js";
+import { Icon, BusyMessage } from "component/common.js";
import ToolTip from "component/tooltip.js";
-const FeaturedCategory = props => {
- const { category, names } = props;
+class FeaturedCategory extends React.PureComponent {
+ componentWillMount() {
+ this.setState({
+ numItems: this.props.names.length,
+ canScrollPrevious: false,
+ canScrollNext: true,
+ });
+ }
- return (
-
-
- {category}
- {category &&
- category.match(/^community/i) &&
- 0) {
+ // check the visible cards
+ const cards = cardRow.getElementsByTagName("section");
+ let firstVisibleCard = null;
+ let firstVisibleIdx = -1;
+ for (var i = 0; i < cards.length; i++) {
+ if (this.isCardVisible(cards[i], cardRow, false)) {
+ firstVisibleCard = cards[i];
+ firstVisibleIdx = i;
+ break;
+ }
+ }
+
+ const numDisplayed = this.numDisplayedCards(cardRow);
+ const scrollToIdx = firstVisibleIdx - numDisplayed;
+ const animationCallback = () => {
+ this.setState({
+ canScrollPrevious: cardRow.scrollLeft !== 0,
+ canScrollNext: true,
+ });
+ };
+ this.scrollCardItemsLeftAnimated(
+ cardRow,
+ scrollToIdx < 0 ? 0 : cards[scrollToIdx].offsetLeft,
+ 100,
+ animationCallback
+ );
+ }
+ }
+
+ handleScrollNext() {
+ const cardRow = ReactDOM.findDOMNode(this.refs.rowitems);
+
+ // check the visible cards
+ const cards = cardRow.getElementsByTagName("section");
+ let lastVisibleCard = null;
+ let lastVisibleIdx = -1;
+ for (var i = 0; i < cards.length; i++) {
+ if (this.isCardVisible(cards[i], cardRow, true)) {
+ lastVisibleCard = cards[i];
+ lastVisibleIdx = i;
+ }
+ }
+
+ if (lastVisibleCard) {
+ const numDisplayed = this.numDisplayedCards(cardRow);
+ const animationCallback = () => {
+ // update last visible index after scroll
+ for (var i = 0; i < cards.length; i++) {
+ if (this.isCardVisible(cards[i], cardRow, true)) {
+ lastVisibleIdx = i;
+ }
+ }
+
+ this.setState({ canScrollPrevious: true });
+ if (lastVisibleIdx === cards.length - 1) {
+ this.setState({ canScrollNext: false });
+ }
+ };
+
+ this.scrollCardItemsLeftAnimated(
+ cardRow,
+ Math.min(
+ lastVisibleCard.offsetLeft,
+ cardRow.scrollWidth - cardRow.clientWidth
+ ),
+ 100,
+ animationCallback
+ );
+ }
+ }
+
+ scrollCardItemsLeftAnimated(cardRow, target, duration, callback) {
+ if (!duration || duration <= diff) {
+ cardRow.scrollLeft = target;
+ if (callback) {
+ callback();
+ }
+ return;
+ }
+
+ const component = this;
+ const diff = target - cardRow.scrollLeft;
+ const tick = diff / duration * 10;
+ setTimeout(() => {
+ cardRow.scrollLeft = cardRow.scrollLeft + tick;
+ if (cardRow.scrollLeft === target) {
+ if (callback) {
+ callback();
+ }
+ return;
+ }
+ component.scrollCardItemsLeftAnimated(
+ cardRow,
+ target,
+ duration - 10,
+ callback
+ );
+ }, 10);
+ }
+
+ isCardVisible(section, cardRow, partialVisibility) {
+ // check if a card is fully or partialy visible in its parent
+ const cardRowWidth = cardRow.offsetWidth;
+ const cardRowLeft = cardRow.scrollLeft;
+ const cardRowEnd = cardRowLeft + cardRow.offsetWidth;
+ const sectionLeft = section.offsetLeft - cardRowLeft;
+ const sectionEnd = sectionLeft + section.offsetWidth;
+
+ return (
+ (sectionLeft >= 0 && sectionEnd <= cardRowWidth) ||
+ (((sectionLeft < 0 && sectionEnd > 0) ||
+ (sectionLeft > 0 && sectionLeft <= cardRowWidth)) &&
+ partialVisibility)
+ );
+ }
+
+ numDisplayedCards(cardRow) {
+ const cards = cardRow.getElementsByTagName("section");
+ const cardRowWidth = cardRow.offsetWidth;
+ // get the width of the first card and then calculate
+ const cardWidth = cards.length > 0 ? cards[0].offsetWidth : 0;
+
+ if (cardWidth > 0) {
+ return Math.ceil(cardRowWidth / cardWidth);
+ }
+
+ // return a default value of 1 card displayed if the card width couldn't be determined
+ return 1;
+ }
+
+ render() {
+ const { category, names } = this.props;
+ const leftNavClassName =
+ "card-row__nav left-nav" +
+ (this.state.canScrollPrevious ? " can-scroll" : "");
+ const rightNavClassName =
+ "card-row__nav right-nav" +
+ (this.state.canScrollNext ? " can-scroll" : "");
+
+ return (
+
+
+
+
+ {category}
+ {category &&
+ category.match(/^community/i) &&
+ }
+
+
+ {names &&
+ names.map(name =>
+
)}
- className="tooltip--header"
- />}
-
- {names &&
- names.map(name =>
-
- )}
-
- );
-};
+
+
+ );
+ }
+}
class DiscoverPage extends React.PureComponent {
componentWillMount() {
diff --git a/ui/scss/component/_card.scss b/ui/scss/component/_card.scss
index d8416d226..a81470bfb 100644
--- a/ui/scss/component/_card.scss
+++ b/ui/scss/component/_card.scss
@@ -1,6 +1,7 @@
@import "../global";
$padding-card-horizontal: $spacing-vertical * 2/3;
+$translate-card-hover: 10px;
.card {
margin-left: auto;
@@ -94,10 +95,13 @@ $card-link-scaling: 1.1;
position: relative;
z-index: 1;
box-shadow: $box-shadow-focus;
- transform: scale($card-link-scaling);
+ transform: scale($card-link-scaling) translate3d($translate-card-hover, 0, 0);
transform-origin: 50% 50%;
overflow-x: visible;
- overflow-y: visible;
+ overflow-y: visible
+}
+.card--link:hover ~ .card--link {
+ transform: translate3d($translate-card-hover * 2, 0, 0);
}
.card__media {
@@ -183,26 +187,73 @@ $height-card-small: $spacing-vertical * 15;
}
.card-row {
+ + .card-row {
+ margin-top: $spacing-vertical * 1/3;
+ }
+}
+.card-row__items {
> .card {
vertical-align: top;
display: inline-block;
margin-right: $spacing-vertical / 3;
}
- + .card-row {
- margin-top: $spacing-vertical * 1/3;
+ > .card:last-child {
+ margin-right: 0
}
}
.card-row--small {
- overflow-x: auto;
- overflow-y: hidden;
+ overflow: hidden;
white-space: nowrap;
+ position: relative;
/*hacky way to give space for hover */
- padding-left: 20px;
- margin-left: -20px;
- padding-right: 20px;
- margin-right: -20px;
+ padding-right: 30px;
+}
+.card-row__items {
+ width: 100%;
+ overflow: hidden;
+
+ /*hacky way to give space for hover */
+ padding-top: 20px;
+ margin-top: -20px;
+ padding-right: 30px;
+ margin-right: -30px;
}
.card-row__header {
margin-bottom: $spacing-vertical / 3;
-}
\ No newline at end of file
+}
+
+.card-row__nav {
+ display: none;
+ position: absolute;
+ padding: 0 $spacing-vertical / 1.5;
+ top: ($font-size * 1.4 * $font-line-height) + ($spacing-vertical / 3);
+ height: ($width-card-small * 9 / 16) + ($font-size * 0.82 * $font-line-height * 4) + ($spacing-vertical * 4/3)
+}
+.card-row__nav .card-row__scroll-button {
+ background: $color-bg;
+ color: $color-help;
+ box-shadow: $box-shadow-layer;
+ padding: $spacing-vertical $spacing-vertical / 2;
+ position: absolute;
+ cursor: pointer;
+ left: 0;
+ top: 36%;
+ z-index: 2;
+ opacity: 0.5;
+ transition: transform 60ms ease-in-out;
+
+ &:hover {
+ opacity: 1.0;
+ transform: scale($card-link-scaling * 1.1)
+ }
+}
+.card-row__nav.left-nav {
+ left: 0;
+}
+.card-row__nav.right-nav {
+ right: 0;
+}
+.card-row__nav.can-scroll {
+ display: block
+}
diff --git a/ui/scss/component/_header.scss b/ui/scss/component/_header.scss
index 1343ddf86..5cc51541e 100644
--- a/ui/scss/component/_header.scss
+++ b/ui/scss/component/_header.scss
@@ -13,7 +13,7 @@ $color-header-active: darken($color-header, 20%);
top: 0;
left: 0;
width: 100%;
- z-index: 2;
+ z-index: 3;
padding: $spacing-vertical / 2;
box-sizing: border-box;
}