Skip to content

Commit

Permalink
feat: resizable panels
Browse files Browse the repository at this point in the history
  • Loading branch information
arshad-yaseen committed Dec 23, 2023
1 parent ad0ea38 commit 6104972
Show file tree
Hide file tree
Showing 22 changed files with 200 additions and 173 deletions.
5 changes: 3 additions & 2 deletions app/(dashboard)/dashboard/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { redirect } from "next/navigation"
import { getTitle } from "@/utils/editor"
import { getMarkdownTitle } from "@/utils/editor"

import { authOptions } from "@/lib/auth"
import { db } from "@/lib/db"
Expand Down Expand Up @@ -51,7 +51,8 @@ async function Dashboard() {
key={post.id}
post={post}
title={
getTitle(post.postCodes[0]?.content) || "Project Title"
getMarkdownTitle(post.postCodes[0]?.content) ||
"Project Title"
}
/>
))}
Expand Down
59 changes: 39 additions & 20 deletions app/(editor)/edit/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ import { useAtom, useAtomValue } from "jotai"

import { editorCode } from "types"
import { defaultEditorContent } from "@/config/editor"
import {
ResizableHandle,
ResizablePanel,
ResizablePanelGroup,
} from "@/components/ui/resizable"
import { withDesktopOnly } from "@/components/HOCs/with-desktop-only"
import AIToolsSection from "@/components/editor/ai-tools-section"
import EditorSection from "@/components/editor/editor-section"
import PreviewSection from "@/components/editor/preview-section"
Expand All @@ -28,7 +34,7 @@ type Markdown = {
}
}

