client: add file dropdown to post view
This commit is contained in:
parent
dfea957046
commit
1ace04985c
5 changed files with 182 additions and 20 deletions
69
client/components/file-dropdown/dropdown.module.css
Normal file
69
client/components/file-dropdown/dropdown.module.css
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
.wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdownButton {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
list-style: none;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0 var(--gap);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content li {
|
||||||
|
transition: var(--transition);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
margin: 0;
|
||||||
|
padding: 2px var(--gap);
|
||||||
|
}
|
||||||
|
|
||||||
|
.content li:hover,
|
||||||
|
.content li:focus {
|
||||||
|
background-color: var(--lighter-gray);
|
||||||
|
}
|
||||||
|
|
||||||
|
.content li a {
|
||||||
|
display: block;
|
||||||
|
height: 100%;
|
||||||
|
width: min-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
border-radius: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content li .fileIcon {
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: var(--gap-half);
|
||||||
|
}
|
||||||
|
|
||||||
|
.content li .fileTitle {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content li::before {
|
||||||
|
content: "";
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cardContent {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: var(--gap-half);
|
||||||
|
padding-top: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 82rem) {
|
||||||
|
}
|
64
client/components/file-dropdown/index.tsx
Normal file
64
client/components/file-dropdown/index.tsx
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
import { Button, Link, Note, Popover, Spacer } from '@geist-ui/core'
|
||||||
|
import FileIcon from '@geist-ui/icons/fileText'
|
||||||
|
import CodeIcon from '@geist-ui/icons/fileLambda'
|
||||||
|
import styles from './dropdown.module.css'
|
||||||
|
import { useCallback, useEffect, useState } from "react"
|
||||||
|
import { codeFileExtensions } from "@lib/constants"
|
||||||
|
import ChevronDown from '@geist-ui/icons/chevronDown'
|
||||||
|
import ShiftBy from "@components/shift-by"
|
||||||
|
import type { File } from '@lib/types'
|
||||||
|
import FileTree from '@geist-ui/icons/list'
|
||||||
|
|
||||||
|
type Item = File & {
|
||||||
|
icon: JSX.Element
|
||||||
|
}
|
||||||
|
|
||||||
|
const FileDropdown = ({
|
||||||
|
files
|
||||||
|
}: {
|
||||||
|
files: File[]
|
||||||
|
}) => {
|
||||||
|
const [expanded, setExpanded] = useState(false)
|
||||||
|
const [items, setItems] = useState<Item[]>([])
|
||||||
|
useEffect(() => {
|
||||||
|
const newItems = files.map(file => {
|
||||||
|
const extension = file.title.split('.').pop()
|
||||||
|
if (codeFileExtensions.includes(extension || '')) {
|
||||||
|
return {
|
||||||
|
...file,
|
||||||
|
icon: <CodeIcon />
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
...file,
|
||||||
|
icon: <FileIcon />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
setItems(newItems)
|
||||||
|
}, [files])
|
||||||
|
|
||||||
|
const content = useCallback(() => (<ul className={styles.content}>
|
||||||
|
{items.map(item => (
|
||||||
|
<li key={item.id} onClick={() => setExpanded(false)}>
|
||||||
|
<Link color={false} href={`#${item.title}`}>
|
||||||
|
<ShiftBy y={5}><span className={styles.fileIcon}>
|
||||||
|
{item.icon}</span></ShiftBy>
|
||||||
|
<span className={styles.fileTitle}>{item.title}</span>
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
), [items])
|
||||||
|
|
||||||
|
// a list of files with an icon and a title
|
||||||
|
return (
|
||||||
|
<Button auto onClick={() => setExpanded(!expanded)} className={styles.button} iconRight={<ChevronDown />}>
|
||||||
|
<Popover content={content} visible={expanded} trigger="click" hideArrow={true}>
|
||||||
|
{files.length} files
|
||||||
|
</Popover>
|
||||||
|
</Button >
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FileDropdown
|
|
@ -17,11 +17,21 @@
|
||||||
.fileTree li {
|
.fileTree li {
|
||||||
transition: var(--transition);
|
transition: var(--transition);
|
||||||
border-radius: var(--radius);
|
border-radius: var(--radius);
|
||||||
padding: var(--gap-half) 0;
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
padding: var(--gap-half) 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fileTree li:hover {
|
.fileTree li a {
|
||||||
|
margin: 0px;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
padding: var(--gap-half);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fileTree li:hover,
|
||||||
|
.fileTree li:focus,
|
||||||
|
.fileTree li:active {
|
||||||
background: var(--lighter-gray);
|
background: var(--lighter-gray);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,11 +6,12 @@ import styles from './post-page.module.css'
|
||||||
import homeStyles from '@styles/Home.module.css'
|
import homeStyles from '@styles/Home.module.css'
|
||||||
|
|
||||||
import type { File, Post } from "@lib/types"
|
import type { File, Post } from "@lib/types"
|
||||||
import { Page, Button, Text, Badge, Tooltip, Spacer } from "@geist-ui/core"
|
import { Page, Button, Text, Badge, Tooltip, Spacer, ButtonDropdown, ButtonGroup } from "@geist-ui/core"
|
||||||
import ShiftBy from "@components/shift-by"
|
import ShiftBy from "@components/shift-by"
|
||||||
import { useMemo, useState } from "react"
|
import { useMemo, useState } from "react"
|
||||||
import timeAgo from "@lib/time-ago"
|
import timeAgo from "@lib/time-ago"
|
||||||
import FileTree from "@components/file-tree"
|
import Archive from '@geist-ui/icons/archive'
|
||||||
|
import FileDropdown from "@components/file-dropdown"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
post: Post
|
post: Post
|
||||||
|
@ -51,18 +52,24 @@ const PostPage = ({ post }: Props) => {
|
||||||
<Page.Content className={homeStyles.main}>
|
<Page.Content className={homeStyles.main}>
|
||||||
{/* {!isLoading && <PostFileExplorer files={post.files} />} */}
|
{/* {!isLoading && <PostFileExplorer files={post.files} />} */}
|
||||||
<div className={styles.header}>
|
<div className={styles.header}>
|
||||||
<div className={styles.titleAndBadge}>
|
<span className={styles.title}>
|
||||||
<Text h2>{post.title}</Text>
|
<Text h2>{post.title}</Text>
|
||||||
<span>
|
<div className={styles.badges}>
|
||||||
<VisibilityBadge visibility={post.visibility} />
|
<VisibilityBadge visibility={post.visibility} />
|
||||||
<Badge type="secondary"><Tooltip text={formattedTime}>{time}</Tooltip></Badge>
|
<Badge type="secondary"><Tooltip text={formattedTime}>{time}</Tooltip></Badge>
|
||||||
</span>
|
</div>
|
||||||
</div>
|
</span>
|
||||||
<Button auto onClick={download}>
|
<span className={styles.buttons}>
|
||||||
Download as ZIP archive
|
<ButtonGroup>
|
||||||
</Button>
|
<Button auto onClick={download} icon={<Archive />}>
|
||||||
|
Download as ZIP archive
|
||||||
|
</Button>
|
||||||
|
<FileDropdown files={post.files} />
|
||||||
|
</ButtonGroup>
|
||||||
|
</span>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{post.files.length > 1 && <FileTree files={post.files} />}
|
{/* {post.files.length > 1 && <FileTree files={post.files} />} */}
|
||||||
{post.files.map(({ id, content, title }: File) => (
|
{post.files.map(({ id, content, title }: File) => (
|
||||||
<DocumentComponent
|
<DocumentComponent
|
||||||
key={id}
|
key={id}
|
||||||
|
|
|
@ -4,32 +4,44 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header .titleAndBadge {
|
.header .title {
|
||||||
display: flex;
|
display: flex;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header .titleAndBadge span {
|
.header .title .badges > * {
|
||||||
margin-left: var(--gap);
|
margin-left: var(--gap);
|
||||||
|
margin-bottom: var(--gap-quarter);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 680px) {
|
.header .buttons {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 900px) {
|
||||||
.header {
|
.header {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.header .titleAndBadge {
|
@media screen and (max-width: 700px) {
|
||||||
|
.header .title {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding-bottom: var(--gap-double);
|
padding-bottom: var(--gap-double);
|
||||||
}
|
}
|
||||||
|
|
||||||
.header .titleAndBadge span {
|
.header .title .badges > * {
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
margin-bottom: var(--gap);
|
}
|
||||||
|
|
||||||
|
.header .title .badges {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-around;
|
flex-direction: row;
|
||||||
width: 100%;
|
justify-content: space-between;
|
||||||
|
width: 80%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue