collapsable sidebar initial commit

This commit is contained in:
Sean Yesmunt 2020-08-10 16:47:39 -04:00
parent f88b98ea62
commit 692862c769
45 changed files with 776 additions and 512 deletions

View file

@ -1275,5 +1275,6 @@
"URL": "URL",
"Total Uploads": "Total Uploads",
"Are you sure you want to purchase %claim_title%?": "Are you sure you want to purchase %claim_title%?",
"Discover": "Discover",
"--end--": "--end--"
}

View file

@ -19,6 +19,7 @@ type Props = {
isBodyList?: boolean,
defaultExpand?: boolean,
nag?: Node,
smallTitle?: boolean,
};
export default function Card(props: Props) {
@ -33,6 +34,7 @@ export default function Card(props: Props) {
isPageTitle = false,
isBodyList = false,
noTitleWrap = false,
smallTitle = false,
defaultExpand,
nag,
} = props;
@ -47,11 +49,18 @@ export default function Card(props: Props) {
'card__header--nowrap': noTitleWrap,
})}
>
<div className={classnames('card__title-section', { 'card__title-section--body-list': isBodyList })}>
<div
className={classnames('card__title-section', {
'card__title-section--body-list': isBodyList,
'card__title-section--small': smallTitle,
})}
>
{icon && <Icon sectionIcon icon={icon} />}
<div>
{isPageTitle && <h1 className="card__title">{title}</h1>}
{!isPageTitle && <h2 className="card__title">{title}</h2>}
{!isPageTitle && (
<h2 className={classnames('card__title', { 'card__title--small': smallTitle })}>{title}</h2>
)}
{subtitle && <div className="card__subtitle">{subtitle}</div>}
</div>
</div>

View file

@ -3,7 +3,7 @@ import React from 'react';
import { withRouter } from 'react-router';
import { Form, FormField } from 'component/common/form';
import ReactPaginate from 'react-paginate';
import useIsMobile from 'effects/use-is-mobile';
import { useIsMobile } from 'effects/use-screensize';
const PAGINATE_PARAM = 'page';

View file

@ -3,7 +3,7 @@ import React, { useEffect } from 'react';
import Button from 'component/button';
import FileViewerEmbeddedTitle from 'component/fileViewerEmbeddedTitle';
import { useHistory } from 'react-router-dom';
import useIsMobile from 'effects/use-is-mobile';
import { useIsMobile } from 'effects/use-screensize';
import { formatLbryUrlForWeb } from 'util/url';
type Props = {

View file

@ -10,7 +10,7 @@ import Button from 'component/button';
import FileDownloadLink from 'component/fileDownloadLink';
import { buildURI } from 'lbry-redux';
import * as RENDER_MODES from 'constants/file_render_modes';
import useIsMobile from 'effects/use-is-mobile';
import { useIsMobile } from 'effects/use-screensize';
import ClaimSupportButton from 'component/claimSupportButton';
type Props = {

View file

@ -1,5 +1,5 @@
// @flow
import React, { Fragment, PureComponent } from 'react';
import React, { PureComponent } from 'react';
import MarkdownPreview from 'component/common/markdown-preview';
import ClaimTags from 'component/claimTags';
import Card from 'component/common/card';
@ -23,23 +23,22 @@ class FileDescription extends PureComponent<Props> {
const { description } = metadata;
if (!description && !(tags && tags.length)) return null;
return (
<Fragment>
<Card
title={__('Description')}
defaultExpand
actions={
<>
{description && (
<div className="media__info-text">
<MarkdownPreview content={description} />
</div>
)}
<ClaimTags uri={uri} type="large" />
</>
}
/>
</Fragment>
<Card
title={__('Description')}
defaultExpand
actions={
<>
{description && (
<div className="media__info-text">
<MarkdownPreview content={description} />
</div>
)}
<ClaimTags uri={uri} type="large" />
</>
}
/>
);
}
}

View file

@ -12,7 +12,7 @@ import { FILE_WRAPPER_CLASS } from 'page/file/view';
import Draggable from 'react-draggable';
import Tooltip from 'component/common/tooltip';
import { onFullscreenChange } from 'util/full-screen';
import useIsMobile from 'effects/use-is-mobile';
import { useIsMobile } from 'effects/use-screensize';
type Props = {
isFloating: boolean,

View file

@ -28,7 +28,6 @@ const perform = dispatch => ({
setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)),
syncSettings: () => dispatch(doSyncClientSettings()),
signOut: () => dispatch(doSignOut()),
openMobileNavigation: () => dispatch(doOpenModal(MODALS.MOBILE_NAVIGATION)),
openChannelCreate: () => dispatch(doOpenModal(MODALS.CREATE_CHANNEL)),
openSignOutModal: () => dispatch(doOpenModal(MODALS.SIGN_OUT)),
clearEmailEntry: () => dispatch(doClearEmailEntry()),

View file

@ -12,9 +12,11 @@ import Icon from 'component/common/icon';
import { Menu, MenuList, MenuButton, MenuItem } from '@reach/menu-button';
import Tooltip from 'component/common/tooltip';
import NavigationButton from 'component/navigationButton';
import NotificationHeaderButton from 'component/notificationHeaderButton';
import { LOGO_TITLE } from 'config';
import useIsMobile from 'effects/use-is-mobile';
import { useIsMobile } from 'effects/use-screensize';
import NotificationBubble from 'component/notificationBubble';
import NotificationHeaderButton from 'component/notificationHeaderButton';
// @if TARGET='app'
import { remote } from 'electron';
import { IS_MAC } from 'component/app/view';
@ -49,13 +51,15 @@ type Props = {
syncError: ?string,
emailToVerify?: string,
signOut: () => void,
openMobileNavigation: () => void,
openChannelCreate: () => void,
openSignOutModal: () => void,
clearEmailEntry: () => void,
clearPasswordEntry: () => void,
hasNavigated: boolean,
syncSettings: () => void,
sidebarOpen: boolean,
setSidebarOpen: boolean => void,
isAbsoluteSideNavHidden: boolean,
};
const Header = (props: Props) => {
@ -71,13 +75,15 @@ const Header = (props: Props) => {
authHeader,
signOut,
syncError,
openMobileNavigation,
openSignOutModal,
clearEmailEntry,
clearPasswordEntry,
emailToVerify,
backout,
syncSettings,
sidebarOpen,
setSidebarOpen,
isAbsoluteSideNavHidden,
} = props;
const isMobile = useIsMobile();
// on the verify page don't let anyone escape other than by closing the tab to keep session data consistent
@ -164,9 +170,6 @@ const Header = (props: Props) => {
<header
className={classnames('header', {
'header--minimal': authHeader,
// @if TARGET='web'
'header--noauth-web': !authenticated,
// @endif
// @if TARGET='app'
'header--mac': IS_MAC,
// @endif
@ -203,6 +206,14 @@ const Header = (props: Props) => {
) : (
<>
<div className="header__navigation">
<span style={{ position: 'relative' }}>
<Button
className="header__navigation-item menu__title header__navigation-item--icon"
icon={ICONS.MENU}
onClick={() => setSidebarOpen(!sidebarOpen)}
/>
{isAbsoluteSideNavHidden && <NotificationBubble />}
</span>
<Button
className="header__navigation-item header__navigation-item--lbry header__navigation-item--button-mobile"
// @if TARGET='app'
@ -223,33 +234,20 @@ const Header = (props: Props) => {
{...homeButtonNavigationProps}
/>
{/* @if TARGET='app' */}
{!authHeader && (
<div className="header__navigation-arrows">
<NavigationButton isBackward history={history} />
<NavigationButton isBackward={false} history={history} />
</div>
)}
{/* @endif */}
<div className="header__center">
{/* @if TARGET='app' */}
{!authHeader && (
<div className="header__buttons">
<NavigationButton isBackward history={history} />
<NavigationButton isBackward={false} history={history} />
</div>
)}
{/* @endif */}
{!authHeader && <WunderBar />}
</div>
{!authHeader && <WunderBar />}
{!authHeader ? (
<div className={classnames('header__menu', { 'header__menu--with-balance': !IS_WEB || authenticated })}>
{(!IS_WEB || authenticated) && (
<Fragment>
<Button
aria-label={__('Your wallet')}
navigate={`/$/${PAGES.WALLET}`}
className="header__navigation-item menu__title header__navigation-item--balance"
label={getWalletTitle()}
// @if TARGET='app'
onDoubleClick={e => {
e.stopPropagation();
}}
// @endif
/>
<div className="header__buttons mobile-hidden">
<Menu>
<MenuButton
aria-label={__('Publish a file, or create a channel')}
@ -263,6 +261,7 @@ const Header = (props: Props) => {
>
<Icon size={18} icon={ICONS.PUBLISH} aria-hidden />
</MenuButton>
<NotificationHeaderButton />
<MenuList className="menu__list--header">
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.UPLOAD}`)}>
<Icon aria-hidden icon={ICONS.PUBLISH} />
@ -275,8 +274,6 @@ const Header = (props: Props) => {
</MenuList>
</Menu>
<NotificationHeaderButton />
<Menu>
<MenuButton
aria-label={__('Your account')}
@ -334,37 +331,57 @@ const Header = (props: Props) => {
)}
</MenuList>
</Menu>
<Menu>
<MenuButton
aria-label={__('Settings')}
title={__('Settings')}
className="header__navigation-item menu__title header__navigation-item--icon"
// @if TARGET='app'
onDoubleClick={e => {
e.stopPropagation();
}}
// @endif
>
<Icon size={18} icon={ICONS.SETTINGS} aria-hidden />
</MenuButton>
<MenuList className="menu__list--header">
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.SETTINGS}`)}>
<Icon aria-hidden tootlip icon={ICONS.SETTINGS} />
{__('Settings')}
</MenuItem>
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.HELP}`)}>
<Icon aria-hidden icon={ICONS.HELP} />
{__('Help')}
</MenuItem>
<MenuItem className="menu__link" onSelect={handleThemeToggle}>
<Icon icon={currentTheme === 'light' ? ICONS.DARK : ICONS.LIGHT} />
{currentTheme === 'light' ? __('Dark') : __('Light')}
</MenuItem>
</MenuList>
</Menu>
</div>
</div>
)}
</div>
{!authHeader ? (
<div className={classnames('header__menu', { 'header__menu--with-balance': !IS_WEB || authenticated })}>
{(!IS_WEB || authenticated) && (
<Fragment>
<Button
aria-label={__('Your wallet')}
navigate={`/$/${PAGES.WALLET}`}
className="header__navigation-item menu__title header__navigation-item--balance"
label={getWalletTitle()}
// @if TARGET='app'
onDoubleClick={e => {
e.stopPropagation();
}}
// @endif
/>
</Fragment>
)}
<Menu>
<MenuButton
aria-label={__('Settings')}
title={__('Settings')}
className="header__navigation-item menu__title header__navigation-item--icon"
// @if TARGET='app'
onDoubleClick={e => {
e.stopPropagation();
}}
// @endif
>
<Icon size={18} icon={ICONS.SETTINGS} aria-hidden />
</MenuButton>
<MenuList className="menu__list--header">
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.SETTINGS}`)}>
<Icon aria-hidden tootlip icon={ICONS.SETTINGS} />
{__('Settings')}
</MenuItem>
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.HELP}`)}>
<Icon aria-hidden icon={ICONS.HELP} />
{__('Help')}
</MenuItem>
<MenuItem className="menu__link" onSelect={handleThemeToggle}>
<Icon icon={currentTheme === 'light' ? ICONS.DARK : ICONS.LIGHT} />
{currentTheme === 'light' ? __('Dark') : __('Light')}
</MenuItem>
</MenuList>
</Menu>
{IS_WEB && !authenticated && (
<div className="header__auth-buttons">
<Button navigate={`/$/${PAGES.AUTH_SIGNIN}`} button="link" label={__('Sign In')} />
@ -394,17 +411,6 @@ const Header = (props: Props) => {
</div>
)
)}
<Button
onClick={openMobileNavigation}
icon={ICONS.MENU}
iconSize={24}
className="header__menu--mobile"
// @if TARGET='app'
onDoubleClick={e => {
e.stopPropagation();
}}
// @endif
/>
</>
)}
</div>

View file

@ -0,0 +1,9 @@
import { connect } from 'react-redux';
import { selectUnreadNotificationCount } from 'redux/selectors/notifications';
import NotificationHeaderButton from './view';
const select = state => ({
unreadCount: selectUnreadNotificationCount(state),
});
export default connect(select)(NotificationHeaderButton);

View file

@ -0,0 +1,20 @@
// @flow
import React from 'react';
type Props = {
unreadCount: number,
};
export default function NotificationHeaderButton(props: Props) {
const { unreadCount } = props;
if (unreadCount === 0) {
return null;
}
return (
<span className="notification__bubble">
<span className="notification__count">{unreadCount}</span>
</span>
);
}

View file

@ -8,6 +8,10 @@ import Footer from 'web/component/footer';
/* @if TARGET='app' */
import StatusBar from 'component/common/status-bar';
/* @endif */
import usePersistedState from 'effects/use-persisted-state';
import { useHistory } from 'react-router';
import { useIsMobile, useIsMediumScreen } from 'effects/use-screensize';
import { parseURI } from 'lbry-redux';
export const MAIN_CLASS = 'main';
type Props = {
@ -16,9 +20,11 @@ type Props = {
autoUpdateDownloaded: boolean,
isUpgradeAvailable: boolean,
authPage: boolean,
filePage: boolean,
noHeader: boolean,
noFooter: boolean,
noSideNavigation: boolean,
fullWidth: boolean,
backout: {
backLabel?: string,
backNavDefault?: string,
@ -32,18 +38,61 @@ function Page(props: Props) {
children,
className,
authPage = false,
filePage = false,
noHeader = false,
noFooter = false,
noSideNavigation = false,
backout,
} = props;
const {
location: { pathname },
} = useHistory();
const [sidebarOpen, setSidebarOpen] = usePersistedState('sidebar', true);
const isMediumScreen = useIsMediumScreen();
const isMobile = useIsMobile();
let isOnFilePage = false;
try {
const url = pathname.slice(1).replace(/:/g, '#');
const { isChannel } = parseURI(url);
if (!isChannel) {
isOnFilePage = true;
}
} catch (e) {}
const isAbsoluteSideNavHidden = (isOnFilePage || isMobile) && !sidebarOpen;
React.useEffect(() => {
if (isOnFilePage || isMediumScreen) {
setSidebarOpen(false);
}
}, [isOnFilePage, isMediumScreen]);
return (
<Fragment>
{!noHeader && <Header authHeader={authPage} backout={backout} />}
<div className={classnames('main-wrapper__inner')}>
<main className={classnames(MAIN_CLASS, className, { 'main--full-width': authPage })}>{children}</main>
{!authPage && !noSideNavigation && <SideNavigation />}
{!noHeader && (
<Header
authHeader={authPage}
backout={backout}
sidebarOpen={sidebarOpen}
isAbsoluteSideNavHidden={isAbsoluteSideNavHidden}
setSidebarOpen={setSidebarOpen}
/>
)}
<div className={classnames('main-wrapper__inner', { 'main-wrapper__inner--filepage': isOnFilePage })}>
{!authPage && !noSideNavigation && (
<SideNavigation
sidebarOpen={sidebarOpen}
setSidebarOpen={setSidebarOpen}
isMediumScreen={isMediumScreen}
isOnFilePage={isOnFilePage}
/>
)}
<main
className={classnames(MAIN_CLASS, className, { 'main--full-width': authPage, 'main--file-page': filePage })}
>
{children}
</main>
{/* @if TARGET='app' */}
<StatusBar />
{/* @endif */}

View file

@ -71,6 +71,8 @@ export default class RecommendedContent extends React.PureComponent<Props> {
return (
<Card
isBodyList
smallTitle
className="file-page__recommended"
title={__('Related')}
body={
<WaitUntilOnPage lastUpdateDate={this.lastReset}>

View file

@ -212,13 +212,7 @@ function AppRouter(props: Props) {
<Route path={`/$/${PAGES.AUTH}`} exact component={SignUpPage} />
<Route path={`/$/${PAGES.AUTH}/*`} exact component={SignUpPage} />
<Route path={`/$/${PAGES.WELCOME}`} exact component={Welcome} />
<Route path={`/$/${PAGES.TAGS_FOLLOWING}`} exact component={TagsFollowingPage} />
<Route
path={`/$/${PAGES.CHANNELS_FOLLOWING}`}
exact
component={isAuthenticated || !IS_WEB ? ChannelsFollowingPage : DiscoverPage}
/>
<Route path={`/$/${PAGES.CHANNELS_FOLLOWING_DISCOVER}`} exact component={ChannelsFollowingDiscoverPage} />
<Route path={`/$/${PAGES.HELP}`} exact component={HelpPage} />
{/* @if TARGET='app' */}
<Route path={`/$/${PAGES.BACKUP}`} exact component={BackupPage} />
@ -235,11 +229,17 @@ function AppRouter(props: Props) {
<Route path={`/$/${PAGES.INVITE}/:referrer`} exact component={InvitedPage} />
<Route path={`/$/${PAGES.CHECKOUT}`} exact component={CheckoutPage} />
<PrivateRoute {...props} path={`/$/${PAGES.TAGS_FOLLOWING}`} component={TagsFollowingPage} />
<PrivateRoute
{...props}
path={`/$/${PAGES.SETTINGS_NOTIFICATIONS}`}
exact
component={SettingsNotificationsPage}
path={`/$/${PAGES.CHANNELS_FOLLOWING}`}
component={isAuthenticated || !IS_WEB ? ChannelsFollowingPage : DiscoverPage}
/>
<PrivateRoute {...props} path={`/$/${PAGES.SETTINGS_NOTIFICATIONS}`} component={SettingsNotificationsPage} />
<PrivateRoute
{...props}
path={`/$/${PAGES.CHANNELS_FOLLOWING_DISCOVER}`}
component={ChannelsFollowingDiscoverPage}
/>
<PrivateRoute {...props} path={`/$/${PAGES.INVITE}`} component={InvitePage} />
<PrivateRoute {...props} path={`/$/${PAGES.CHANNEL_NEW}`} component={ChannelNew} />

View file

@ -1,19 +1,19 @@
import { connect } from 'react-redux';
import { selectSubscriptions } from 'redux/selectors/subscriptions';
import { selectFollowedTags, selectPurchaseUriSuccess, doClearPurchasedUriSuccess, SETTINGS } from 'lbry-redux';
import { selectUploadCount } from 'lbryinc';
import { selectPurchaseUriSuccess, doClearPurchasedUriSuccess, SETTINGS } from 'lbry-redux';
import { selectUserVerifiedEmail } from 'redux/selectors/user';
import { makeSelectClientSetting } from 'redux/selectors/settings';
import { doSignOut } from 'redux/actions/app';
import { selectUnreadNotificationCount } from 'redux/selectors/notifications';
import SideNavigation from './view';
const select = state => ({
subscriptions: selectSubscriptions(state),
followedTags: selectFollowedTags(state),
language: makeSelectClientSetting(SETTINGS.LANGUAGE)(state), // trigger redraw on language change
uploadCount: selectUploadCount(state),
email: selectUserVerifiedEmail(state),
purchaseSuccess: selectPurchaseUriSuccess(state),
unreadCount: selectUnreadNotificationCount(state),
});
export default connect(select, {

View file

@ -1,31 +1,129 @@
// @flow
import type { Node } from 'react';
import * as PAGES from 'constants/pages';
import * as ICONS from 'constants/icons';
import React from 'react';
import { withRouter } from 'react-router';
import Button from 'component/button';
import Tag from 'component/tag';
import StickyBox from 'react-sticky-box/dist/esnext';
import Spinner from 'component/spinner';
import usePersistedState from 'effects/use-persisted-state';
import classnames from 'classnames';
import { PINNED_LABEL_1, PINNED_URI_1, PINNED_URI_2, PINNED_LABEL_2 } from 'config';
// @if TARGET='web'
// import Ads from 'web/component/ads';
// @endif
import NotificationBubble from 'component/notificationBubble';
const SHOW_CHANNELS = 'SHOW_CHANNELS';
const SHOW_TAGS = 'SHOW_TAGS';
const ESCAPE_KEY_CODE = 27;
const BACKSLASH_KEY_CODE = 220;
const TOP_LEVEL_LINKS: Array<{
label: string,
navigate: string,
icon: string,
extra?: Node,
hideForUnauth?: boolean,
}> = [
{
label: __('Home'),
navigate: `/`,
icon: ICONS.HOME,
},
{
label: __('Following'),
navigate: `/$/${PAGES.CHANNELS_FOLLOWING}`,
icon: ICONS.SUBSCRIBE,
},
{
label: __('Your Tags'),
navigate: `/$/${PAGES.TAGS_FOLLOWING}`,
icon: ICONS.TAG,
hideForUnauth: true,
},
{
label: __('Discover'),
navigate: `/$/${PAGES.DISCOVER}`,
icon: ICONS.DISCOVER,
},
{
label: __('Purchased'),
navigate: `/$/${PAGES.LIBRARY}`,
icon: ICONS.PURCHASED,
hideForUnauth: true,
},
];
const ABSOLUTE_LINKS: Array<{
label: string,
navigate: string,
icon: string,
extra?: Node,
hideForUnauth?: boolean,
}> = [
{
label: __('Upload'),
navigate: `/$/${PAGES.UPLOAD}`,
icon: ICONS.PUBLISH,
},
{
label: __('New Channel'),
navigate: `/$/${PAGES.CHANNEL_NEW}`,
icon: ICONS.CHANNEL,
},
{
label: __('Uploads'),
navigate: `/$/${PAGES.UPLOADS}`,
icon: ICONS.PUBLISH,
hideForUnauth: true,
},
{
label: __('Channels'),
navigate: `/$/${PAGES.CHANNELS}`,
icon: ICONS.CHANNEL,
hideForUnauth: true,
},
{
label: __('Creator Analytics'),
navigate: `/$/${PAGES.CREATOR_ANALYTICS}`,
icon: ICONS.ANALYTICS,
},
{
label: __('Notifications'),
navigate: `/$/${PAGES.NOTIFICATIONS}`,
icon: ICONS.NOTIFICATION,
extra: <NotificationBubble />,
hideForUnauth: true,
},
{
label: __('Rewards'),
navigate: `/$/${PAGES.REWARDS}`,
icon: ICONS.REWARDS,
hideForUnauth: true,
},
{
label: __('Invites'),
navigate: `/$/${PAGES.INVITES}`,
icon: ICONS.INVITE,
hideForUnauth: true,
},
{
label: __('Settings'),
navigate: `/$/${PAGES.SETTINGS}`,
icon: ICONS.SETTINGS,
hideForUnauth: true,
},
{
label: __('Help'),
navigate: `/$/${PAGES.HELP}`,
icon: ICONS.HELP,
hideForUnauth: true,
},
];
type Props = {
subscriptions: Array<Subscription>,
followedTags: Array<Tag>,
email: ?string,
uploadCount: number,
sticky: boolean,
expanded: boolean,
doSignOut: () => void,
location: { pathname: string },
sidebarOpen: boolean,
setSidebarOpen: boolean => void,
isMediumScreen: boolean,
isOnFilePage: boolean,
unreadCount: number,
purchaseSuccess: boolean,
doClearPurchasedUriSuccess: () => void,
};
@ -33,44 +131,20 @@ type Props = {
function SideNavigation(props: Props) {
const {
subscriptions,
followedTags,
uploadCount,
doSignOut,
// doSignOut,
email,
sticky = true,
expanded = false,
location,
purchaseSuccess,
doClearPurchasedUriSuccess,
sidebarOpen,
setSidebarOpen,
isMediumScreen,
isOnFilePage,
unreadCount,
} = props;
const { pathname } = location;
const isAuthenticated = Boolean(email);
const [pulseLibrary, setPulseLibrary] = React.useState(false);
const [sideInformation, setSideInformation] = usePersistedState(
'side-navigation:information',
getSideInformation(pathname)
);
const isPersonalized = !IS_WEB || isAuthenticated;
const requireAuthOnPersonalizedActions = IS_WEB;
function getSideInformation(path) {
switch (path) {
case `/$/${PAGES.CHANNELS_FOLLOWING}`:
return SHOW_CHANNELS;
case `/$/${PAGES.TAGS_FOLLOWING}`:
return SHOW_TAGS;
default:
return sideInformation;
}
}
React.useEffect(() => {
const sideInfo = getSideInformation(pathname);
setSideInformation(sideInfo);
}, [pathname, setSideInformation]);
const isAbsolute = isOnFilePage || isMediumScreen;
React.useEffect(() => {
if (purchaseSuccess) {
@ -85,172 +159,119 @@ function SideNavigation(props: Props) {
}
}, [setPulseLibrary, purchaseSuccess, doClearPurchasedUriSuccess]);
function buildLink(path, label, icon, onClick, requiresAuth = false, isLiteral = false) {
return {
navigate: !isLiteral ? `$/${path}` : `${path}`,
label,
icon,
onClick,
requiresAuth,
};
}
React.useEffect(() => {
function handleKeydown(e: SyntheticKeyboardEvent<*>) {
if (e.keyCode === ESCAPE_KEY_CODE && isAbsolute) {
setSidebarOpen(false);
} else if (e.keyCode === BACKSLASH_KEY_CODE) {
const hasActiveInput = document.querySelector('input:focus');
if (!hasActiveInput) {
setSidebarOpen(!sidebarOpen);
}
}
}
window.addEventListener('keydown', handleKeydown);
return () => window.removeEventListener('keydown', handleKeydown);
}, [sidebarOpen, setSidebarOpen, isAbsolute]);
const Wrapper = ({ children }: any) =>
sticky ? (
!isOnFilePage && !isMediumScreen ? (
<StickyBox offsetTop={100} offsetBottom={20}>
{children}
</StickyBox>
) : (
<div>{children}</div>
children
);
return (
<Wrapper>
<nav className="navigation">
<ul className="navigation-links">
{[
{
...(expanded && !isAuthenticated ? { ...buildLink(PAGES.AUTH, __('Sign Up'), ICONS.SIGN_UP) } : {}),
},
{
...(expanded && !isAuthenticated
? { ...buildLink(PAGES.AUTH_SIGNIN, __('Sign In'), ICONS.SIGN_IN) }
: {}),
},
{
...buildLink('/', __('Home'), ICONS.HOME, null, null, true),
},
{
...buildLink(
PAGES.CHANNELS_FOLLOWING,
__('Following'),
ICONS.SUBSCRIBE,
null,
requireAuthOnPersonalizedActions
),
},
{
...buildLink(PAGES.TAGS_FOLLOWING, __('Your Tags'), ICONS.TAG, null, requireAuthOnPersonalizedActions),
},
{
...buildLink(PAGES.DISCOVER, __('All Content'), ICONS.DISCOVER),
},
{
...buildLink(PAGES.LIBRARY, IS_WEB ? __('Purchased') : __('Library'), ICONS.LIBRARY),
},
// @if TARGET='web'
{
...(PINNED_URI_1
? { ...buildLink(`${PINNED_URI_1}`, `${PINNED_LABEL_1}`, ICONS.PINNED, null, null, true) }
: {}),
},
{
...(PINNED_URI_2
? { ...buildLink(`${PINNED_URI_2}`, `${PINNED_LABEL_2}`, ICONS.PINNED, null, null, true) }
: {}),
},
// @endif
{
...(expanded ? { ...buildLink(PAGES.SETTINGS, __('Settings'), ICONS.SETTINGS) } : {}),
},
].map(
linkProps =>
linkProps.navigate && (
{!isOnFilePage && (
<nav className={classnames('navigation', { 'navigation--micro': !sidebarOpen || isMediumScreen })}>
<ul className={classnames('navigation-links--relative', { 'navigation-links--micro': !sidebarOpen })}>
{TOP_LEVEL_LINKS.map(linkProps =>
!email && linkProps.hideForUnauth && IS_WEB ? null : (
<li key={linkProps.navigate}>
<Button
{...linkProps}
icon={pulseLibrary && linkProps.icon === ICONS.LIBRARY ? ICONS.PURCHASED : linkProps.icon}
className={classnames('navigation-link', {
'navigation-link--pulse': linkProps.icon === ICONS.LIBRARY && pulseLibrary,
'navigation-link--extra': linkProps.icon === ICONS.NOTIFICATION && unreadCount > 0,
})}
activeClass="navigation-link--active"
/>
{linkProps.extra}
</li>
)
)}
{expanded &&
isPersonalized &&
[
{
...buildLink(PAGES.CHANNELS, __('Channels'), ICONS.CHANNEL),
},
{
...buildLink(
PAGES.UPLOADS,
uploadCount ? (
<span>
{__('Uploads')}
<Spinner type="small" />
</span>
) : (
__('Uploads')
),
ICONS.PUBLISH
),
},
{
...buildLink(PAGES.CREATOR_DASHBOARD, __('Creator Analytics'), ICONS.ANALYTICS),
},
{
...buildLink(PAGES.WALLET, __('Wallet'), ICONS.WALLET),
},
{
...buildLink(PAGES.REWARDS, __('Rewards'), ICONS.REWARDS),
},
{
...buildLink(PAGES.INVITE, __('Invites'), ICONS.INVITE),
},
{
...buildLink(PAGES.UPLOAD, __('Upload'), ICONS.PUBLISH),
},
{
...buildLink(PAGES.HELP, __('Help'), ICONS.HELP),
},
{
...(isAuthenticated ? { ...buildLink(PAGES.AUTH, __('Sign Out'), ICONS.SIGN_OUT, doSignOut) } : {}),
},
].map(
linkProps =>
Object.keys(linkProps).length > 0 &&
linkProps && (
<li key={linkProps.navigate}>
<Button {...linkProps} className="navigation-link" activeClass="navigation-link--active" />
</li>
)
)}
</ul>
{sideInformation === SHOW_TAGS && !expanded && isPersonalized && (
<ul className="navigation__secondary navigation-links--small tags--vertical">
{followedTags.map(({ name }, key) => (
<li className="navigation-link__wrapper" key={name}>
<Tag navigate={`/$/tags?t${name}`} name={name} />
</li>
))}
</ul>
)}
{sideInformation === SHOW_CHANNELS && !expanded && isPersonalized && (
<ul className="navigation__secondary navigation-links--small">
{subscriptions.map(({ uri, channelName }, index) => (
<li key={uri} className="navigation-link__wrapper">
<Button
navigate={uri}
label={channelName}
className="navigation-link"
activeClass="navigation-link--active"
/>
</li>
))}
</ul>
)}
</nav>
// @if TARGET='web'
{/* {!isAuthenticated && !expanded && <Ads />} commenting out sidebar ads for test */}
// @endif
{sidebarOpen && isPersonalized && subscriptions && subscriptions.length > 0 && (
<ul className="navigation__secondary navigation-links--relative navigation-links--small">
{subscriptions.map(({ uri, channelName }, index) => (
<li key={uri} className="navigation-link__wrapper">
<Button
navigate={uri}
label={channelName}
className="navigation-link"
activeClass="navigation-link--active"
/>
</li>
))}
</ul>
)}
</nav>
)}
{(isOnFilePage || isMediumScreen) && sidebarOpen && (
<>
<nav className={classnames('navigation--filepage')}>
<ul className="navigation-links--absolute">
{TOP_LEVEL_LINKS.map(linkProps => (
<li key={linkProps.navigate}>
<Button
{...linkProps}
icon={pulseLibrary && linkProps.icon === ICONS.LIBRARY ? ICONS.PURCHASED : linkProps.icon}
className={classnames('navigation-link', {
'navigation-link--pulse': linkProps.icon === ICONS.LIBRARY && pulseLibrary,
'navigation-link--extra': linkProps.icon === ICONS.NOTIFICATION && unreadCount > 0,
})}
activeClass="navigation-link--active"
/>
{linkProps.extra}
</li>
))}
</ul>
<ul className="navigation-links--absolute">
{ABSOLUTE_LINKS.map(linkProps => (
<li key={linkProps.navigate}>
<Button {...linkProps} className="navigation-link" activeClass="navigation-link--active" />
{linkProps.extra}
</li>
))}
</ul>
{isPersonalized && subscriptions && subscriptions.length > 0 && (
<ul className="navigation__secondary navigation-links--small">
{subscriptions.map(({ uri, channelName }, index) => (
<li key={uri} className="navigation-link__wrapper">
<Button
navigate={uri}
label={channelName}
className="navigation-link"
activeClass="navigation-link--active"
/>
</li>
))}
</ul>
)}
</nav>
<div className="navigation__overlay" onClick={() => setSidebarOpen(false)} />
</>
)}
</Wrapper>
);
}
export default withRouter(SideNavigation);
export default SideNavigation;

View file

@ -5,7 +5,7 @@ import Button from 'component/button';
import CopyableText from 'component/copyableText';
import EmbedTextArea from 'component/embedTextArea';
import { generateDownloadUrl } from 'util/web';
import useIsMobile from 'effects/use-is-mobile';
import { useIsMobile } from 'effects/use-screensize';
import { FormField } from 'component/common/form';
import { hmsToSeconds, secondsToHms } from 'util/time';
import {

View file

@ -5,7 +5,7 @@ import React, { useRef } from 'react';
import { parseURI } from 'lbry-redux';
import Button from 'component/button';
import useHover from 'effects/use-hover';
import useIsMobile from 'effects/use-is-mobile';
import { useIsMobile } from 'effects/use-screensize';
type SubscriptionArgs = {
channelName: string,

View file

@ -34,7 +34,6 @@ export const WALLET_SEND = 'wallet_send';
export const WALLET_RECEIVE = 'wallet_receive';
export const CREATE_CHANNEL = 'create_channel';
export const YOUTUBE_WELCOME = 'youtube_welcome';
export const MOBILE_NAVIGATION = 'mobile_navigation';
export const SET_REFERRER = 'set_referrer';
export const REPOST = 'repost';
export const SIGN_OUT = 'sign_out';

View file

@ -1,6 +0,0 @@
import useMedia from './use-media';
export default function useIsMobile() {
const isMobile = useMedia(['(min-width: 901px)'], [false], true);
return isMobile;
}

View file

@ -0,0 +1,11 @@
import useMedia from './use-media';
export function useIsMobile() {
const isMobile = useMedia(['(min-width: 901px)'], [false], true);
return isMobile;
}
export function useIsMediumScreen() {
const isMobile = useMedia(['(min-width: 1151px)'], [false], true);
return isMobile;
}

View file

@ -5,7 +5,7 @@ import * as React from 'react';
import ReactModal from 'react-modal';
import Button from 'component/button';
import classnames from 'classnames';
import useIsMobile from 'effects/use-is-mobile';
import { useIsMobile } from 'effects/use-screensize';
type ModalProps = {
type?: string,

View file

@ -1,11 +0,0 @@
import { connect } from 'react-redux';
import { doHideModal, doSignOut } from 'redux/actions/app';
import ModalMobileNavigation from './view';
export default connect(
null,
{
doHideModal,
doSignOut,
}
)(ModalMobileNavigation);

View file

@ -1,18 +0,0 @@
// @flow
import React from 'react';
import { Modal } from 'modal/modal';
import SideNavigation from 'component/sideNavigation';
type Props = {
doHideModal: () => void,
};
export default function ModalMobileNavigation(props: Props) {
const { doHideModal } = props;
return (
<Modal type="card" isOpen contentLabel={__('Navigation')} onAborted={doHideModal}>
<SideNavigation sticky={false} expanded />
</Modal>
);
}

View file

@ -32,7 +32,6 @@ import ModalWalletSend from 'modal/modalWalletSend';
import ModalWalletReceive from 'modal/modalWalletReceive';
import ModalYoutubeWelcome from 'modal/modalYoutubeWelcome';
import ModalCreateChannel from 'modal/modalChannelCreate';
import ModalMobileNavigation from 'modal/modalMobileNavigation';
import ModalSetReferrer from 'modal/modalSetReferrer';
import ModalRepost from 'modal/modalRepost';
import ModalSignOut from 'modal/modalSignOut';
@ -125,8 +124,6 @@ function ModalRouter(props: Props) {
return <ModalYoutubeWelcome />;
case MODALS.CREATE_CHANNEL:
return <ModalCreateChannel {...modalProps} />;
case MODALS.MOBILE_NAVIGATION:
return <ModalMobileNavigation {...modalProps} />;
case MODALS.SET_REFERRER:
return <ModalSetReferrer {...modalProps} />;
case MODALS.REPOST:

View file

@ -6,7 +6,7 @@ import Page from 'component/page';
import ClaimListDiscover from 'component/claimListDiscover';
import Button from 'component/button';
import useHover from 'effects/use-hover';
import useIsMobile from 'effects/use-is-mobile';
import { useIsMobile } from 'effects/use-screensize';
import analytics from 'analytics';
import HiddenNsfw from 'component/common/hidden-nsfw';
import Icon from 'component/common/icon';

View file

@ -4,7 +4,6 @@ import { doSetContentHistoryItem } from 'redux/actions/content';
import {
doFetchFileInfo,
makeSelectFileInfoForUri,
makeSelectClaimForUri,
makeSelectMetadataForUri,
makeSelectChannelForClaimUri,
makeSelectClaimIsNsfw,
@ -16,7 +15,6 @@ import { makeSelectFileRenderModeForUri } from 'redux/selectors/content';
import FilePage from './view';
const select = (state, props) => ({
claim: makeSelectClaimForUri(props.uri)(state),
costInfo: makeSelectCostInfoForUri(props.uri)(state),
metadata: makeSelectMetadataForUri(props.uri)(state),
obscureNsfw: !selectShowMatureContent(state),

View file

@ -15,12 +15,10 @@ import WaitUntilOnPage from 'component/common/wait-until-on-page';
import RecommendedContent from 'component/recommendedContent';
import CommentsList from 'component/commentsList';
import CommentCreate from 'component/commentCreate';
import YoutubeBadge from 'component/youtubeBadge';
export const FILE_WRAPPER_CLASS = 'file-page__video-container';
type Props = {
claim: StreamClaim,
costInfo: ?{ includesData: boolean, cost: number },
fileInfo: FileListItem,
uri: string,
@ -86,13 +84,9 @@ class FilePage extends React.Component<Props> {
}
renderFilePageLayout(uri: string, mode: string, cost: ?number) {
const { claim } = this.props;
const channelClaimId = claim.signing_channel ? claim.signing_channel.claim_id : null;
if (RENDER_MODES.FLOATING_MODES.includes(mode)) {
return (
<React.Fragment>
<YoutubeBadge channelClaimId={channelClaimId} includeSyncDate={false} />
<div className={FILE_WRAPPER_CLASS}>
<FileRenderInitiator uri={uri} />
</div>
@ -105,7 +99,6 @@ class FilePage extends React.Component<Props> {
if (RENDER_MODES.UNRENDERABLE_MODES.includes(mode)) {
return (
<React.Fragment>
<YoutubeBadge channelClaimId={channelClaimId} includeSyncDate={false} />
<FileTitle uri={uri} />
<FileRenderDownload uri={uri} isFree={cost === 0} />
</React.Fragment>
@ -115,7 +108,6 @@ class FilePage extends React.Component<Props> {
if (RENDER_MODES.TEXT_MODES.includes(mode)) {
return (
<React.Fragment>
<YoutubeBadge channelClaimId={channelClaimId} includeSyncDate={false} />
<FileTitle uri={uri} />
<FileRenderInitiator uri={uri} />
<FileRenderInline uri={uri} />
@ -125,7 +117,6 @@ class FilePage extends React.Component<Props> {
return (
<React.Fragment>
<YoutubeBadge channelClaimId={channelClaimId} includeSyncDate={false} />
<FileRenderInitiator uri={uri} />
<FileRenderInline uri={uri} />
<FileTitle uri={uri} />
@ -152,29 +143,26 @@ class FilePage extends React.Component<Props> {
}
return (
<Page className="file-page">
<Page className="file-page" filePage>
<div className={classnames('section card-stack', `file-page__${renderMode}`)}>
{this.renderFilePageLayout(uri, renderMode, costInfo ? costInfo.cost : null)}
<FileDescription uri={uri} />
<FileValues uri={uri} />
<FileDetails uri={uri} />
<Card
title={__('Leave a Comment')}
actions={
<div>
<CommentCreate uri={uri} />
<WaitUntilOnPage lastUpdateDate={this.lastReset}>
<CommentsList uri={uri} />
</WaitUntilOnPage>
</div>
}
/>
</div>
<div className="section columns">
<div className="card-stack">
<FileDescription uri={uri} />
<FileValues uri={uri} />
<FileDetails uri={uri} />
<Card
title={__('Leave a Comment')}
actions={
<div>
<CommentCreate uri={uri} />
<WaitUntilOnPage lastUpdateDate={this.lastReset}>
<CommentsList uri={uri} />
</WaitUntilOnPage>
</div>
}
/>
</div>
<RecommendedContent uri={uri} />
</div>
<RecommendedContent uri={uri} />
</Page>
);
}

View file

@ -4,6 +4,7 @@ import {
selectIsFetchingNotifications,
selectUnreadNotificationCount,
} from 'redux/selectors/notifications';
import { doReadNotifications } from 'redux/actions/notifications';
import NotificationsPage from './view';
const select = state => ({
@ -12,4 +13,6 @@ const select = state => ({
unreadCount: selectUnreadNotificationCount(state),
});
export default connect(select)(NotificationsPage);
export default connect(select, {
doReadNotifications,
})(NotificationsPage);

View file

@ -9,10 +9,12 @@ import Notification from 'component/notification';
type Props = {
notifications: ?Array<Notification>,
fetching: boolean,
unreadCount: number,
doReadNotifications: () => void,
};
export default function NotificationsPage(props: Props) {
const { notifications, fetching } = props;
const { notifications, fetching, unreadCount, doReadNotifications } = props;
// Group sequential comment notifications if they are by the same author
let groupedCount = 1;
@ -59,6 +61,12 @@ export default function NotificationsPage(props: Props) {
}
}, []);
React.useEffect(() => {
if (unreadCount > 0) {
doReadNotifications();
}
}, [unreadCount, doReadNotifications]);
return (
<Page>
{fetching && (

View file

@ -78,3 +78,14 @@
opacity: 1;
}
}
@keyframes shadow-pulse {
0% {
background-color: rgba(37, 119, 97, 0.2);
box-shadow: 0 0 0 0px rgba(37, 119, 97, 0.2);
}
100% {
background-color: rgba(37, 119, 97, 0);
box-shadow: 0 0 0 35px rgba(37, 119, 97, 0);
}
}

View file

@ -97,6 +97,10 @@
}
}
.card__title-section--small {
padding: var(--spacing-s) var(--spacing-m);
}
.card__actions--inline {
@extend .card__actions;
margin-top: 0;
@ -181,10 +185,14 @@
}
@media (max-width: $breakpoint-small) {
font-size: var(--font-body);
font-size: var(--font-large);
}
}
.card__title--small {
font-size: var(--font-body);
}
.card__title-actions {
align-self: flex-start;
padding: var(--spacing-m);

View file

@ -350,7 +350,7 @@
}
@media (max-width: $breakpoint-medium) and (min-width: $breakpoint-small) {
$width: calc((100vw - var(--side-nav-width) - (var(--spacing-l) * 4) - var(--spacing-m) * 2) / 3);
$width: calc((100vw - var(--side-nav-width) - (var(--spacing-m) * 3)) / 3);
width: $width;
@include handleClaimTileGifThumbnail($width);

View file

@ -30,6 +30,10 @@
}
}
}
@media (max-width: $breakpoint-medium) {
flex-direction: column;
}
}
.file-render {

View file

@ -24,19 +24,11 @@
padding-top: var(--mac-titlebar-height);
}
.header--noauth-web {
.header__navigation-item--icon {
margin: 0 10px;
}
}
.header__contents {
max-width: var(--page-max-width);
height: calc(var(--header-height) - 1px);
height: calc(var(--header-height));
display: flex;
align-items: center;
margin: auto;
padding: 0 var(--spacing-l);
padding: 0 var(--spacing-m);
@media (max-width: $breakpoint-small) {
padding: var(--spacing-s);
@ -47,7 +39,6 @@
flex: 1;
display: flex;
align-items: center;
height: var(--height-input);
}
.header__menu {
@ -65,29 +56,8 @@
}
}
.header__menu--mobile {
display: none;
@media (max-width: $breakpoint-small) {
display: block;
margin-left: var(--spacing-m);
svg {
stroke: var(--color-text);
}
}
}
.header__menu--with-balance {
button:first-child {
margin-left: var(--spacing-l);
margin-right: var(--spacing-m);
}
}
.header__navigation-arrows {
.header__buttons {
display: flex;
margin-right: var(--spacing-s);
}
.header__navigation-item {
@ -98,6 +68,7 @@
border-radius: var(--border-radius);
color: var(--color-text);
position: relative;
font-weight: var(--font-weight-bold);
svg {
stroke: var(--color-text);
@ -118,7 +89,7 @@
width: var(--height-button);
background-color: var(--color-header-button);
border-radius: 1.5rem;
margin-left: var(--spacing-s);
margin-right: var(--spacing-s);
&:hover {
background-color: var(--color-primary-alt);
@ -137,13 +108,24 @@
}
}
.header__navigation-item--icon {
@media (max-width: $breakpoint-small) {
margin-left: 0;
}
}
.header__navigation-item--lbry {
font-weight: var(--font-weight-bold);
margin-right: var(--spacing-m);
padding: var(--spacing-s);
.lbry-icon {
height: 2rem;
width: 2rem;
height: var(--height-button);
width: var(--height-button);
}
@media (max-width: $breakpoint-small) {
.button__label {
display: none;
}
}
}
@ -155,11 +137,6 @@
}
}
.header__navigation-item--balance {
margin: 0 var(--spacing-m);
font-weight: var(--font-weight-bold);
}
.header__navigation-dropdown {
@extend .menu__list--header;
padding: 0;
@ -195,3 +172,9 @@
margin: 0 var(--spacing-m);
}
}
.header__center {
display: flex;
justify-content: center;
width: 100%;
}

View file

@ -13,27 +13,68 @@
align-items: flex-start;
justify-content: space-between;
min-height: calc(100vh - var(--header-height));
max-width: var(--page-max-width);
margin-left: auto;
margin-right: auto;
margin-top: var(--header-height);
padding: var(--spacing-l); // Unfortunately this is coupled with .claim-preview--tile width calculation
padding-left: 0;
padding-right: 0;
@media (max-width: $breakpoint-small) {
padding: var(--spacing-s);
padding: var(--spacing-xs);
padding-top: var(--spacing-m);
}
@media (min-width: $breakpoint-large) {
width: 100%;
}
}
.main-wrapper__inner--filepage {
padding: 0;
padding-top: var(--spacing-s);
}
.main {
position: relative;
width: calc(100% - var(--side-nav-width) - var(--spacing-l));
max-width: var(--page-max-width);
z-index: 0;
margin-right: auto;
margin-left: auto;
@media (max-width: $breakpoint-small) {
@media (max-width: $breakpoint-medium) {
width: 100%;
margin-right: 0;
margin-left: 0;
margin-right: var(--spacing-s);
margin-left: var(--spacing-s);
}
}
.main--file-page {
width: 100%;
max-width: var(--page-max-width--filepage);
margin-left: auto;
margin-right: auto;
display: flex;
padding-left: var(--spacing-m);
padding-right: var(--spacing-m);
> :first-child {
flex: 1;
margin-right: var(--spacing-m);
}
.file-page__recommended {
width: 25rem;
height: 0%;
}
@media (max-width: $breakpoint-medium) {
flex-direction: column;
> :first-child {
margin-right: 0;
}
}
}

View file

@ -46,20 +46,6 @@
box-shadow: none;
border: none;
}
.navigation {
width: 100%;
display: block;
padding: var(--spacing-l);
.navigation-links:not(.navigation-links--small) {
.navigation-link {
font-size: var(--font-large);
border-bottom: 1px solid var(--color-gray-3);
padding: var(--spacing-m) 0;
}
}
}
}
}

View file

@ -1,22 +1,128 @@
.navigation {
width: var(--side-nav-width);
font-size: var(--font-body);
margin-left: var(--spacing-main-padding);
@media (max-width: $breakpoint-small) {
display: none;
margin-left: 0;
}
}
.navigation + .ads-wrapper {
margin-top: var(--spacing-l);
.navigation--filepage {
z-index: 4;
position: fixed;
top: var(--header-height);
left: 0;
height: calc(100vh - var(--header-height));
width: var(--side-nav-width--large);
overflow-y: scroll;
margin-top: 0;
padding: var(--spacing-l);
padding-top: var(--spacing-s);
padding-left: 0;
border-top: 1px solid var(--color-border);
background-color: var(--color-card-background);
box-shadow: var(--card-box-shadow);
.navigation-link {
padding-left: var(--spacing-m);
}
}
.navigation--micro {
width: var(--side-nav-width--micro);
}
.navigation__secondary {
margin-top: var(--spacing-l);
}
.navigation-link {
display: block;
position: relative;
transition: color 0.2s;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
color: var(--color-navigation-link);
margin-bottom: var(--spacing-s);
padding-left: var(--spacing-s);
.icon {
height: 1.5rem;
width: 1.5rem;
stroke: var(--color-gray-5);
}
.button__content {
padding: var(--spacing-s);
justify-content: flex-start;
flex-direction: column;
}
.button__label {
font-size: var(--font-small);
}
&:hover {
@extend .navigation-link--extra;
color: var(--color-gray-5);
.icon {
stroke: var(--color-gray-5);
}
}
@media (min-width: $breakpoint-medium) {
text-align: left;
margin-bottom: 0;
.icon {
height: 1rem;
width: 1rem;
}
.button__content {
flex-direction: row;
}
.button__label {
margin-top: 0;
font-size: var(--font-body);
font-weight: var(--font-weight-bold);
}
&:focus {
box-shadow: none;
}
}
}
.navigation-link--active {
background-color: var(--color-primary-alt);
border-radius: var(--border-radius);
border-top-left-radius: 0;
border-bottom-left-radius: 0;
color: var(--color-primary);
.icon {
stroke: var(--color-primary);
}
}
.navigation-link--pulse {
overflow: visible;
.icon {
animation: shadow-pulse 2.5s infinite;
}
}
.navigation-link--extra {
border-radius: var(--border-radius);
border-top-left-radius: 0;
border-bottom-left-radius: 0;
background-color: var(--color-gray-1);
}
.navigation-links {
@extend .ul--no-style;
flex-direction: column;
@ -24,64 +130,88 @@
list-style: none;
}
.navigation-links--small {
.navigation-links--relative {
@extend .navigation-links;
font-size: var(--font-small);
margin-right: var(--spacing-m);
.navigation-link {
padding-left: var(--spacing-s);
}
}
.navigation-links__inline {
margin-left: calc(var(--spacing-m) + var(--spacing-s));
}
.navigation-link__wrapper {
margin: var(--spacing-xxs) 0;
}
.navigation-link {
display: block;
position: relative;
text-align: left;
transition: color 0.2s;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
color: var(--color-navigation-link);
margin-top: var(--spacing-s);
.navigation-links--micro {
.icon {
stroke: var(--color-link-icon);
height: 1.5rem;
width: 1.5rem;
}
&:hover,
&.navigation-link--active {
color: var(--color-link-active);
.button__content {
padding: var(--spacing-s);
justify-content: flex-start;
flex-direction: column;
}
.button__label {
font-size: var(--font-xsmall);
margin-left: 0;
margin-top: var(--spacing-xs);
}
.navigation-link {
margin-bottom: var(--spacing-xs);
padding-left: 0;
}
@media (max-width: $breakpoint-small) {
display: none;
}
}
.navigation-links--absolute {
@extend .navigation-links;
.navigation-link {
margin-bottom: 0;
.icon {
stroke: var(--color-link-active);
height: 1rem;
width: 1rem;
}
.button__content {
flex-direction: row;
}
.button__label {
margin-top: 0;
font-size: var(--font-body);
font-weight: var(--font-weight-bold);
}
&:focus {
box-shadow: none;
}
}
}
&:focus {
box-shadow: none;
.navigation-links--small {
@extend .navigation-links;
margin-right: 0;
padding-right: 0;
font-size: var(--font-small);
.button__content {
align-items: flex-start;
}
}
.navigation-link--pulse {
border-radius: var(--border-radius);
overflow: visible;
.icon {
border-radius: 50%;
animation: shadow-pulse 2.5s infinite;
}
}
@keyframes shadow-pulse {
0% {
background-color: rgba(37, 119, 97, 0.2);
box-shadow: 0 0 0 0px rgba(37, 119, 97, 0.2);
}
100% {
background-color: rgba(37, 119, 97, 0);
box-shadow: 0 0 0 35px rgba(37, 119, 97, 0);
}
.navigation__overlay {
position: fixed;
width: 100vw;
height: 100vh;
opacity: 0.3;
background-color: black;
z-index: 3;
left: 0;
top: var(--header-height);
}

View file

@ -51,3 +51,20 @@
margin-bottom: 0;
margin-top: 0;
}
.notification__bubble {
height: 1.5rem;
width: 1.5rem;
border-radius: 50%;
background-color: var(--color-notification);
position: absolute;
top: -0.5rem;
right: -0.5rem;
color: white;
font-size: var(--font-small);
font-weight: bold;
line-height: 1;
display: flex;
align-items: center;
justify-content: center;
}

View file

@ -7,10 +7,13 @@
z-index: 1;
font-size: var(--font-small);
height: var(--height-input);
max-width: 20rem;
max-width: 30rem;
margin-left: var(--spacing-s);
margin-right: var(--spacing-s);
@media (max-width: $breakpoint-small) {
max-width: none;
margin: 0;
}
> .icon {

View file

@ -12,6 +12,7 @@
--color-warning: #fff58c;
--color-cost: #ffd580;
--color-focus: #93cff2;
--color-notification: #f02849;
--color-black: #111;
--color-white: #fdfdfd;
@ -47,7 +48,7 @@
--color-input-color: #111111;
--color-input-label: var(--color-gray-5);
--color-input-placeholder: #212529;
--color-input-bg: #f2f2f2;
--color-input-bg: var(--color-gray-1);
--color-input-bg-copyable: #434b53;
--color-input-border: var(--color-border);
--color-input-border-active: var(--color-secondary);

View file

@ -53,6 +53,7 @@ p {
ul,
ol {
li {
position: relative;
list-style-position: outside;
margin: var(--spacing-xs) var(--spacing-m);
margin-bottom: 0;
@ -312,16 +313,8 @@ textarea {
}
}
.notification__bubble {
height: 1.5rem;
width: 1.5rem;
border-radius: 50%;
background-color: #f02849;
position: absolute;
top: -0.5rem;
right: -0.5rem;
color: white;
font-size: var(--font-small);
font-weight: bold;
line-height: 1;
.mobile-hidden {
@media (max-width: $breakpoint-small) {
display: none !important;
}
}

View file

@ -5,6 +5,7 @@ $spacing-width: 36px;
$breakpoint-xsmall: 600px;
$breakpoint-small: 900px;
$breakpoint-medium: 1150px;
$breakpoint-large: 1600px;
:root {
--border-radius: 5px;
@ -44,10 +45,13 @@ $breakpoint-medium: 1150px;
--font-heading: 2.94rem;
// Width & spacing
--page-max-width: 1420px;
--page-max-width: 1280px;
--page-max-width--filepage: 1700px;
--mac-titlebar-height: 1.5rem;
--mobile: 600px;
--side-nav-width: 170px;
--side-nav-width: 200px;
--side-nav-width--micro: 100px;
--side-nav-width--large: 250px;
--spacing-main-padding: var(--spacing-xl);
--floating-viewer-width: 32rem;

View file

@ -3,7 +3,7 @@
--color-link-icon: var(--color-gray-4);
--color-link-active: var(--color-primary);
--color-navigation-link: var(--color-gray-5);
--color-header-button: #f7f7f7;
--color-header-button: var(--color-button-alt-bg);
--color-button-border: var(--color-gray-3);
// Color

View file

@ -3,7 +3,7 @@ import React from 'react';
import Nag from 'component/common/nag';
import I18nMessage from 'component/i18nMessage';
import Button from 'component/button';
import useIsMobile from 'effects/use-is-mobile';
import { useIsMobile } from 'effects/use-screensize';
type Props = {
onClose: () => void,
@ -41,8 +41,7 @@ export default function NagDegradedPerformance(props: Props) {
),
}}
>
lbry.tv collects usage information for itself only (%more_information%). Want control over
this and more?
lbry.tv collects usage information for itself only (%more_information%). Want control over this and more?
</I18nMessage>
}
actionText={__('Get The App')}