Improve header components that need to be loaded in to display Skeleton and avoid layout shifting
This commit is contained in:
parent
76147d89c6
commit
5c6fb9de66
4 changed files with 57 additions and 30 deletions
|
@ -1,7 +1,6 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { doClearEmailEntry, doClearPasswordEntry } from 'redux/actions/user';
|
import { doClearEmailEntry, doClearPasswordEntry } from 'redux/actions/user';
|
||||||
import { doSignOut, doOpenModal } from 'redux/actions/app';
|
import { doSignOut, doOpenModal } from 'redux/actions/app';
|
||||||
import { formatCredits } from 'util/format-credits';
|
|
||||||
import { selectClientSetting } from 'redux/selectors/settings';
|
import { selectClientSetting } from 'redux/selectors/settings';
|
||||||
import { selectGetSyncErrorMessage } from 'redux/selectors/sync';
|
import { selectGetSyncErrorMessage } from 'redux/selectors/sync';
|
||||||
import { selectHasNavigated } from 'redux/selectors/app';
|
import { selectHasNavigated } from 'redux/selectors/app';
|
||||||
|
@ -17,8 +16,7 @@ const select = (state) => ({
|
||||||
emailToVerify: selectEmailToVerify(state),
|
emailToVerify: selectEmailToVerify(state),
|
||||||
hasNavigated: selectHasNavigated(state),
|
hasNavigated: selectHasNavigated(state),
|
||||||
hideBalance: selectClientSetting(state, SETTINGS.HIDE_BALANCE),
|
hideBalance: selectClientSetting(state, SETTINGS.HIDE_BALANCE),
|
||||||
roundedBalance: formatCredits(selectTotalBalance(state), 2, true),
|
totalBalance: selectTotalBalance(state),
|
||||||
roundedSpendableBalance: formatCredits(selectBalance(state), 2, true),
|
|
||||||
syncError: selectGetSyncErrorMessage(state),
|
syncError: selectGetSyncErrorMessage(state),
|
||||||
user: selectUser(state),
|
user: selectUser(state),
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// @flow
|
// @flow
|
||||||
import 'scss/component/_header.scss';
|
import 'scss/component/_header.scss';
|
||||||
|
|
||||||
|
import { formatCredits } from 'util/format-credits';
|
||||||
import { useIsMobile } from 'effects/use-screensize';
|
import { useIsMobile } from 'effects/use-screensize';
|
||||||
import { withRouter } from 'react-router';
|
import { withRouter } from 'react-router';
|
||||||
import * as ICONS from 'constants/icons';
|
import * as ICONS from 'constants/icons';
|
||||||
|
@ -12,6 +13,7 @@ import HeaderProfileMenuButton from 'component/headerProfileMenuButton';
|
||||||
import Logo from 'component/logo';
|
import Logo from 'component/logo';
|
||||||
import NotificationBubble from 'component/notificationBubble';
|
import NotificationBubble from 'component/notificationBubble';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import Skeleton from '@mui/material/Skeleton';
|
||||||
import SkipNavigationButton from 'component/skipNavigationButton';
|
import SkipNavigationButton from 'component/skipNavigationButton';
|
||||||
import Tooltip from 'component/common/tooltip';
|
import Tooltip from 'component/common/tooltip';
|
||||||
import WunderBar from 'component/wunderbar';
|
import WunderBar from 'component/wunderbar';
|
||||||
|
@ -37,10 +39,9 @@ type Props = {
|
||||||
replace: (string) => void,
|
replace: (string) => void,
|
||||||
},
|
},
|
||||||
isAbsoluteSideNavHidden: boolean,
|
isAbsoluteSideNavHidden: boolean,
|
||||||
roundedBalance: string,
|
|
||||||
roundedSpendableBalance: string,
|
|
||||||
sidebarOpen: boolean,
|
sidebarOpen: boolean,
|
||||||
syncError: ?string,
|
syncError: ?string,
|
||||||
|
totalBalance?: number,
|
||||||
user: ?User,
|
user: ?User,
|
||||||
clearEmailEntry: () => void,
|
clearEmailEntry: () => void,
|
||||||
clearPasswordEntry: () => void,
|
clearPasswordEntry: () => void,
|
||||||
|
@ -60,10 +61,9 @@ const Header = (props: Props) => {
|
||||||
hideCancel,
|
hideCancel,
|
||||||
history,
|
history,
|
||||||
isAbsoluteSideNavHidden,
|
isAbsoluteSideNavHidden,
|
||||||
roundedBalance,
|
|
||||||
roundedSpendableBalance,
|
|
||||||
sidebarOpen,
|
sidebarOpen,
|
||||||
syncError,
|
syncError,
|
||||||
|
totalBalance,
|
||||||
user,
|
user,
|
||||||
clearEmailEntry,
|
clearEmailEntry,
|
||||||
clearPasswordEntry,
|
clearPasswordEntry,
|
||||||
|
@ -90,6 +90,10 @@ const Header = (props: Props) => {
|
||||||
const canBackout = Boolean(backout);
|
const canBackout = Boolean(backout);
|
||||||
const { backLabel, backNavDefault, title: backTitle, simpleTitle: simpleBackTitle } = backout || {};
|
const { backLabel, backNavDefault, title: backTitle, simpleTitle: simpleBackTitle } = backout || {};
|
||||||
|
|
||||||
|
const balanceLoading = totalBalance === undefined;
|
||||||
|
const roundedSpendableBalance = formatCredits(balance, 2, true);
|
||||||
|
const roundedTotalBalance = formatCredits(totalBalance, 2, true);
|
||||||
|
|
||||||
// Sign out if they click the "x" when they are on the password prompt
|
// Sign out if they click the "x" when they are on the password prompt
|
||||||
const authHeaderAction = syncError && { onClick: signOut };
|
const authHeaderAction = syncError && { onClick: signOut };
|
||||||
const homeButtonNavigationProps = (isVerifyPage && {}) || (authHeader && authHeaderAction) || { navigate: '/' };
|
const homeButtonNavigationProps = (isVerifyPage && {}) || (authHeader && authHeaderAction) || { navigate: '/' };
|
||||||
|
@ -138,12 +142,16 @@ const Header = (props: Props) => {
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<Button
|
{balanceLoading ? (
|
||||||
navigate={`/$/${PAGES.WALLET}`}
|
<Skeleton variant="text" animation="wave" className="header__navigationItem--balanceLoading" />
|
||||||
className="button--file-action header__navigationItem--balance"
|
) : (
|
||||||
label={hideBalance || Number(roundedBalance) === 0 ? __('Your Wallet') : roundedBalance}
|
<Button
|
||||||
icon={ICONS.LBC}
|
navigate={`/$/${PAGES.WALLET}`}
|
||||||
/>
|
className="button--file-action header__navigationItem--balance"
|
||||||
|
label={hideBalance || Number(roundedTotalBalance) === 0 ? __('Your Wallet') : roundedTotalBalance}
|
||||||
|
icon={ICONS.LBC}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import classnames from 'classnames';
|
||||||
import HeaderMenuLink from 'component/common/header-menu-link';
|
import HeaderMenuLink from 'component/common/header-menu-link';
|
||||||
import Icon from 'component/common/icon';
|
import Icon from 'component/common/icon';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import Skeleton from '@mui/material/Skeleton';
|
||||||
|
|
||||||
type HeaderMenuButtonProps = {
|
type HeaderMenuButtonProps = {
|
||||||
activeChannelClaim: ?ChannelClaim,
|
activeChannelClaim: ?ChannelClaim,
|
||||||
|
@ -24,20 +25,24 @@ export default function HeaderProfileMenuButton(props: HeaderMenuButtonProps) {
|
||||||
return (
|
return (
|
||||||
<div className="header__buttons">
|
<div className="header__buttons">
|
||||||
<Menu>
|
<Menu>
|
||||||
<MenuButton
|
{activeChannelUrl === undefined ? (
|
||||||
aria-label={__('Your account')}
|
<Skeleton variant="circular" animation="wave" className="header__navigationItem--iconSkeleton" />
|
||||||
title={__('Your account')}
|
) : (
|
||||||
className={classnames('header__navigationItem', {
|
<MenuButton
|
||||||
'header__navigationItem--icon': !activeChannelUrl,
|
aria-label={__('Your account')}
|
||||||
'header__navigationItem--profilePic': activeChannelUrl,
|
title={__('Your account')}
|
||||||
})}
|
className={classnames('header__navigationItem', {
|
||||||
>
|
'header__navigationItem--icon': !activeChannelUrl,
|
||||||
{activeChannelUrl ? (
|
'header__navigationItem--profilePic': activeChannelUrl,
|
||||||
<ChannelThumbnail uri={activeChannelUrl} small noLazyLoad />
|
})}
|
||||||
) : (
|
>
|
||||||
<Icon size={18} icon={ICONS.ACCOUNT} aria-hidden />
|
{activeChannelUrl ? (
|
||||||
)}
|
<ChannelThumbnail uri={activeChannelUrl} small noLazyLoad />
|
||||||
</MenuButton>
|
) : (
|
||||||
|
<Icon size={18} icon={ICONS.ACCOUNT} aria-hidden />
|
||||||
|
)}
|
||||||
|
</MenuButton>
|
||||||
|
)}
|
||||||
|
|
||||||
<MenuList className="menu__list--header">
|
<MenuList className="menu__list--header">
|
||||||
<HeaderMenuLink page={PAGES.UPLOADS} icon={ICONS.PUBLISH} name={__('Uploads')} />
|
<HeaderMenuLink page={PAGES.UPLOADS} icon={ICONS.PUBLISH} name={__('Uploads')} />
|
||||||
|
|
|
@ -90,9 +90,12 @@
|
||||||
.header__menu--right {
|
.header__menu--right {
|
||||||
@extend .header__menu;
|
@extend .header__menu;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
width: 10rem;
|
||||||
|
min-width: 10rem;
|
||||||
|
|
||||||
@media (max-width: $breakpoint-small) {
|
@media (max-width: $breakpoint-small) {
|
||||||
max-width: 4rem;
|
width: unset;
|
||||||
|
min-width: unset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,6 +158,7 @@
|
||||||
|
|
||||||
.header__navigationItem--profilePic {
|
.header__navigationItem--profilePic {
|
||||||
margin-right: var(--spacing-s);
|
margin-right: var(--spacing-s);
|
||||||
|
background-color: var(--color-header-button);
|
||||||
|
|
||||||
.channel-thumbnail {
|
.channel-thumbnail {
|
||||||
height: var(--height-button);
|
height: var(--height-button);
|
||||||
|
@ -167,6 +171,12 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header__navigationItem--iconSkeleton {
|
||||||
|
@extend .header__navigationItem--icon;
|
||||||
|
height: var(--height-button) !important;
|
||||||
|
width: var(--height-button) !important;
|
||||||
|
}
|
||||||
|
|
||||||
.header__navigationItem--balance {
|
.header__navigationItem--balance {
|
||||||
@extend .header__navigationItem;
|
@extend .header__navigationItem;
|
||||||
margin: 0 var(--spacing-s);
|
margin: 0 var(--spacing-s);
|
||||||
|
@ -177,22 +187,28 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header__navigationItem--balanceLoading {
|
||||||
|
margin: 0 var(--spacing-s);
|
||||||
|
width: 4rem;
|
||||||
|
background-color: var(--color-header-button);
|
||||||
|
}
|
||||||
|
|
||||||
.header__navigationItem--logo {
|
.header__navigationItem--logo {
|
||||||
@extend .header__navigationItem;
|
@extend .header__navigationItem;
|
||||||
height: 4rem;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-left: var(--spacing-m);
|
margin-left: var(--spacing-m);
|
||||||
margin-right: var(--spacing-m);
|
margin-right: var(--spacing-m);
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
|
|
||||||
// move to lbry theme?
|
// move to lbry theme?
|
||||||
.lbry-icon {
|
.lbry-icon {
|
||||||
height: var(--height-button);
|
height: var(--height-button);
|
||||||
width: var(--height-button);
|
width: var(--height-button);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: $breakpoint-small) {
|
@media (max-width: $breakpoint-small) {
|
||||||
margin-right: var(--spacing-m);
|
margin-right: var(--spacing-m);
|
||||||
height: 5rem;
|
|
||||||
|
|
||||||
.button__label {
|
.button__label {
|
||||||
display: none;
|
display: none;
|
||||||
|
|
Loading…
Reference in a new issue