discover page horizontal scroll initial progress
added translate3d transform for card hover and sibling cards implemented simple horizontal scroll animation updated header z-index switched z-index of cards on hover with horizontal left/right nav buttons removed unnecessary code added transition and states for horizontal scroll arrows, and some css tweaks and fixes
This commit is contained in:
parent
47a5f6a442
commit
a76afb0253
3 changed files with 258 additions and 39 deletions
|
@ -1,38 +1,206 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import ReactDOM from "react-dom";
|
||||||
import lbryio from "lbryio.js";
|
import lbryio from "lbryio.js";
|
||||||
import lbryuri from "lbryuri";
|
import lbryuri from "lbryuri";
|
||||||
import FileCard from "component/fileCard";
|
import FileCard from "component/fileCard";
|
||||||
import { BusyMessage } from "component/common.js";
|
import { Icon, BusyMessage } from "component/common.js";
|
||||||
import ToolTip from "component/tooltip.js";
|
import ToolTip from "component/tooltip.js";
|
||||||
|
|
||||||
const FeaturedCategory = props => {
|
class FeaturedCategory extends React.PureComponent {
|
||||||
const { category, names } = props;
|
componentWillMount() {
|
||||||
|
this.setState({
|
||||||
|
numItems: this.props.names.length,
|
||||||
|
canScrollPrevious: false,
|
||||||
|
canScrollNext: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
handleScrollPrevious() {
|
||||||
<div className="card-row card-row--small">
|
const cardRow = ReactDOM.findDOMNode(this.refs.rowitems);
|
||||||
<h3 className="card-row__header">
|
if (cardRow.scrollLeft > 0) {
|
||||||
{category}
|
// check the visible cards
|
||||||
{category &&
|
const cards = cardRow.getElementsByTagName("section");
|
||||||
category.match(/^community/i) &&
|
let firstVisibleCard = null;
|
||||||
<ToolTip
|
let firstVisibleIdx = -1;
|
||||||
label={__("What's this?")}
|
for (var i = 0; i < cards.length; i++) {
|
||||||
body={__(
|
if (this.isCardVisible(cards[i], cardRow, false)) {
|
||||||
'Community Content is a public space where anyone can share content with the rest of the LBRY community. Bid on the names "one," "two," "three," "four" and "five" to put your content here!'
|
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 (
|
||||||
|
<div className="card-row card-row--small">
|
||||||
|
<div className={leftNavClassName}>
|
||||||
|
<a
|
||||||
|
className="card-row__scroll-button"
|
||||||
|
onClick={this.handleScrollPrevious.bind(this)}
|
||||||
|
>
|
||||||
|
<Icon icon="icon-chevron-left" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className={rightNavClassName}>
|
||||||
|
<a
|
||||||
|
className="card-row__scroll-button"
|
||||||
|
onClick={this.handleScrollNext.bind(this)}
|
||||||
|
>
|
||||||
|
<Icon icon="icon-chevron-right" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<h3 className="card-row__header">
|
||||||
|
{category}
|
||||||
|
{category &&
|
||||||
|
category.match(/^community/i) &&
|
||||||
|
<ToolTip
|
||||||
|
label={__("What's this?")}
|
||||||
|
body={__(
|
||||||
|
'Community Content is a public space where anyone can share content with the rest of the LBRY community. Bid on the names "one," "two," "three," "four" and "five" to put your content here!'
|
||||||
|
)}
|
||||||
|
className="tooltip--header"
|
||||||
|
/>}
|
||||||
|
</h3>
|
||||||
|
<div ref="rowitems" className="card-row__items">
|
||||||
|
{names &&
|
||||||
|
names.map(name =>
|
||||||
|
<FileCard
|
||||||
|
key={name}
|
||||||
|
displayStyle="card"
|
||||||
|
uri={lbryuri.normalize(name)}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
className="tooltip--header"
|
</div>
|
||||||
/>}
|
</div>
|
||||||
</h3>
|
);
|
||||||
{names &&
|
}
|
||||||
names.map(name =>
|
}
|
||||||
<FileCard
|
|
||||||
key={name}
|
|
||||||
displayStyle="card"
|
|
||||||
uri={lbryuri.normalize(name)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
class DiscoverPage extends React.PureComponent {
|
class DiscoverPage extends React.PureComponent {
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
@import "../global";
|
@import "../global";
|
||||||
|
|
||||||
$padding-card-horizontal: $spacing-vertical * 2/3;
|
$padding-card-horizontal: $spacing-vertical * 2/3;
|
||||||
|
$translate-card-hover: 10px;
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
|
@ -94,10 +95,13 @@ $card-link-scaling: 1.1;
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
box-shadow: $box-shadow-focus;
|
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%;
|
transform-origin: 50% 50%;
|
||||||
overflow-x: visible;
|
overflow-x: visible;
|
||||||
overflow-y: visible;
|
overflow-y: visible
|
||||||
|
}
|
||||||
|
.card--link:hover ~ .card--link {
|
||||||
|
transform: translate3d($translate-card-hover * 2, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
.card__media {
|
.card__media {
|
||||||
|
@ -183,26 +187,73 @@ $height-card-small: $spacing-vertical * 15;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-row {
|
.card-row {
|
||||||
|
+ .card-row {
|
||||||
|
margin-top: $spacing-vertical * 1/3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.card-row__items {
|
||||||
> .card {
|
> .card {
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-right: $spacing-vertical / 3;
|
margin-right: $spacing-vertical / 3;
|
||||||
}
|
}
|
||||||
+ .card-row {
|
> .card:last-child {
|
||||||
margin-top: $spacing-vertical * 1/3;
|
margin-right: 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.card-row--small {
|
.card-row--small {
|
||||||
overflow-x: auto;
|
overflow: hidden;
|
||||||
overflow-y: hidden;
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
/*hacky way to give space for hover */
|
/*hacky way to give space for hover */
|
||||||
padding-left: 20px;
|
padding-right: 30px;
|
||||||
margin-left: -20px;
|
}
|
||||||
padding-right: 20px;
|
.card-row__items {
|
||||||
margin-right: -20px;
|
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 {
|
.card-row__header {
|
||||||
margin-bottom: $spacing-vertical / 3;
|
margin-bottom: $spacing-vertical / 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.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
|
||||||
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ $color-header-active: darken($color-header, 20%);
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
z-index: 2;
|
z-index: 3;
|
||||||
padding: $spacing-vertical / 2;
|
padding: $spacing-vertical / 2;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue