Add horizontal layout (#636)
* Test out a horizontal scroll for upcoming (tile only for now) * - add support for list layout - add following label on home page - clan up css and naming conventions * Update header type + show only if scheduled streams are showing
This commit is contained in:
parent
58bdcbd1ed
commit
a89cb17ce4
10 changed files with 92 additions and 17 deletions
|
@ -47,6 +47,7 @@ type Props = {
|
|||
maxClaimRender?: number,
|
||||
excludeUris?: Array<string>,
|
||||
loadedCallback?: (number) => void,
|
||||
swipeLayout: boolean,
|
||||
};
|
||||
|
||||
export default function ClaimList(props: Props) {
|
||||
|
@ -80,6 +81,7 @@ export default function ClaimList(props: Props) {
|
|||
maxClaimRender,
|
||||
excludeUris = [],
|
||||
loadedCallback,
|
||||
swipeLayout = false,
|
||||
} = props;
|
||||
|
||||
const [currentSort, setCurrentSort] = usePersistedState(persistedStorageKey, SORT_NEW);
|
||||
|
@ -148,7 +150,7 @@ export default function ClaimList(props: Props) {
|
|||
}, [loading, onScrollBottom, urisLength, pageSize, page]);
|
||||
|
||||
return tileLayout && !header ? (
|
||||
<section className="claim-grid">
|
||||
<section className={classnames('claim-grid', { 'swipe-list': swipeLayout })}>
|
||||
{urisLength > 0 &&
|
||||
tileUris.map((uri) => (
|
||||
<ClaimPreviewTile
|
||||
|
@ -158,6 +160,7 @@ export default function ClaimList(props: Props) {
|
|||
properties={renderProperties}
|
||||
collectionId={collectionId}
|
||||
showNoSourceClaims={showNoSourceClaims}
|
||||
swipeLayout={swipeLayout}
|
||||
/>
|
||||
))}
|
||||
{!timedOut && urisLength === 0 && !loading && <div className="empty main--empty">{empty || noResultMsg}</div>}
|
||||
|
@ -200,8 +203,9 @@ export default function ClaimList(props: Props) {
|
|||
{urisLength > 0 && (
|
||||
<ul
|
||||
className={classnames('ul--no-style', {
|
||||
card: !(tileLayout || type === 'small'),
|
||||
card: !(tileLayout || swipeLayout || type === 'small'),
|
||||
'claim-list--card-body': tileLayout,
|
||||
'swipe-list': swipeLayout,
|
||||
})}
|
||||
>
|
||||
{sortedUris.map((uri, index) => (
|
||||
|
@ -223,6 +227,7 @@ export default function ClaimList(props: Props) {
|
|||
showNoSourceClaims={showNoSourceClaims}
|
||||
customShouldHide={customShouldHide}
|
||||
onClick={handleClaimClicked}
|
||||
swipeLayout={swipeLayout}
|
||||
/>
|
||||
</React.Fragment>
|
||||
))}
|
||||
|
|
|
@ -100,6 +100,8 @@ type Props = {
|
|||
maxClaimRender?: number,
|
||||
useSkeletonScreen?: boolean,
|
||||
excludeUris?: Array<string>,
|
||||
|
||||
swipeLayout: boolean,
|
||||
};
|
||||
|
||||
function ClaimListDiscover(props: Props) {
|
||||
|
@ -169,6 +171,7 @@ function ClaimListDiscover(props: Props) {
|
|||
maxClaimRender,
|
||||
useSkeletonScreen = true,
|
||||
excludeUris = [],
|
||||
swipeLayout = false,
|
||||
} = props;
|
||||
const didNavigateForward = history.action === 'PUSH';
|
||||
const { search } = location;
|
||||
|
@ -634,6 +637,7 @@ function ClaimListDiscover(props: Props) {
|
|||
maxClaimRender={maxClaimRender}
|
||||
excludeUris={excludeUris}
|
||||
loadedCallback={loadedCallback}
|
||||
swipeLayout={swipeLayout}
|
||||
/>
|
||||
{loading && useSkeletonScreen && (
|
||||
<div className="claim-grid">
|
||||
|
@ -670,6 +674,7 @@ function ClaimListDiscover(props: Props) {
|
|||
maxClaimRender={maxClaimRender}
|
||||
excludeUris={excludeUris}
|
||||
loadedCallback={loadedCallback}
|
||||
swipeLayout={swipeLayout}
|
||||
/>
|
||||
{loading &&
|
||||
useSkeletonScreen &&
|
||||
|
|
|
@ -86,6 +86,7 @@ type Props = {
|
|||
date?: any,
|
||||
indexInContainer?: number, // The index order of this component within 'containerId'.
|
||||
channelSubCount?: number,
|
||||
swipeLayout: boolean,
|
||||
};
|
||||
|
||||
const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
||||
|
@ -146,6 +147,7 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
|||
disableNavigation,
|
||||
indexInContainer,
|
||||
channelSubCount,
|
||||
swipeLayout = false,
|
||||
} = props;
|
||||
|
||||
const isCollection = claim && claim.value_type === 'collection';
|
||||
|
@ -341,6 +343,7 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
|||
'claim-preview--channel': isChannelUri,
|
||||
'claim-preview--visited': !isChannelUri && !claimIsMine && hasVisitedUri,
|
||||
'claim-preview--pending': pending,
|
||||
'swipe-list__item': swipeLayout,
|
||||
})}
|
||||
>
|
||||
{isChannelUri && claim ? (
|
||||
|
|
|
@ -46,6 +46,7 @@ type Props = {
|
|||
isLivestream: boolean,
|
||||
viewCount: string,
|
||||
isLivestreamActive: boolean,
|
||||
swipeLayout: boolean,
|
||||
};
|
||||
|
||||
// preview image cards used in related video functionality, channel overview page and homepage
|
||||
|
@ -73,6 +74,7 @@ function ClaimPreviewTile(props: Props) {
|
|||
collectionId,
|
||||
mediaDuration,
|
||||
viewCount,
|
||||
swipeLayout = false,
|
||||
} = props;
|
||||
const isRepost = claim && claim.repost_channel_url;
|
||||
const isCollection = claim && claim.value_type === 'collection';
|
||||
|
@ -178,6 +180,7 @@ function ClaimPreviewTile(props: Props) {
|
|||
className={classnames('card claim-preview--tile', {
|
||||
'claim-preview__wrapper--channel': isChannel,
|
||||
'claim-preview__live': isLivestreamActive,
|
||||
'swipe-list__item claim-preview--horizontal-tile': swipeLayout,
|
||||
})}
|
||||
>
|
||||
<NavLink {...navLinkProps} role="none" tabIndex={-1} aria-hidden>
|
||||
|
|
|
@ -16,13 +16,22 @@ type Props = {
|
|||
tileLayout: boolean,
|
||||
liveUris: Array<string>,
|
||||
limitClaimsPerChannel?: number,
|
||||
onLoad: (number) => void,
|
||||
// --- perform ---
|
||||
setClientSetting: (string, boolean | string | number, boolean) => void,
|
||||
doShowSnackBar: (string) => void,
|
||||
};
|
||||
|
||||
const ScheduledStreams = (props: Props) => {
|
||||
const { channelIds, tileLayout, liveUris = [], limitClaimsPerChannel, setClientSetting, doShowSnackBar } = props;
|
||||
const {
|
||||
channelIds,
|
||||
tileLayout,
|
||||
liveUris = [],
|
||||
limitClaimsPerChannel,
|
||||
setClientSetting,
|
||||
doShowSnackBar,
|
||||
onLoad,
|
||||
} = props;
|
||||
const isMediumScreen = useIsMediumScreen();
|
||||
const isLargeScreen = useIsLargeScreen();
|
||||
|
||||
|
@ -30,16 +39,18 @@ const ScheduledStreams = (props: Props) => {
|
|||
const [showAllUpcoming, setShowAllUpcoming] = React.useState(false);
|
||||
|
||||
const showUpcomingLivestreams = totalUpcomingLivestreams > 0;
|
||||
const useSwipeLayout = totalUpcomingLivestreams > 1 && isMediumScreen;
|
||||
|
||||
const upcomingMax = React.useMemo(() => {
|
||||
if (showAllUpcoming) return 50;
|
||||
if (showAllUpcoming || useSwipeLayout) return 50;
|
||||
if (isLargeScreen) return 6;
|
||||
if (isMediumScreen) return 3;
|
||||
return 4;
|
||||
}, [showAllUpcoming, isMediumScreen, isLargeScreen]);
|
||||
}, [showAllUpcoming, isMediumScreen, isLargeScreen, useSwipeLayout]);
|
||||
|
||||
const loadedCallback = (total) => {
|
||||
setTotalUpcomingLivestreams(total);
|
||||
if (typeof onLoad === 'function') onLoad(total);
|
||||
};
|
||||
|
||||
const hideScheduledStreams = () => {
|
||||
|
@ -57,8 +68,9 @@ const ScheduledStreams = (props: Props) => {
|
|||
};
|
||||
|
||||
return (
|
||||
<div className={'mb-xl'} style={{ display: showUpcomingLivestreams ? 'block' : 'none' }}>
|
||||
<div className={'mb-m mt-m md:mb-xl'} style={{ display: showUpcomingLivestreams ? 'block' : 'none' }}>
|
||||
<ClaimListDiscover
|
||||
swipeLayout={useSwipeLayout}
|
||||
useSkeletonScreen={false}
|
||||
channelIds={channelIds}
|
||||
limitClaimsPerChannel={limitClaimsPerChannel}
|
||||
|
@ -78,7 +90,7 @@ const ScheduledStreams = (props: Props) => {
|
|||
excludeUris={liveUris}
|
||||
loadedCallback={loadedCallback}
|
||||
/>
|
||||
{totalUpcomingLivestreams > upcomingMax && !showAllUpcoming && (
|
||||
{totalUpcomingLivestreams > upcomingMax && !showAllUpcoming && !useSwipeLayout && (
|
||||
<div className="livestream-list--view-more">
|
||||
<Button
|
||||
label={__('Show more upcoming livestreams')}
|
||||
|
|
|
@ -3,7 +3,7 @@ import * as ICONS from 'constants/icons';
|
|||
import * as PAGES from 'constants/pages';
|
||||
import { SITE_NAME, SIMPLE_SITE, ENABLE_NO_SOURCE_CLAIMS, SHOW_ADS } from 'config';
|
||||
import Ads, { injectAd } from 'web/component/ads';
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import Page from 'component/page';
|
||||
import Button from 'component/button';
|
||||
import ClaimTilesDiscover from 'component/claimTilesDiscover';
|
||||
|
@ -65,6 +65,24 @@ function HomePage(props: Props) {
|
|||
showNsfw
|
||||
);
|
||||
|
||||
type SectionHeaderProps = {
|
||||
title: string,
|
||||
navigate?: string,
|
||||
icon?: string,
|
||||
help?: string,
|
||||
};
|
||||
const SectionHeader = ({ title, navigate = '/', icon = '', help }: SectionHeaderProps) => {
|
||||
return (
|
||||
<h1 className="claim-grid__header">
|
||||
<Button navigate={navigate} button="link">
|
||||
<Icon className="claim-grid__header-icon" sectionIcon icon={icon} size={20} />
|
||||
<span className="claim-grid__title">{title}</span>
|
||||
{help}
|
||||
</Button>
|
||||
</h1>
|
||||
);
|
||||
};
|
||||
|
||||
function getRowElements(title, route, link, icon, help, options, index, pinUrls) {
|
||||
const tilePlaceholder = (
|
||||
<ul className="claim-grid">
|
||||
|
@ -88,13 +106,7 @@ function HomePage(props: Props) {
|
|||
<div key={title} className="claim-grid__wrapper">
|
||||
{/* category header */}
|
||||
{index !== 0 && title && typeof title === 'string' && (
|
||||
<h1 className="claim-grid__header">
|
||||
<Button navigate={route || link} button="link">
|
||||
{icon && <Icon className="claim-grid__header-icon" sectionIcon icon={icon} size={20} />}
|
||||
<span className="claim-grid__title">{__(title)}</span>
|
||||
{help}
|
||||
</Button>
|
||||
</h1>
|
||||
<SectionHeader title={__(title)} navigate={route || link} icon={icon} help={help} />
|
||||
)}
|
||||
|
||||
{index === 0 && <>{claimTiles}</>}
|
||||
|
@ -128,6 +140,9 @@ function HomePage(props: Props) {
|
|||
injectAd(shouldShowAds);
|
||||
}, []);
|
||||
|
||||
const [hasScheduledStreams, setHasScheduledStreams] = useState(false);
|
||||
const scheduledStreamsLoaded = (total) => setHasScheduledStreams(total > 0);
|
||||
|
||||
return (
|
||||
<Page fullWidthPage>
|
||||
{!SIMPLE_SITE && (authenticated || !IS_WEB) && !subscribedChannels.length && (
|
||||
|
@ -158,8 +173,14 @@ function HomePage(props: Props) {
|
|||
tileLayout
|
||||
liveUris={getLivestreamUris(activeLivestreams, channelIds)}
|
||||
limitClaimsPerChannel={2}
|
||||
onLoad={scheduledStreamsLoaded}
|
||||
/>
|
||||
)}
|
||||
|
||||
{authenticated && hasScheduledStreams && !hideScheduledLivestreams && (
|
||||
<SectionHeader title={__('Following')} navigate={`/$/${PAGES.CHANNELS_FOLLOWING}`} icon={ICONS.SUBSCRIBE} />
|
||||
)}
|
||||
|
||||
{rowData.map(({ title, route, link, icon, help, pinnedUrls: pinUrls, options = {} }, index) => {
|
||||
// add pins here
|
||||
return getRowElements(title, route, link, icon, help, options, index, pinUrls);
|
||||
|
|
|
@ -68,4 +68,5 @@
|
|||
@import 'component/empty';
|
||||
@import 'component/stripe-card';
|
||||
@import 'component/wallet-tip-send';
|
||||
@import 'component/swipe-list';
|
||||
@import 'component/utils';
|
||||
|
|
|
@ -552,6 +552,12 @@
|
|||
}
|
||||
}
|
||||
|
||||
.claim-preview--horizontal-tile {
|
||||
&:not(:first-child) {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.claim-tile__title {
|
||||
position: relative;
|
||||
padding: var(--spacing-s);
|
||||
|
@ -562,7 +568,7 @@
|
|||
font-weight: 600;
|
||||
color: var(--color-text);
|
||||
font-size: var(--font-small);
|
||||
min-height: 2rem;
|
||||
min-height: 3.2rem;
|
||||
|
||||
@media (min-width: $breakpoint-small) {
|
||||
min-height: 2.5rem;
|
||||
|
|
13
ui/scss/component/_swipe-list.scss
Normal file
13
ui/scss/component/_swipe-list.scss
Normal file
|
@ -0,0 +1,13 @@
|
|||
.swipe-list {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
overflow-x: auto;
|
||||
scroll-snap-type: x mandatory;
|
||||
}
|
||||
|
||||
.swipe-list__item {
|
||||
width: 80vw;
|
||||
margin-right: var(--spacing-s);
|
||||
flex-shrink: 0;
|
||||
scroll-snap-align: start;
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.flex-col {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
@ -25,6 +24,10 @@
|
|||
height: 3rem;
|
||||
}
|
||||
|
||||
.mt-0 {
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
.mt-s {
|
||||
margin-top: var(--spacing-s);
|
||||
}
|
||||
|
@ -80,6 +83,9 @@
|
|||
.md\:mt-0 {
|
||||
margin-top: 0;
|
||||
}
|
||||
.md\:mb-xl {
|
||||
margin-bottom: var(--spacing-xl);
|
||||
}
|
||||
.md\:h-12 {
|
||||
height: 3rem;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue