Merge pull request #18 from MaxLeiter/loadingSkeletons
client: add some loading skeletons
This commit is contained in:
commit
5c6336fe8b
11 changed files with 113 additions and 55 deletions
|
@ -13,6 +13,7 @@
|
|||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
.fileNameContainer {
|
||||
|
|
|
@ -4,17 +4,19 @@ import styles from './document.module.css'
|
|||
import MarkdownPreview from '../preview'
|
||||
import { Trash } from '@geist-ui/icons'
|
||||
import FormattingIcons from "../formatting-icons"
|
||||
import Skeleton from "react-loading-skeleton"
|
||||
type Props = {
|
||||
editable: boolean
|
||||
editable?: boolean
|
||||
remove?: () => void
|
||||
title?: string
|
||||
content?: string
|
||||
setTitle?: (title: string) => void
|
||||
setContent?: (content: string) => void
|
||||
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 [tab, setTab] = useState(initialTab)
|
||||
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 (
|
||||
<>
|
||||
<Spacer height={1} />
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import { Loading, Card, Divider, Input, Text, Grid, Spacer } from "@geist-ui/core"
|
||||
import Preview from "../preview"
|
||||
import ShiftBy from "../shift-by"
|
||||
import VisibilityBadge from "../visibility-badge"
|
||||
import { Text } from "@geist-ui/core"
|
||||
import Link from '../Link'
|
||||
|
||||
import styles from './post-list.module.css'
|
||||
import ListItemSkeleton from "./list-item-skeleton"
|
||||
import ListItem from "./list-item"
|
||||
|
||||
type Props = {
|
||||
posts: any
|
||||
|
@ -12,49 +11,23 @@ type 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 (
|
||||
<div className={styles.container}>
|
||||
{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 && <div>
|
||||
<ul>
|
||||
{posts.map((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>
|
||||
return <ListItem post={post} key={post.id} />
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
|
|
19
client/components/post-list/list-item-skeleton.tsx
Normal file
19
client/components/post-list/list-item-skeleton.tsx
Normal 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
|
43
client/components/post-list/list-item.tsx
Normal file
43
client/components/post-list/list-item.tsx
Normal 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
|
|
@ -23,7 +23,7 @@ const ReactMarkdownPreview = ({ content, height }: Props) => {
|
|||
return !inline && match ? (
|
||||
<SyntaxHighlighter
|
||||
lineNumberStyle={{
|
||||
minWidth: "1.25rem"
|
||||
minWidth: "2.25rem"
|
||||
}}
|
||||
customStyle={{
|
||||
padding: 0,
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
"react": "17.0.2",
|
||||
"react-debounce-render": "^8.0.2",
|
||||
"react-dom": "17.0.2",
|
||||
"react-loading-skeleton": "^3.0.3",
|
||||
"react-markdown": "^8.0.0",
|
||||
"react-syntax-highlighter": "^15.4.5",
|
||||
"react-syntax-highlighter-virtualized-renderer": "^1.1.0",
|
||||
|
|
|
@ -4,6 +4,8 @@ import { useEffect, useState } from 'react'
|
|||
import type { AppProps as NextAppProps } from "next/app";
|
||||
import useSharedState from '../lib/hooks/use-shared-state';
|
||||
|
||||
import 'react-loading-skeleton/dist/skeleton.css'
|
||||
|
||||
export type ThemeProps = {
|
||||
theme: "light" | "dark" | string,
|
||||
changeTheme: () => void
|
||||
|
|
|
@ -3,13 +3,9 @@ import styles from '../styles/Home.module.css'
|
|||
import { Page } from '@geist-ui/core'
|
||||
|
||||
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'
|
||||
|
||||
const Home = ({ theme, changeTheme }: { theme: "light" | "dark", changeTheme: () => void }) => {
|
||||
const { isLoading, isSignedIn } = useSignedIn({ redirectIfNotAuthed: true })
|
||||
|
||||
return (
|
||||
<Page className={styles.container} width="100%">
|
||||
<Head>
|
||||
|
@ -21,8 +17,7 @@ const Home = ({ theme, changeTheme }: { theme: "light" | "dark", changeTheme: ()
|
|||
<Header theme={theme} changeTheme={changeTheme} />
|
||||
</Page.Header>
|
||||
<Page.Content paddingTop={"var(--gap)"} width={"var(--main-content-width)"} margin="0 auto" className={styles.main}>
|
||||
{isLoading && <div style={{ margin: "0 auto" }}><Loader /></div>}
|
||||
{isSignedIn && <MyPosts />}
|
||||
<MyPosts />
|
||||
</Page.Content>
|
||||
</Page >
|
||||
)
|
||||
|
|
|
@ -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 { useCallback, useEffect, useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import Document from '../../components/document'
|
||||
import Header from "../../components/header";
|
||||
import VisibilityBadge from "../../components/visibility-badge";
|
||||
|
@ -51,7 +52,10 @@ const Post = ({ theme, changeTheme }: ThemeProps) => {
|
|||
</Page.Header>
|
||||
<Page.Content width={"var(--main-content-width)"} margin="auto">
|
||||
{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>
|
||||
{post.files.map(({ id, content, title }: { id: any, content: string, title: string }) => (
|
||||
<Document
|
||||
|
@ -62,8 +66,7 @@ const Post = ({ theme, changeTheme }: ThemeProps) => {
|
|||
initialTab={'preview'}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
}
|
||||
</>}
|
||||
</Page.Content>
|
||||
|
||||
</Page >
|
||||
|
|
|
@ -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"
|
||||
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:
|
||||
version "8.0.0"
|
||||
resolved "https://registry.yarnpkg.com/react-markdown/-/react-markdown-8.0.0.tgz#3243296a59ddb0f451d262cc2e11123674b416c2"
|
||||
|
|
Loading…
Reference in a new issue