chore: Loading placeholders (#2322)

* Improve visual of loading mask

* Normalize placeholder naming

* Remove unused file
This commit is contained in:
Tom Moor
2021-07-15 15:26:34 -04:00
committed by GitHub
parent 3f030540b3
commit 30cf244610
17 changed files with 70 additions and 100 deletions

View File

@ -15,7 +15,7 @@ import RevisionsStore from "stores/RevisionsStore";
import Button from "components/Button"; import Button from "components/Button";
import Flex from "components/Flex"; import Flex from "components/Flex";
import { ListPlaceholder } from "components/LoadingPlaceholder"; import PlaceholderList from "components/List/Placeholder";
import Revision from "./components/Revision"; import Revision from "./components/Revision";
import { documentHistoryUrl, documentUrl } from "utils/routeHelpers"; import { documentHistoryUrl, documentUrl } from "utils/routeHelpers";
@ -120,7 +120,7 @@ class DocumentHistory extends React.Component<Props> {
</Header> </Header>
{showLoading ? ( {showLoading ? (
<Loading> <Loading>
<ListPlaceholder count={5} /> <PlaceholderList count={5} />
</Loading> </Loading>
) : ( ) : (
<ArrowKeyNavigation <ArrowKeyNavigation

View File

@ -45,7 +45,7 @@ const Container = styled.div`
align-items: ${({ align }) => align}; align-items: ${({ align }) => align};
justify-content: ${({ justify }) => justify}; justify-content: ${({ justify }) => justify};
flex-shrink: ${({ shrink }) => (shrink ? 1 : "initial")}; flex-shrink: ${({ shrink }) => (shrink ? 1 : "initial")};
gap: ${({ gap }) => `${gap}px` || "initial"}; gap: ${({ gap }) => (gap ? `${gap}px` : "initial")};
min-height: 0; min-height: 0;
min-width: 0; min-width: 0;
`; `;

View File

@ -4,18 +4,19 @@ import * as React from "react";
import styled from "styled-components"; import styled from "styled-components";
import Fade from "components/Fade"; import Fade from "components/Fade";
import Flex from "components/Flex"; import Flex from "components/Flex";
import Mask from "components/Mask"; import PlaceholderText from "components/PlaceholderText";
type Props = { type Props = {
count?: number, count?: number,
}; };
const Placeholder = ({ count }: Props) => { const ListPlaceHolder = ({ count }: Props) => {
return ( return (
<Fade> <Fade>
{times(count || 2, (index) => ( {times(count || 2, (index) => (
<Item key={index} column auto> <Item key={index} column auto>
<Mask /> <PlaceholderText header delay={0.2 * index} />
<PlaceholderText delay={0.2 * index} />
</Item> </Item>
))} ))}
</Fade> </Fade>
@ -23,7 +24,7 @@ const Placeholder = ({ count }: Props) => {
}; };
const Item = styled(Flex)` const Item = styled(Flex)`
padding: 15px 0 16px; padding: 10px 0;
`; `;
export default Placeholder; export default ListPlaceHolder;

View File

@ -1,30 +0,0 @@
// @flow
import { times } from "lodash";
import * as React from "react";
import styled from "styled-components";
import Fade from "components/Fade";
import Flex from "components/Flex";
import Mask from "components/Mask";
type Props = {
count?: number,
};
const ListPlaceHolder = ({ count }: Props) => {
return (
<Fade>
{times(count || 2, (index) => (
<Item key={index} column auto>
<Mask header />
<Mask />
</Item>
))}
</Fade>
);
};
const Item = styled(Flex)`
padding: 10px 0;
`;
export default ListPlaceHolder;

View File

@ -1,6 +0,0 @@
// @flow
import ListPlaceholder from "./ListPlaceholder";
import LoadingPlaceholder from "./LoadingPlaceholder";
export default LoadingPlaceholder;
export { ListPlaceholder };

View File

@ -7,7 +7,7 @@ import * as React from "react";
import { Waypoint } from "react-waypoint"; import { Waypoint } from "react-waypoint";
import { DEFAULT_PAGINATION_LIMIT } from "stores/BaseStore"; import { DEFAULT_PAGINATION_LIMIT } from "stores/BaseStore";
import DelayedMount from "components/DelayedMount"; import DelayedMount from "components/DelayedMount";
import { ListPlaceholder } from "components/LoadingPlaceholder"; import PlaceholderList from "components/List/Placeholder";
type Props = { type Props = {
fetch?: (options: ?Object) => Promise<void>, fetch?: (options: ?Object) => Promise<void>,
@ -128,7 +128,7 @@ class PaginatedList extends React.Component<Props> {
)} )}
{showLoading && ( {showLoading && (
<DelayedMount> <DelayedMount>
<ListPlaceholder count={5} /> <PlaceholderList count={5} />
</DelayedMount> </DelayedMount>
)} )}
</> </>

View File

@ -4,18 +4,19 @@ import styled from "styled-components";
import DelayedMount from "components/DelayedMount"; import DelayedMount from "components/DelayedMount";
import Fade from "components/Fade"; import Fade from "components/Fade";
import Flex from "components/Flex"; import Flex from "components/Flex";
import Mask from "components/Mask"; import PlaceholderText from "components/PlaceholderText";
export default function LoadingPlaceholder(props: Object) { export default function PlaceholderDocument(props: Object) {
return ( return (
<DelayedMount> <DelayedMount>
<Wrapper> <Wrapper>
<Flex column auto {...props}> <Flex column auto {...props}>
<Mask height={34} /> <PlaceholderText height={34} maxWidth={70} />
<PlaceholderText delay={0.2} maxWidth={40} />
<br /> <br />
<Mask /> <PlaceholderText delay={0.2} />
<Mask /> <PlaceholderText delay={0.4} />
<Mask /> <PlaceholderText delay={0.6} />
</Flex> </Flex>
</Wrapper> </Wrapper>
</DelayedMount> </DelayedMount>

View File

@ -10,36 +10,40 @@ type Props = {|
height?: number, height?: number,
minWidth?: number, minWidth?: number,
maxWidth?: number, maxWidth?: number,
delay?: number,
|}; |};
class Mask extends React.Component<Props> { class PlaceholderText extends React.Component<Props> {
width: number; width = randomInteger(this.props.minWidth || 75, this.props.maxWidth || 100);
shouldComponentUpdate() { shouldComponentUpdate() {
return false; return false;
} }
constructor(props: Props) {
super();
this.width = randomInteger(props.minWidth || 75, props.maxWidth || 100);
}
render() { render() {
return <Redacted width={this.width} height={this.props.height} />; return (
<Mask
width={this.width}
height={this.props.height}
delay={this.props.delay}
/>
);
} }
} }
const Redacted = styled(Flex)` const Mask = styled(Flex)`
width: ${(props) => (props.header ? props.width / 2 : props.width)}%; width: ${(props) => (props.header ? props.width / 2 : props.width)}%;
height: ${(props) => height: ${(props) =>
props.height ? props.height : props.header ? 24 : 18}px; props.height ? props.height : props.header ? 24 : 18}px;
margin-bottom: 6px; margin-bottom: 6px;
border-radius: 6px;
background-color: ${(props) => props.theme.divider}; background-color: ${(props) => props.theme.divider};
animation: ${pulsate} 1.3s infinite; animation: ${pulsate} 2s infinite;
animation-delay: ${(props) => props.delay || 0}s;
&:last-child { &:last-child {
margin-bottom: 0; margin-bottom: 0;
} }
`; `;
export default Mask; export default PlaceholderText;

View File

@ -9,9 +9,9 @@ import Fade from "components/Fade";
import Flex from "components/Flex"; import Flex from "components/Flex";
import useStores from "../../../hooks/useStores"; import useStores from "../../../hooks/useStores";
import CollectionLink from "./CollectionLink"; import CollectionLink from "./CollectionLink";
import CollectionsLoading from "./CollectionsLoading";
import DropCursor from "./DropCursor"; import DropCursor from "./DropCursor";
import Header from "./Header"; import Header from "./Header";
import PlaceholderCollections from "./PlaceholderCollections";
import SidebarLink from "./SidebarLink"; import SidebarLink from "./SidebarLink";
import useCurrentTeam from "hooks/useCurrentTeam"; import useCurrentTeam from "hooks/useCurrentTeam";
type Props = { type Props = {
@ -105,7 +105,7 @@ function Collections({ onCreateCollection }: Props) {
return ( return (
<Flex column> <Flex column>
<Header>{t("Collections")}</Header> <Header>{t("Collections")}</Header>
<CollectionsLoading /> <PlaceholderCollections />
</Flex> </Flex>
); );
} }

View File

@ -1,21 +0,0 @@
// @flow
import * as React from "react";
import styled from "styled-components";
import Mask from "components/Mask";
function CollectionsLoading() {
return (
<Wrapper>
<Mask />
<Mask />
<Mask />
</Wrapper>
);
}
const Wrapper = styled.div`
margin: 4px 16px;
width: 75%;
`;
export default CollectionsLoading;

View File

@ -0,0 +1,21 @@
// @flow
import * as React from "react";
import styled from "styled-components";
import PlaceholderText from "components/PlaceholderText";
function PlaceholderCollections() {
return (
<Wrapper>
<PlaceholderText />
<PlaceholderText delay={0.2} />
<PlaceholderText delay={0.4} />
</Wrapper>
);
}
const Wrapper = styled.div`
margin: 4px 16px;
width: 75%;
`;
export default PlaceholderCollections;

View File

@ -8,7 +8,7 @@ import styled from "styled-components";
import Button from "components/Button"; import Button from "components/Button";
import Empty from "components/Empty"; import Empty from "components/Empty";
import Flex from "components/Flex"; import Flex from "components/Flex";
import Mask from "components/Mask"; import PlaceholderText from "components/PlaceholderText";
export type Props = {| export type Props = {|
data: any[], data: any[],
@ -170,7 +170,7 @@ export const Placeholder = ({
<Row key={row}> <Row key={row}>
{new Array(columns).fill().map((_, col) => ( {new Array(columns).fill().map((_, col) => (
<Cell key={col}> <Cell key={col}>
<Mask minWidth={25} maxWidth={75} /> <PlaceholderText minWidth={25} maxWidth={75} />
</Cell> </Cell>
))} ))}
</Row> </Row>

View File

@ -14,7 +14,7 @@ import Trash from "scenes/Trash";
import CenteredContent from "components/CenteredContent"; import CenteredContent from "components/CenteredContent";
import Layout from "components/Layout"; import Layout from "components/Layout";
import LoadingPlaceholder from "components/LoadingPlaceholder"; import PlaceholderDocument from "components/PlaceholderDocument";
import Route from "components/ProfiledRoute"; import Route from "components/ProfiledRoute";
import SocketProvider from "components/SocketProvider"; import SocketProvider from "components/SocketProvider";
import { matchDocumentSlug as slug } from "utils/routeHelpers"; import { matchDocumentSlug as slug } from "utils/routeHelpers";
@ -43,7 +43,7 @@ export default function AuthenticatedRoutes() {
<React.Suspense <React.Suspense
fallback={ fallback={
<CenteredContent> <CenteredContent>
<LoadingPlaceholder /> <PlaceholderDocument />
</CenteredContent> </CenteredContent>
} }
> >

View File

@ -27,11 +27,11 @@ import Flex from "components/Flex";
import Heading from "components/Heading"; import Heading from "components/Heading";
import HelpText from "components/HelpText"; import HelpText from "components/HelpText";
import InputSearchPage from "components/InputSearchPage"; import InputSearchPage from "components/InputSearchPage";
import PlaceholderList from "components/List/Placeholder";
import LoadingIndicator from "components/LoadingIndicator"; import LoadingIndicator from "components/LoadingIndicator";
import { ListPlaceholder } from "components/LoadingPlaceholder";
import Mask from "components/Mask";
import Modal from "components/Modal"; import Modal from "components/Modal";
import PaginatedDocumentList from "components/PaginatedDocumentList"; import PaginatedDocumentList from "components/PaginatedDocumentList";
import PlaceholderText from "components/PlaceholderText";
import Scene from "components/Scene"; import Scene from "components/Scene";
import Subheading from "components/Subheading"; import Subheading from "components/Subheading";
import Tab from "components/Tab"; import Tab from "components/Tab";
@ -376,9 +376,9 @@ function CollectionScene() {
) : ( ) : (
<CenteredContent> <CenteredContent>
<Heading> <Heading>
<Mask height={35} /> <PlaceholderText height={35} />
</Heading> </Heading>
<ListPlaceholder count={5} /> <PlaceholderList count={5} />
</CenteredContent> </CenteredContent>
); );
} }

View File

@ -18,10 +18,10 @@ import Branding from "components/Branding";
import ErrorBoundary from "components/ErrorBoundary"; import ErrorBoundary from "components/ErrorBoundary";
import Flex from "components/Flex"; import Flex from "components/Flex";
import LoadingIndicator from "components/LoadingIndicator"; import LoadingIndicator from "components/LoadingIndicator";
import LoadingPlaceholder from "components/LoadingPlaceholder";
import Modal from "components/Modal"; import Modal from "components/Modal";
import Notice from "components/Notice"; import Notice from "components/Notice";
import PageTitle from "components/PageTitle"; import PageTitle from "components/PageTitle";
import PlaceholderDocument from "components/PlaceholderDocument";
import Time from "components/Time"; import Time from "components/Time";
import Container from "./Container"; import Container from "./Container";
import Contents from "./Contents"; import Contents from "./Contents";
@ -410,7 +410,7 @@ class DocumentScene extends React.Component<Props> {
)} )}
</Notice> </Notice>
)} )}
<React.Suspense fallback={<LoadingPlaceholder />}> <React.Suspense fallback={<PlaceholderDocument />}>
<Flex auto={!readOnly}> <Flex auto={!readOnly}>
{showContents && <Contents headings={headings} />} {showContents && <Contents headings={headings} />}
<Editor <Editor

View File

@ -2,8 +2,8 @@
import * as React from "react"; import * as React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import CenteredContent from "components/CenteredContent"; import CenteredContent from "components/CenteredContent";
import LoadingPlaceholder from "components/LoadingPlaceholder";
import PageTitle from "components/PageTitle"; import PageTitle from "components/PageTitle";
import PlaceholderDocument from "components/PlaceholderDocument";
import Container from "./Container"; import Container from "./Container";
import type { LocationWithState } from "types"; import type { LocationWithState } from "types";
@ -20,7 +20,7 @@ export default function Loading({ location }: Props) {
title={location.state ? location.state.title : t("Untitled")} title={location.state ? location.state.title : t("Untitled")}
/> />
<CenteredContent> <CenteredContent>
<LoadingPlaceholder /> <PlaceholderDocument />
</CenteredContent> </CenteredContent>
</Container> </Container>
); );

View File

@ -7,7 +7,7 @@ import { useTranslation } from "react-i18next";
import { useHistory, useLocation, useRouteMatch } from "react-router-dom"; import { useHistory, useLocation, useRouteMatch } from "react-router-dom";
import CenteredContent from "components/CenteredContent"; import CenteredContent from "components/CenteredContent";
import Flex from "components/Flex"; import Flex from "components/Flex";
import LoadingPlaceholder from "components/LoadingPlaceholder"; import PlaceholderDocument from "components/PlaceholderDocument";
import useStores from "hooks/useStores"; import useStores from "hooks/useStores";
import { editDocumentUrl } from "utils/routeHelpers"; import { editDocumentUrl } from "utils/routeHelpers";
@ -48,7 +48,7 @@ function DocumentNew() {
return ( return (
<Flex column auto> <Flex column auto>
<CenteredContent> <CenteredContent>
<LoadingPlaceholder /> <PlaceholderDocument />
</CenteredContent> </CenteredContent>
</Flex> </Flex>
); );