mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-24 18:31:38 +01:00
Shared sidebar layout
This commit is contained in:
105
packages/ui/src/components/SidebarLayout.tsx
Normal file
105
packages/ui/src/components/SidebarLayout.tsx
Normal file
@@ -0,0 +1,105 @@
|
||||
import classNames from 'classnames';
|
||||
import type { CSSProperties, ReactNode } from 'react';
|
||||
import { useCallback, useMemo, useRef, useState } from 'react';
|
||||
import type { ResizeHandleEvent } from './ResizeHandle';
|
||||
import { ResizeHandle } from './ResizeHandle';
|
||||
|
||||
const side = { gridArea: 'side', minWidth: 0 };
|
||||
const drag = { gridArea: 'drag' };
|
||||
const body = { gridArea: 'body', minWidth: 0 };
|
||||
|
||||
interface Props {
|
||||
width: number;
|
||||
onWidthChange: (width: number) => void;
|
||||
hidden?: boolean;
|
||||
onHiddenChange?: (hidden: boolean) => void;
|
||||
defaultWidth?: number;
|
||||
minWidth?: number;
|
||||
className?: string;
|
||||
sidebar: ReactNode;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export function SidebarLayout({
|
||||
width,
|
||||
onWidthChange,
|
||||
hidden = false,
|
||||
onHiddenChange,
|
||||
defaultWidth = 250,
|
||||
minWidth = 50,
|
||||
className,
|
||||
sidebar,
|
||||
children,
|
||||
}: Props) {
|
||||
const [isResizing, setIsResizing] = useState(false);
|
||||
const startWidth = useRef<number | null>(null);
|
||||
|
||||
const sideWidth = hidden ? 0 : width;
|
||||
|
||||
const styles = useMemo<CSSProperties>(
|
||||
() => ({
|
||||
gridTemplate: `
|
||||
' ${side.gridArea} ${drag.gridArea} ${body.gridArea}' minmax(0,1fr)
|
||||
/ ${sideWidth}px 0 1fr`,
|
||||
}),
|
||||
[sideWidth],
|
||||
);
|
||||
|
||||
const handleResizeStart = useCallback(() => {
|
||||
startWidth.current = width;
|
||||
setIsResizing(true);
|
||||
}, [width]);
|
||||
|
||||
const handleResizeEnd = useCallback(() => {
|
||||
setIsResizing(false);
|
||||
startWidth.current = null;
|
||||
}, []);
|
||||
|
||||
const handleResizeMove = useCallback(
|
||||
({ x, xStart }: ResizeHandleEvent) => {
|
||||
if (startWidth.current == null) return;
|
||||
|
||||
const newWidth = startWidth.current + (x - xStart);
|
||||
if (newWidth < minWidth) {
|
||||
onHiddenChange?.(true);
|
||||
onWidthChange(defaultWidth);
|
||||
} else {
|
||||
if (hidden) onHiddenChange?.(false);
|
||||
onWidthChange(newWidth);
|
||||
}
|
||||
},
|
||||
[minWidth, hidden, onHiddenChange, onWidthChange, defaultWidth],
|
||||
);
|
||||
|
||||
const handleReset = useCallback(() => {
|
||||
onWidthChange(defaultWidth);
|
||||
}, [onWidthChange, defaultWidth]);
|
||||
|
||||
return (
|
||||
<div
|
||||
style={styles}
|
||||
className={classNames(
|
||||
className,
|
||||
'grid w-full h-full',
|
||||
!isResizing && 'transition-grid',
|
||||
)}
|
||||
>
|
||||
<div style={side} className="overflow-hidden">
|
||||
{sidebar}
|
||||
</div>
|
||||
<ResizeHandle
|
||||
style={drag}
|
||||
className="-translate-x-[1px]"
|
||||
justify="end"
|
||||
side="right"
|
||||
onResizeStart={handleResizeStart}
|
||||
onResizeEnd={handleResizeEnd}
|
||||
onResizeMove={handleResizeMove}
|
||||
onReset={handleReset}
|
||||
/>
|
||||
<div style={body} className="min-w-0">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -21,6 +21,7 @@ export type { TreeItemProps } from "./components/tree/TreeItem";
|
||||
export { isSelectedFamily, selectedIdsFamily } from "./components/tree/atoms";
|
||||
export { minPromiseMillis } from "./lib/minPromiseMillis";
|
||||
export { ResizeHandle } from "./components/ResizeHandle";
|
||||
export { SidebarLayout } from "./components/SidebarLayout";
|
||||
export type { ResizeHandleEvent } from "./components/ResizeHandle";
|
||||
export { SplitLayout } from "./components/SplitLayout";
|
||||
export type { SplitLayoutLayout, SlotProps } from "./components/SplitLayout";
|
||||
|
||||
Reference in New Issue
Block a user