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

View file

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

View file

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

View file

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

View file

@ -28,21 +28,3 @@
.textarea {
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 { RefObject, useMemo } from "react"
import styles from "../document.module.css"
import styles from "./formatting-icons.module.css"
import { TextareaMarkdownRef } from "textarea-markdown-editor"
import Tooltip from "@components/tooltip"
import Button from "@components/button"
import ButtonGroup from "@components/button-group"
// TODO: clean up
const FormattingIcons = ({
textareaRef
textareaRef,
}: {
textareaRef?: RefObject<TextareaMarkdownRef>
}) => {

View file

@ -2,17 +2,9 @@ import {
ChangeEvent,
memo,
useCallback,
useMemo,
useRef,
useState
} from "react"
import styles from "./document.module.css"
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 Input from "@components/input"
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 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 Archive from "@geist-ui/icons/archive"
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 VisibilityControl from "@components/badges/visibility-control"
import { File, PostWithFilesAndAuthor } from "@lib/server/prisma"
import ButtonGroup from "@components/button-group"
import Button from "@components/button"
type Props = {
post: string | PostWithFilesAndAuthor
@ -99,39 +101,36 @@ const PostPage = ({ post: initialPost, isProtected, isAuthor }: Props) => {
{!isAvailable && <PasswordModalPage setPost={setPost} postId={post.id} />}
<div className={styles.header}>
<span className={styles.buttons}>
<ButtonGroup
vertical={isMobile}
marginLeft={0}
marginRight={0}
marginTop={1}
marginBottom={1}
>
<ButtonGroup vertical={isMobile}>
<Button
auto
icon={<Edit />}
iconLeft={<Edit />}
onClick={editACopy}
style={{ textTransform: "none" }}
>
Edit a Copy
</Button>
{post.parentId && (
<Button auto icon={<Parent />} onClick={viewParentClick}>
<Button iconLeft={<Parent />} onClick={viewParentClick}>
View Parent
</Button>
)}
<Button
auto
onClick={download}
icon={<Archive />}
iconLeft={<Archive />}
style={{ textTransform: "none" }}
>
Download as ZIP Archive
</Button>
<FileDropdown isMobile={isMobile} files={post.files || []} />
<FileDropdown files={post.files || []} />
</ButtonGroup>
</span>
<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}>
<VisibilityBadge visibility={visibility} />
<CreatedAgoBadge createdAt={post.createdAt} />

View file

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

View file

@ -1,17 +1,15 @@
import { memo, useRef } from "react"
import { memo } from "react"
import styles from "./document.module.css"
import Download from "@geist-ui/icons/download"
import ExternalLink from "@geist-ui/icons/externalLink"
import Skeleton from "@components/skeleton"
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 Button from "@components/button"
import ButtonGroup from "@components/button-group"
import DocumentTabs from "app/(posts)/components/tabs"
import Input from "@components/input"
// import Link from "next/link"
type Props = {
@ -66,7 +64,6 @@ const Document = ({
if (skeleton) {
return (
<>
<Spacer height={1} />
<div className={styles.card}>
<div className={styles.fileNameContainer}>
<Skeleton width={275} height={36} />
@ -86,14 +83,14 @@ const Document = ({
<>
<div className={styles.card}>
<Link href={`#${title}`} className={styles.fileNameContainer}>
<Tag
height={"100%"}
<Input
id={`${title}`}
width={"100%"}
height={'2rem'}
style={{ borderRadius: 0 }}
>
{title || "Untitled"}
</Tag>
value={title || "Untitled"}
disabled
/>
</Link>
<div className={styles.descriptionContainer}>
<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 { useCallback, useState } from "react"
import ButtonGroup from "@components/button-group"
import Button from "@components/button"
type Props = {
postId: string
@ -65,12 +67,12 @@ const VisibilityControl = ({ postId, visibility, setVisibility }: Props) => {
{isSubmitting ? (
<Loading />
) : (
<ButtonGroup margin={0}>
<ButtonGroup>
<Button
disabled={visibility === "private"}
onClick={() => onSubmit("private")}
>
Make private
Make Private
</Button>
<Button
disabled={visibility === "public"}
@ -87,7 +89,7 @@ const VisibilityControl = ({ postId, visibility, setVisibility }: Props) => {
<Button onClick={() => onSubmit("protected")}>
{visibility === "protected"
? "Change Password"
: "Protect with password"}
: "Protect with Password"}
</Button>
</ButtonGroup>
)}

View file

@ -3,8 +3,6 @@
flex-direction: row;
flex-wrap: nowrap;
justify-content: flex-start;
align-items: stretch;
align-content: stretch;
}
.button-group > * {
@ -12,8 +10,20 @@
margin: 0;
}
/* all buttons on the inside should have no border radius */
.button-group :global(button) {
.button-group > * {
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 clsx from "clsx"
export default function ButtonGroup({
children,
vertical,
...props
}: {
children: React.ReactNode | React.ReactNode[]
vertical?: boolean
} & React.HTMLAttributes<HTMLDivElement>) {
return (
<div className={styles["button-group"]} {...props}>
<div
className={clsx(
props.className,
styles["button-group"],
vertical && styles.vertical
)}
{...props}
>
{children}
</div>
)

View file

@ -1,8 +1,3 @@
.button:root {
--hover: var(--bg);
--hover-bg: var(--fg);
}
.button {
user-select: none;
cursor: pointer;
@ -14,8 +9,8 @@
.button:hover,
.button:focus {
color: var(--hover);
background: var(--hover-bg);
color: var(--fg);
background: var(--bg);
border: 1px solid var(--darker-gray);
}
@ -25,6 +20,11 @@
color: var(--gray);
}
.button[disabled]:hover,
.button[disabled]:focus {
border: 1px solid currentColor;
}
.secondary {
background: var(--bg);
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 { Button, Spacer } from "@geist-ui/core/dist"
import ChevronUp from "@geist-ui/icons/chevronUpCircleFill"
import ChevronUp from "@geist-ui/icons/chevronUp"
import { useEffect, useState } from "react"
import styles from "./scroll.module.css"
@ -26,15 +26,7 @@ const ScrollToTop = () => {
}
return (
<div
style={{
display: "flex",
flexDirection: "row",
width: "100%",
height: 24,
justifyContent: "flex-end"
}}
>
<div className={styles.root}>
<Tooltip
content="Scroll to Top"
className={`${styles["scroll-up"]} ${
@ -44,12 +36,8 @@ const ScrollToTop = () => {
<Button
aria-label="Scroll to Top"
onClick={onClick}
style={{ background: "var(--light-gray)" }}
auto
>
<Spacer height={2 / 3} inline width={0} />
<ChevronUp />
</Button>
iconLeft={<ChevronUp />}
/>
</Tooltip>
</div>
)

View file

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

View file

@ -25,7 +25,6 @@ export default async function RootLayout({ children }: RootLayoutProps) {
.find(row => row.startsWith('${THEME_COOKIE_NAME}='))
.split('=')[1];
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",
"bcrypt": "^5.1.0",
"client-zip": "2.2.1",
"clsx": "^1.2.1",
"cookies-next": "^2.1.1",
"jest": "^29.3.1",
"next": "13.0.3",
@ -46,6 +45,7 @@
"@types/react": "18.0.9",
"@types/react-datepicker": "4.4.1",
"@types/react-dom": "18.0.3",
"clsx": "^1.2.1",
"cross-env": "7.0.3",
"eslint": "8.27.0",
"eslint-config-next": "13.0.3",

View file

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