From 58570b16f022bd016295fcc1c807e418fa36f371 Mon Sep 17 00:00:00 2001 From: Jacco Flenter Date: Mon, 26 Aug 2024 16:29:08 +0200 Subject: [PATCH] Remove react-resizable (#182) * Use react-resizable-panel on RequestorPage And also: * tweak the mobile layout * make the AiTestGenerationPanel resizable * Set autoSaveId so the panel layout is saved in localstorage * Fix build * Cleanup * Also migrate routes panel to react-resizable-panel Please enter the commit message for your changes. Lines starting * Cleanup * Remove react-resizable * Remove commented out code * Fix build * Remove import of react-resizable css * Increase minimum size for RequestPanel * Feedback on PR * Remove line * Add missing dependency for effect --- frontend/package.json | 1 - .../components.tsx} | 0 frontend/src/components/ui/resizable/index.ts | 6 + .../ui/resizable/usePanelConstraints.ts | 94 ++++++ frontend/src/hooks/useIsSmScreen.ts | 2 +- .../RequestPanel/RequestPanel.tsx | 51 +-- .../src/pages/RequestorPage/RequestorPage.tsx | 315 +++++++++++------- .../src/pages/RequestorPage/Resizable.tsx | 17 - .../src/pages/RequestorPage/ResponsePanel.tsx | 6 +- .../ResponsePanel/ResponsePanel.tsx | 2 - .../src/pages/RequestorPage/RoutesPanel.tsx | 161 ++++----- frontend/src/pages/RequestorPage/Tabs.tsx | 2 +- .../ai/AiTestGenerationPanel.tsx | 2 +- frontend/src/pages/RequestorPage/hooks.ts | 26 -- pnpm-lock.yaml | 35 -- 15 files changed, 382 insertions(+), 338 deletions(-) rename frontend/src/components/ui/{resizable.tsx => resizable/components.tsx} (100%) create mode 100644 frontend/src/components/ui/resizable/index.ts create mode 100644 frontend/src/components/ui/resizable/usePanelConstraints.ts delete mode 100644 frontend/src/pages/RequestorPage/Resizable.tsx delete mode 100644 frontend/src/pages/RequestorPage/hooks.ts diff --git a/frontend/package.json b/frontend/package.json index 9335d37e4..2ba451ce0 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -57,7 +57,6 @@ "react-highlight-words": "^0.20.0", "react-hook-form": "^7.52.0", "react-hotkeys-hook": "^4.5.0", - "react-resizable": "^3.0.5", "react-resizable-panels": "^2.0.23", "react-router-dom": "^6.23.1", "react-use-websocket": "^4.8.1", diff --git a/frontend/src/components/ui/resizable.tsx b/frontend/src/components/ui/resizable/components.tsx similarity index 100% rename from frontend/src/components/ui/resizable.tsx rename to frontend/src/components/ui/resizable/components.tsx diff --git a/frontend/src/components/ui/resizable/index.ts b/frontend/src/components/ui/resizable/index.ts new file mode 100644 index 000000000..d22fed126 --- /dev/null +++ b/frontend/src/components/ui/resizable/index.ts @@ -0,0 +1,6 @@ +export { + ResizableHandle, + ResizablePanel, + ResizablePanelGroup, +} from "./components"; +export { usePanelConstraints } from "./usePanelConstraints"; diff --git a/frontend/src/components/ui/resizable/usePanelConstraints.ts b/frontend/src/components/ui/resizable/usePanelConstraints.ts new file mode 100644 index 000000000..bd2e9669a --- /dev/null +++ b/frontend/src/components/ui/resizable/usePanelConstraints.ts @@ -0,0 +1,94 @@ +import { useHandler } from "@fiberplane/hooks"; +import { useLayoutEffect, useState } from "react"; +import { getPanelGroupElement } from "react-resizable-panels"; + +type PanelConstraintOptions = { + groupId: string; + minPixelSize?: number; + maxPixelSize?: number; + /** + * The initial estimated size of the panel in pixels. + */ + initialGroupSize: number; + /** + * The minimum size of the panel in pixels. If the panel is + * not big enough, there won't be any min/max value applied. + */ + minimalGroupSize?: number; +}; + +type SizeConstraint = { + /* Size in percentages. If set to undefined, the panel group wasn't big engouh */ + minSize?: number; + maxSize?: number; +}; + +/** + * This function determines the min/max size of a panel group based on the + * initial size of the panel group and the min/max pixel size for a panel + * + * Note: it is assumed the panel is a horizontal panel group + */ +export function usePanelConstraints( + options: PanelConstraintOptions, +): SizeConstraint { + const { + groupId, + initialGroupSize, + maxPixelSize, + minPixelSize, + minimalGroupSize, + } = options; + + const getConstraint = useHandler((size: number) => { + if (minimalGroupSize && size < minimalGroupSize) { + return {}; + } + const minSize = minPixelSize ? (minPixelSize / size) * 100 : undefined; + const maxSize = maxPixelSize ? (maxPixelSize / size) * 100 : maxPixelSize; + + return { + minSize, + maxSize, + }; + }); + + const [current, setCurrent] = useState(() => getConstraint(initialGroupSize)); + + const updateCurrent = useHandler((size: number) => { + const result = getConstraint(size); + if ( + result.minSize !== current.minSize || + result.maxSize !== current.maxSize + ) { + setCurrent(result); + } + }); + + useLayoutEffect(() => { + const group = getPanelGroupElement(groupId); + if (!group) { + return; + } + + if (!group) { + console.warn("Unable to find the group"); + return; + } + + const observer = new ResizeObserver((entries) => { + const width = entries[0].contentRect.width; + updateCurrent(width); + }); + observer.observe(group); + + // Trigger the initial update + updateCurrent(group.offsetWidth); + + return () => { + observer.disconnect(); + }; + }, [groupId, updateCurrent]); + + return current; +} diff --git a/frontend/src/hooks/useIsSmScreen.ts b/frontend/src/hooks/useIsSmScreen.ts index f7e0de393..e1473a614 100644 --- a/frontend/src/hooks/useIsSmScreen.ts +++ b/frontend/src/hooks/useIsSmScreen.ts @@ -6,5 +6,5 @@ import { useMedia } from "@fiberplane/hooks"; * IMPROVE - Use css variable instead of hardcoded 640px? */ export function useIsSmScreen() { - return useMedia("(min-width: 640px)"); + return useMedia("(max-width: 640px)"); } diff --git a/frontend/src/pages/RequestorPage/RequestPanel/RequestPanel.tsx b/frontend/src/pages/RequestorPage/RequestPanel/RequestPanel.tsx index c37fb6654..9d2fe9208 100644 --- a/frontend/src/pages/RequestorPage/RequestPanel/RequestPanel.tsx +++ b/frontend/src/pages/RequestorPage/RequestPanel/RequestPanel.tsx @@ -1,18 +1,14 @@ import { Button } from "@/components/ui/button"; import { Tabs } from "@/components/ui/tabs"; import { useToast } from "@/components/ui/use-toast"; -import { useIsSmScreen } from "@/hooks"; import { cn } from "@/utils"; import { EraserIcon, InfoCircledIcon } from "@radix-ui/react-icons"; import { Dispatch, SetStateAction } from "react"; -import { Resizable } from "react-resizable"; import { CodeMirrorJsonEditor } from "../Editors"; import { FormDataForm } from "../FormDataForm"; import { KeyValueForm, KeyValueParameter } from "../KeyValueForm"; -import { ResizableHandle } from "../Resizable"; import { CustomTabTrigger, CustomTabsContent, CustomTabsList } from "../Tabs"; import { AiTestingPersona } from "../ai"; -import { useResizableWidth, useStyleWidth } from "../hooks"; import type { RequestBodyType, RequestorBody, @@ -62,41 +58,6 @@ type RequestPanelProps = { }; export function RequestPanel(props: RequestPanelProps) { - const shouldBeResizable = useIsSmScreen(); - - return shouldBeResizable ? ( - - ) : ( - - ); -} - -function ResizableRequestMeta(props: RequestPanelProps) { - // TODO - I tried setting the width based off of result of `useMedia` but I think something is wrong with that fiberplane hook, - // since it was matching (min-width: 1024px) even on small screens, and setting the panel too wide by default for smaller devices... - const { width, handleResize } = useResizableWidth(320); - const styleWidth = useStyleWidth(width); - return ( - ( - // Render a custom handle component, so we can indicate "resizability" - // along the entire right side of the container - - )} - > -
- -
-
- ); -} - -function RequestMeta(props: RequestPanelProps) { const { handleRequestBodyTypeChange, activeRequestsPanelTab, @@ -137,9 +98,9 @@ function RequestMeta(props: RequestPanelProps) { value={activeRequestsPanelTab} onValueChange={setActiveRequestsPanelTab} className={cn( - "min-w-[200px] border-none sm:border-r", + "border-none sm:border-r", "grid grid-rows-[auto_1fr]", - "overflow-hidden h-full max-h-full", + "lg:overflow-hidden lg:h-full max-h-full", )} > @@ -375,10 +336,14 @@ export function PanelSectionHeader({ {children} {handleClearData && ( - + tabIndex={0} + > + + )} ); diff --git a/frontend/src/pages/RequestorPage/RequestorPage.tsx b/frontend/src/pages/RequestorPage/RequestorPage.tsx index 3952f520a..93f6e9458 100644 --- a/frontend/src/pages/RequestorPage/RequestorPage.tsx +++ b/frontend/src/pages/RequestorPage/RequestorPage.tsx @@ -1,7 +1,14 @@ // We need some special CSS for grid layout that tailwind cannot handle import "./styles.css"; +import { + ResizableHandle, + ResizablePanel, + ResizablePanelGroup, + usePanelConstraints, +} from "@/components/ui/resizable"; import { useToast } from "@/components/ui/use-toast"; +import { useIsLgScreen, useIsSmScreen } from "@/hooks"; import { cn } from "@/utils"; import { useCallback, useMemo, useRef, useState } from "react"; import { useHotkeys } from "react-hotkeys-hook"; @@ -20,6 +27,13 @@ import { useRequestorHistory } from "./useRequestorHistory"; import { useRequestorSubmitHandler } from "./useRequestorSubmitHandler"; import { sortRequestornatorsDescending } from "./utils"; +/** + * Estimate the size of the main section based on the window width + */ +function getMainSectionWidth() { + return window.innerWidth - 400; +} + export const RequestorPage = () => { const { toast } = useToast(); @@ -209,146 +223,217 @@ export const RequestorPage = () => { [], ); + const width = getMainSectionWidth(); + const isSmallScreen = useIsSmScreen(); + const isLgScreen = useIsLgScreen(); + + const { minSize, maxSize } = usePanelConstraints({ + groupId: "requestor-page-main", + initialGroupSize: width + 320, + minPixelSize: 250, + minimalGroupSize: 944, + }); + + const { minSize: requestPanelMinSize, maxSize: requestPanelMaxSize } = + usePanelConstraints({ + // Change the groupId to `""` on small screens because we're not rendering + // the resizable panel group + groupId: isSmallScreen ? "" : "requestor-page-main-panel", + initialGroupSize: width, + minPixelSize: 300, + }); + + const requestContent = ( + + ); + + const responseContent = ( + + ); + return (
-
- -
-
- -
- - -
- - - - {isAiTestGenerationPanelOpen && ( - + + + + - )} -
-
+ + )} + +
+ + {isSmallScreen ? ( + <> + {requestContent} + {responseContent} + + ) : ( + + + {requestContent} + + + + {responseContent} + + {isAiTestGenerationPanelOpen && !isSmallScreen && ( + <> + + + + + + )} + + )} +
+
+
); }; diff --git a/frontend/src/pages/RequestorPage/Resizable.tsx b/frontend/src/pages/RequestorPage/Resizable.tsx deleted file mode 100644 index d9e84dcd0..000000000 --- a/frontend/src/pages/RequestorPage/Resizable.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import "react-resizable/css/styles.css"; // Import the styles for the resizable component - -import { forwardRef } from "react"; - -interface ResizableHandleProps extends React.HTMLAttributes {} - -export const ResizableHandle = forwardRef( - (props, ref) => { - return ( -
- ); - }, -); diff --git a/frontend/src/pages/RequestorPage/ResponsePanel.tsx b/frontend/src/pages/RequestorPage/ResponsePanel.tsx index e471e0ef2..bcc1dc693 100644 --- a/frontend/src/pages/RequestorPage/ResponsePanel.tsx +++ b/frontend/src/pages/RequestorPage/ResponsePanel.tsx @@ -1,5 +1,3 @@ -import "react-resizable/css/styles.css"; // Import the styles for the resizable component - import RobotIcon from "@/assets/Robot.svg"; import { Button } from "@/components/ui/button"; import { @@ -98,7 +96,7 @@ export function ResponsePanel({ Response @@ -162,7 +160,7 @@ export function ResponsePanel({ ) } > -
+
{ return routes?.some((r) => !r.currentlyRegistered) ?? false; }, [routes]); @@ -112,103 +106,83 @@ export function RoutesPanel({ }, [filterValue, history]); return ( -