Skip to content

Commit

Permalink
feat(CanvasSidePanel): add resizable feature
Browse files Browse the repository at this point in the history
  • Loading branch information
plagoa committed Nov 13, 2024
1 parent de9c471 commit 9fc13b6
Show file tree
Hide file tree
Showing 2 changed files with 132 additions and 1 deletion.
26 changes: 25 additions & 1 deletion packages/pentaho/src/Canvas/SidePanel/SidePanel.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
import { forwardRef } from "react";
import {
ExtractNames,
Expand All @@ -14,6 +15,7 @@ import { End } from "@hitachivantara/uikit-react-icons";
import { HvCanvasPanelTab } from "../PanelTab";
import { HvCanvasPanelTabs, HvCanvasPanelTabsProps } from "../PanelTabs";
import { staticClasses, useClasses } from "./SidePanel.styles";
import { useResizable } from "./useResizable";

export { staticClasses as canvasSidePanelClasses };

Expand Down Expand Up @@ -49,6 +51,8 @@ export interface HvCanvasSidePanelProps
) => void;
/** An object containing all the labels. */
labels?: Partial<typeof DEFAULT_LABELS>;
/** Whether the side panel is resizable horizontally. */
resizable?: boolean;
/** The content that will be rendered within the canvas panel. */
children?: React.ReactNode;
/** A Jss Object used to override or extend the styles applied. */
Expand All @@ -71,6 +75,7 @@ export const HvCanvasSidePanel = forwardRef<
onToggle,
onTabChange,
labels: labelsProp,
resizable = true,
className,
children,
classes: classesProp,
Expand All @@ -89,6 +94,15 @@ export const HvCanvasSidePanel = forwardRef<
tabs?.[0]?.id ?? "none",
);

const { width, isDragging, getContainerProps, getSeparatorProps } =
useResizable({
resizable,
ref,
initialWidth: 320,
minWidth: 100,
maxWidth: 500,
});

const handleTogglePanel = (event: React.MouseEvent | React.KeyboardEvent) => {
setOpen((prev) => !prev);
onToggle?.(event, !open);
Expand All @@ -105,12 +119,17 @@ export const HvCanvasSidePanel = forwardRef<
return (
<>
<div
ref={ref}
id={id}
className={cx(classes.root, className, {
[classes.open]: open,
[classes.close]: !open,
})}
{...getContainerProps()}
style={{
...getContainerProps().style,
width: open ? width : 0,
transition: isDragging ? "none" : undefined,
}}
{...others}
>
{tabs && (
Expand Down Expand Up @@ -138,6 +157,7 @@ export const HvCanvasSidePanel = forwardRef<
{children}
</HvPanel>
</div>
{resizable && <div {...getSeparatorProps()} />}
<HvIconButton
variant="primaryGhost"
title={open ? labels.close : labels.open}
Expand All @@ -146,6 +166,10 @@ export const HvCanvasSidePanel = forwardRef<
[classes.handleOpen]: open,
[classes.handleClose]: !open,
})}
style={{
left: open ? width : 0,
transition: isDragging ? "none" : undefined,
}}
>
<End style={{ rotate: open ? "180deg" : undefined }} />
</HvIconButton>
Expand Down
107 changes: 107 additions & 0 deletions packages/pentaho/src/Canvas/SidePanel/useResizable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { useRef, useState } from "react";
import { useForkRef } from "@hitachivantara/uikit-react-core";

interface ContainerProps {
ref: any;
style: React.CSSProperties;
}

interface HandleProps {
style: React.CSSProperties;
onMouseMove?: (event: React.MouseEvent) => void;
onMouseLeave?: () => void;
onMouseDown?: () => void;
role: string;
}

interface ResizableProps {
resizable: boolean;
ref: any;
initialWidth?: number;
minWidth?: number;
maxWidth?: number;
}

export const useResizable = (
resizableOptions: ResizableProps,
): {
width: number;
isDragging: boolean;
getContainerProps: () => ContainerProps;
getSeparatorProps: () => HandleProps;
} => {
const {
resizable,
ref,
initialWidth = 320,
minWidth = 100,
maxWidth = 600,
} = resizableOptions;

const [width, setWidth] = useState(initialWidth);
const [isHover, setIsHover] = useState(false);
const [isDragging, setIsDragging] = useState(false);

const panelRef = useRef<HTMLDivElement>(null);

const forkedRef = useForkRef(ref, panelRef);

const mouseMove = (event: any) => {
if (panelRef.current) {
const rect = panelRef.current.getBoundingClientRect();
const newWidth = event.clientX - rect.left;
if (newWidth >= minWidth && newWidth <= maxWidth) {
setWidth(newWidth);
}
}
};

const handleMouseMove = (event: any) => {
if (panelRef.current) {
const rect = panelRef.current.getBoundingClientRect();
const isHoverBorder =
event.clientX >= rect.right - 5 && event.clientX <= rect.right + 5;
setIsHover(isHoverBorder);
}
};

const handleMouseUp = () => {
document.removeEventListener("mousemove", mouseMove);
document.removeEventListener("mouseup", handleMouseUp);
setIsDragging(false);
};

const startResizing = () => {
document.addEventListener("mousemove", mouseMove);
document.addEventListener("mouseup", handleMouseUp);
setIsDragging(true);
};

const getContainerProps = (): ContainerProps => ({
ref: forkedRef,
style: {
width,
},
});

const getSeparatorProps = (): HandleProps => ({
style: {
left: width,
position: "absolute",
top: 0,
bottom: 0,
width: 5,
cursor: resizable ? "col-resize" : "default",
},
onMouseMove: resizable ? handleMouseMove : undefined,
onMouseLeave: resizable ? () => setIsHover(false) : undefined,
onMouseDown: resizable
? () => {
if (isHover) startResizing();
}
: undefined,
role: "separator",
});

return { width, isDragging, getContainerProps, getSeparatorProps };
};

0 comments on commit 9fc13b6

Please sign in to comment.