From 1a7b810badad9d0d331bf085299ff79ec7717f41 Mon Sep 17 00:00:00 2001 From: Scott Wilson Date: Tue, 1 Jul 2025 17:22:49 -0700 Subject: [PATCH] improvement: allow users to expand collapsed environment view header --- .../v2/HeaderResizer/HeaderResizer.tsx | 28 +++++++ .../src/components/v2/HeaderResizer/index.tsx | 0 frontend/src/components/v2/Table/Table.tsx | 12 ++- frontend/src/hooks/index.ts | 1 + .../src/hooks/useResizableHeaderHeight.tsx | 71 ++++++++++++++++ .../OverviewPage/OverviewPage.tsx | 83 +++++++++++++++---- .../SecretOverviewTableRow.tsx | 6 +- 7 files changed, 178 insertions(+), 23 deletions(-) create mode 100644 frontend/src/components/v2/HeaderResizer/HeaderResizer.tsx create mode 100644 frontend/src/components/v2/HeaderResizer/index.tsx create mode 100644 frontend/src/hooks/useResizableHeaderHeight.tsx diff --git a/frontend/src/components/v2/HeaderResizer/HeaderResizer.tsx b/frontend/src/components/v2/HeaderResizer/HeaderResizer.tsx new file mode 100644 index 000000000..1f50133e9 --- /dev/null +++ b/frontend/src/components/v2/HeaderResizer/HeaderResizer.tsx @@ -0,0 +1,28 @@ +import { MouseEventHandler } from "react"; + +export const HeaderResizer = ({ + onMouseDown, + isActive +}: { + onMouseDown: MouseEventHandler; + isActive: boolean; +}) => { + return ( + <> +
+
+
+
+ + ); +}; diff --git a/frontend/src/components/v2/HeaderResizer/index.tsx b/frontend/src/components/v2/HeaderResizer/index.tsx new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/src/components/v2/Table/Table.tsx b/frontend/src/components/v2/Table/Table.tsx index 1d5a9c394..cc180ffb6 100644 --- a/frontend/src/components/v2/Table/Table.tsx +++ b/frontend/src/components/v2/Table/Table.tsx @@ -45,10 +45,14 @@ export const Table = ({ children, className }: TableProps): JSX.Element => ( export type THeadProps = { children: ReactNode; className?: string; + style?: React.CSSProperties; }; -export const THead = ({ children, className }: THeadProps): JSX.Element => ( - +export const THead = ({ children, className, style }: THeadProps): JSX.Element => ( + {children} ); @@ -96,14 +100,16 @@ export const Tr = ({ export type ThProps = { children?: ReactNode; className?: string; + style?: React.CSSProperties; }; -export const Th = ({ children, className }: ThProps): JSX.Element => ( +export const Th = ({ children, className, style }: ThProps): JSX.Element => ( {children} diff --git a/frontend/src/hooks/index.ts b/frontend/src/hooks/index.ts index 9e5eff713..0ebdb9843 100644 --- a/frontend/src/hooks/index.ts +++ b/frontend/src/hooks/index.ts @@ -4,6 +4,7 @@ export { usePagination } from "./usePagination"; export { usePersistentState } from "./usePersistentState"; export { usePopUp } from "./usePopUp"; export { useResetPageHelper } from "./useResetPageHelper"; +export * from "./useResizableHeaderHeight"; export { useSyntaxHighlight } from "./useSyntaxHighlight"; export { useTimedReset } from "./useTimedReset"; export { useToggle } from "./useToggle"; diff --git a/frontend/src/hooks/useResizableHeaderHeight.tsx b/frontend/src/hooks/useResizableHeaderHeight.tsx new file mode 100644 index 000000000..9624e9052 --- /dev/null +++ b/frontend/src/hooks/useResizableHeaderHeight.tsx @@ -0,0 +1,71 @@ +import { MouseEvent, useCallback, useEffect, useRef, useState } from "react"; + +type Params = { + minHeight: number; + maxHeight: number; + initialHeight: number; +}; + +export const useResizableHeaderHeight = ({ minHeight, maxHeight, initialHeight }: Params) => { + const [headerHeight, setHeaderHeight] = useState(initialHeight); + const [isResizing, setIsResizing] = useState(false); + const startY = useRef(0); + const startHeight = useRef(0); + + const handleMouseDown = useCallback( + (e: MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + setIsResizing(true); + startY.current = e.clientY; + startHeight.current = headerHeight; + }, + [headerHeight] + ); + + const handleMouseMove = useCallback( + (e: MouseEvent) => { + if (!isResizing) return; + + const deltaY = e.clientY - startY.current; + const newHeight = Math.max(minHeight, Math.min(maxHeight, startHeight.current + deltaY)); + + setHeaderHeight(newHeight); + }, + [isResizing] + ); + + const handleMouseUp = useCallback(() => { + setIsResizing(false); + }, []); + + useEffect(() => { + if (isResizing) { + document.addEventListener( + "mousemove", + // @ts-expect-error native discrepancy + handleMouseMove + ); + document.addEventListener("mouseup", handleMouseUp); + document.body.style.cursor = "ns-resize"; + document.body.style.userSelect = "none"; + } + + return () => { + document.removeEventListener( + "mousemove", + // @ts-expect-error native discrepancy + handleMouseMove + ); + document.removeEventListener("mouseup", handleMouseUp); + document.body.style.cursor = ""; + document.body.style.userSelect = ""; + }; + }, [isResizing, handleMouseMove, handleMouseUp]); + + return { + headerHeight, + handleMouseDown, + isResizing + }; +}; diff --git a/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx b/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx index 045ee58cd..eb160236c 100644 --- a/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx +++ b/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx @@ -57,6 +57,7 @@ import { Tooltip, Tr } from "@app/components/v2"; +import { HeaderResizer } from "@app/components/v2/HeaderResizer/HeaderResizer"; import { ROUTE_PATHS } from "@app/const/routes"; import { ProjectPermissionActions, @@ -72,7 +73,14 @@ import { PreferenceKey, setUserTablePreference } from "@app/helpers/userTablePreferences"; -import { useDebounce, usePagination, usePopUp, useResetPageHelper, useToggle } from "@app/hooks"; +import { + useDebounce, + usePagination, + usePopUp, + useResetPageHelper, + useResizableHeaderHeight, + useToggle +} from "@app/hooks"; import { useCreateFolder, useCreateSecretV3, @@ -860,6 +868,19 @@ export const OverviewPage = () => { ); }, [importedByEnvs, selectedEntries, selectedKeysCount]); + const storedHeight = Number.parseInt(localStorage.getItem("overview-header-height") ?? "96"); + const { headerHeight, handleMouseDown, isResizing } = useResizableHeaderHeight({ + initialHeight: Number.isNaN(storedHeight) ? 96 : storedHeight, + minHeight: 96, + maxHeight: 192 + }); + + const debouncedHeaderHeight = useDebounce(headerHeight); + + useEffect(() => { + localStorage.setItem("overview-header-height", debouncedHeaderHeight.toString()); + }, [debouncedHeaderHeight]); + if (isProjectV3 && visibleEnvs.length > 0 && isOverviewLoading) { return (
@@ -1188,15 +1209,17 @@ export const OverviewPage = () => { className="thin-scrollbar rounded-b-none" > - + + {collapseEnvironments && ( + + )} {canViewOverviewPage && isOverviewLoading && ( diff --git a/frontend/src/pages/secret-manager/OverviewPage/components/SecretOverviewTableRow/SecretOverviewTableRow.tsx b/frontend/src/pages/secret-manager/OverviewPage/components/SecretOverviewTableRow/SecretOverviewTableRow.tsx index c4f6a7c76..63955e381 100644 --- a/frontend/src/pages/secret-manager/OverviewPage/components/SecretOverviewTableRow/SecretOverviewTableRow.tsx +++ b/frontend/src/pages/secret-manager/OverviewPage/components/SecretOverviewTableRow/SecretOverviewTableRow.tsx @@ -1,8 +1,8 @@ import { subject } from "@casl/ability"; +import { faCircle } from "@fortawesome/free-regular-svg-icons"; import { faAngleDown, faCheck, - faCircle, faCodeBranch, faEye, faEyeSlash, @@ -148,7 +148,7 @@ export const SecretOverviewTableRow = ({ "border-r border-mineshaft-600 px-0 py-3 group-hover:bg-mineshaft-700", isFormExpanded && "border-t-2 border-mineshaft-500", (isSecretPresent && !isSecretEmpty) || isSecretImported ? "text-green-600" : "", - isSecretPresent && isSecretEmpty && !isSecretImported ? "text-yellow" : "", + isSecretPresent && isSecretEmpty && !isSecretImported ? "text-mineshaft-400" : "", !isSecretPresent && !isSecretEmpty && !isSecretImported ? "text-red-600" : "" )} > @@ -174,7 +174,7 @@ export const SecretOverviewTableRow = ({ )} {isSecretEmpty && ( - + )}
{ const importedSecKeyCount = getEnvImportedSecretKeyCount(slug); const missingKeyCount = secKeys.length - envSecKeyCount - importedSecKeyCount; + const isLast = index === visibleEnvs.length - 1; + return (
{ className={twMerge( "border-b border-mineshaft-600", collapseEnvironments - ? "relative h-24 w-[2.9rem]" + ? "relative min-w-[2.9rem]" : "flex items-center justify-center px-5 pb-[0.82rem] pt-3.5", - collapseEnvironments && - index === visibleEnvs.length - 1 && - "overflow-clip" + collapseEnvironments && isLast && "overflow-clip" )} + style={{ height: collapseEnvironments ? headerHeight : undefined }} >