mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-07-02 19:11:39 +02:00
Split codebase (#455)
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
import classNames from "classnames";
|
||||
import type { CSSProperties, ReactNode } from "react";
|
||||
import { useCallback, useMemo, useRef } from "react";
|
||||
import { useLocalStorage } from "react-use";
|
||||
import { useContainerSize } from "../hooks/useContainerSize";
|
||||
import { clamp } from "../lib/clamp";
|
||||
import type { ResizeHandleEvent } from "./ResizeHandle";
|
||||
import { ResizeHandle } from "./ResizeHandle";
|
||||
|
||||
export type SplitLayoutLayout = "responsive" | "horizontal" | "vertical";
|
||||
|
||||
export interface SlotProps {
|
||||
orientation: "horizontal" | "vertical";
|
||||
style: CSSProperties;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
storageKey: string;
|
||||
firstSlot: (props: SlotProps) => ReactNode;
|
||||
secondSlot: null | ((props: SlotProps) => ReactNode);
|
||||
style?: CSSProperties;
|
||||
className?: string;
|
||||
defaultRatio?: number;
|
||||
minHeightPx?: number;
|
||||
minWidthPx?: number;
|
||||
layout?: SplitLayoutLayout;
|
||||
resizeHandleClassName?: string;
|
||||
}
|
||||
|
||||
const baseProperties = { minWidth: 0 };
|
||||
const areaL = { ...baseProperties, gridArea: "left" };
|
||||
const areaR = { ...baseProperties, gridArea: "right" };
|
||||
const areaD = { ...baseProperties, gridArea: "drag" };
|
||||
|
||||
const STACK_VERTICAL_WIDTH = 500;
|
||||
|
||||
export function SplitLayout({
|
||||
style,
|
||||
firstSlot,
|
||||
secondSlot,
|
||||
className,
|
||||
storageKey,
|
||||
layout = "responsive",
|
||||
resizeHandleClassName,
|
||||
defaultRatio = 0.5,
|
||||
minHeightPx = 10,
|
||||
minWidthPx = 10,
|
||||
}: Props) {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [widthRaw, setWidth] = useLocalStorage<number>(`${storageKey}_width`);
|
||||
const [heightRaw, setHeight] = useLocalStorage<number>(`${storageKey}_height`);
|
||||
const width = widthRaw ?? defaultRatio;
|
||||
let height = heightRaw ?? defaultRatio;
|
||||
|
||||
if (!secondSlot) {
|
||||
height = 0;
|
||||
minHeightPx = 0;
|
||||
}
|
||||
|
||||
const size = useContainerSize(containerRef);
|
||||
const verticalBasedOnSize = size.width !== 0 && size.width < STACK_VERTICAL_WIDTH;
|
||||
const vertical = layout !== "horizontal" && (layout === "vertical" || verticalBasedOnSize);
|
||||
|
||||
const styles = useMemo<CSSProperties>(() => {
|
||||
return {
|
||||
...style,
|
||||
gridTemplate: vertical
|
||||
? `
|
||||
' ${areaL.gridArea}' minmax(0,${1 - height}fr)
|
||||
' ${areaD.gridArea}' 0
|
||||
' ${areaR.gridArea}' minmax(${minHeightPx}px,${height}fr)
|
||||
/ 1fr
|
||||
`
|
||||
: `
|
||||
' ${areaL.gridArea} ${areaD.gridArea} ${areaR.gridArea}' minmax(0,1fr)
|
||||
/ ${1 - width}fr 0 ${width}fr
|
||||
`,
|
||||
};
|
||||
}, [style, vertical, height, minHeightPx, width]);
|
||||
|
||||
const handleReset = useCallback(() => {
|
||||
if (vertical) setHeight(defaultRatio);
|
||||
else setWidth(defaultRatio);
|
||||
}, [vertical, setHeight, defaultRatio, setWidth]);
|
||||
|
||||
const handleResizeMove = useCallback(
|
||||
(e: ResizeHandleEvent) => {
|
||||
if (containerRef.current === null) return;
|
||||
|
||||
const { paddingLeft, paddingRight, paddingTop, paddingBottom } = getComputedStyle(
|
||||
containerRef.current,
|
||||
);
|
||||
const $c = containerRef.current;
|
||||
const containerWidth =
|
||||
$c.clientWidth - Number.parseFloat(paddingLeft) - Number.parseFloat(paddingRight);
|
||||
const containerHeight =
|
||||
$c.clientHeight - Number.parseFloat(paddingTop) - Number.parseFloat(paddingBottom);
|
||||
|
||||
const mouseStartX = e.xStart;
|
||||
const mouseStartY = e.yStart;
|
||||
const startWidth = containerWidth * width;
|
||||
const startHeight = containerHeight * height;
|
||||
|
||||
if (vertical) {
|
||||
const maxHeightPx = containerHeight - minHeightPx;
|
||||
const newHeightPx = clamp(startHeight - (e.y - mouseStartY), minHeightPx, maxHeightPx);
|
||||
setHeight(newHeightPx / containerHeight);
|
||||
} else {
|
||||
const maxWidthPx = containerWidth - minWidthPx;
|
||||
const newWidthPx = clamp(startWidth - (e.x - mouseStartX), minWidthPx, maxWidthPx);
|
||||
setWidth(newWidthPx / containerWidth);
|
||||
}
|
||||
},
|
||||
[width, height, vertical, minHeightPx, setHeight, minWidthPx, setWidth],
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
style={styles}
|
||||
className={classNames(className, "grid w-full h-full overflow-hidden")}
|
||||
>
|
||||
{firstSlot({ style: areaL, orientation: vertical ? "vertical" : "horizontal" })}
|
||||
{secondSlot && (
|
||||
<>
|
||||
<ResizeHandle
|
||||
style={areaD}
|
||||
className={classNames(
|
||||
resizeHandleClassName,
|
||||
vertical ? "-translate-y-1" : "-translate-x-1",
|
||||
)}
|
||||
onResizeMove={handleResizeMove}
|
||||
onReset={handleReset}
|
||||
side={vertical ? "top" : "left"}
|
||||
justify="center"
|
||||
/>
|
||||
{secondSlot({ style: areaR, orientation: vertical ? "vertical" : "horizontal" })}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user