Custom tabs

This commit is contained in:
Max Leiter 2022-11-16 00:49:12 -08:00
parent 45c2e59105
commit 3c5dcc24ac
16 changed files with 356 additions and 233 deletions

View file

@ -14,10 +14,10 @@ type Props = {
const MarkdownPreview = ({ const MarkdownPreview = ({
height = 500, height = 500,
fileId, fileId,
content: initial = "", content = "",
title title
}: Props) => { }: Props) => {
const [content, setPreview] = useState<string>(initial) const [preview, setPreview] = useState<string>(content)
const [isLoading, setIsLoading] = useState<boolean>(true) const [isLoading, setIsLoading] = useState<boolean>(true)
useEffect(() => { useEffect(() => {
async function fetchPost() { async function fetchPost() {
@ -28,7 +28,7 @@ const MarkdownPreview = ({
? undefined ? undefined
: JSON.stringify({ : JSON.stringify({
title: title || "", title: title || "",
content: initial content: content
}) })
const resp = await fetch(path, { const resp = await fetch(path, {
@ -47,14 +47,14 @@ const MarkdownPreview = ({
setIsLoading(false) setIsLoading(false)
} }
fetchPost() fetchPost()
}, [initial, fileId, title]) }, [content, fileId, title])
return ( return (
<> <>
{isLoading ? ( {isLoading ? (
<><Spinner /></> <><Spinner /></>
) : ( ) : (
<StaticPreview content={content} height={height} /> <StaticPreview preview={preview} height={height} />
)} )}
</> </>
) )
@ -63,16 +63,17 @@ const MarkdownPreview = ({
export default memo(MarkdownPreview) export default memo(MarkdownPreview)
export const StaticPreview = ({ export const StaticPreview = ({
content, preview,
height = 500 height = 500
}: { }: {
content: string preview: string
height: string | number height: string | number
}) => { }) => {
console.log("content", preview)
return ( return (
<article <article
className={styles.markdownPreview} className={styles.markdownPreview}
dangerouslySetInnerHTML={{ __html: content }} dangerouslySetInnerHTML={{ __html: preview }}
style={{ style={{
height height
}} }}

View file

@ -0,0 +1,84 @@
import * as RadixTabs from "@radix-ui/react-tabs"
import FormattingIcons from "app/(posts)/new/components/edit-document-list/edit-document/formatting-icons"
import { ChangeEvent, useRef } from "react"
import TextareaMarkdown, { TextareaMarkdownRef } from "textarea-markdown-editor"
import Preview, { StaticPreview } from "../preview"
import styles from "./tabs.module.css"
type Props = RadixTabs.TabsProps & {
isEditing: boolean
defaultTab: "preview" | "edit"
handleOnContentChange?: (e: ChangeEvent<HTMLTextAreaElement>) => void
onPaste?: (e: any) => void
title?: string
content?: string
preview?: string
}
export default function DocumentTabs({
isEditing,
defaultTab,
handleOnContentChange,
onPaste,
title,
content,
preview,
...props
}: Props) {
const codeEditorRef = useRef<TextareaMarkdownRef>(null)
const handleTabChange = (newTab: string) => {
if (newTab === "preview") {
codeEditorRef.current?.focus()
}
}
return (
<RadixTabs.Root
{...props}
onValueChange={handleTabChange}
className={styles.root}
defaultValue={defaultTab}
>
<RadixTabs.List className={styles.list}>
<RadixTabs.Trigger value="edit" className={styles.trigger}>
{isEditing ? "Edit" : "Raw"}
</RadixTabs.Trigger>
<RadixTabs.Trigger value="preview" className={styles.trigger}>
{isEditing ? "Preview" : "Rendered"}
</RadixTabs.Trigger>
</RadixTabs.List>
<RadixTabs.Content value="edit">
<FormattingIcons textareaRef={codeEditorRef} />
<div
style={{
marginTop: 6,
display: "flex",
flexDirection: "column"
}}
>
<TextareaMarkdown.Wrapper ref={codeEditorRef}>
<textarea
readOnly={!isEditing}
onPaste={onPaste ? onPaste : undefined}
ref={codeEditorRef}
placeholder=""
value={content}
onChange={handleOnContentChange}
// TODO: Textarea should grow to fill parent if height == 100%
style={{ flex: 1, minHeight: 350 }}
// className={styles.textarea}
/>
</TextareaMarkdown.Wrapper>
</div>
</RadixTabs.Content>
<RadixTabs.Content value="preview">
{isEditing ? (
<Preview height={"100%"} title={title} content={content} />
) : (
<StaticPreview height={"100%"} preview={preview || ""} />
)}
</RadixTabs.Content>
</RadixTabs.Root>
)
}

View file

@ -0,0 +1,42 @@
.root {
display: flex;
flex-direction: column;
}
.list {
flex-shrink: 0;
display: flex;
}
.trigger {
width: 80px;
height: 30px;
margin: 4px 0;
font-family: inherit;
display: flex;
align-items: center;
justify-content: center;
font-size: 15px;
line-height: 1;
user-select: none;
cursor: pointer;
transition: color 0.1s ease;
}
.trigger:hover {
background-color: var(--lighter-gray);
color: var(--fg);
}
.trigger:first-child {
border-top-left-radius: 4px;
}
.trigger:last-child {
border-top-right-radius: 4px;
}
.trigger[data-state="active"] {
color: var(--darkest-gray);
box-shadow: inset 0 -1px 0 0 currentColor, 0 1px 0 0 currentColor;
}

View file

@ -1,5 +1,5 @@
import Input from "@components/input"
import { ChangeEvent, memo } from "react" import { ChangeEvent, memo } from "react"
import { Input } from "@geist-ui/core/dist"
import styles from "../post.module.css" import styles from "../post.module.css"

View file

@ -14,14 +14,17 @@
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
padding: 20px; padding: 20px;
border-width: 2px;
border-radius: 2px; border-radius: 2px;
border-style: dashed; border: 2px dashed var(--border) !important;
outline: none; outline: none;
transition: all 0.24s ease-in-out; transition: all 0.24s ease-in-out;
cursor: pointer; cursor: pointer;
} }
.dropzone:hover {
border-color: var(--gray) !important;
}
.dropzone:focus { .dropzone:focus {
box-shadow: 0 0 4px 1px rgba(124, 124, 124, 0.5); box-shadow: 0 0 4px 1px rgba(124, 124, 124, 0.5);
} }

View file

@ -11,9 +11,11 @@ import Trash from "@geist-ui/icons/trash"
import FormattingIcons from "./formatting-icons" import FormattingIcons from "./formatting-icons"
import TextareaMarkdown, { TextareaMarkdownRef } from "textarea-markdown-editor" import TextareaMarkdown, { TextareaMarkdownRef } from "textarea-markdown-editor"
import { Input, Tabs, Textarea } from "@geist-ui/core/dist" import { Tabs } from "@geist-ui/core/dist"
import Preview from "../../../../components/preview" import Preview from "../../../../components/preview"
import Button from "@components/button" import Button from "@components/button"
import Input from "@components/input"
import DocumentTabs from "app/(posts)/components/tabs"
// import Link from "next/link" // import Link from "next/link"
type Props = { type Props = {
@ -35,18 +37,9 @@ const Document = ({
initialTab = "edit", initialTab = "edit",
handleOnContentChange handleOnContentChange
}: Props) => { }: Props) => {
const codeEditorRef = useRef<TextareaMarkdownRef>(null)
const [tab, setTab] = useState(initialTab)
// const height = editable ? "500px" : '100%' // const height = editable ? "500px" : '100%'
const height = "100%" const height = "100%"
const handleTabChange = (newTab: string) => {
if (newTab === "edit") {
codeEditorRef.current?.focus()
}
setTab(newTab as "edit" | "preview")
}
const onTitleChange = useCallback( const onTitleChange = useCallback(
(event: ChangeEvent<HTMLInputElement>) => (event: ChangeEvent<HTMLInputElement>) =>
setTitle ? setTitle(event.target.value) : null, setTitle ? setTitle(event.target.value) : null,
@ -71,22 +64,6 @@ const Document = ({
[content] [content]
) )
// if (skeleton) {
// return <>
// <Spacer height={1} />
// <div className={styles.card}>
// <div className={styles.fileNameContainer}>
// <Skeleton width={275} height={36} />
// {remove && <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 >
// </div>
// </>
// }
return ( return (
<> <>
<div className={styles.card}> <div className={styles.card}>
@ -95,63 +72,38 @@ const Document = ({
placeholder="MyFile.md" placeholder="MyFile.md"
value={title} value={title}
onChange={onTitleChange} onChange={onTitleChange}
marginTop="var(--gap-double)"
size={1.2}
font={1.2}
label="Filename" label="Filename"
width={"100%"} width={"100%"}
id={title} id={title}
style={{
borderTopRightRadius: remove ? 0 : "var(--radius)",
borderBottomRightRadius: remove ? 0 : "var(--radius)"
}}
/> />
{remove && ( {remove && (
<Button <Button
iconLeft={<Trash />} iconLeft={<Trash />}
height={"36px"} height={"39px"}
width={"48px"} width={"48px"}
padding={0} padding={0}
margin={0} margin={0}
onClick={() => removeFile(remove)} onClick={() => removeFile(remove)}
style={{
borderTopLeftRadius: 0,
borderBottomLeftRadius: 0
}}
/> />
)} )}
</div> </div>
<div className={styles.descriptionContainer}> <div className={styles.descriptionContainer}>
{tab === "edit" && <FormattingIcons textareaRef={codeEditorRef} />} <DocumentTabs
<Tabs isEditing={true}
onChange={handleTabChange} defaultTab={"edit"}
initialValue={initialTab} handleOnContentChange={handleOnContentChange}
hideDivider onPaste={onPaste}
leftSpace={0} title={title}
> content={content}
<Tabs.Item label={"Edit"} value="edit"> />
{/* <textarea className={styles.lineCounter} wrap='off' readOnly ref={lineNumberRef}>1.</textarea> */}
<div
style={{
marginTop: 6,
display: "flex",
flexDirection: "column"
}}
>
<TextareaMarkdown.Wrapper ref={codeEditorRef}>
<Textarea
onPaste={onPaste ? onPaste : undefined}
ref={codeEditorRef}
placeholder=""
value={content}
onChange={handleOnContentChange}
width="100%"
// TODO: Textarea should grow to fill parent if height == 100%
style={{ flex: 1, minHeight: 350 }}
resize="vertical"
className={styles.textarea}
/>
</TextareaMarkdown.Wrapper>
</div>
</Tabs.Item>
<Tabs.Item label="Preview" value="preview">
<div>
<Preview height={height} title={title} content={content} />
</div>
</Tabs.Item>
</Tabs>
</div> </div>
</div> </div>
</> </>

View file

@ -1,6 +1,6 @@
"use client" "use client"
import { useToasts, Input, ButtonDropdown } from "@geist-ui/core/dist" import { useToasts, ButtonDropdown } from "@geist-ui/core/dist"
import { useRouter } from "next/navigation" import { useRouter } from "next/navigation"
import { useCallback, useState } from "react" import { useCallback, useState } from "react"
import generateUUID from "@lib/generate-uuid" import generateUUID from "@lib/generate-uuid"
@ -15,6 +15,7 @@ import PasswordModal from "../../../components/password-modal"
import Title from "./title" import Title from "./title"
import FileDropzone from "./drag-and-drop" import FileDropzone from "./drag-and-drop"
import Button from "@components/button" import Button from "@components/button"
import Input from "@components/input"
const emptyDoc = { const emptyDoc = {
title: "", title: "",
content: "", content: "",
@ -287,7 +288,7 @@ const Post = ({
<DatePicker <DatePicker
onChange={onChangeExpiration} onChange={onChangeExpiration}
customInput={ customInput={
<Input label="Expires at" clearable width="100%" height="40px" /> <Input label="Expires at" width="100%" height="40px" />
} }
placeholderText="Won't expire" placeholderText="Won't expire"
selected={expiresAt} selected={expiresAt}

View file

@ -1,9 +1,8 @@
import { ChangeEvent, memo, useEffect, useState } from "react" import { ChangeEvent, memo, useEffect, useState } from "react"
import { Text } from "@geist-ui/core/dist"
import ShiftBy from "@components/shift-by" import ShiftBy from "@components/shift-by"
import styles from "../post.module.css" import styles from "../post.module.css"
import { Input } from "@geist-ui/core/dist" import Input from "@components/input"
const titlePlaceholders = [ const titlePlaceholders = [
"How to...", "How to...",
@ -30,20 +29,15 @@ const Title = ({ onChange, title }: props) => {
}, []) }, [])
return ( return (
<div className={styles.title}> <div className={styles.title}>
<Text h1 width={"150px"} className={styles.drift}> <h1 className={styles.drift}>Drift</h1>
Drift <Input
</Text> placeholder={placeholder}
<ShiftBy y={-3}> value={title || ""}
<Input onChange={onChange}
placeholder={placeholder} height={"55px"}
value={title || ""} label="Post title"
onChange={onChange} style={{ width: "100%", fontSize: 18 }}
height={"55px"} />
font={1.5}
label="Post title"
style={{ width: "100%" }}
/>
</ShiftBy>
</div> </div>
) )
} }

View file

@ -5,17 +5,13 @@ 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 { import { Tabs, Textarea, Tag, Spacer } from "@geist-ui/core/dist"
Button,
ButtonGroup,
Spacer,
Tabs,
Textarea,
Tag
} from "@geist-ui/core/dist"
import { StaticPreview } from "app/(posts)/components/preview" import { StaticPreview } from "app/(posts)/components/preview"
import FadeIn from "@components/fade-in" import FadeIn from "@components/fade-in"
import Tooltip from "@components/tooltip" import Tooltip from "@components/tooltip"
import Button from "@components/button"
import ButtonGroup from "@components/button-group"
import DocumentTabs from "app/(posts)/components/tabs"
// import Link from "next/link" // import Link from "next/link"
type Props = { type Props = {
@ -37,22 +33,13 @@ const DownloadButton = ({ rawLink }: { rawLink?: string }) => {
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >
<Button <Button iconRight={<Download />} aria-label="Download" />
scale={2 / 3}
px={0.6}
icon={<Download />}
auto
aria-label="Download"
/>
</Link> </Link>
</Tooltip> </Tooltip>
<Tooltip content="Open raw in new tab"> <Tooltip content="Open raw in new tab">
<Link href={rawLink || ""} target="_blank" rel="noopener noreferrer"> <Link href={rawLink || ""} target="_blank" rel="noopener noreferrer">
<Button <Button
scale={2 / 3} iconRight={<ExternalLink />}
px={0.6}
icon={<ExternalLink />}
auto
aria-label="Open raw file in new tab" aria-label="Open raw file in new tab"
/> />
</Link> </Link>
@ -70,15 +57,6 @@ const Document = ({
skeleton, skeleton,
id id
}: Props) => { }: Props) => {
const codeEditorRef = useRef<HTMLTextAreaElement>(null)
const height = "100%"
const handleTabChange = (newTab: string) => {
if (newTab === "edit") {
codeEditorRef.current?.focus()
}
}
const rawLink = () => { const rawLink = () => {
if (id) { if (id) {
return `/file/raw/${id}` return `/file/raw/${id}`
@ -105,8 +83,7 @@ const Document = ({
} }
return ( return (
<FadeIn> <>
<Spacer height={1} />
<div className={styles.card}> <div className={styles.card}>
<Link href={`#${title}`} className={styles.fileNameContainer}> <Link href={`#${title}`} className={styles.fileNameContainer}>
<Tag <Tag
@ -120,47 +97,15 @@ const Document = ({
</Link> </Link>
<div className={styles.descriptionContainer}> <div className={styles.descriptionContainer}>
<DownloadButton rawLink={rawLink()} /> <DownloadButton rawLink={rawLink()} />
<Tabs <DocumentTabs
onChange={handleTabChange} defaultTab={initialTab}
initialValue={initialTab} preview={preview}
hideDivider content={content}
leftSpace={0} isEditing={false}
> />
<Tabs.Item label={"Raw"} value="edit">
{/* <textarea className={styles.lineCounter} wrap='off' readOnly ref={lineNumberRef}>1.</textarea> */}
<div
style={{
marginTop: 6,
display: "flex",
flexDirection: "column"
}}
>
<Textarea
readOnly
ref={codeEditorRef}
value={content}
width="100%"
// TODO: Textarea should grow to fill parent if height == 100%
style={{ flex: 1, minHeight: 350 }}
resize="vertical"
className={styles.textarea}
/>
</div>
</Tabs.Item>
<Tabs.Item label="Preview" value="preview">
<div>
<StaticPreview
height={height}
// fileId={id}
content={preview}
// title={title}
/>
</div>
</Tabs.Item>
</Tabs>
</div> </div>
</div> </div>
</FadeIn> </>
) )
} }

View file

@ -11,3 +11,9 @@
flex: 1 1 auto; flex: 1 1 auto;
margin: 0; margin: 0;
} }
/* all buttons on the inside should have no border radius */
.button-group :global(button) {
border-radius: 0 !important;
}

View file

@ -1,10 +1,9 @@
"use client" "use client"
import ShiftBy from "@components/shift-by" import { Tabs, Textarea } from "@geist-ui/core/dist"
import { Spacer, Tabs, Textarea, Text } from "@geist-ui/core/dist"
import Image from "next/image" import Image from "next/image"
import styles from "./home.module.css" import styles from "./home.module.css"
// TODO:components/new-post/ move these styles // TODO:components/new-post/ move these styles
import markdownStyles from "app/(posts)/components/preview/preview.module.css"; import markdownStyles from "app/(posts)/components/preview/preview.module.css"
import Card from "./card" import Card from "./card"
const Home = ({ const Home = ({
introTitle, introTitle,
@ -20,19 +19,14 @@ const Home = ({
<div <div
style={{ display: "flex", flexDirection: "row", alignItems: "center" }} style={{ display: "flex", flexDirection: "row", alignItems: "center" }}
> >
<ShiftBy y={-2}> <Image
<Image src={"/assets/logo-optimized.svg"}
src={"/assets/logo-optimized.svg"} width={48}
width={48} height={48}
height={48} alt=""
alt="" priority
priority />
/> <h1 style={{ marginLeft: "var(--gap)" }}>{introTitle}</h1>
</ShiftBy>
<Spacer />
<Text style={{ display: "inline" }} h1>
{introTitle}
</Text>
</div> </div>
<Card> <Card>
<Tabs initialValue={"preview"} hideDivider leftSpace={0}> <Tabs initialValue={"preview"} hideDivider leftSpace={0}>
@ -45,13 +39,11 @@ const Home = ({
flexDirection: "column" flexDirection: "column"
}} }}
> >
<Textarea <textarea
readOnly readOnly
value={introContent} value={introContent}
width="100%"
// TODO: Textarea should grow to fill parent if height == 100% // TODO: Textarea should grow to fill parent if height == 100%
style={{ flex: 1, minHeight: 350 }} style={{ flex: 1, minHeight: 350 }}
resize="vertical"
className={styles.textarea} className={styles.textarea}
/> />
</div> </div>

View file

@ -3,19 +3,32 @@ import styles from "./input.module.css"
type Props = React.HTMLProps<HTMLInputElement> & { type Props = React.HTMLProps<HTMLInputElement> & {
label?: string label?: string
fontSize?: number | string width?: number | string
height?: number | string
} }
// eslint-disable-next-line react/display-name // eslint-disable-next-line react/display-name
const Input = React.forwardRef<HTMLInputElement, Props>( const Input = React.forwardRef<HTMLInputElement, Props>(
({ label, className, ...props }, ref) => { ({ label, className, width, height, ...props }, ref) => {
const classes = [styles.input, styles.withLabel, className].join(" ")
return ( return (
<div className={styles.wrapper}> <div
className={styles.wrapper}
style={{
width,
height
}}
>
{label && <label className={styles.label}>{label}</label>} {label && <label className={styles.label}>{label}</label>}
<input <input
ref={ref} ref={ref}
className={className ? `${styles.input} ${className}` : styles.input} className={classes}
{...props} {...props}
style={{
width,
height,
...(props.style || {})
}}
/> />
</div> </div>
) )

View file

@ -1,62 +1,68 @@
.wrapper { .wrapper {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
height: 100%; height: 100%;
font-size: 1rem; /* font-size: 1rem; */
height: 2.5rem;
} }
.input { .input {
height: 2.5rem; height: 2.5rem;
border-radius: var(--inline-radius); border-radius: var(--inline-radius);
background: var(--bg); background: var(--bg);
color: var(--fg); color: var(--fg);
border: 1px solid var(--light-gray); border: 1px solid var(--border);
padding: 0 var(--gap-half); padding: 0 var(--gap-half);
outline: none; outline: none;
transition: border-color var(--transition); transition: border-color var(--transition);
display: flex; display: flex;
justify-content: center; justify-content: center;
margin: 0; margin: 0;
width: 100%; width: 100%;
}
.withLabel {
/* if with label, then left border should be flat */
border-top-left-radius: 0;
border-bottom-left-radius: 0;
} }
.input::placeholder { .input::placeholder {
font-size: 1rem; /* font-size: 1rem; */
} }
.input:focus { .input:focus {
border-color: var(--input-border-focus); border-color: var(--light-gray);
} }
.label { .label {
display: inline-flex; display: inline-flex;
width: initial; width: initial;
height: 100%; height: 100%;
align-items: center; align-items: center;
pointer-events: none; pointer-events: none;
margin: 0; margin: 0;
padding: 0 var(--gap-half); padding: 0 var(--gap-half);
color: var(--fg); color: var(--darker-gray);
background-color: var(--light-gray); background-color: var(--lighter-gray);
border-top-left-radius: var(--radius); border-top-left-radius: var(--radius);
border-bottom-left-radius: var(--radius); border-bottom-left-radius: var(--radius);
border-top: 1px solid var(--input-border); border-top: 1px solid var(--input-border);
border-left: 1px solid var(--input-border); border-left: 1px solid var(--input-border);
border-bottom: 1px solid var(--input-border); border-bottom: 1px solid var(--input-border);
font-size: inherit; line-height: 1;
line-height: 1; white-space: nowrap;
white-space: nowrap;
} }
@media screen and (max-width: 768px) { @media screen and (max-width: 768px) {
.wrapper { .wrapper {
margin-bottom: var(--gap); margin-bottom: var(--gap);
} }
} }
.input:disabled { .input:disabled {
background-color: var(--lighter-gray); background-color: var(--lighter-gray);
color: var(--fg); color: var(--fg);
cursor: not-allowed; cursor: not-allowed;
} }

View file

@ -78,6 +78,10 @@
box-sizing: border-box; box-sizing: border-box;
} }
*:focus-visible {
outline: 1px solid var(--gray);
}
::selection { ::selection {
text-shadow: none; text-shadow: none;
background: var(--fg) !important; background: var(--fg) !important;
@ -111,7 +115,7 @@ input,
button, button,
textarea, textarea,
select { select {
border: var(--border); border: none;
font-size: 1rem; font-size: 1rem;
background: var(--bg); background: var(--bg);
} }
@ -175,3 +179,17 @@ main {
padding-top: 0 !important; padding-top: 0 !important;
} }
textarea {
resize: vertical;
border: 1px solid var(--border);
font-family: var(--font-sans);
padding: var(--gap-half);
border-radius: var(--radius);
}
textarea:focus {
outline: none;
border-color: var(--fg);
}

View file

@ -18,6 +18,7 @@
"@next-auth/prisma-adapter": "^1.0.5", "@next-auth/prisma-adapter": "^1.0.5",
"@prisma/client": "^4.6.1", "@prisma/client": "^4.6.1",
"@radix-ui/react-popover": "^1.0.2", "@radix-ui/react-popover": "^1.0.2",
"@radix-ui/react-tabs": "^1.0.1",
"@radix-ui/react-tooltip": "^1.0.2", "@radix-ui/react-tooltip": "^1.0.2",
"@wcj/markdown-to-html": "^2.1.2", "@wcj/markdown-to-html": "^2.1.2",
"bcrypt": "^5.1.0", "bcrypt": "^5.1.0",

View file

@ -7,6 +7,7 @@ specifiers:
'@next/bundle-analyzer': 12.1.6 '@next/bundle-analyzer': 12.1.6
'@prisma/client': ^4.6.1 '@prisma/client': ^4.6.1
'@radix-ui/react-popover': ^1.0.2 '@radix-ui/react-popover': ^1.0.2
'@radix-ui/react-tabs': ^1.0.1
'@radix-ui/react-tooltip': ^1.0.2 '@radix-ui/react-tooltip': ^1.0.2
'@types/bcrypt': ^5.0.0 '@types/bcrypt': ^5.0.0
'@types/node': 17.0.23 '@types/node': 17.0.23
@ -48,6 +49,7 @@ dependencies:
'@next-auth/prisma-adapter': 1.0.5_2pl3b2nwmjya7el2zbe6cwkney '@next-auth/prisma-adapter': 1.0.5_2pl3b2nwmjya7el2zbe6cwkney
'@prisma/client': 4.6.1_prisma@4.6.1 '@prisma/client': 4.6.1_prisma@4.6.1
'@radix-ui/react-popover': 1.0.2_jbvntnid6ohjelon6ccj5dhg2u '@radix-ui/react-popover': 1.0.2_jbvntnid6ohjelon6ccj5dhg2u
'@radix-ui/react-tabs': 1.0.1_biqbaboplfbrettd7655fr4n2y
'@radix-ui/react-tooltip': 1.0.2_jbvntnid6ohjelon6ccj5dhg2u '@radix-ui/react-tooltip': 1.0.2_jbvntnid6ohjelon6ccj5dhg2u
'@wcj/markdown-to-html': 2.1.2 '@wcj/markdown-to-html': 2.1.2
bcrypt: 5.1.0 bcrypt: 5.1.0
@ -1024,6 +1026,21 @@ packages:
react-dom: 18.2.0_react@18.2.0 react-dom: 18.2.0_react@18.2.0
dev: false dev: false
/@radix-ui/react-collection/1.0.1_biqbaboplfbrettd7655fr4n2y:
resolution: {integrity: sha512-uuiFbs+YCKjn3X1DTSx9G7BHApu4GHbi3kgiwsnFUbOKCrwejAJv4eE4Vc8C0Oaxt9T0aV4ox0WCOdx+39Xo+g==}
peerDependencies:
react: ^16.8 || ^17.0 || ^18.0
react-dom: ^16.8 || ^17.0 || ^18.0
dependencies:
'@babel/runtime': 7.20.1
'@radix-ui/react-compose-refs': 1.0.0_react@18.2.0
'@radix-ui/react-context': 1.0.0_react@18.2.0
'@radix-ui/react-primitive': 1.0.1_biqbaboplfbrettd7655fr4n2y
'@radix-ui/react-slot': 1.0.1_react@18.2.0
react: 18.2.0
react-dom: 18.2.0_react@18.2.0
dev: false
/@radix-ui/react-compose-refs/1.0.0_react@18.2.0: /@radix-ui/react-compose-refs/1.0.0_react@18.2.0:
resolution: {integrity: sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==} resolution: {integrity: sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==}
peerDependencies: peerDependencies:
@ -1042,6 +1059,15 @@ packages:
react: 18.2.0 react: 18.2.0
dev: false dev: false
/@radix-ui/react-direction/1.0.0_react@18.2.0:
resolution: {integrity: sha512-2HV05lGUgYcA6xgLQ4BKPDmtL+QbIZYH5fCOTAOOcJ5O0QbWS3i9lKaurLzliYUDhORI2Qr3pyjhJh44lKA3rQ==}
peerDependencies:
react: ^16.8 || ^17.0 || ^18.0
dependencies:
'@babel/runtime': 7.20.1
react: 18.2.0
dev: false
/@radix-ui/react-dismissable-layer/1.0.2_biqbaboplfbrettd7655fr4n2y: /@radix-ui/react-dismissable-layer/1.0.2_biqbaboplfbrettd7655fr4n2y:
resolution: {integrity: sha512-WjJzMrTWROozDqLB0uRWYvj4UuXsM/2L19EmQ3Au+IJWqwvwq9Bwd+P8ivo0Deg9JDPArR1I6MbWNi1CmXsskg==} resolution: {integrity: sha512-WjJzMrTWROozDqLB0uRWYvj4UuXsM/2L19EmQ3Au+IJWqwvwq9Bwd+P8ivo0Deg9JDPArR1I6MbWNi1CmXsskg==}
peerDependencies: peerDependencies:
@ -1178,6 +1204,26 @@ packages:
react-dom: 18.2.0_react@18.2.0 react-dom: 18.2.0_react@18.2.0
dev: false dev: false
/@radix-ui/react-roving-focus/1.0.1_biqbaboplfbrettd7655fr4n2y:
resolution: {integrity: sha512-TB76u5TIxKpqMpUAuYH2VqMhHYKa+4Vs1NHygo/llLvlffN6mLVsFhz0AnSFlSBAvTBYVHYAkHAyEt7x1gPJOA==}
peerDependencies:
react: ^16.8 || ^17.0 || ^18.0
react-dom: ^16.8 || ^17.0 || ^18.0
dependencies:
'@babel/runtime': 7.20.1
'@radix-ui/primitive': 1.0.0
'@radix-ui/react-collection': 1.0.1_biqbaboplfbrettd7655fr4n2y
'@radix-ui/react-compose-refs': 1.0.0_react@18.2.0
'@radix-ui/react-context': 1.0.0_react@18.2.0
'@radix-ui/react-direction': 1.0.0_react@18.2.0
'@radix-ui/react-id': 1.0.0_react@18.2.0
'@radix-ui/react-primitive': 1.0.1_biqbaboplfbrettd7655fr4n2y
'@radix-ui/react-use-callback-ref': 1.0.0_react@18.2.0
'@radix-ui/react-use-controllable-state': 1.0.0_react@18.2.0
react: 18.2.0
react-dom: 18.2.0_react@18.2.0
dev: false
/@radix-ui/react-slot/1.0.1_react@18.2.0: /@radix-ui/react-slot/1.0.1_react@18.2.0:
resolution: {integrity: sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw==} resolution: {integrity: sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw==}
peerDependencies: peerDependencies:
@ -1188,6 +1234,25 @@ packages:
react: 18.2.0 react: 18.2.0
dev: false dev: false
/@radix-ui/react-tabs/1.0.1_biqbaboplfbrettd7655fr4n2y:
resolution: {integrity: sha512-mVNEwHwgjy2G9F7b39f9VY+jF0QUZykTm0Sdv+Uz6KC4KOEIa4HLDiHU8MeEZluRtZE3aqGYDhl93O7QbJDwhg==}
peerDependencies:
react: ^16.8 || ^17.0 || ^18.0
react-dom: ^16.8 || ^17.0 || ^18.0
dependencies:
'@babel/runtime': 7.20.1
'@radix-ui/primitive': 1.0.0
'@radix-ui/react-context': 1.0.0_react@18.2.0
'@radix-ui/react-direction': 1.0.0_react@18.2.0
'@radix-ui/react-id': 1.0.0_react@18.2.0
'@radix-ui/react-presence': 1.0.0_biqbaboplfbrettd7655fr4n2y
'@radix-ui/react-primitive': 1.0.1_biqbaboplfbrettd7655fr4n2y
'@radix-ui/react-roving-focus': 1.0.1_biqbaboplfbrettd7655fr4n2y
'@radix-ui/react-use-controllable-state': 1.0.0_react@18.2.0
react: 18.2.0
react-dom: 18.2.0_react@18.2.0
dev: false
/@radix-ui/react-tooltip/1.0.2_jbvntnid6ohjelon6ccj5dhg2u: /@radix-ui/react-tooltip/1.0.2_jbvntnid6ohjelon6ccj5dhg2u:
resolution: {integrity: sha512-11gUlok2rv5mu+KBtxniOKKNKjqC/uTbgFHWoQdbF46vMV+zjDaBvCtVDK9+MTddlpmlisGPGvvojX7Qm0yr+g==} resolution: {integrity: sha512-11gUlok2rv5mu+KBtxniOKKNKjqC/uTbgFHWoQdbF46vMV+zjDaBvCtVDK9+MTddlpmlisGPGvvojX7Qm0yr+g==}
peerDependencies: peerDependencies: