From b232410d83e1cf10c0678578aae1f16f4a6cf205 Mon Sep 17 00:00:00 2001 From: Khairul Hidayat Date: Thu, 22 Feb 2024 19:49:13 +0000 Subject: [PATCH] fix: responsive fix --- components/ui/resizable.tsx | 80 +++++++------------ hooks/useBreakpoint.ts | 57 +++++++++++++ hooks/useBreakpointValue.ts | 55 +++++++++++++ package.json | 1 - pages/index/+Page.tsx | 1 + pages/project/@slug/+Page.tsx | 24 +++--- pages/project/@slug/components/editor.tsx | 33 ++++---- .../project/@slug/components/file-viewer.tsx | 26 ++---- pnpm-lock.yaml | 7 -- renderer/client-only.tsx | 12 ++- server/api/preview/postcss.ts | 9 +-- server/db/seed.ts | 32 ++++---- server/lib/unpack-project.ts | 1 + 13 files changed, 209 insertions(+), 129 deletions(-) create mode 100644 hooks/useBreakpoint.ts create mode 100644 hooks/useBreakpointValue.ts diff --git a/components/ui/resizable.tsx b/components/ui/resizable.tsx index b4b1fb6..d5d5b31 100644 --- a/components/ui/resizable.tsx +++ b/components/ui/resizable.tsx @@ -1,73 +1,51 @@ import { GripVertical } from "lucide-react"; -import { createContext, forwardRef, useContext } from "react"; +import { forwardRef } from "react"; import * as ResizablePrimitive from "react-resizable-panels"; -import cookieJs from "cookiejs"; import { cn } from "~/lib/utils"; -import { usePageContext } from "~/renderer/context"; -import { useDebounce } from "~/hooks/useDebounce"; +import { + BreakpointValues, + useBreakpointValue, +} from "~/hooks/useBreakpointValue"; -const ResizableContext = createContext<{ initialSize: number[] }>(null!); +type Direction = "horizontal" | "vertical"; + +type ResizablePanelGroupProps = Omit< + React.ComponentProps, + "direction" +> & { + direction: Direction | BreakpointValues; +}; const ResizablePanelGroup = ({ className, - autoSaveId, direction, ...props -}: React.ComponentProps) => { - const { cookies } = usePageContext(); - const [debouncePersistLayout] = useDebounce((sizes: number[]) => { - if (autoSaveId && typeof window !== "undefined") { - cookieJs.set(panelKey, JSON.stringify(sizes)); - } - }, 500); - - const panelKey = ["panel", direction, autoSaveId].join(":"); - let initialSize: number[] = []; - - if (autoSaveId && cookies && cookies[panelKey]) { - initialSize = JSON.parse(cookies[panelKey]) || []; - } - - const onLayout = (sizes: number[]) => { - if (props.onLayout) { - props.onLayout(sizes); - } - debouncePersistLayout(sizes); - }; +}: ResizablePanelGroupProps) => { + const directionValue = useBreakpointValue(direction); return ( - - - + ); }; -type ResizablePanelProps = React.ComponentProps< - typeof ResizablePrimitive.Panel +type ResizablePanelProps = Omit< + React.ComponentProps, + "defaultSize" > & { - panelId: number; + defaultSize: number | BreakpointValues; }; const ResizablePanel = forwardRef((props: ResizablePanelProps, ref: any) => { - const { panelId, defaultSize, ...restProps } = props; - const ctx = useContext(ResizableContext); - let initialSize = defaultSize; - - if (panelId != null) { - const size = ctx?.initialSize[panelId]; - if (size != null) { - initialSize = size; - } - } + const { defaultSize, ...restProps } = props; + const initialSize = useBreakpointValue(defaultSize); return ( void) => { + const prevRef = useRef(0); + const [curBreakpoint, setBreakpoint] = useState( + getScreenBreakpoint() + ); + + const onResize = useCallback(() => { + const breakpointIdx = getScreenBreakpoint(); + + if (breakpointIdx >= 0 && prevRef.current !== breakpointIdx) { + if (onChange) { + onChange(breakpointIdx); + } else { + setBreakpoint(breakpointIdx); + } + prevRef.current = breakpointIdx; + } + }, [onChange]); + + useEffect(() => { + window.addEventListener("resize", onResize); + onResize(); + + return () => { + window.removeEventListener("resize", onResize); + }; + }, [onResize]); + + const breakpoint = breakpointKeys[curBreakpoint]; + + return [curBreakpoint, breakpoint] as [number, Breakpoint]; +}; + +export function getScreenBreakpoint() { + const width = typeof window !== "undefined" ? window.innerWidth : 0; + let breakpointIdx = breakpointValues.findIndex((i) => width <= i); + if (breakpointIdx < 0) { + breakpointIdx = breakpointKeys.length - 1; + } + + return breakpointIdx; +} diff --git a/hooks/useBreakpointValue.ts b/hooks/useBreakpointValue.ts new file mode 100644 index 0000000..1e1e071 --- /dev/null +++ b/hooks/useBreakpointValue.ts @@ -0,0 +1,55 @@ +import { useState } from "react"; +import { + Breakpoint, + breakpointKeys, + getScreenBreakpoint, + useBreakpoint, +} from "./useBreakpoint"; + +export type BreakpointValues = Partial>; + +export const useBreakpointValue = (values: T | BreakpointValues) => { + const [value, setValue] = useState( + typeof values === "object" + ? getValueByBreakpoint( + values as BreakpointValues, + getScreenBreakpoint() + ) + : values + ); + + useBreakpoint((breakpoint) => { + if (typeof values !== "object") { + return; + } + + const newValue = getValueByBreakpoint( + values as BreakpointValues, + breakpoint + ); + + if (newValue !== value) { + setValue(newValue); + } + }); + + return value as T; +}; + +export function getValueByBreakpoint( + values: BreakpointValues, + breakpoint: number +) { + const valueEntries = Object.entries(values as never); + + let resIdx = valueEntries.findIndex(([key]) => { + const bpIdx = breakpointKeys.indexOf(key); + return breakpoint <= bpIdx; + }); + if (resIdx < 0) { + resIdx = valueEntries.length - 1; + } + + const value = valueEntries[resIdx]?.[1] as T; + return value; +} diff --git a/package.json b/package.json index 1da0611..b0436ed 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,6 @@ "clsx": "^2.1.0", "console-feed": "^3.5.0", "cookie-parser": "^1.4.6", - "cookiejs": "^2.1.3", "copy-to-clipboard": "^3.3.3", "cssnano": "^6.0.3", "drizzle-orm": "^0.29.3", diff --git a/pages/index/+Page.tsx b/pages/index/+Page.tsx index d335975..ccc6579 100644 --- a/pages/index/+Page.tsx +++ b/pages/index/+Page.tsx @@ -4,6 +4,7 @@ import Link from "~/renderer/link"; const HomePage = () => { const { posts } = useData(); + if (!posts?.length) { return

No posts.

; } diff --git a/pages/project/@slug/+Page.tsx b/pages/project/@slug/+Page.tsx index d546433..8ef4d63 100644 --- a/pages/project/@slug/+Page.tsx +++ b/pages/project/@slug/+Page.tsx @@ -4,15 +4,15 @@ import { ResizablePanelGroup, } from "~/components/ui/resizable"; import WebPreview from "./components/web-preview"; -import { usePortrait } from "~/hooks/usePortrait"; import Editor from "./components/editor"; import ProjectContext from "./context/project"; import { cn } from "~/lib/utils"; import { useParams, useSearchParams } from "~/renderer/hooks"; import { BASE_URL } from "~/lib/consts"; +import { withClientOnly } from "~/renderer/client-only"; +import Spinner from "~/components/ui/spinner"; const ViewProjectPage = () => { - const isPortrait = usePortrait(); const searchParams = useSearchParams(); const params = useParams(); const isCompact = @@ -24,15 +24,14 @@ const ViewProjectPage = () => { @@ -45,8 +44,7 @@ const ViewProjectPage = () => { } /> { ); }; -export default ViewProjectPage; +const LoadingPage = () => { + return ( +
+ +
+ ); +}; + +export default withClientOnly(ViewProjectPage, LoadingPage); diff --git a/pages/project/@slug/components/editor.tsx b/pages/project/@slug/components/editor.tsx index 47d8d77..d04212f 100644 --- a/pages/project/@slug/components/editor.tsx +++ b/pages/project/@slug/components/editor.tsx @@ -20,12 +20,14 @@ import { FaCompress, FaCompressArrowsAlt } from "react-icons/fa"; import ConsoleLogger from "./console-logger"; import { useData } from "~/renderer/hooks"; import { Data } from "../+data"; +import { useBreakpoint } from "~/hooks/useBreakpoint"; const Editor = () => { const { pinnedFiles } = useData(); const trpcUtils = trpc.useUtils(); const project = useProjectContext(); const sidebarPanel = useRef(null); + const [breakpoint] = useBreakpoint(); const [sidebarExpanded, setSidebarExpanded] = useState(false); const [curTabIdx, setCurTabIdx] = useState(0); @@ -168,8 +170,7 @@ const Editor = () => { { - + - + { /> - - - - + {breakpoint >= 2 ? ( + <> + + + + + + + ) : null} diff --git a/pages/project/@slug/components/file-viewer.tsx b/pages/project/@slug/components/file-viewer.tsx index 9843224..b18c755 100644 --- a/pages/project/@slug/components/file-viewer.tsx +++ b/pages/project/@slug/components/file-viewer.tsx @@ -1,12 +1,10 @@ "use client"; import { getFileExt } from "~/lib/utils"; -import React from "react"; import CodeEditor from "../../../../components/ui/code-editor"; import trpc from "~/lib/trpc"; import { useData } from "~/renderer/hooks"; import { Data } from "../+data"; -import ClientOnly from "~/renderer/client-only"; import Spinner from "~/components/ui/spinner"; type Props = { @@ -42,14 +40,12 @@ const FileViewer = ({ id, onFileContentChange }: Props) => { const ext = getFileExt(filename); return ( - }> - updateFileContent.mutate({ id, content: val })} - /> - + updateFileContent.mutate({ id, content: val })} + /> ); } @@ -64,14 +60,4 @@ const LoadingLayout = () => { ); }; -const SSRCodeEditor = ({ value }: { value?: string | null }) => { - return ( -