{suggested.map(({ uri, label }) => (
))}
diff --git a/src/ui/index.jsx b/src/ui/index.jsx
index e01169358..a928ebd7d 100644
--- a/src/ui/index.jsx
+++ b/src/ui/index.jsx
@@ -11,20 +11,13 @@ import * as MODALS from 'constants/modal_types';
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
-import {
- doConditionalAuthNavigate,
- doDaemonReady,
- doAutoUpdate,
- doOpenModal,
- doHideModal,
-} from 'redux/actions/app';
+import { doConditionalAuthNavigate, doDaemonReady, doAutoUpdate, doOpenModal, doHideModal } from 'redux/actions/app';
import { Lbry, doToast, isURIValid, setSearchApi } from 'lbry-redux';
import { doDownloadLanguages, doUpdateIsNightAsync } from 'redux/actions/settings';
import { doAuthenticate, Lbryio, rewards, doBlackListedOutpointsSubscribe } from 'lbryinc';
import { store, history } from 'store';
import pjson from 'package.json';
import app from './app';
-import analytics from './analytics';
import doLogWarningConsoleMessage from './logWarningConsoleMessage';
import { ConnectedRouter, push } from 'connected-react-router';
import cookie from 'cookie';
diff --git a/src/ui/page/channel/index.js b/src/ui/page/channel/index.js
index 8c798ab0b..0974411c7 100644
--- a/src/ui/page/channel/index.js
+++ b/src/ui/page/channel/index.js
@@ -1,30 +1,20 @@
import { connect } from 'react-redux';
-import { doFetchClaimsByChannel } from 'redux/actions/content';
-import { PAGE_SIZE } from 'constants/claim';
import {
- makeSelectClaimForUri,
- makeSelectClaimsInChannelForCurrentPageState,
- makeSelectFetchingChannelClaims,
makeSelectClaimIsMine,
- makeSelectTotalPagesForChannel,
+ makeSelectTitleForUri,
+ makeSelectThumbnailForUri,
+ makeSelectCoverForUri,
} from 'lbry-redux';
-import { doOpenModal } from 'redux/actions/app';
import ChannelPage from './view';
const select = (state, props) => ({
- claim: makeSelectClaimForUri(props.uri)(state),
- claimsInChannel: makeSelectClaimsInChannelForCurrentPageState(props.uri)(state),
- fetching: makeSelectFetchingChannelClaims(props.uri)(state),
- totalPages: makeSelectTotalPagesForChannel(props.uri, PAGE_SIZE)(state),
+ title: makeSelectTitleForUri(props.uri)(state),
+ thumbnail: makeSelectThumbnailForUri(props.uri)(state),
+ cover: makeSelectCoverForUri(props.uri)(state),
channelIsMine: makeSelectClaimIsMine(props.uri)(state),
});
-const perform = dispatch => ({
- fetchClaims: (uri, page) => dispatch(doFetchClaimsByChannel(uri, page)),
- openModal: (modal, props) => dispatch(doOpenModal(modal, props)),
-});
-
export default connect(
select,
- perform
+ null
)(ChannelPage);
diff --git a/src/ui/page/channel/view.jsx b/src/ui/page/channel/view.jsx
index 7171bd64d..28b9c8b05 100644
--- a/src/ui/page/channel/view.jsx
+++ b/src/ui/page/channel/view.jsx
@@ -1,143 +1,86 @@
// @flow
-import * as icons from 'constants/icons';
-import * as MODALS from 'constants/modal_types';
-import React, { useEffect } from 'react';
-import BusyIndicator from 'component/common/busy-indicator';
-import { FormField, Form } from 'component/common/form';
-import ReactPaginate from 'react-paginate';
-import SubscribeButton from 'component/subscribeButton';
+import React from 'react';
+import { parseURI } from 'lbry-redux';
import Page from 'component/page';
-import FileList from 'component/fileList';
-import HiddenNsfwClaims from 'component/hiddenNsfwClaims';
-import Button from 'component/button';
-import { withRouter } from 'react-router-dom';
+import SubscribeButton from 'component/subscribeButton';
+import ShareButton from 'component/shareButton';
+import { Tabs, TabList, Tab, TabPanels, TabPanel } from 'component/common/tabs';
+import { withRouter } from 'react-router';
+import { formatLbryUriForWeb } from 'util/uri';
+import ChannelContent from 'component/channelContent';
+import ChannelAbout from 'component/channelAbout';
+import ChannelThumbnail from 'component/channelThumbnail';
+
+const PAGE_VIEW_QUERY = `view`;
+const ABOUT_PAGE = `about`;
type Props = {
uri: string,
- totalPages: number,
- fetching: boolean,
- params: { page: number },
- claim: ChannelClaim,
- claimsInChannel: Array
,
- channelIsMine: boolean,
- fetchClaims: (string, number) => void,
+ title: ?string,
+ cover: ?string,
+ thumbnail: ?string,
+ location: { search: string },
history: { push: string => void },
- openModal: (id: string, { uri: string }) => void,
- location: UrlLocation,
+ match: { params: { attribute: ?string } },
};
function ChannelPage(props: Props) {
- const {
- uri,
- fetching,
- claimsInChannel,
- claim,
- totalPages,
- channelIsMine,
- openModal,
- fetchClaims,
- location,
- history,
- } = props;
-
- const { name, permanent_url: permanentUrl } = claim;
+ const { uri, title, cover, history, location } = props;
+ const { channelName, claimName, claimId } = parseURI(uri);
const { search } = location;
const urlParams = new URLSearchParams(search);
- const page = Number(urlParams.get('page')) || 1;
+ const currentView = urlParams.get(PAGE_VIEW_QUERY) || undefined;
- useEffect(() => {
- // Fetch new claims if the channel or page number changes
- fetchClaims(uri, page);
- }, [uri, page]);
-
- const changePage = (pageNumber: number) => {
- if (!page && pageNumber === 1) {
- return;
+ // If a user changes tabs, update the url so it stays on the same page if they refresh.
+ // We don't want to use links here because we can't animate the tab change and using links
+ // would alter the Tab label's role attribute, which should stay role="tab" to work with keyboards/screen readers.
+ const tabIndex = currentView === ABOUT_PAGE ? 1 : 0;
+ const onTabChange = newTabIndex => {
+ let url = formatLbryUriForWeb(uri);
+ if (newTabIndex !== 0) {
+ url += `?${PAGE_VIEW_QUERY}=${ABOUT_PAGE}`;
}
- history.push(`?page=${pageNumber}`);
- };
-
- const paginate = (e: SyntheticKeyboardEvent<*>) => {
- // Change page if enter was pressed, and the given page is between the first and the last page
- const pageFromInput = Number(e.currentTarget.value);
-
- if (
- pageFromInput &&
- e.keyCode === 13 &&
- !Number.isNaN(pageFromInput) &&
- pageFromInput > 0 &&
- pageFromInput <= totalPages
- ) {
- changePage(pageFromInput);
- }
+ history.push(url);
};
return (
-
-
-
- {name}
- {fetching && }
-
- {permanentUrl}
+
+
+ {cover && }
-
-
-
- openModal(MODALS.SOCIAL_SHARE, { uri, speechShareable: true, isChannel: true })
- }
- />
+
+
+
+
+
{title || channelName}
+
+ {claimName}
+ {claimId && `#${claimId}`}
+
+
-
- {claimsInChannel && claimsInChannel.length ? (
-
- ) : (
- !fetching && {__('No content found.')}
- )}
-
+
+
+ {__('Content')}
+ {__('About')}
+
+
+
+
+
- {(!fetching || (claimsInChannel && claimsInChannel.length)) && totalPages > 1 && (
-
- )}
-
- {!channelIsMine && }
+
+
+
+
+
+
+
+
+
);
}
diff --git a/src/ui/page/discover/view.jsx b/src/ui/page/discover/view.jsx
index faac6c831..f9ecc8648 100644
--- a/src/ui/page/discover/view.jsx
+++ b/src/ui/page/discover/view.jsx
@@ -61,7 +61,7 @@ class DiscoverPage extends React.PureComponent {
const failedToLoad = !fetchingFeaturedUris && !hasContent;
return (
-
+
{hasContent &&
Object.keys(featuredUris).map(category => (
diff --git a/src/ui/page/fileListPublished/view.jsx b/src/ui/page/fileListPublished/view.jsx
index 2871286fc..e72971574 100644
--- a/src/ui/page/fileListPublished/view.jsx
+++ b/src/ui/page/fileListPublished/view.jsx
@@ -23,29 +23,17 @@ class FileListPublished extends React.PureComponent {
return (
{claims && claims.length ? (
-
+
) : (
-
- {__("It looks like you haven't published anything to LBRY yet.")}
-
+ {__("It looks like you haven't published anything to LBRY yet.")}
diff --git a/src/ui/page/search/view.jsx b/src/ui/page/search/view.jsx
index ddf7264e5..40aa9c91b 100644
--- a/src/ui/page/search/view.jsx
+++ b/src/ui/page/search/view.jsx
@@ -1,13 +1,10 @@
// @flow
-import * as ICONS from 'constants/icons';
import React, { useEffect, Fragment } from 'react';
import { isURIValid, normalizeURI, parseURI } from 'lbry-redux';
import FileTile from 'component/fileTile';
import ChannelTile from 'component/channelTile';
import FileListSearch from 'component/fileListSearch';
import Page from 'component/page';
-import ToolTip from 'component/common/tooltip';
-import Icon from 'component/common/icon';
import SearchOptions from 'component/searchOptions';
import Button from 'component/button';
@@ -24,7 +21,6 @@ export default function SearchPage(props: Props) {
let uri;
let isChannel;
- let label;
if (isValid) {
uri = normalizeURI(urlQuery);
({ isChannel } = parseURI(uri));
@@ -34,10 +30,10 @@ export default function SearchPage(props: Props) {
if (urlQuery) {
doSearch(urlQuery);
}
- }, [urlQuery]);
+ }, [doSearch, urlQuery]);
return (
-
+
{urlQuery && (
@@ -58,9 +54,7 @@ export default function SearchPage(props: Props) {
-
- {__('These search results are provided by LBRY, Inc.')}
-
+ {__('These search results are provided by LBRY, Inc.')}
)}
diff --git a/src/ui/page/subscriptions/internal/first-run.jsx b/src/ui/page/subscriptions/internal/first-run.jsx
index 7c4806dbd..130e11050 100644
--- a/src/ui/page/subscriptions/internal/first-run.jsx
+++ b/src/ui/page/subscriptions/internal/first-run.jsx
@@ -1,5 +1,5 @@
// @flow
-import React, { Fragment } from 'react';
+import React from 'react';
import Button from 'component/button';
import SuggestedSubscriptions from 'component/subscribeSuggested';
import Yrbl from 'component/yrbl';
@@ -12,17 +12,11 @@ type Props = {
doShowSuggestedSubs: () => void,
};
-export default (props: Props) => {
- const {
- showSuggested,
- loadingSuggested,
- numberOfSubscriptions,
- doShowSuggestedSubs,
- onFinish,
- } = props;
+export default function SubscriptionsFirstRun(props: Props) {
+ const { showSuggested, loadingSuggested, numberOfSubscriptions, doShowSuggestedSubs, onFinish } = props;
return (
-
+
0 ? __('Woohoo!') : __('No subscriptions... yet.')}
subtitle={
@@ -52,6 +46,6 @@ export default (props: Props) => {
}
/>
{showSuggested && !loadingSuggested && }
-
+
);
-};
+}
diff --git a/src/ui/page/subscriptions/internal/user-subscriptions.jsx b/src/ui/page/subscriptions/internal/user-subscriptions.jsx
index d96cda951..77c78b904 100644
--- a/src/ui/page/subscriptions/internal/user-subscriptions.jsx
+++ b/src/ui/page/subscriptions/internal/user-subscriptions.jsx
@@ -11,7 +11,7 @@ import SuggestedSubscriptions from 'component/subscribeSuggested';
import MarkAsRead from 'component/subscribeMarkAsRead';
import Tooltip from 'component/common/tooltip';
import Yrbl from 'component/yrbl';
-import { formatLbryUriForWeb } from 'util/uri';
+import { Tabs, TabList, Tab, TabPanels, TabPanel } from 'component/common/tabs';
type Props = {
viewMode: ViewMode,
@@ -33,27 +33,18 @@ export default (props: Props) => {
onChangeAutoDownload,
unreadSubscriptions,
} = props;
+
+ const index = viewMode === VIEW_ALL ? 0 : 1;
+ const onTabChange = index => (index === 0 ? doSetViewMode(VIEW_ALL) : doSetViewMode(VIEW_LATEST_FIRST));
+
return (
{hasSubscriptions && (
-
-
-
- doSetViewMode(VIEW_ALL)}
- />
- doSetViewMode(VIEW_LATEST_FIRST)}
- />
-
+
+
+ {__('All Subscriptions')}
+ {__('Latest Only')}
+
{
labelOnLeft
/>
-
-
- )}
+
- {
- if (name && claimId) {
- arr.push(`lbry://${name}#${claimId}`);
- }
- return arr;
- }, [])}
- />
-
- {!hasSubscriptions && (
-
-
-
-
- )}
-
- {hasSubscriptions && (
-
- {viewMode === VIEW_ALL && (
-
+ {
+ if (name && claimId) {
+ arr.push(`lbry://${name}#${claimId}`);
+ }
+ return arr;
+ }, [])}
+ />
+ }
+ >
+
{__('Your subscriptions')}
{unreadSubscriptions.length > 0 && }
-
- )}
+
- {viewMode === VIEW_LATEST_FIRST && (
-
+
{unreadSubscriptions.length ? (
unreadSubscriptions.map(({ channel, uris }) => {
const { claimName } = parseURI(channel);
@@ -124,16 +100,24 @@ export default (props: Props) => {
})
) : (
-
+
)}
-
- )}
-
+
+
+
+ )}
+
+ {!hasSubscriptions && (
+
+
+
+
)}
);
diff --git a/src/ui/page/subscriptions/view.jsx b/src/ui/page/subscriptions/view.jsx
index b359f8c17..a33475d24 100644
--- a/src/ui/page/subscriptions/view.jsx
+++ b/src/ui/page/subscriptions/view.jsx
@@ -26,7 +26,7 @@ type Props = {
showSuggestedSubs: boolean,
};
-export default class extends PureComponent {
+export default class SubscriptionsPage extends PureComponent {
constructor() {
super();
@@ -78,7 +78,7 @@ export default class extends PureComponent {
// Only pass in the loading prop if there are no subscriptions
// If there are any, let the page update in the background
// The loading prop removes children and shows a loading spinner
-
+
{firstRunCompleted ? (
*:not(:last-child) {
- margin-right: var(--spacing-vertical-large);
+ margin-right: var(--spacing-vertical-medium);
}
}
@@ -118,6 +121,37 @@
.card__list {
display: grid;
grid-gap: var(--spacing-vertical-medium);
+
+ // Depending on screen width, the amount of items in
+ // each row change and are auto-sized
+
+ @media (min-width: 2001px) {
+ grid-template-columns: repeat(auto-fill, minmax(calc(100% / 10), 1fr));
+ }
+
+ @media (min-width: 1801px) and (max-width: 2000px) {
+ grid-template-columns: repeat(auto-fill, minmax(calc(100% / 8), 1fr));
+ }
+
+ @media (min-width: 1551px) and (max-width: 1800px) {
+ grid-template-columns: repeat(auto-fill, minmax(calc(100% / 7), 1fr));
+ }
+
+ @media (min-width: 1051px) and (max-width: 1550px) {
+ grid-template-columns: repeat(auto-fill, minmax(calc(100% / 6), 1fr));
+ }
+
+ @media (min-width: 901px) and (max-width: 1050px) {
+ grid-template-columns: repeat(auto-fill, minmax(calc(100% / 5), 1fr));
+ }
+
+ @media (min-width: 751px) and (max-width: 900px) {
+ grid-template-columns: repeat(auto-fill, minmax(calc(100% / 4), 1fr));
+ }
+
+ @media (max-width: 750px) {
+ grid-template-columns: repeat(auto-fill, minmax(calc(100% / 3), 1fr));
+ }
}
.card__list--rewards {
@@ -137,8 +171,8 @@
.card__message {
border-left: 0.5rem solid;
- padding: var(--spacing-vertical-medium) var(--spacing-vertical-medium)
- var(--spacing-vertical-medium) var(--spacing-vertical-large);
+ padding: var(--spacing-vertical-medium) var(--spacing-vertical-medium) var(--spacing-vertical-medium)
+ var(--spacing-vertical-large);
&:not(&--error):not(&--failure):not(&--success) {
background-color: rgba($lbry-teal-1, 0.1);
@@ -184,6 +218,7 @@
.card__title {
font-size: 2rem;
font-weight: 600;
+ padding-bottom: var(--spacing-vertical-medium);
+ .card__content {
margin-top: var(--spacing-vertical-medium);
diff --git a/src/ui/scss/component/_channel.scss b/src/ui/scss/component/_channel.scss
index 829bc1ace..3552b21c1 100644
--- a/src/ui/scss/component/_channel.scss
+++ b/src/ui/scss/component/_channel.scss
@@ -1,27 +1,71 @@
-.channel-info {
- .media__title {
- display: block;
- user-select: text;
- margin-bottom: var(--spacing-vertical-medium);
- }
+$cover-z-index: 0;
+$metadata-z-index: 1;
- .channel-info__actions__group {
- margin-bottom: var(--spacing-vertical-large);
- }
-}
-
-.channel-info__actions {
+.channel__cover {
+ background-image: linear-gradient(to right, $lbry-indigo-4, $lbry-cyan-5 80%);
display: flex;
+ align-items: flex-end;
+ box-sizing: content-box;
+ color: $lbry-white;
}
-.channel-info__actions__group {
- @extend .media__action-group;
- @extend .media__action-group--large;
+.channel__cover--custom {
+ z-index: $cover-z-index;
+ align-self: flex-start;
+ position: absolute;
+ object-fit: cover;
+ filter: brightness(40%);
}
-.channel-name {
- overflow: hidden;
- text-align: left;
- text-overflow: ellipsis;
- white-space: nowrap;
+.channel__cover,
+.channel__cover--custom {
+ height: var(--cover-photo-height);
+ width: 100%;
}
+
+.channel__thumbnail {
+ position: absolute;
+ left: var(--spacing-main-padding);
+ height: var(--channel-thumbnail-size);
+ width: var(--channel-thumbnail-size);
+ background-color: $lbry-gray-3;
+ background-image: linear-gradient(to right, $lbry-white, $lbry-gray-3 80%);
+ background-size: cover;
+ box-shadow: 0px 8px 40px -3px $lbry-black;
+}
+
+.channel__thumbnail--custom {
+ width: 100%;
+ object-fit: cover;
+}
+
+.channel__thumbnail,
+.channel__thumbnail--custom {
+ border-radius: var(--card-radius);
+}
+
+.channel__primary-info {
+ // Ensure the profile pic/title sit ontop of the default cover background
+ z-index: $metadata-z-index;
+ // Jump over the thumbnail photo because it is absolutely positioned
+ // Then add normal page spacing, _then_ add the actual padding
+ margin-left: calc(var(--channel-thumbnail-size) + var(--spacing-main-padding));
+ padding-left: var(--spacing-vertical-large);
+ padding-bottom: var(--spacing-vertical-medium);
+}
+
+.channel__title {
+ font-size: 3rem;
+ font-weight: 800;
+}
+
+.channel__url {
+ font-size: 1.2rem;
+ user-select: all;
+ margin-top: -0.25rem;
+}
+
+// .channel__description {
+// font-size: 1.3rem;
+// margin: var(--spacing-vertical-large) 0;
+// }
diff --git a/src/ui/scss/component/_form-field.scss b/src/ui/scss/component/_form-field.scss
index f2ad6dea8..b2ae23630 100644
--- a/src/ui/scss/component/_form-field.scss
+++ b/src/ui/scss/component/_form-field.scss
@@ -87,7 +87,7 @@ fieldset-group {
}
&.fieldgroup--paginate {
- margin-top: var(--spacing-vertical-medium);
+ margin: var(--spacing-vertical-large) 0;
align-items: center;
justify-content: center;
diff --git a/src/ui/scss/component/_main.scss b/src/ui/scss/component/_main.scss
index 93fdaf86d..9f38ce6e8 100644
--- a/src/ui/scss/component/_main.scss
+++ b/src/ui/scss/component/_main.scss
@@ -1,14 +1,18 @@
.main-wrapper {
position: absolute;
top: var(--header-height);
- left: var(--side-nav-width);
min-height: calc(100% - var(--header-height));
- width: calc(100% - var(--side-nav-width));
+ width: 100%;
background-color: mix($lbry-white, $lbry-gray-1, 70%);
html[data-mode='dark'] & {
background-color: $lbry-black;
}
+
+ @media (min-width: 600px) {
+ left: var(--side-nav-width);
+ width: calc(100% - var(--side-nav-width));
+ }
}
.main {
@@ -19,13 +23,23 @@
.main--contained {
max-width: 900px;
+ padding: var(--spacing-main-padding);
}
-.main:not(.main--no-padding) {
- padding: var(--spacing-vertical-large);
+.main--not-contained {
+ padding: var(--spacing-main-padding);
+}
+
+.main--no-padding {
+ padding: 0;
+}
+
+.main--no-padding-top {
+ padding-top: 0;
}
.main--file-page {
+ padding: var(--spacing-main-padding);
max-width: var(--file-page-max-width);
display: grid;
grid-gap: var(--spacing-vertical-large);
@@ -35,7 +49,7 @@
'content content'
'info related';
- @media (min-width: 1470px) {
+ @media (min-width: 1300px) {
grid-template-areas:
'content related'
'info related';
@@ -60,3 +74,16 @@
margin-bottom: 100px;
text-align: center;
}
+
+// On pages that are not contained, they still might want to have items inside the page
+// that extend the full width ex: cover photo
+// But the components inside of those pages should be still have "page" padding
+.main__item--extend-outside {
+ $main-width: calc(100vw - var(--side-nav-width));
+ width: $main-width;
+ position: relative;
+ left: 50%;
+ right: 50%;
+ margin-left: calc(-50vw + (var(--side-nav-width) * 0.5));
+ margin-right: calc(-50vw + (var(--side-nav-width) * 0.5));
+}
diff --git a/src/ui/scss/component/_media.scss b/src/ui/scss/component/_media.scss
index e26ca8f4a..3d75cd3a9 100644
--- a/src/ui/scss/component/_media.scss
+++ b/src/ui/scss/component/_media.scss
@@ -345,42 +345,6 @@
// G R O U P
.media-group--list {
- .card__list {
- padding-top: var(--spacing-vertical-large);
- padding-bottom: var(--spacing-vertical-large);
-
- // Depending on screen width, the amount of items in
- // each row change and are auto-sized
-
- @media (min-width: 2001px) {
- grid-template-columns: repeat(auto-fill, minmax(calc(100% / 10), 1fr));
- }
-
- @media (min-width: 1801px) and (max-width: 2000px) {
- grid-template-columns: repeat(auto-fill, minmax(calc(100% / 8), 1fr));
- }
-
- @media (min-width: 1551px) and (max-width: 1800px) {
- grid-template-columns: repeat(auto-fill, minmax(calc(100% / 7), 1fr));
- }
-
- @media (min-width: 1051px) and (max-width: 1550px) {
- grid-template-columns: repeat(auto-fill, minmax(calc(100% / 6), 1fr));
- }
-
- @media (min-width: 901px) and (max-width: 1050px) {
- grid-template-columns: repeat(auto-fill, minmax(calc(100% / 5), 1fr));
- }
-
- @media (min-width: 751px) and (max-width: 900px) {
- grid-template-columns: repeat(auto-fill, minmax(calc(100% / 4), 1fr));
- }
-
- @media (max-width: 750px) {
- grid-template-columns: repeat(auto-fill, minmax(calc(100% / 3), 1fr));
- }
- }
-
.media-card {
display: inline-block;
margin-bottom: var(--spacing-vertical-large);
@@ -473,11 +437,7 @@
background-image: linear-gradient(to right, $lbry-white 80%, transparent 100%);
html[data-mode='dark'] & {
- background-image: linear-gradient(
- to right,
- mix($lbry-white, $lbry-gray-3, 50%) 80%,
- transparent 100%
- );
+ background-image: linear-gradient(to right, mix($lbry-white, $lbry-gray-3, 50%) 80%, transparent 100%);
}
}
diff --git a/src/ui/scss/component/_navigation.scss b/src/ui/scss/component/_navigation.scss
index 5ef13c6af..1440578c9 100644
--- a/src/ui/scss/component/_navigation.scss
+++ b/src/ui/scss/component/_navigation.scss
@@ -35,11 +35,7 @@
right: 0;
// TODO: Make this gradient "to bottom" on mobile view
- background-image: linear-gradient(
- to right,
- transparent,
- rgba(mix($lbry-blue-3, $lbry-gray-4, 15%), 0.2) 100%
- );
+ background-image: linear-gradient(to right, transparent, rgba(mix($lbry-blue-3, $lbry-gray-4, 15%), 0.2) 100%);
content: '';
position: absolute;
@@ -93,7 +89,7 @@
}
&::before {
- width: 0.5rem;
+ width: var(--tab-indicator-size);
}
}
diff --git a/src/ui/scss/component/_search.scss b/src/ui/scss/component/_search.scss
index 160ecf35c..0e43769da 100644
--- a/src/ui/scss/component/_search.scss
+++ b/src/ui/scss/component/_search.scss
@@ -1,25 +1,13 @@
.search__header {
- background-color: $lbry-black;
- color: $lbry-white;
padding: var(--spacing-vertical-large);
.placeholder {
background-color: rgba($lbry-white, 0.1);
}
- .media__subtext {
- color: rgba($lbry-white, 0.6);
- }
-
.media__subtitle {
- color: rgba($lbry-white, 0.9);
font-size: 0.7em;
}
-
- html[data-mode='dark'] & {
- background-color: transparent;
- border-bottom: 1px solid rgba($lbry-white, 0.1);
- }
}
.search__title {
@@ -37,7 +25,7 @@
}
.search__results-wrapper {
- padding: var(--spacing-vertical-large);
+ margin: var(--spacing-vertical-large);
}
.search__results-section {
@@ -53,7 +41,7 @@
.search__options-wrapper {
font-size: 1.25em;
- padding-bottom: var(--spacing-vertical-large);
+ margin-bottom: var(--spacing-vertical-large);
}
.search__options {
diff --git a/src/ui/scss/component/_subscriptions.scss b/src/ui/scss/component/_subscriptions.scss
index 8ce693adc..5c5b23e74 100644
--- a/src/ui/scss/component/_subscriptions.scss
+++ b/src/ui/scss/component/_subscriptions.scss
@@ -1,8 +1,4 @@
-// The gerbil is tied to subscriptions currently, but this style should move to it's own file once
-// the gerbil is added in more places with different layouts
.subscriptions__suggested {
animation: expand 0.2s;
- left: -2rem;
position: relative;
- width: calc(100% + 4rem);
}
diff --git a/src/ui/scss/component/tabs.scss b/src/ui/scss/component/tabs.scss
new file mode 100644
index 000000000..5f83fbb34
--- /dev/null
+++ b/src/ui/scss/component/tabs.scss
@@ -0,0 +1,62 @@
+.tabs {
+ position: relative;
+}
+
+.tabs__list {
+ display: flex;
+ align-items: center;
+ background-color: $lbry-black;
+ color: $lbry-white;
+ padding: var(--spacing-vertical-medium) var(--spacing-main-padding);
+
+ & > *:not(.tab) {
+ // If there is anything after the tabs, render it on the opposite side of the page
+ margin-left: auto;
+ }
+
+ [data-mode='dark'] & {
+ background-color: darken($lbry-black, 5%);
+ }
+}
+
+.tabs__list--channel-page {
+ padding-left: calc(var(--channel-thumbnail-size) + var(--spacing-main-padding) + var(--spacing-vertical-large));
+}
+
+.tab {
+ margin-right: var(--spacing-vertical-large);
+ padding: 5px 0;
+ font-weight: 700;
+ font-size: var(--tab-header-size);
+ color: $lbry-white;
+ position: relative;
+
+ &::after {
+ position: absolute;
+ bottom: calc(var(--tab-indicator-size) * -2);
+ height: 0;
+ width: 100%;
+ content: '';
+ }
+}
+
+.tab__divider {
+ position: absolute;
+ margin-top: calc(var(--tab-indicator-size) * -1);
+}
+
+.tab::after,
+.tab__divider {
+ display: block;
+ transition: all var(--animation-duration) var(--animation-style);
+}
+
+.tab:hover::after,
+.tab__divider {
+ height: var(--tab-indicator-size);
+ background-color: $lbry-teal-3;
+}
+
+.tab__panel {
+ margin-top: var(--spacing-vertical-large);
+}
diff --git a/src/ui/scss/init/_gui.scss b/src/ui/scss/init/_gui.scss
index 22e0f845e..2f61ea48b 100644
--- a/src/ui/scss/init/_gui.scss
+++ b/src/ui/scss/init/_gui.scss
@@ -134,6 +134,7 @@ code {
padding: 1rem;
margin-top: var(--spacing-vertical-large);
margin-bottom: var(--spacing-vertical-large);
+ border-radius: 5px;
html[data-mode='dark'] & {
color: inherit;
diff --git a/src/ui/scss/init/_reset.scss b/src/ui/scss/init/_reset.scss
index 015835566..0a969e1a9 100644
--- a/src/ui/scss/init/_reset.scss
+++ b/src/ui/scss/init/_reset.scss
@@ -79,16 +79,6 @@ dl {
width: 100%;
}
-dt {
- float: left;
- width: 20%;
-}
-
-dd {
- float: left;
- width: 80%;
-}
-
textarea {
border: 1px solid $lbry-gray-2;
padding: $spacing-vertical * 1/3;
diff --git a/src/ui/scss/init/_vars.scss b/src/ui/scss/init/_vars.scss
index 9e1392c6c..54e18fb9e 100644
--- a/src/ui/scss/init/_vars.scss
+++ b/src/ui/scss/init/_vars.scss
@@ -16,6 +16,7 @@ $large-breakpoint: 1921px;
--spacing-vertical-medium: calc(2rem / 2);
--spacing-vertical-large: 2rem;
--spacing-vertical-xlarge: 3rem;
+ --spacing-main-padding: var(--spacing-vertical-xlarge);
--file-page-max-width: 1787px;
--file-max-height: 788px;
@@ -23,10 +24,16 @@ $large-breakpoint: 1921px;
--video-aspect-ratio: 56.25%; // 9 x 16
+ --channel-thumbnail-size: 10rem;
+
// Text
--text-max-width: 660px;
--text-link-padding: 4px;
+ // Tabs
+ --tab-indicator-size: 0.5rem;
+ --tab-header-size: 1.5rem; // Needs to be static so the animated styling always works
+
// Input
--input-border-size: 1px;
@@ -44,8 +51,9 @@ $large-breakpoint: 1921px;
--search-modal-input-height: 70px;
// Card
- --card-radius: 2px;
+ --card-radius: 5px;
--card-max-width: 1000px;
+ --card-box-shadow: 0px 8px 20px;
// File
--file-tile-media-height: 125px;
@@ -62,10 +70,11 @@ $large-breakpoint: 1921px;
--modal-width: 440px;
// Animation :)
- --animation-duration: 0.3s;
- --animation-style: cubic-bezier(0.55, 0, 0.1, 1);
+ --animation-duration: 0.2s;
+ --animation-style: ease-in-out;
// Image
--thumbnail-preview-height: 100px;
--thumbnail-preview-width: 177px;
+ --cover-photo-height: 250px;
}
diff --git a/webpack.web.config.js b/webpack.web.config.js
index b0e89c0a7..731c4bed8 100644
--- a/webpack.web.config.js
+++ b/webpack.web.config.js
@@ -18,6 +18,9 @@ const webConfig = {
path: __dirname + '/dist/web',
publicPath: '/',
},
+ devServer: {
+ historyApiFallback: true,
+ },
module: {
rules: [
{
diff --git a/yarn.lock b/yarn.lock
index de1413017..dc237e15d 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -885,6 +885,43 @@
resolved "https://registry.yarnpkg.com/@posthtml/esm/-/esm-1.0.0.tgz#09bcb28a02438dcee22ad1970ca1d85a000ae0cf"
integrity sha512-dEVG+ITnvqKGa4v040tP+n8LOKOqr94qjLva7bE5pnfm2KHJwsKz69J4KMxgWLznbpBJzy8vQfCayEk3vLZnZQ==
+"@reach/auto-id@^0.2.0":
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/@reach/auto-id/-/auto-id-0.2.0.tgz#97f9e48fe736aa5c6f4f32cf73c1f19d005f8550"
+ integrity sha512-lVK/svL2HuQdp7jgvlrLkFsUx50Az9chAhxpiPwBqcS83I2pVWvXp98FOcSCCJCV++l115QmzHhFd+ycw1zLBg==
+
+"@reach/component-component@^0.1.3":
+ version "0.1.3"
+ resolved "https://registry.yarnpkg.com/@reach/component-component/-/component-component-0.1.3.tgz#5d156319572dc38995b246f81878bc2577c517e5"
+ integrity sha512-a1USH7L3bEfDdPN4iNZGvMEFuBfkdG+QNybeyDv8RloVFgZYRoM+KGXyy2KOfEnTUM8QWDRSROwaL3+ts5Angg==
+
+"@reach/observe-rect@^1.0.3":
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/@reach/observe-rect/-/observe-rect-1.0.3.tgz#2ea3dcc369ab22bd9f050a92ea319321356a61e8"
+ integrity sha1-LqPcw2mrIr2fBQqS6jGTITVqYeg=
+
+"@reach/rect@^0.2.1":
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/@reach/rect/-/rect-0.2.1.tgz#7343020174c90e2290b844d17c03fd9c78e6b601"
+ integrity sha512-aZ9RsNHDMQ3zETonikqu9/85iXxj+LPqZ9Gr9UAncj3AufYmGeWG3XG6b37B+7ORH+mkhVpLU2ZlIWxmOe9Cqg==
+ dependencies:
+ "@reach/component-component" "^0.1.3"
+ "@reach/observe-rect" "^1.0.3"
+
+"@reach/tabs@^0.1.5":
+ version "0.1.5"
+ resolved "https://registry.yarnpkg.com/@reach/tabs/-/tabs-0.1.5.tgz#0a3a8c863cc50ac661b3a66afea0f9315c8d8b2b"
+ integrity sha512-thQKlbN7kN/YoFfBjTVxAlRlYor0dFg7QnZwUN9v1OYFLHMoPpmwaQkae8mAEibRb4BPGgjnoSpdfco2lzP37A==
+ dependencies:
+ "@reach/auto-id" "^0.2.0"
+ "@reach/utils" "^0.2.2"
+ warning "^4.0.2"
+
+"@reach/utils@^0.2.2":
+ version "0.2.2"
+ resolved "https://registry.yarnpkg.com/@reach/utils/-/utils-0.2.2.tgz#c3a05ae9fd1f921988ae8a89b5a0d28d1a2b92df"
+ integrity sha512-jYeIi46AA5jh2gfdXD/nInUYfeLp3girRafiajP7AVHF6B4hpYAzUSx/ZH4xmPyf5alut5rml2DHxrv+X+Xu+A==
+
"@samverschueren/stream-to-observable@^0.3.0":
version "0.3.0"
resolved "https://registry.yarnpkg.com/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz#ecdf48d532c58ea477acfcab80348424f8d0662f"
@@ -4092,6 +4129,11 @@ eslint-plugin-promise@^4.0.1:
resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.1.1.tgz#1e08cb68b5b2cd8839f8d5864c796f56d82746db"
integrity sha512-faAHw7uzlNPy7b45J1guyjazw28M+7gJokKUjC5JSFoYfUEyy6Gw/i7YQvmv2Yk00sUjWcmzXQLpU1Ki/C2IZQ==
+eslint-plugin-react-hooks@^1.6.0:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-1.6.0.tgz#348efcda8fb426399ac7b8609607c7b4025a6f5f"
+ integrity sha512-lHBVRIaz5ibnIgNG07JNiAuBUeKhEf8l4etNx5vfAEwqQ5tcuK3jV9yjmopPgQDagQb7HwIuQVsE3IVcGrRnag==
+
eslint-plugin-react@^7.7.0:
version "7.12.4"
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.12.4.tgz#b1ecf26479d61aee650da612e425c53a99f48c8c"
@@ -6456,9 +6498,9 @@ lazy-val@^1.0.3, lazy-val@^1.0.4:
yargs "^13.2.2"
zstd-codec "^0.1.1"
-lbry-redux@lbryio/lbry-redux#cc42856676541120b088e4228c04246ba8ff3274:
+lbry-redux@lbryio/lbry-redux#459bea2257d61003e591daf169fefe9624522680:
version "0.0.1"
- resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/cc42856676541120b088e4228c04246ba8ff3274"
+ resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/459bea2257d61003e591daf169fefe9624522680"
dependencies:
proxy-polyfill "0.1.6"
reselect "^3.0.0"
@@ -11671,6 +11713,13 @@ warning@^3.0.0:
dependencies:
loose-envify "^1.0.0"
+warning@^4.0.2:
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3"
+ integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==
+ dependencies:
+ loose-envify "^1.0.0"
+
watchpack@^1.5.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.6.0.tgz#4bc12c2ebe8aa277a71f1d3f14d685c7b446cd00"