more geist removal; add popover, convert more of post editing and viewing
This commit is contained in:
parent
3c5dcc24ac
commit
4cf448c35d
22 changed files with 191 additions and 155 deletions
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,55 +1,39 @@
|
||||||
|
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
|
const extension = file.title.split(".").pop()
|
||||||
}: {
|
if (codeFileExtensions.includes(extension || "")) {
|
||||||
files: File[]
|
return {
|
||||||
isMobile: boolean
|
...file,
|
||||||
}) => {
|
icon: <CodeIcon />
|
||||||
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 />
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
} else {
|
||||||
setItems(newItems)
|
return {
|
||||||
}, [files])
|
...file,
|
||||||
|
icon: <FileIcon />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
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}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>
|
||||||
}) => {
|
}) => {
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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} />
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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()} />
|
||||||
|
|
|
@ -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>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
|
|
|
@ -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);
|
||||||
|
|
38
client/app/components/popover/index.tsx
Normal file
38
client/app/components/popover/index.tsx
Normal 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}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
25
client/app/components/popover/popover.module.css
Normal file
25
client/app/components/popover/popover.module.css
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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)
|
|
||||||
})();
|
})();
|
||||||
`,
|
`,
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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==}
|
||||||
|
|
Loading…
Reference in a new issue