Merge pull request #18 from MaxLeiter/loadingSkeletons

client: add some loading skeletons
This commit is contained in:
Max Leiter 2022-03-09 17:15:53 -08:00 committed by GitHub
commit 5c6336fe8b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 113 additions and 55 deletions

View file

@ -13,6 +13,7 @@
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
height: 36px;
} }
.fileNameContainer { .fileNameContainer {

View file

@ -4,17 +4,19 @@ import styles from './document.module.css'
import MarkdownPreview from '../preview' import MarkdownPreview from '../preview'
import { Trash } from '@geist-ui/icons' import { Trash } from '@geist-ui/icons'
import FormattingIcons from "../formatting-icons" import FormattingIcons from "../formatting-icons"
import Skeleton from "react-loading-skeleton"
type Props = { type Props = {
editable: boolean editable?: boolean
remove?: () => void remove?: () => void
title?: string title?: string
content?: string content?: string
setTitle?: (title: string) => void setTitle?: (title: string) => void
setContent?: (content: string) => void setContent?: (content: string) => void
initialTab?: "edit" | "preview" initialTab?: "edit" | "preview"
skeleton?: boolean
} }
const Document = ({ remove, editable, title, content, setTitle, setContent, initialTab = 'edit' }: Props) => { const Document = ({ remove, editable, title, content, setTitle, setContent, initialTab = 'edit', skeleton }: Props) => {
const codeEditorRef = useRef<HTMLTextAreaElement>(null) const codeEditorRef = useRef<HTMLTextAreaElement>(null)
const [tab, setTab] = useState(initialTab) const [tab, setTab] = useState(initialTab)
const height = editable ? "500px" : '100%' const height = editable ? "500px" : '100%'
@ -46,7 +48,21 @@ const Document = ({ remove, editable, title, content, setTitle, setContent, init
} }
} }
} }
if (skeleton) {
return <>
<Spacer height={1} />
<Card marginBottom={'var(--gap)'} marginTop={'var(--gap)'} style={{ maxWidth: 980, margin: "0 auto" }}>
<div className={styles.fileNameContainer}>
<Skeleton width={275} height={36} />
{editable && <Skeleton width={36} height={36} />}
</div>
<div className={styles.descriptionContainer}>
<div style={{ flexDirection: 'row', display: 'flex' }}><Skeleton width={125} height={36} /></div>
<Skeleton width={'100%'} height={350} />
</div >
</Card>
</>
}
return ( return (
<> <>
<Spacer height={1} /> <Spacer height={1} />

View file

@ -1,10 +1,9 @@
import { Loading, Card, Divider, Input, Text, Grid, Spacer } from "@geist-ui/core" import { Text } from "@geist-ui/core"
import Preview from "../preview"
import ShiftBy from "../shift-by"
import VisibilityBadge from "../visibility-badge"
import Link from '../Link' import Link from '../Link'
import styles from './post-list.module.css' import styles from './post-list.module.css'
import ListItemSkeleton from "./list-item-skeleton"
import ListItem from "./list-item"
type Props = { type Props = {
posts: any posts: any
@ -12,49 +11,23 @@ type Props = {
} }
const PostList = ({ posts, error }: Props) => { const PostList = ({ posts, error }: Props) => {
const FilenameInput = ({ title }: { title: string }) => <Input
value={title}
marginTop="var(--gap-double)"
size={1.2}
font={1.2}
label="Filename"
readOnly
width={"100%"}
/>
return ( return (
<div className={styles.container}> <div className={styles.container}>
{error && <Text type='error'>Failed to load.</Text>} {error && <Text type='error'>Failed to load.</Text>}
{!posts && <Loading />} {!posts && <ul>
<li>
<ListItemSkeleton />
</li>
<li>
<ListItemSkeleton />
</li>
</ul>}
{posts?.length === 0 && <Text>You have no posts. Create one <Link href="/new">here</Link>.</Text>} {posts?.length === 0 && <Text>You have no posts. Create one <Link href="/new">here</Link>.</Text>}
{ {
posts?.length > 0 && <div> posts?.length > 0 && <div>
<ul> <ul>
{posts.map((post: any) => { {posts.map((post: any) => {
return <li key={post.id}> return <ListItem post={post} key={post.id} />
<Card style={{ overflowY: 'scroll' }}>
<Spacer height={1 / 2} />
<Grid.Container justify={'space-between'}>
<Grid xs={8}>
<Text h3 paddingLeft={1 / 2}>
<Link color href={`/post/${post.id}`}>{post.title}
<ShiftBy y={-1}><VisibilityBadge visibility={post.visibility} /></ShiftBy>
</Link>
</Text></Grid>
<Grid xs={7}><Text type="secondary" h5>{new Date(post.createdAt).toLocaleDateString()} </Text></Grid>
<Grid xs={4}><Text type="secondary" h5>{post.files.length === 1 ? "1 file" : `${post.files.length} files`}</Text></Grid>
</Grid.Container>
<Divider h="1px" my={0} />
<Card.Content >
{post.files.map((file: any) => {
return <FilenameInput key={file.id} title={file.title} />
})}
</Card.Content>
</Card>
</li>
})} })}
</ul> </ul>
</div> </div>
@ -63,4 +36,4 @@ const PostList = ({ posts, error }: Props) => {
) )
} }
export default PostList export default PostList

View file

@ -0,0 +1,19 @@
import { Card, Spacer, Grid, Divider } from "@geist-ui/core";
import Skeleton from "react-loading-skeleton";
const ListItemSkeleton = () => (<Card>
<Spacer height={1 / 2} />
<Grid.Container justify={'space-between'} marginBottom={1 / 2}>
<Grid xs={8} paddingLeft={1 / 2}><Skeleton width={150} /></Grid>
<Grid xs={7}><Skeleton width={100} /></Grid>
<Grid xs={4}><Skeleton width={70} /></Grid>
</Grid.Container>
<Divider h="1px" my={0} />
<Card.Content >
<Skeleton width={200} />
</Card.Content>
</Card>)
export default ListItemSkeleton

View file

@ -0,0 +1,43 @@
import { Card, Spacer, Grid, Divider, Link, Text, Input } from "@geist-ui/core"
import post from "../post"
import ShiftBy from "../shift-by"
import VisibilityBadge from "../visibility-badge"
const FilenameInput = ({ title }: { title: string }) => <Input
value={title}
marginTop="var(--gap-double)"
size={1.2}
font={1.2}
label="Filename"
readOnly
width={"100%"}
/>
const ListItem = ({ post }: { post: any }) => {
return (<li key={post.id}>
<Card style={{ overflowY: 'scroll' }}>
<Spacer height={1 / 2} />
<Grid.Container justify={'space-between'}>
<Grid xs={8}>
<Text h3 paddingLeft={1 / 2}>
<Link color href={`/post/${post.id}`}>{post.title}
<ShiftBy y={-1}><VisibilityBadge visibility={post.visibility} /></ShiftBy>
</Link>
</Text></Grid>
<Grid xs={7}><Text type="secondary" h5>{new Date(post.createdAt).toLocaleDateString()} </Text></Grid>
<Grid xs={4}><Text type="secondary" h5>{post.files.length === 1 ? "1 file" : `${post.files.length} files`}</Text></Grid>
</Grid.Container>
<Divider h="1px" my={0} />
<Card.Content >
{post.files.map((file: any) => {
return <FilenameInput key={file.id} title={file.title} />
})}
</Card.Content>
</Card>
</li>)
}
export default ListItem

View file

@ -23,7 +23,7 @@ const ReactMarkdownPreview = ({ content, height }: Props) => {
return !inline && match ? ( return !inline && match ? (
<SyntaxHighlighter <SyntaxHighlighter
lineNumberStyle={{ lineNumberStyle={{
minWidth: "1.25rem" minWidth: "2.25rem"
}} }}
customStyle={{ customStyle={{
padding: 0, padding: 0,

View file

@ -19,6 +19,7 @@
"react": "17.0.2", "react": "17.0.2",
"react-debounce-render": "^8.0.2", "react-debounce-render": "^8.0.2",
"react-dom": "17.0.2", "react-dom": "17.0.2",
"react-loading-skeleton": "^3.0.3",
"react-markdown": "^8.0.0", "react-markdown": "^8.0.0",
"react-syntax-highlighter": "^15.4.5", "react-syntax-highlighter": "^15.4.5",
"react-syntax-highlighter-virtualized-renderer": "^1.1.0", "react-syntax-highlighter-virtualized-renderer": "^1.1.0",

View file

@ -4,6 +4,8 @@ import { useEffect, useState } from 'react'
import type { AppProps as NextAppProps } from "next/app"; import type { AppProps as NextAppProps } from "next/app";
import useSharedState from '../lib/hooks/use-shared-state'; import useSharedState from '../lib/hooks/use-shared-state';
import 'react-loading-skeleton/dist/skeleton.css'
export type ThemeProps = { export type ThemeProps = {
theme: "light" | "dark" | string, theme: "light" | "dark" | string,
changeTheme: () => void changeTheme: () => void

View file

@ -3,13 +3,9 @@ import styles from '../styles/Home.module.css'
import { Page } from '@geist-ui/core' import { Page } from '@geist-ui/core'
import Header from '../components/header' import Header from '../components/header'
import useSignedIn from '../lib/hooks/use-signed-in'
import { Loader } from '@geist-ui/icons'
import MyPosts from '../components/my-posts' import MyPosts from '../components/my-posts'
const Home = ({ theme, changeTheme }: { theme: "light" | "dark", changeTheme: () => void }) => { const Home = ({ theme, changeTheme }: { theme: "light" | "dark", changeTheme: () => void }) => {
const { isLoading, isSignedIn } = useSignedIn({ redirectIfNotAuthed: true })
return ( return (
<Page className={styles.container} width="100%"> <Page className={styles.container} width="100%">
<Head> <Head>
@ -21,8 +17,7 @@ const Home = ({ theme, changeTheme }: { theme: "light" | "dark", changeTheme: ()
<Header theme={theme} changeTheme={changeTheme} /> <Header theme={theme} changeTheme={changeTheme} />
</Page.Header> </Page.Header>
<Page.Content paddingTop={"var(--gap)"} width={"var(--main-content-width)"} margin="0 auto" className={styles.main}> <Page.Content paddingTop={"var(--gap)"} width={"var(--main-content-width)"} margin="0 auto" className={styles.main}>
{isLoading && <div style={{ margin: "0 auto" }}><Loader /></div>} <MyPosts />
{isSignedIn && <MyPosts />}
</Page.Content> </Page.Content>
</Page > </Page >
) )

View file

@ -1,7 +1,8 @@
import { Loading, Page, Text } from "@geist-ui/core"; import { Page, Text } from "@geist-ui/core";
import Skeleton from 'react-loading-skeleton';
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { useCallback, useEffect, useState } from "react"; import { useEffect, useState } from "react";
import Document from '../../components/document' import Document from '../../components/document'
import Header from "../../components/header"; import Header from "../../components/header";
import VisibilityBadge from "../../components/visibility-badge"; import VisibilityBadge from "../../components/visibility-badge";
@ -51,7 +52,10 @@ const Post = ({ theme, changeTheme }: ThemeProps) => {
</Page.Header> </Page.Header>
<Page.Content width={"var(--main-content-width)"} margin="auto"> <Page.Content width={"var(--main-content-width)"} margin="auto">
{error && <Text type="error">{error}</Text>} {error && <Text type="error">{error}</Text>}
{!error && (isLoading || !post?.files) && <Loading />} {/* {!error && (isLoading || !post?.files) && <Loading />} */}
{!error && isLoading && <><Text h2><Skeleton width={400} /></Text>
<Document skeleton={true} />
</>}
{!isLoading && post && <><Text h2>{post.title} <VisibilityBadge visibility={post.visibility} /></Text> {!isLoading && post && <><Text h2>{post.title} <VisibilityBadge visibility={post.visibility} /></Text>
{post.files.map(({ id, content, title }: { id: any, content: string, title: string }) => ( {post.files.map(({ id, content, title }: { id: any, content: string, title: string }) => (
<Document <Document
@ -62,8 +66,7 @@ const Post = ({ theme, changeTheme }: ThemeProps) => {
initialTab={'preview'} initialTab={'preview'}
/> />
))} ))}
</> </>}
}
</Page.Content> </Page.Content>
</Page > </Page >

View file

@ -2268,6 +2268,11 @@ react-lifecycles-compat@^3.0.4:
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
react-loading-skeleton@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/react-loading-skeleton/-/react-loading-skeleton-3.0.3.tgz#4f45467cf3193fb25456bc02aeb81922b82b8f1f"
integrity sha512-HPkEqQGwmbg1ImcYA9n4hDiLC3u92xUdU+sSfrv/9l3lNBChAubcl1azjV2WakapdkbA3gko+hPzZkZGIb/9xA==
react-markdown@^8.0.0: react-markdown@^8.0.0:
version "8.0.0" version "8.0.0"
resolved "https://registry.yarnpkg.com/react-markdown/-/react-markdown-8.0.0.tgz#3243296a59ddb0f451d262cc2e11123674b416c2" resolved "https://registry.yarnpkg.com/react-markdown/-/react-markdown-8.0.0.tgz#3243296a59ddb0f451d262cc2e11123674b416c2"