From 5f853048d54595588fc2030b58e01db365d9212e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20Lago=C3=A1?= Date: Fri, 22 Nov 2024 14:51:03 +0000 Subject: [PATCH] feat: add CanvasContext (#4439) --- packages/cli/src/templates/Canvas/index.tsx | 170 +++++++++--------- packages/cli/src/templates/Canvas/styles.tsx | 10 -- .../src/Canvas/BottomPanel/BottomPanel.tsx | 8 + packages/pentaho/src/Canvas/CanvasContext.tsx | 54 ++++++ .../src/Canvas/SidePanel/SidePanel.test.tsx | 9 +- .../src/Canvas/SidePanel/SidePanel.tsx | 40 ++++- .../src/Canvas/SidePanel/useResizable.tsx | 15 +- .../src/Canvas/Toolbar/Toolbar.styles.tsx | 4 +- .../pentaho/src/Canvas/Toolbar/Toolbar.tsx | 15 +- packages/pentaho/src/Canvas/index.ts | 1 + 10 files changed, 209 insertions(+), 117 deletions(-) create mode 100644 packages/pentaho/src/Canvas/CanvasContext.tsx diff --git a/packages/cli/src/templates/Canvas/index.tsx b/packages/cli/src/templates/Canvas/index.tsx index 2e3454d2ac..cead118f15 100644 --- a/packages/cli/src/templates/Canvas/index.tsx +++ b/packages/cli/src/templates/Canvas/index.tsx @@ -1,5 +1,4 @@ import { useMemo, useState } from "react"; -import { cx } from "@emotion/css"; import { ReactFlowInstance } from "reactflow"; import { HvButton, @@ -28,6 +27,7 @@ import { import { HvCanvasBottomPanel, HvCanvasBottomPanelProps, + HvCanvasProvider, HvCanvasToolbar, } from "@hitachivantara/uikit-react-pentaho"; @@ -201,97 +201,93 @@ const Page = () => { return (
- setSidePanelOpen(value)} - onTabChange={(event, value) => setSidePanelTab(value as number)} - > - {sidePanelContent[sidePanelTab]} - - } - > - - Drag and Drop your Nodes - - } - message={ - + setSidePanelOpen(value)} + onTabChange={(event, value) => setSidePanelTab(value as number)} > - Then you can start configuring your flow. - + {sidePanelContent[sidePanelTab]} + } - icon={null} - /> - - - - } - > - - Execute - - - {bottomTabs.length > 0 && bottomPanelOpen && ( - - - - )} - {bottomPanelOpen && ( - setFullscreen((prev) => !prev)} + + Drag and Drop your Nodes + + } + message={ + + Then you can start configuring your flow. + + } + icon={null} + /> + + + + } > - - {( - bottomTabs?.find((x) => x.id === selectedTable)?.title as Function - )(false)} - - + + Execute + + + {bottomTabs.length > 0 && bottomPanelOpen && ( + - - - )} + + )} + {bottomPanelOpen && ( + setFullscreen((prev) => !prev)} + > + + {( + bottomTabs?.find((x) => x.id === selectedTable) + ?.title as Function + )(false)} + + + + + + )} +
); }; diff --git a/packages/cli/src/templates/Canvas/styles.tsx b/packages/cli/src/templates/Canvas/styles.tsx index bce6781890..3e31aa5ad4 100644 --- a/packages/cli/src/templates/Canvas/styles.tsx +++ b/packages/cli/src/templates/Canvas/styles.tsx @@ -22,16 +22,6 @@ export const classes = { toolbar: css({ top: `calc(${theme.header.height} + ${theme.header.secondLevelHeight} + ${theme.space.md})`, }), - fullWidth: css({ - right: theme.space.lg, - marginLeft: "auto", - marginRight: "auto", - width: `calc(100% - 2 * ${theme.space.lg})`, - }), - minWidth: css({ - right: theme.space.lg, - width: `calc(100% - 320px - 3 * ${theme.space.lg})`, - }), panel: css({ top: `calc(${theme.header.height} + ${theme.header.secondLevelHeight})`, height: `calc(100% - ${theme.header.height} - ${theme.header.secondLevelHeight})`, diff --git a/packages/pentaho/src/Canvas/BottomPanel/BottomPanel.tsx b/packages/pentaho/src/Canvas/BottomPanel/BottomPanel.tsx index 89d5649501..4df01a206d 100644 --- a/packages/pentaho/src/Canvas/BottomPanel/BottomPanel.tsx +++ b/packages/pentaho/src/Canvas/BottomPanel/BottomPanel.tsx @@ -13,6 +13,7 @@ import { useUniqueId, } from "@hitachivantara/uikit-react-core"; +import { useCanvasContext } from "../CanvasContext"; import { HvCanvasPanelTab } from "../PanelTab"; import { HvCanvasPanelTabs, HvCanvasPanelTabsProps } from "../PanelTabs"; import { staticClasses, useClasses } from "./BottomPanel.styles"; @@ -91,6 +92,9 @@ export const HvCanvasBottomPanel = forwardRef< const { classes, cx } = useClasses(classesProp); + const canvasContext = useCanvasContext(); + const sidePanelWidth = canvasContext?.sidePanelWidth ?? 0; + const id = useUniqueId(idProp); // Tab resize detector: to position tab actions and set the panel top right border radius @@ -158,6 +162,10 @@ export const HvCanvasBottomPanel = forwardRef< }, className, )} + style={{ + width: `calc(100% - ${sidePanelWidth}px - 2 * ${theme.space.sm})`, + right: theme.space.sm, + }} {...others} >
diff --git a/packages/pentaho/src/Canvas/CanvasContext.tsx b/packages/pentaho/src/Canvas/CanvasContext.tsx new file mode 100644 index 0000000000..a9522bf251 --- /dev/null +++ b/packages/pentaho/src/Canvas/CanvasContext.tsx @@ -0,0 +1,54 @@ +import { + createContext, + useCallback, + useContext, + useMemo, + useState, +} from "react"; + +type HvCanvasContextValue = { + sidePanelOpen: boolean; + handleSidePanelOpen: (open: boolean) => void; + sidePanelWidth: number; + handleSidePanelWidth: (width: number) => void; +}; + +export const HvCanvasContext = createContext(null); + +export const HvCanvasProvider = ({ + children, +}: { + children: React.ReactNode; + onSidePanelResize?: (width: number) => void; +}) => { + const [sidePanelOpen, setSidePanelOpen] = useState(false); + const [width, setWidth] = useState(0); + + const handleSidePanelWidth = useCallback((newWidth: number) => { + setWidth(newWidth); + }, []); + + const handleSidePanelOpen = useCallback((open: boolean) => { + setSidePanelOpen(open); + }, []); + + const value = useMemo( + () => ({ + sidePanelOpen, + handleSidePanelOpen, + sidePanelWidth: sidePanelOpen ? width : 0, + handleSidePanelWidth, + }), + [sidePanelOpen, handleSidePanelOpen, width, handleSidePanelWidth], + ); + + return ( + + {children} + + ); +}; + +export const useCanvasContext = () => { + return useContext(HvCanvasContext); +}; diff --git a/packages/pentaho/src/Canvas/SidePanel/SidePanel.test.tsx b/packages/pentaho/src/Canvas/SidePanel/SidePanel.test.tsx index 2dd86afc9f..b12fa08332 100644 --- a/packages/pentaho/src/Canvas/SidePanel/SidePanel.test.tsx +++ b/packages/pentaho/src/Canvas/SidePanel/SidePanel.test.tsx @@ -4,6 +4,7 @@ import userEvent from "@testing-library/user-event"; import { describe, expect, it, vi } from "vitest"; import { HvButton } from "@hitachivantara/uikit-react-core"; +import { HvCanvasProvider } from "../CanvasContext"; import { HvCanvasSidePanel, HvCanvasSidePanelProps } from "./SidePanel"; const label = "Test"; @@ -21,9 +22,11 @@ const tabs = [ const renderSimplePanel = (props?: HvCanvasSidePanelProps) => render( - - {label} - , + + + {label} + + , ); const ControlledPanel = ({ diff --git a/packages/pentaho/src/Canvas/SidePanel/SidePanel.tsx b/packages/pentaho/src/Canvas/SidePanel/SidePanel.tsx index f933063f34..8cf1746004 100644 --- a/packages/pentaho/src/Canvas/SidePanel/SidePanel.tsx +++ b/packages/pentaho/src/Canvas/SidePanel/SidePanel.tsx @@ -1,4 +1,4 @@ -import { forwardRef } from "react"; +import { forwardRef, useEffect } from "react"; import { ExtractNames, HvBaseProps, @@ -11,6 +11,7 @@ import { } from "@hitachivantara/uikit-react-core"; import { End } from "@hitachivantara/uikit-react-icons"; +import { useCanvasContext } from "../CanvasContext"; import { HvCanvasPanelTab } from "../PanelTab"; import { HvCanvasPanelTabs, HvCanvasPanelTabsProps } from "../PanelTabs"; import { staticClasses, useClasses } from "./SidePanel.styles"; @@ -26,7 +27,7 @@ const DEFAULT_LABELS = { }; export interface HvCanvasSidePanelProps - extends HvBaseProps { + extends HvBaseProps { /** When controlled, defines id the panel is open or not. */ open?: boolean; /** When uncontrolled, defines the initial state of the panel. */ @@ -50,6 +51,14 @@ export interface HvCanvasSidePanelProps ) => void; /** An object containing all the labels. */ labels?: Partial; + /** The minimum width of the side panel. */ + minWidth?: number; + /** The maximum width of the side panel. */ + maxWidth?: number; + /** The initial width of the side panel. */ + initialWidth?: number; + /** Callback triggered when the panel width changes. */ + onResize?: (width: number) => void; /** The content that will be rendered within the canvas panel. */ children?: React.ReactNode; /** A Jss Object used to override or extend the styles applied. */ @@ -72,6 +81,10 @@ export const HvCanvasSidePanel = forwardRef< onToggle, onTabChange, labels: labelsProp, + minWidth = 100, + maxWidth = 500, + initialWidth = 320, + onResize, className, children, classes: classesProp, @@ -80,6 +93,11 @@ export const HvCanvasSidePanel = forwardRef< const id = useUniqueId(idProp); + const canvasContext = useCanvasContext(); + const handleSidePanelWidth = canvasContext?.handleSidePanelWidth; + const sidePanelOpen = canvasContext?.sidePanelOpen; + const handleSidePanelOpen = canvasContext?.handleSidePanelOpen; + const { classes, cx } = useClasses(classesProp); const labels = useLabels(DEFAULT_LABELS, labelsProp); @@ -90,16 +108,28 @@ export const HvCanvasSidePanel = forwardRef< tabs?.[0]?.id ?? "none", ); + useEffect(() => { + handleSidePanelWidth?.(initialWidth); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [initialWidth]); + + const updateWidth = (width: number) => { + handleSidePanelWidth?.(width); + onResize?.(width); + }; + const { width, isDragging, getContainerProps, getSeparatorProps } = useResizable({ ref, - initialWidth: 320, - minWidth: 100, - maxWidth: 500, + initialWidth, + minWidth, + maxWidth, + onResize: updateWidth, }); const handleTogglePanel = (event: React.MouseEvent | React.KeyboardEvent) => { setOpen((prev) => !prev); + handleSidePanelOpen?.(!sidePanelOpen); onToggle?.(event, !open); }; diff --git a/packages/pentaho/src/Canvas/SidePanel/useResizable.tsx b/packages/pentaho/src/Canvas/SidePanel/useResizable.tsx index 82d56b8b89..64743ef3b3 100644 --- a/packages/pentaho/src/Canvas/SidePanel/useResizable.tsx +++ b/packages/pentaho/src/Canvas/SidePanel/useResizable.tsx @@ -16,9 +16,10 @@ interface SeparatorProps { interface ResizableProps { ref: any; - initialWidth?: number; - minWidth?: number; - maxWidth?: number; + initialWidth: number; + minWidth: number; + maxWidth: number; + onResize?: (width: number) => void; } export const useResizable = ( @@ -29,12 +30,7 @@ export const useResizable = ( getContainerProps: (overrides: any) => ContainerProps; getSeparatorProps: () => SeparatorProps; } => { - const { - ref, - initialWidth = 320, - minWidth = 100, - maxWidth = 600, - } = resizableOptions; + const { ref, initialWidth, minWidth, maxWidth, onResize } = resizableOptions; const [width, setWidth] = useState(initialWidth); const [isHover, setIsHover] = useState(false); @@ -50,6 +46,7 @@ export const useResizable = ( const newWidth = event.clientX - rect.left; if (newWidth >= minWidth && newWidth <= maxWidth) { setWidth(newWidth); + onResize?.(newWidth); } } }; diff --git a/packages/pentaho/src/Canvas/Toolbar/Toolbar.styles.tsx b/packages/pentaho/src/Canvas/Toolbar/Toolbar.styles.tsx index b8d35d06ae..9ebbcfba6e 100644 --- a/packages/pentaho/src/Canvas/Toolbar/Toolbar.styles.tsx +++ b/packages/pentaho/src/Canvas/Toolbar/Toolbar.styles.tsx @@ -2,14 +2,14 @@ import { createClasses, theme } from "@hitachivantara/uikit-react-core"; export const { staticClasses, useClasses } = createClasses("HvCanvasToolbar", { root: { - width: "100%", + width: `calc(100% - var(--sidepanel-width) - 2 * ${theme.space.sm})`, height: 54, display: "flex", alignItems: "center", borderRadius: theme.radii.full, backgroundColor: theme.colors.atmo1, position: "absolute", - right: 0, + right: theme.space.sm, top: 0, transition: "width 0.3s ease", }, diff --git a/packages/pentaho/src/Canvas/Toolbar/Toolbar.tsx b/packages/pentaho/src/Canvas/Toolbar/Toolbar.tsx index 2ecb5d0afb..e74748ca37 100644 --- a/packages/pentaho/src/Canvas/Toolbar/Toolbar.tsx +++ b/packages/pentaho/src/Canvas/Toolbar/Toolbar.tsx @@ -1,4 +1,5 @@ import { forwardRef } from "react"; +import { mergeStyles } from "packages/utils/src"; import { ExtractNames, HvBaseProps, @@ -10,6 +11,7 @@ import { } from "@hitachivantara/uikit-react-core"; import { Previous } from "@hitachivantara/uikit-react-icons"; +import { useCanvasContext } from "../CanvasContext"; import { staticClasses, useClasses } from "./Toolbar.styles"; export { staticClasses as canvasToolbarClasses }; @@ -46,6 +48,7 @@ export const HvCanvasToolbar = forwardRef( backButton, labels: labelsProp, className, + style, children, backButtonProps, classes: classesProp, @@ -55,6 +58,9 @@ export const HvCanvasToolbar = forwardRef( const { classes, cx } = useClasses(classesProp); const labels = useLabels(DEFAULT_LABELS, labelsProp); + const canvasContext = useCanvasContext(); + const sidePanelWidth = canvasContext?.sidePanelWidth ?? 0; + const title = typeof titleProp === "string" ? ( {titleProp} @@ -63,7 +69,14 @@ export const HvCanvasToolbar = forwardRef( ); return ( -
+
{backButton ?? (