export default function page({ params }: { params: { id: string } }) {
function EditorPage({ params }: { params: { id: string } }) {
const [editorCodes, setEditorCodes] = useAtom(editorCodesState)
const editorActiveSection = useAtomValue(editorActiveSectionState)
const [markdownCode, setMarkdownCode] = useState("")
Expand Down Expand Up @@ -71,6 +77,20 @@ export default function page({ params }: { params: { id: string } }) {
}
}

const onEditorCodeChange = (code: string) => {
setEditorCodes((prev) => {
return prev.map((prevCode: prevCodeType) => {
if (prevCode.section_id === editorActiveSection) {
return {
...prevCode,
content: code,
}
}
return prevCode
})
})
}

useEffect(() => {
editorCodes
.filter((code: editorCode) => {
Expand All @@ -93,25 +113,24 @@ export default function page({ params }: { params: { id: string } }) {
className="flex h-[92vh] w-full flex-col lg:flex-row"
>
<AIToolsSection />
<EditorSection
markdown={markdownCode}
onCodeChange={(code) => {
setEditorCodes((prev) => {
return prev.map((prevCode: prevCodeType) => {
if (prevCode.section_id === editorActiveSection) {
return {
...prevCode,
content: code,
}
}
return prevCode
})
})
}}
/>
<PreviewSection
code={editorCodes.map((code: editorCode) => code.content).join("\n\n")}
/>
<ResizablePanelGroup direction="horizontal">
<ResizablePanel>
<EditorSection
markdown={markdownCode}
onCodeChange={onEditorCodeChange}
/>
</ResizablePanel>
<ResizableHandle withHandle />
<ResizablePanel minSize={30} maxSize={70}>
<PreviewSection
code={editorCodes
.map((code: editorCode) => code.content)
.join("\n\n")}
/>
</ResizablePanel>
</ResizablePanelGroup>
</div>
)
}

export default withDesktopOnly(EditorPage)
23 changes: 23 additions & 0 deletions components/HOCs/with-desktop-only.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Fragment } from "react"

export const withDesktopOnly = (Component: React.FC<any>) => {
const WithDesktopOnly = (props: any) => {
return (
<Fragment>
<div className="hidden md:block">
<Component {...props} />
</div>
<div className="flex flex-col items-center px-6 py-32 md:hidden">
<p className="mb-4 text-center text-2xl">🙂</p>
<p className="text-center text-muted-foreground">
This page is not available on mobile due to the editor&apos;s
complexity and screen size limitations. Please access it on a
desktop.
</p>
</div>
</Fragment>
)
}

return WithDesktopOnly
}
1 change: 1 addition & 0 deletions components/dashboard/post-operations.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ export function PostOperations({ post }: PostOperationsProps) {
<AlertDialogAction asChild>
<Button
disabled={isDeleteLoading}
variant={"destructive"}
onClick={async (event: any) => {
event.preventDefault()
setIsDeleteLoading(true)
Expand Down
4 changes: 2 additions & 2 deletions components/editor/ai-tools-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,15 @@ function AIToolsSection() {
<div
className={`absolute bottom-0 ${
isToolsPanelCollapsed ? "ml-6 w-[4%] -rotate-180" : "w-[18%] px-6"
} left-0 z-10 flex h-20 items-center justify-end`}
} left-0 z-10 flex h-20 items-center justify-end`}
>
<Button
onClick={() => {
setIsToolsPanelCollapsed(!isToolsPanelCollapsed)
setIsToolsPanelCollapsedStore(String(!isToolsPanelCollapsed)) // Setting to localStorage
}}
variant="outline"
className="hidden h-8 w-8 items-center justify-center px-0 lg:flex"
className="hidden h-8 w-8 items-center justify-center bg-background px-0 lg:flex"
>
<ChevronLeftIcon className="h-4 w-4" />
</Button>
Expand Down
2 changes: 1 addition & 1 deletion components/editor/assets.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export default function Assets() {
/>
<SearchIcon className="absolute left-3.5 h-3.5 w-3.5 text-slate-500" />
</div>
<div className="no-scrollbar w-full space-y-4 overflow-scroll py-4">
<div className="no-scrollbar w-full space-y-4 overflow-scroll px-px py-4">
{assets
.filter((asset) =>
searchValue.length === 0 || asset.keywords
Expand Down
10 changes: 4 additions & 6 deletions components/editor/editor-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { editorSelectedContentAtom, monacoInstanceState } from "@/atoms/editor"
import { monacoInstance } from "@/types"
import { useAtom } from "jotai"

import SectionLoading from "@/components/editor/section-loading"

function EditorSection({
markdown,
onCodeChange,
Expand Down Expand Up @@ -36,7 +38,7 @@ function EditorSection({
const loading = markdown === ""

return (
<div className="relative flex h-[50%] w-full flex-1 flex-col items-center lg:h-full lg:w-[46%]">
<div className="relative flex h-[50%] w-full flex-1 flex-col items-center lg:h-full">
<Editor
language="markdown"
value={markdown}
Expand Down Expand Up @@ -71,11 +73,7 @@ function EditorSection({
}}
className="px-14"
/>
{loading && (
<div className="absolute left-0 top-0 flex h-full w-full items-center justify-center">
Loading...
</div>
)}
{loading && <SectionLoading />}
</div>
)
}
Expand Down
79 changes: 4 additions & 75 deletions components/editor/preview-section.tsx
Original file line number Diff line number Diff line change
@@ -1,85 +1,14 @@
import ParseMarkdown from "./parse-markdown"
import "@/styles/mdx.css"
import { useEffect, useRef } from "react"
import { previewSectionRefAtom } from "@/atoms/editor"
import { useAtom } from "jotai"

import { useLocalStorage } from "@/lib/hooks/use-localstorage"
import SectionLoading from "@/components/editor/section-loading"

const BORDER_SIZE = 4
import ParseMarkdown from "./parse-markdown"

function PreviewSection({ code }: { code: string }) {
const [, setPreviewSectionRefState] = useAtom(previewSectionRefAtom)
const mPos = useRef<number | null>(null)
const previewSectionRef = useRef<HTMLDivElement>(null)
const [previewSectionWidth, setPreviewSectionWidth] = useLocalStorage(
"preview-section-width",
"0px"
)

// Set preview section ref
useEffect(() => {
setPreviewSectionRefState(previewSectionRef)
}, [previewSectionRef])

// Set preview section width
useEffect(() => {
const panel = previewSectionRef?.current
if (panel) {
panel.style.width = previewSectionWidth
}
}, [previewSectionWidth])

// Resize preview section
useEffect(() => {
const panel = previewSectionRef?.current

const resize = (e: MouseEvent) => {
if (mPos.current !== null && panel) {
const dx = mPos.current - e.x
mPos.current = e.x
panel.style.width = `${parseInt(getComputedStyle(panel).width) + dx}px`
}
}

const handleMouseDown = (e: MouseEvent) => {
e.preventDefault()
if (e.offsetX < BORDER_SIZE) {
mPos.current = e.x
document.addEventListener("mousemove", resize)
}
}

const handleMouseUp = () => {
mPos.current = null
setPreviewSectionWidth(getComputedStyle(panel!).width)
document.removeEventListener("mousemove", resize)
}

panel?.addEventListener("mousedown", handleMouseDown)
document.addEventListener("mouseup", handleMouseUp)

return () => {
panel?.removeEventListener("mousedown", handleMouseDown)
document.removeEventListener("mouseup", handleMouseUp)
document.removeEventListener("mousemove", resize)
}
}, [])

const loading = code === ""

return (
<div
ref={previewSectionRef}
className="preview-section relative flex h-full w-full flex-col overflow-scroll border-l border-t px-12 py-8 lg:w-[36%] lg:min-w-[25%] lg:border-t-0"
>
<div className="absolute left-0 top-0 h-full w-1 cursor-ew-resize transition-colors duration-300 hover:bg-foreground"></div>
{loading && (
<div className="absolute left-0 top-0 flex h-full w-full items-center justify-center">
Loading...
</div>
)}

<div className="relative flex h-full w-full flex-col overflow-scroll border-t px-12 py-8 lg:border-t-0">
{loading && <SectionLoading />}
<ParseMarkdown code={code} className="pb-[80vh]" />
</div>
)
Expand Down
11 changes: 11 additions & 0 deletions components/editor/section-loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from "react"

const SectionLoading = () => {
return (
<div className="absolute left-0 top-0 flex h-full w-full items-center justify-center">
Loading...
</div>
)
}

export default SectionLoading
49 changes: 16 additions & 33 deletions components/mode-toggle.tsx
Original file line number Diff line number Diff line change
@@ -1,48 +1,31 @@
import React from "react"
import React, { Fragment } from "react"
import { MoonIcon, SunIcon } from "lucide-react"
import { useTheme } from "next-themes"

import { Button } from "./ui/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "./ui/dropdown-menu"

function ModeToggle() {
const { setTheme } = useTheme()
const { setTheme, resolvedTheme } = useTheme()

const toggleTheme = (value: string) => {
setTheme(value)
}

return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="sm"
className="flex h-8 w-8 items-center justify-center px-0 "
>
<SunIcon className="h-4 w-4 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<MoonIcon className="absolute h-4 w-4 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span className="sr-only">Toggle theme</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => toggleTheme("light")}>
<span className="flex">
<SunIcon className="mr-2 h-4 w-4" /> Light
</span>
</DropdownMenuItem>
<DropdownMenuItem onClick={() => toggleTheme("dark")}>
<span className="flex">
<MoonIcon className="mr-2 h-4 w-4" /> Dark
</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<Fragment>
<Button
variant="ghost"
size="sm"
className="flex h-8 w-8 items-center justify-center px-0"
onClick={() => {
toggleTheme(resolvedTheme === "dark" ? "light" : "dark")
}}
>
<SunIcon className="h-4 w-4 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<MoonIcon className="absolute h-4 w-4 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span className="sr-only">Toggle theme</span>
</Button>
</Fragment>
)
}
export default ModeToggle
2 changes: 1 addition & 1 deletion components/ui/alert-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const AlertDialogContent = React.forwardRef<
<AlertDialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg md:w-full",
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 bg-background p-6 shadow-dialog duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg md:w-full",
className
)}
{...props}
Expand Down
6 changes: 3 additions & 3 deletions components/ui/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ const buttonVariants = cva(
variants: {
variant: {
default:
"bg-primary text-primary-foreground shadow hover:bg-primary/90",
"bg-primary text-primary-foreground shadow-border-medium hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
"bg-destructive text-destructive-foreground shadow-border-medium hover:bg-destructive/90",
outline:
"shadow-border-small bg-transparent hover:bg-accent/50 hover:text-accent-foreground",
"shadow-border-small bg-transparent hover:bg-accent/50 hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
Expand Down
2 changes: 1 addition & 1 deletion components/ui/dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ const DialogContent = React.forwardRef<
<DialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg md:w-full",
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 bg-background p-6 shadow-dialog duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg md:w-full",
className
)}
{...props}
Expand Down
Loading

1 comment on commit 6104972

@vercel
Copy link

@vercel vercel bot commented on 6104972 Dec 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.