more geist removal; add popover, convert more of post editing and viewing

This commit is contained in:
Max Leiter 2022-11-16 02:16:56 -08:00
parent 3c5dcc24ac
commit 4cf448c35d
22 changed files with 191 additions and 155 deletions

View file

@ -15,7 +15,7 @@
.content { .content {
list-style: none; list-style: none;
width: 100%; width: 100%;
padding: 0 var(--gap); padding: var(--gap-quarter) var(--gap);
margin: 0; margin: 0;
} }
@ -26,7 +26,7 @@
padding: 0 0; padding: 0 0;
} }
.content li:hover, .contenthover,
.content li:focus { .content li:focus {
background-color: var(--lighter-gray); background-color: var(--lighter-gray);
} }

View file

@ -1,35 +1,21 @@
import Button from "@components/button"
import { Popover } from "@components/popover"
import ShiftBy from "@components/shift-by" import ShiftBy from "@components/shift-by"
import { Button, Popover } from "@geist-ui/core/dist"
import ChevronDown from "@geist-ui/icons/chevronDown" import ChevronDown from "@geist-ui/icons/chevronDown"
import CodeIcon from "@geist-ui/icons/fileFunction" import CodeIcon from "@geist-ui/icons/fileFunction"
import FileIcon from "@geist-ui/icons/fileText" import FileIcon from "@geist-ui/icons/fileText"
import { codeFileExtensions } from "@lib/constants" import { codeFileExtensions } from "@lib/constants"
import clsx from "clsx"
import type { File } from "lib/server/prisma" import type { File } from "lib/server/prisma"
import { useCallback, useEffect, useState } from "react"
import styles from "./dropdown.module.css" import styles from "./dropdown.module.css"
import buttonStyles from "@components/button/button.module.css"
type Item = File & { type Item = File & {
icon: JSX.Element icon: JSX.Element
} }
const FileDropdown = ({ const FileDropdown = ({ files }: { files: File[] }) => {
files, const items = files.map((file) => {
isMobile
}: {
files: File[]
isMobile: boolean
}) => {
const [expanded, setExpanded] = useState(false)
const [items, setItems] = useState<Item[]>([])
const changeHandler = (next: boolean) => {
setExpanded(next)
}
const onOpen = () => setExpanded(true)
const onClose = useCallback(() => setExpanded(false), [setExpanded])
useEffect(() => {
const newItems = files.map((file) => {
const extension = file.title.split(".").pop() const extension = file.title.split(".").pop()
if (codeFileExtensions.includes(extension || "")) { if (codeFileExtensions.includes(extension || "")) {
return { return {
@ -43,13 +29,11 @@ const FileDropdown = ({
} }
} }
}) })
setItems(newItems)
}, [files])
const content = ( const content = (
<ul className={styles.content}> <ul className={styles.content}>
{items.map((item) => ( {items.map((item) => (
<li key={item.id} onClick={onClose}> <li key={item.id}>
<a href={`#${item.title}`}> <a href={`#${item.title}`}>
<ShiftBy y={5}> <ShiftBy y={5}>
<span className={styles.fileIcon}>{item.icon}</span> <span className={styles.fileIcon}>{item.icon}</span>
@ -63,29 +47,16 @@ const FileDropdown = ({
</ul> </ul>
) )
// a list of files with an icon and a title
return ( return (
<> <Popover>
<Button <Popover.Trigger className={clsx(buttonStyles.button)}>
auto <div className={buttonStyles.icon}>
onClick={onOpen} <ChevronDown />
className={styles.button} </div>
iconRight={<ChevronDown />} <span>Jump to {files.length} {files.length === 1 ? "file" : "files"}</span>
style={{ textTransform: "none" }} </Popover.Trigger>
> <Popover.Content>{content}</Popover.Content>
Jump to {files.length} {files.length === 1 ? "file" : "files"} </Popover>
</Button>
<Popover
style={{
transform: isMobile ? "translateX(110px)" : "translateX(-75px)"
}}
onVisibleChange={changeHandler}
content={content}
visible={expanded}
hideArrow={true}
onClick={onClose}
/>
</>
) )
} }

View file

@ -69,7 +69,6 @@ export const StaticPreview = ({
preview: string preview: string
height: string | number height: string | number
}) => { }) => {
console.log("content", preview)
return ( return (
<article <article
className={styles.markdownPreview} className={styles.markdownPreview}

View file

@ -49,7 +49,7 @@ export default function DocumentTabs({
</RadixTabs.Trigger> </RadixTabs.Trigger>
</RadixTabs.List> </RadixTabs.List>
<RadixTabs.Content value="edit"> <RadixTabs.Content value="edit">
<FormattingIcons textareaRef={codeEditorRef} /> {isEditing && <FormattingIcons textareaRef={codeEditorRef} />}
<div <div
style={{ style={{
marginTop: 6, marginTop: 6,

View file

@ -28,21 +28,3 @@
.textarea { .textarea {
height: 100%; height: 100%;
} }
.actionWrapper {
position: relative;
z-index: 1;
}
.actionWrapper .actions {
position: absolute;
right: 0;
top: 4px;
}
@media (max-width: 768px) {
.actionWrapper .actions {
position: relative;
margin-left: 0 !important;
}
}

View file

@ -0,0 +1,17 @@
.actionWrapper {
position: relative;
z-index: 1;
}
.actionWrapper .actions {
position: absolute;
right: 0;
top: 4px;
}
@media (max-width: 768px) {
.actionWrapper .actions {
position: relative;
margin-left: 0 !important;
}
}

View file

@ -6,16 +6,15 @@ import List from "@geist-ui/icons/list"
import ImageIcon from "@geist-ui/icons/image" import ImageIcon from "@geist-ui/icons/image"
import { RefObject, useMemo } from "react" import { RefObject, useMemo } from "react"
import styles from "../document.module.css" import styles from "./formatting-icons.module.css"
import { TextareaMarkdownRef } from "textarea-markdown-editor" import { TextareaMarkdownRef } from "textarea-markdown-editor"
import Tooltip from "@components/tooltip" import Tooltip from "@components/tooltip"
import Button from "@components/button" import Button from "@components/button"
import ButtonGroup from "@components/button-group" import ButtonGroup from "@components/button-group"
// TODO: clean up // TODO: clean up
const FormattingIcons = ({ const FormattingIcons = ({
textareaRef textareaRef,
}: { }: {
textareaRef?: RefObject<TextareaMarkdownRef> textareaRef?: RefObject<TextareaMarkdownRef>
}) => { }) => {

View file

@ -2,17 +2,9 @@ import {
ChangeEvent, ChangeEvent,
memo, memo,
useCallback, useCallback,
useMemo,
useRef,
useState
} from "react" } from "react"
import styles from "./document.module.css" import styles from "./document.module.css"
import Trash from "@geist-ui/icons/trash" import Trash from "@geist-ui/icons/trash"
import FormattingIcons from "./formatting-icons"
import TextareaMarkdown, { TextareaMarkdownRef } from "textarea-markdown-editor"
import { Tabs } from "@geist-ui/core/dist"
import Preview from "../../../../components/preview"
import Button from "@components/button" import Button from "@components/button"
import Input from "@components/input" import Input from "@components/input"
import DocumentTabs from "app/(posts)/components/tabs" import DocumentTabs from "app/(posts)/components/tabs"

View file

@ -4,7 +4,7 @@ import VisibilityBadge from "@components/badges/visibility-badge"
import DocumentComponent from "./view-document" import DocumentComponent from "./view-document"
import styles from "./post-page.module.css" import styles from "./post-page.module.css"
import { Button, ButtonGroup, useMediaQuery } from "@geist-ui/core/dist" import { useMediaQuery } from "@geist-ui/core/dist"
import { useEffect, useState } from "react" import { useEffect, useState } from "react"
import Archive from "@geist-ui/icons/archive" import Archive from "@geist-ui/icons/archive"
import Edit from "@geist-ui/icons/edit" import Edit from "@geist-ui/icons/edit"
@ -17,6 +17,8 @@ import CreatedAgoBadge from "@components/badges/created-ago-badge"
import PasswordModalPage from "./password-modal-wrapper" import PasswordModalPage from "./password-modal-wrapper"
import VisibilityControl from "@components/badges/visibility-control" import VisibilityControl from "@components/badges/visibility-control"
import { File, PostWithFilesAndAuthor } from "@lib/server/prisma" import { File, PostWithFilesAndAuthor } from "@lib/server/prisma"
import ButtonGroup from "@components/button-group"
import Button from "@components/button"
type Props = { type Props = {
post: string | PostWithFilesAndAuthor post: string | PostWithFilesAndAuthor
@ -99,39 +101,36 @@ const PostPage = ({ post: initialPost, isProtected, isAuthor }: Props) => {
{!isAvailable && <PasswordModalPage setPost={setPost} postId={post.id} />} {!isAvailable && <PasswordModalPage setPost={setPost} postId={post.id} />}
<div className={styles.header}> <div className={styles.header}>
<span className={styles.buttons}> <span className={styles.buttons}>
<ButtonGroup <ButtonGroup vertical={isMobile}>
vertical={isMobile}
marginLeft={0}
marginRight={0}
marginTop={1}
marginBottom={1}
>
<Button <Button
auto iconLeft={<Edit />}
icon={<Edit />}
onClick={editACopy} onClick={editACopy}
style={{ textTransform: "none" }} style={{ textTransform: "none" }}
> >
Edit a Copy Edit a Copy
</Button> </Button>
{post.parentId && ( {post.parentId && (
<Button auto icon={<Parent />} onClick={viewParentClick}> <Button iconLeft={<Parent />} onClick={viewParentClick}>
View Parent View Parent
</Button> </Button>
)} )}
<Button <Button
auto
onClick={download} onClick={download}
icon={<Archive />} iconLeft={<Archive />}
style={{ textTransform: "none" }} style={{ textTransform: "none" }}
> >
Download as ZIP Archive Download as ZIP Archive
</Button> </Button>
<FileDropdown isMobile={isMobile} files={post.files || []} /> <FileDropdown files={post.files || []} />
</ButtonGroup> </ButtonGroup>
</span> </span>
<span className={styles.title}> <span className={styles.title}>
<h3>{post.title} <span style={{color: 'var(--gray)'}}>by {post.author?.displayName}</span></h3> <h3>
{post.title}{" "}
<span style={{ color: "var(--gray)" }}>
by {post.author?.displayName}
</span>
</h3>
<span className={styles.badges}> <span className={styles.badges}>
<VisibilityBadge visibility={visibility} /> <VisibilityBadge visibility={visibility} />
<CreatedAgoBadge createdAt={post.createdAt} /> <CreatedAgoBadge createdAt={post.createdAt} />

View file

@ -8,6 +8,7 @@
.header .title .badges { .header .title .badges {
display: flex; display: flex;
gap: var(--gap-half); gap: var(--gap-half);
flex-wrap: wrap;
} }
.header .title h3 { .header .title h3 {
@ -41,8 +42,8 @@
} }
.header .title .badges { .header .title .badges {
flex-direction: column;
align-items: center; align-items: center;
justify-content: center;
} }
.header .buttons { .header .buttons {

View file

@ -1,17 +1,15 @@
import { memo, useRef } from "react" import { memo } from "react"
import styles from "./document.module.css" import styles from "./document.module.css"
import Download from "@geist-ui/icons/download" import Download from "@geist-ui/icons/download"
import ExternalLink from "@geist-ui/icons/externalLink" import ExternalLink from "@geist-ui/icons/externalLink"
import Skeleton from "@components/skeleton" import Skeleton from "@components/skeleton"
import Link from "next/link" import Link from "next/link"
import { Tabs, Textarea, Tag, Spacer } from "@geist-ui/core/dist"
import { StaticPreview } from "app/(posts)/components/preview"
import FadeIn from "@components/fade-in"
import Tooltip from "@components/tooltip" import Tooltip from "@components/tooltip"
import Button from "@components/button" import Button from "@components/button"
import ButtonGroup from "@components/button-group" import ButtonGroup from "@components/button-group"
import DocumentTabs from "app/(posts)/components/tabs" import DocumentTabs from "app/(posts)/components/tabs"
import Input from "@components/input"
// import Link from "next/link" // import Link from "next/link"
type Props = { type Props = {
@ -66,7 +64,6 @@ const Document = ({
if (skeleton) { if (skeleton) {
return ( return (
<> <>
<Spacer height={1} />
<div className={styles.card}> <div className={styles.card}>
<div className={styles.fileNameContainer}> <div className={styles.fileNameContainer}>
<Skeleton width={275} height={36} /> <Skeleton width={275} height={36} />
@ -86,14 +83,14 @@ const Document = ({
<> <>
<div className={styles.card}> <div className={styles.card}>
<Link href={`#${title}`} className={styles.fileNameContainer}> <Link href={`#${title}`} className={styles.fileNameContainer}>
<Tag <Input
height={"100%"}
id={`${title}`} id={`${title}`}
width={"100%"} width={"100%"}
height={'2rem'}
style={{ borderRadius: 0 }} style={{ borderRadius: 0 }}
> value={title || "Untitled"}
{title || "Untitled"} disabled
</Tag> />
</Link> </Link>
<div className={styles.descriptionContainer}> <div className={styles.descriptionContainer}>
<DownloadButton rawLink={rawLink()} /> <DownloadButton rawLink={rawLink()} />

View file

@ -1,6 +1,8 @@
import { Button, ButtonGroup, Loading, useToasts } from "@geist-ui/core/dist" import { Loading, useToasts } from "@geist-ui/core/dist"
import PasswordModal from "@components/password-modal" import PasswordModal from "@components/password-modal"
import { useCallback, useState } from "react" import { useCallback, useState } from "react"
import ButtonGroup from "@components/button-group"
import Button from "@components/button"
type Props = { type Props = {
postId: string postId: string
@ -65,12 +67,12 @@ const VisibilityControl = ({ postId, visibility, setVisibility }: Props) => {
{isSubmitting ? ( {isSubmitting ? (
<Loading /> <Loading />
) : ( ) : (
<ButtonGroup margin={0}> <ButtonGroup>
<Button <Button
disabled={visibility === "private"} disabled={visibility === "private"}
onClick={() => onSubmit("private")} onClick={() => onSubmit("private")}
> >
Make private Make Private
</Button> </Button>
<Button <Button
disabled={visibility === "public"} disabled={visibility === "public"}
@ -87,7 +89,7 @@ const VisibilityControl = ({ postId, visibility, setVisibility }: Props) => {
<Button onClick={() => onSubmit("protected")}> <Button onClick={() => onSubmit("protected")}>
{visibility === "protected" {visibility === "protected"
? "Change Password" ? "Change Password"
: "Protect with password"} : "Protect with Password"}
</Button> </Button>
</ButtonGroup> </ButtonGroup>
)} )}

View file

@ -3,8 +3,6 @@
flex-direction: row; flex-direction: row;
flex-wrap: nowrap; flex-wrap: nowrap;
justify-content: flex-start; justify-content: flex-start;
align-items: stretch;
align-content: stretch;
} }
.button-group > * { .button-group > * {
@ -12,8 +10,20 @@
margin: 0; margin: 0;
} }
/* all buttons on the inside should have no border radius */ .button-group > * {
.button-group :global(button) {
border-radius: 0 !important; border-radius: 0 !important;
} }
.button-group > button:first-of-type {
border-top-left-radius: var(--radius) !important;
border-bottom-left-radius: var(--radius) !important;
}
.button-group > button:last-of-type {
border-top-right-radius: var(--radius) !important;
border-bottom-right-radius: var(--radius) !important;
}
.vertical {
flex-direction: column;
}

View file

@ -1,13 +1,22 @@
import styles from "./button-group.module.css" import styles from "./button-group.module.css"
import clsx from "clsx"
export default function ButtonGroup({ export default function ButtonGroup({
children, children,
vertical,
...props ...props
}: { }: {
children: React.ReactNode | React.ReactNode[] children: React.ReactNode | React.ReactNode[]
vertical?: boolean
} & React.HTMLAttributes<HTMLDivElement>) { } & React.HTMLAttributes<HTMLDivElement>) {
return ( return (
<div className={styles["button-group"]} {...props}> <div
className={clsx(
props.className,
styles["button-group"],
vertical && styles.vertical
)}
{...props}
>
{children} {children}
</div> </div>
) )

View file

@ -1,8 +1,3 @@
.button:root {
--hover: var(--bg);
--hover-bg: var(--fg);
}
.button { .button {
user-select: none; user-select: none;
cursor: pointer; cursor: pointer;
@ -14,8 +9,8 @@
.button:hover, .button:hover,
.button:focus { .button:focus {
color: var(--hover); color: var(--fg);
background: var(--hover-bg); background: var(--bg);
border: 1px solid var(--darker-gray); border: 1px solid var(--darker-gray);
} }
@ -25,6 +20,11 @@
color: var(--gray); color: var(--gray);
} }
.button[disabled]:hover,
.button[disabled]:focus {
border: 1px solid currentColor;
}
.secondary { .secondary {
background: var(--bg); background: var(--bg);
color: var(--fg); color: var(--fg);

View file

@ -0,0 +1,38 @@
// largely from https://github.com/shadcn/taxonomy
import * as React from "react"
import * as PopoverPrimitive from "@radix-ui/react-popover"
import clsx from "clsx"
import styles from './popover.module.css'
type PopoverProps = PopoverPrimitive.PopoverProps
export function Popover({ ...props }: PopoverProps) {
return <PopoverPrimitive.Root {...props} />
}
Popover.Trigger = React.forwardRef<
HTMLButtonElement,
PopoverPrimitive.PopoverTriggerProps
>(function PopoverTrigger({ ...props }, ref) {
return <PopoverPrimitive.Trigger {...props} ref={ref} />
})
Popover.Portal = PopoverPrimitive.Portal
Popover.Content = React.forwardRef<
HTMLDivElement,
PopoverPrimitive.PopoverContentProps
>(function PopoverContent({ className, ...props }, ref) {
return (
<PopoverPrimitive.Content
ref={ref}
align="end"
className={clsx(
styles.root,
className
)}
{...props}
/>
)
})

View file

@ -0,0 +1,25 @@
.root {
overflow: hidden;
border-radius: 0.375rem;
border: 1px solid var(--border);
background-color: var(--bg);
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
animation: slide-in-from-top 0.1s cubic-bezier(0.4, 0, 1, 1) 0.1s;
animation-fill-mode: both;
width: 100%;
}
@keyframes slide-in-from-top {
0% {
transform: translateY(-10px);
opacity: 0;
}
100% {
transform: translateY(0);
opacity: 1;
}
}

View file

@ -1,6 +1,6 @@
import Button from "@components/button"
import Tooltip from "@components/tooltip" import Tooltip from "@components/tooltip"
import { Button, Spacer } from "@geist-ui/core/dist" import ChevronUp from "@geist-ui/icons/chevronUp"
import ChevronUp from "@geist-ui/icons/chevronUpCircleFill"
import { useEffect, useState } from "react" import { useEffect, useState } from "react"
import styles from "./scroll.module.css" import styles from "./scroll.module.css"
@ -26,15 +26,7 @@ const ScrollToTop = () => {
} }
return ( return (
<div <div className={styles.root}>
style={{
display: "flex",
flexDirection: "row",
width: "100%",
height: 24,
justifyContent: "flex-end"
}}
>
<Tooltip <Tooltip
content="Scroll to Top" content="Scroll to Top"
className={`${styles["scroll-up"]} ${ className={`${styles["scroll-up"]} ${
@ -44,12 +36,8 @@ const ScrollToTop = () => {
<Button <Button
aria-label="Scroll to Top" aria-label="Scroll to Top"
onClick={onClick} onClick={onClick}
style={{ background: "var(--light-gray)" }} iconLeft={<ChevronUp />}
auto />
>
<Spacer height={2 / 3} inline width={0} />
<ChevronUp />
</Button>
</Tooltip> </Tooltip>
</div> </div>
) )

View file

@ -1,3 +1,11 @@
.root {
display: flex;
flex-direction: row;
width: 100%;
height: 24px;
justify-content: flex-end;
}
.scroll-up { .scroll-up {
position: fixed; position: fixed;
z-index: 2; z-index: 2;

View file

@ -25,7 +25,6 @@ export default async function RootLayout({ children }: RootLayoutProps) {
.find(row => row.startsWith('${THEME_COOKIE_NAME}=')) .find(row => row.startsWith('${THEME_COOKIE_NAME}='))
.split('=')[1]; .split('=')[1];
document.documentElement.setAttribute('data-theme', theme); document.documentElement.setAttribute('data-theme', theme);
console.log("theme on load", theme)
})(); })();
`, `,
}} }}

View file

@ -23,7 +23,6 @@
"@wcj/markdown-to-html": "^2.1.2", "@wcj/markdown-to-html": "^2.1.2",
"bcrypt": "^5.1.0", "bcrypt": "^5.1.0",
"client-zip": "2.2.1", "client-zip": "2.2.1",
"clsx": "^1.2.1",
"cookies-next": "^2.1.1", "cookies-next": "^2.1.1",
"jest": "^29.3.1", "jest": "^29.3.1",
"next": "13.0.3", "next": "13.0.3",
@ -46,6 +45,7 @@
"@types/react": "18.0.9", "@types/react": "18.0.9",
"@types/react-datepicker": "4.4.1", "@types/react-datepicker": "4.4.1",
"@types/react-dom": "18.0.3", "@types/react-dom": "18.0.3",
"clsx": "^1.2.1",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"eslint": "8.27.0", "eslint": "8.27.0",
"eslint-config-next": "13.0.3", "eslint-config-next": "13.0.3",

View file

@ -54,7 +54,6 @@ dependencies:
'@wcj/markdown-to-html': 2.1.2 '@wcj/markdown-to-html': 2.1.2
bcrypt: 5.1.0 bcrypt: 5.1.0
client-zip: 2.2.1 client-zip: 2.2.1
clsx: 1.2.1
cookies-next: 2.1.1 cookies-next: 2.1.1
jest: 29.3.1_@types+node@17.0.23 jest: 29.3.1_@types+node@17.0.23
next: 13.0.3_biqbaboplfbrettd7655fr4n2y next: 13.0.3_biqbaboplfbrettd7655fr4n2y
@ -80,6 +79,7 @@ devDependencies:
'@types/react': 18.0.9 '@types/react': 18.0.9
'@types/react-datepicker': 4.4.1_biqbaboplfbrettd7655fr4n2y '@types/react-datepicker': 4.4.1_biqbaboplfbrettd7655fr4n2y
'@types/react-dom': 18.0.3 '@types/react-dom': 18.0.3
clsx: 1.2.1
cross-env: 7.0.3 cross-env: 7.0.3
eslint: 8.27.0 eslint: 8.27.0
eslint-config-next: 13.0.3_hsmo2rtalirsvadpuxki35bq2i eslint-config-next: 13.0.3_hsmo2rtalirsvadpuxki35bq2i
@ -2159,7 +2159,7 @@ packages:
/clsx/1.2.1: /clsx/1.2.1:
resolution: {integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==} resolution: {integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==}
engines: {node: '>=6'} engines: {node: '>=6'}
dev: false dev: true
/co/4.6.0: /co/4.6.0:
resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==}