mirror of https://github.com/buster-so/buster.git
Better and more stable splitter
This commit is contained in:
parent
d725be5505
commit
9e0b975999
|
@ -1,8 +1,9 @@
|
||||||
import type React from 'react';
|
import type React from 'react';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
import type { LayoutSize } from './AppSplitter';
|
||||||
import { AppSplitter } from './AppSplitter/AppSplitter';
|
import { AppSplitter } from './AppSplitter/AppSplitter';
|
||||||
|
|
||||||
const DEFAULT_LAYOUT = ['230px', 'auto'];
|
const DEFAULT_LAYOUT: LayoutSize = ['230px', 'auto'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param floating - Applies floating styles with padding and border (default: true)
|
* @param floating - Applies floating styles with padding and border (default: true)
|
||||||
|
@ -17,7 +18,7 @@ export const AppLayout: React.FC<
|
||||||
floating?: boolean;
|
floating?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
sidebar?: React.ReactNode;
|
sidebar?: React.ReactNode;
|
||||||
defaultLayout?: [string, string];
|
defaultLayout?: LayoutSize;
|
||||||
leftHidden?: boolean;
|
leftHidden?: boolean;
|
||||||
autoSaveId?: string;
|
autoSaveId?: string;
|
||||||
}>
|
}>
|
||||||
|
|
|
@ -3,7 +3,8 @@ import React, { useRef } from 'react';
|
||||||
import { Button } from '@/components/ui/buttons/Button';
|
import { Button } from '@/components/ui/buttons/Button';
|
||||||
import { Text } from '@/components/ui/typography/Text';
|
import { Text } from '@/components/ui/typography/Text';
|
||||||
import { Title } from '@/components/ui/typography/Title';
|
import { Title } from '@/components/ui/typography/Title';
|
||||||
import { AppSplitter, type AppSplitterRef } from './AppSplitter';
|
import { AppSplitter } from './AppSplitter';
|
||||||
|
import type { AppSplitterRef } from './AppSplitter.types';
|
||||||
import { useAppSplitterContext } from './AppSplitterProvider';
|
import { useAppSplitterContext } from './AppSplitterProvider';
|
||||||
|
|
||||||
const meta: Meta<typeof AppSplitter> = {
|
const meta: Meta<typeof AppSplitter> = {
|
||||||
|
|
|
@ -15,177 +15,17 @@ import { useCookieState } from '@/hooks/useCookieState';
|
||||||
import { useMemoizedFn } from '@/hooks/useMemoizedFn';
|
import { useMemoizedFn } from '@/hooks/useMemoizedFn';
|
||||||
import { useMount } from '@/hooks/useMount';
|
import { useMount } from '@/hooks/useMount';
|
||||||
import { cn } from '@/lib/classMerge';
|
import { cn } from '@/lib/classMerge';
|
||||||
|
import type { AppSplitterRef, IAppSplitterProps, SplitterState } from './AppSplitter.types';
|
||||||
import { AppSplitterProvider } from './AppSplitterProvider';
|
import { AppSplitterProvider } from './AppSplitterProvider';
|
||||||
import { createAutoSaveId, easeInOutCubic, sizeToPixels } from './helpers';
|
import { createAutoSaveId, easeInOutCubic, sizeToPixels } from './helpers';
|
||||||
import { Panel } from './Panel';
|
import { Panel } from './Panel';
|
||||||
import { Splitter } from './Splitter';
|
import { Splitter } from './Splitter';
|
||||||
|
import { useInitialValue } from './useInitialValue';
|
||||||
|
|
||||||
// ================================
|
// ================================
|
||||||
// INTERFACES AND TYPES
|
// INTERFACES AND TYPES
|
||||||
// ================================
|
// ================================
|
||||||
|
|
||||||
/**
|
|
||||||
* Props for the AppSplitter component
|
|
||||||
*/
|
|
||||||
interface IAppSplitterProps {
|
|
||||||
/** Content to display in the left panel */
|
|
||||||
leftChildren: React.ReactNode;
|
|
||||||
|
|
||||||
/** Content to display in the right panel */
|
|
||||||
rightChildren: React.ReactNode;
|
|
||||||
|
|
||||||
/** Unique identifier for auto-saving layout to cookies */
|
|
||||||
autoSaveId: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initial preserved-side size from cookies (in pixels)
|
|
||||||
*/
|
|
||||||
initialLayout?: (`${number}px` | `${number}%` | 'auto' | number)[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Default layout configuration as [left, right] sizes
|
|
||||||
* Can be numbers (pixels), percentages (strings like "50%"), or "auto"
|
|
||||||
*/
|
|
||||||
defaultLayout: (`${number}px` | `${number}%` | 'auto' | number)[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Minimum size for the left panel
|
|
||||||
* Can be a number (pixels) or string (percentage)
|
|
||||||
* @default 0
|
|
||||||
*/
|
|
||||||
leftPanelMinSize?: number | string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Minimum size for the right panel
|
|
||||||
* Can be a number (pixels) or string (percentage)
|
|
||||||
* @default 0
|
|
||||||
*/
|
|
||||||
rightPanelMinSize?: number | string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Maximum size for the left panel
|
|
||||||
* Can be a number (pixels) or string (percentage)
|
|
||||||
* If not specified, defaults to container size
|
|
||||||
*/
|
|
||||||
leftPanelMaxSize?: number | string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Maximum size for the right panel
|
|
||||||
* Can be a number (pixels) or string (percentage)
|
|
||||||
* If not specified, defaults to container size
|
|
||||||
*/
|
|
||||||
rightPanelMaxSize?: number | string;
|
|
||||||
|
|
||||||
/** Additional CSS classes for the container */
|
|
||||||
className?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether the splitter can be resized by dragging
|
|
||||||
* @default true
|
|
||||||
*/
|
|
||||||
allowResize?: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Split direction
|
|
||||||
* @default 'vertical'
|
|
||||||
*/
|
|
||||||
split?: 'vertical' | 'horizontal';
|
|
||||||
|
|
||||||
/** Additional CSS classes for the splitter element */
|
|
||||||
splitterClassName?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Which side to preserve when resizing
|
|
||||||
* 'left' - left panel maintains its size, right panel adjusts
|
|
||||||
* 'right' - right panel maintains its size, left panel adjusts
|
|
||||||
*/
|
|
||||||
preserveSide: 'left' | 'right';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether to hide the right panel completely
|
|
||||||
* @default false
|
|
||||||
*/
|
|
||||||
rightHidden?: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether to hide the left panel completely
|
|
||||||
* @default false
|
|
||||||
*/
|
|
||||||
leftHidden?: boolean;
|
|
||||||
|
|
||||||
/** Inline styles for the container */
|
|
||||||
style?: React.CSSProperties;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether to hide the splitter handle
|
|
||||||
* @default false
|
|
||||||
*/
|
|
||||||
hideSplitter?: boolean;
|
|
||||||
|
|
||||||
/** Additional CSS classes for the left panel */
|
|
||||||
leftPanelClassName?: string;
|
|
||||||
|
|
||||||
/** Additional CSS classes for the right panel */
|
|
||||||
rightPanelClassName?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether to clear saved layout from cookies on initialization
|
|
||||||
* Can be a boolean or a function that returns a boolean based on preserved side value and container width
|
|
||||||
*/
|
|
||||||
bustStorageOnInit?: boolean | ((preservedSideValue: number | null, refSize: number) => boolean);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ref interface for controlling the AppSplitter imperatively
|
|
||||||
*/
|
|
||||||
export interface AppSplitterRef {
|
|
||||||
/**
|
|
||||||
* Animate a panel to a specific width
|
|
||||||
* @param width - Target width (pixels or percentage)
|
|
||||||
* @param side - Which side to animate
|
|
||||||
* @param duration - Animation duration in milliseconds
|
|
||||||
*/
|
|
||||||
animateWidth: (
|
|
||||||
width: string | number,
|
|
||||||
side: 'left' | 'right',
|
|
||||||
duration?: number
|
|
||||||
) => Promise<void>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the split sizes programmatically
|
|
||||||
* @param sizes - [left, right] sizes as pixels or percentages
|
|
||||||
*/
|
|
||||||
setSplitSizes: (sizes: [string | number, string | number]) => void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if a side is closed (hidden or 0px)
|
|
||||||
* @param side - Which side to check
|
|
||||||
*/
|
|
||||||
isSideClosed: (side: 'left' | 'right') => boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get current sizes in pixels
|
|
||||||
* @returns [leftSize, rightSize] in pixels
|
|
||||||
*/
|
|
||||||
getSizesInPixels: () => [number, number];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal state interface for the splitter
|
|
||||||
*/
|
|
||||||
interface SplitterState {
|
|
||||||
/** Current container size in pixels */
|
|
||||||
containerSize: number;
|
|
||||||
/** Whether the user is currently dragging the splitter */
|
|
||||||
isDragging: boolean;
|
|
||||||
/** Whether an animation is currently in progress */
|
|
||||||
isAnimating: boolean;
|
|
||||||
/** Whether the current size was set by an animation */
|
|
||||||
sizeSetByAnimation: boolean;
|
|
||||||
/** Whether the user has interacted with the splitter */
|
|
||||||
hasUserInteracted: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const AppSplitterContext = createContext<{
|
const AppSplitterContext = createContext<{
|
||||||
splitterAutoSaveId: string;
|
splitterAutoSaveId: string;
|
||||||
containerRef: React.RefObject<HTMLDivElement | null>;
|
containerRef: React.RefObject<HTMLDivElement | null>;
|
||||||
|
@ -227,6 +67,26 @@ const AppSplitterWrapper = forwardRef<AppSplitterRef, IAppSplitterProps>(
|
||||||
const splitterAutoSaveId = createAutoSaveId(autoSaveId);
|
const splitterAutoSaveId = createAutoSaveId(autoSaveId);
|
||||||
|
|
||||||
const { splitterAutoSaveId: parentSplitterAutoSaveId } = useAppSplitterContext();
|
const { splitterAutoSaveId: parentSplitterAutoSaveId } = useAppSplitterContext();
|
||||||
|
const {
|
||||||
|
leftPanelMinSize,
|
||||||
|
preserveSide,
|
||||||
|
rightPanelMinSize,
|
||||||
|
leftPanelMaxSize,
|
||||||
|
rightPanelMaxSize,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
// Calculate initialValue using custom hook
|
||||||
|
const initialValue = useInitialValue({
|
||||||
|
initialLayout: props.initialLayout,
|
||||||
|
split,
|
||||||
|
preserveSide,
|
||||||
|
leftPanelMinSize,
|
||||||
|
rightPanelMinSize,
|
||||||
|
leftPanelMaxSize,
|
||||||
|
rightPanelMaxSize,
|
||||||
|
containerRef,
|
||||||
|
mounted,
|
||||||
|
});
|
||||||
|
|
||||||
useMount(async () => {
|
useMount(async () => {
|
||||||
//we need to wait for the parent to be mounted and the container to be sized
|
//we need to wait for the parent to be mounted and the container to be sized
|
||||||
|
@ -255,6 +115,7 @@ const AppSplitterWrapper = forwardRef<AppSplitterRef, IAppSplitterProps>(
|
||||||
containerRef={containerRef}
|
containerRef={containerRef}
|
||||||
splitterAutoSaveId={splitterAutoSaveId}
|
splitterAutoSaveId={splitterAutoSaveId}
|
||||||
split={split}
|
split={split}
|
||||||
|
calculatedInitialValue={initialValue}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -271,17 +132,17 @@ AppSplitterWrapper.displayName = 'AppSplitterWrapper';
|
||||||
|
|
||||||
const AppSplitterBase = forwardRef<
|
const AppSplitterBase = forwardRef<
|
||||||
AppSplitterRef,
|
AppSplitterRef,
|
||||||
Omit<IAppSplitterProps, 'autoSaveId' | 'style' | 'className'> & {
|
Omit<IAppSplitterProps, 'autoSaveId' | 'style' | 'className' | 'initialLayout'> & {
|
||||||
isVertical: boolean;
|
isVertical: boolean;
|
||||||
containerRef: React.RefObject<HTMLDivElement | null>;
|
containerRef: React.RefObject<HTMLDivElement | null>;
|
||||||
splitterAutoSaveId: string;
|
splitterAutoSaveId: string;
|
||||||
|
calculatedInitialValue: number | null;
|
||||||
}
|
}
|
||||||
>(
|
>(
|
||||||
(
|
(
|
||||||
{
|
{
|
||||||
leftChildren,
|
leftChildren,
|
||||||
rightChildren,
|
rightChildren,
|
||||||
initialLayout,
|
|
||||||
defaultLayout,
|
defaultLayout,
|
||||||
leftPanelMinSize = 0,
|
leftPanelMinSize = 0,
|
||||||
rightPanelMinSize = 0,
|
rightPanelMinSize = 0,
|
||||||
|
@ -299,6 +160,7 @@ const AppSplitterBase = forwardRef<
|
||||||
splitterAutoSaveId,
|
splitterAutoSaveId,
|
||||||
containerRef,
|
containerRef,
|
||||||
split = 'vertical',
|
split = 'vertical',
|
||||||
|
calculatedInitialValue,
|
||||||
},
|
},
|
||||||
ref
|
ref
|
||||||
) => {
|
) => {
|
||||||
|
@ -344,63 +206,10 @@ const AppSplitterBase = forwardRef<
|
||||||
return result;
|
return result;
|
||||||
}, [defaultLayout, split, preserveSide]);
|
}, [defaultLayout, split, preserveSide]);
|
||||||
|
|
||||||
const initialValue = useCallback(() => {
|
|
||||||
if (initialLayout) {
|
|
||||||
const [leftValue, rightValue] = initialLayout;
|
|
||||||
const containerSize =
|
|
||||||
split === 'vertical'
|
|
||||||
? (containerRef.current?.offsetWidth ?? 0)
|
|
||||||
: (containerRef.current?.offsetHeight ?? 0);
|
|
||||||
|
|
||||||
if (preserveSide === 'left' && leftValue === 'auto') {
|
|
||||||
return containerSize;
|
|
||||||
}
|
|
||||||
if (preserveSide === 'right' && rightValue === 'auto') {
|
|
||||||
return containerSize;
|
|
||||||
}
|
|
||||||
const preserveValue = preserveSide === 'left' ? leftValue : rightValue;
|
|
||||||
const result = sizeToPixels(preserveValue, containerSize);
|
|
||||||
|
|
||||||
// Check if the result is within min/max bounds
|
|
||||||
if (containerSize > 0) {
|
|
||||||
const minSize =
|
|
||||||
preserveSide === 'left'
|
|
||||||
? sizeToPixels(leftPanelMinSize, containerSize)
|
|
||||||
: sizeToPixels(rightPanelMinSize, containerSize);
|
|
||||||
const maxSize =
|
|
||||||
preserveSide === 'left'
|
|
||||||
? leftPanelMaxSize
|
|
||||||
? sizeToPixels(leftPanelMaxSize, containerSize)
|
|
||||||
: containerSize
|
|
||||||
: rightPanelMaxSize
|
|
||||||
? sizeToPixels(rightPanelMaxSize, containerSize)
|
|
||||||
: containerSize;
|
|
||||||
|
|
||||||
// If the result is outside the min/max bounds, use the default value
|
|
||||||
if (result < minSize || result > maxSize) {
|
|
||||||
return defaultValue();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
return defaultValue();
|
|
||||||
}, [
|
|
||||||
initialLayout,
|
|
||||||
split,
|
|
||||||
preserveSide,
|
|
||||||
leftPanelMinSize,
|
|
||||||
rightPanelMinSize,
|
|
||||||
leftPanelMaxSize,
|
|
||||||
rightPanelMaxSize,
|
|
||||||
defaultValue,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Load saved layout from cookies
|
// Load saved layout from cookies
|
||||||
|
|
||||||
const [savedLayout, setSavedLayout] = useCookieState<number | null>(splitterAutoSaveId, {
|
const [savedLayout, setSavedLayout] = useCookieState<number | null>(splitterAutoSaveId, {
|
||||||
defaultValue,
|
defaultValue,
|
||||||
initialValue,
|
initialValue: () => calculatedInitialValue ?? defaultValue(),
|
||||||
});
|
});
|
||||||
|
|
||||||
// ================================
|
// ================================
|
||||||
|
@ -814,10 +623,7 @@ const AppSplitterBase = forwardRef<
|
||||||
|
|
||||||
const showSplitter = !leftHidden && !rightHidden;
|
const showSplitter = !leftHidden && !rightHidden;
|
||||||
|
|
||||||
const sizes = useMemo<[string | number, string | number]>(
|
const sizes: [string | number, string | number] = [`${leftSize}px`, `${rightSize}px`];
|
||||||
() => [`${leftSize}px`, `${rightSize}px`],
|
|
||||||
[leftSize, rightSize]
|
|
||||||
);
|
|
||||||
|
|
||||||
const content = (
|
const content = (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -0,0 +1,165 @@
|
||||||
|
export type PanelSize = `${number}px` | `${number}%` | 'auto' | number;
|
||||||
|
export type LayoutSize = [PanelSize, PanelSize];
|
||||||
|
|
||||||
|
export interface IAppSplitterProps {
|
||||||
|
/** Content to display in the left panel */
|
||||||
|
leftChildren: React.ReactNode;
|
||||||
|
|
||||||
|
/** Content to display in the right panel */
|
||||||
|
rightChildren: React.ReactNode;
|
||||||
|
|
||||||
|
/** Unique identifier for auto-saving layout to cookies */
|
||||||
|
autoSaveId: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initial preserved-side size from cookies (in pixels)
|
||||||
|
*/
|
||||||
|
initialLayout?: LayoutSize;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default layout configuration as [left, right] sizes
|
||||||
|
* Can be numbers (pixels), percentages (strings like "50%"), or "auto"
|
||||||
|
*/
|
||||||
|
defaultLayout: LayoutSize;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minimum size for the left panel
|
||||||
|
* Can be a number (pixels) or string (percentage)
|
||||||
|
* @default 0
|
||||||
|
*/
|
||||||
|
leftPanelMinSize?: number | string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minimum size for the right panel
|
||||||
|
* Can be a number (pixels) or string (percentage)
|
||||||
|
* @default 0
|
||||||
|
*/
|
||||||
|
rightPanelMinSize?: number | string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum size for the left panel
|
||||||
|
* Can be a number (pixels) or string (percentage)
|
||||||
|
* If not specified, defaults to container size
|
||||||
|
*/
|
||||||
|
leftPanelMaxSize?: number | string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum size for the right panel
|
||||||
|
* Can be a number (pixels) or string (percentage)
|
||||||
|
* If not specified, defaults to container size
|
||||||
|
*/
|
||||||
|
rightPanelMaxSize?: number | string;
|
||||||
|
|
||||||
|
/** Additional CSS classes for the container */
|
||||||
|
className?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the splitter can be resized by dragging
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
allowResize?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Split direction
|
||||||
|
* @default 'vertical'
|
||||||
|
*/
|
||||||
|
split?: 'vertical' | 'horizontal';
|
||||||
|
|
||||||
|
/** Additional CSS classes for the splitter element */
|
||||||
|
splitterClassName?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Which side to preserve when resizing
|
||||||
|
* 'left' - left panel maintains its size, right panel adjusts
|
||||||
|
* 'right' - right panel maintains its size, left panel adjusts
|
||||||
|
*/
|
||||||
|
preserveSide: 'left' | 'right';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to hide the right panel completely
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
rightHidden?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to hide the left panel completely
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
leftHidden?: boolean;
|
||||||
|
|
||||||
|
/** Inline styles for the container */
|
||||||
|
style?: React.CSSProperties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to hide the splitter handle
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
hideSplitter?: boolean;
|
||||||
|
|
||||||
|
/** Additional CSS classes for the left panel */
|
||||||
|
leftPanelClassName?: string;
|
||||||
|
|
||||||
|
/** Additional CSS classes for the right panel */
|
||||||
|
rightPanelClassName?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to clear saved layout from cookies on initialization
|
||||||
|
* Can be a boolean or a function that returns a boolean based on preserved side value and container width
|
||||||
|
*/
|
||||||
|
bustStorageOnInit?: boolean | ((preservedSideValue: number | null, refSize: number) => boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for the AppSplitter component
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ref interface for controlling the AppSplitter imperatively
|
||||||
|
*/
|
||||||
|
export interface AppSplitterRef {
|
||||||
|
/**
|
||||||
|
* Animate a panel to a specific width
|
||||||
|
* @param width - Target width (pixels or percentage)
|
||||||
|
* @param side - Which side to animate
|
||||||
|
* @param duration - Animation duration in milliseconds
|
||||||
|
*/
|
||||||
|
animateWidth: (
|
||||||
|
width: string | number,
|
||||||
|
side: 'left' | 'right',
|
||||||
|
duration?: number
|
||||||
|
) => Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the split sizes programmatically
|
||||||
|
* @param sizes - [left, right] sizes as pixels or percentages
|
||||||
|
*/
|
||||||
|
setSplitSizes: (sizes: [string | number, string | number]) => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a side is closed (hidden or 0px)
|
||||||
|
* @param side - Which side to check
|
||||||
|
*/
|
||||||
|
isSideClosed: (side: 'left' | 'right') => boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current sizes in pixels
|
||||||
|
* @returns [leftSize, rightSize] in pixels
|
||||||
|
*/
|
||||||
|
getSizesInPixels: () => [number, number];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal state interface for the splitter
|
||||||
|
*/
|
||||||
|
export interface SplitterState {
|
||||||
|
/** Current container size in pixels */
|
||||||
|
containerSize: number;
|
||||||
|
/** Whether the user is currently dragging the splitter */
|
||||||
|
isDragging: boolean;
|
||||||
|
/** Whether an animation is currently in progress */
|
||||||
|
isAnimating: boolean;
|
||||||
|
/** Whether the current size was set by an animation */
|
||||||
|
sizeSetByAnimation: boolean;
|
||||||
|
/** Whether the user has interacted with the splitter */
|
||||||
|
hasUserInteracted: boolean;
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
export { AppSplitter, type AppSplitterRef } from './AppSplitter';
|
export { AppSplitter } from './AppSplitter';
|
||||||
|
export type { AppSplitterRef, IAppSplitterProps, LayoutSize, PanelSize } from './AppSplitter.types';
|
||||||
export { AppSplitterProvider, useAppSplitterContext } from './AppSplitterProvider';
|
export { AppSplitterProvider, useAppSplitterContext } from './AppSplitterProvider';
|
||||||
export { createAutoSaveId } from './helpers';
|
export { createAutoSaveId } from './helpers';
|
||||||
export { Panel } from './Panel';
|
export { Panel } from './Panel';
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { sizeToPixels } from './helpers';
|
||||||
|
|
||||||
|
interface UseInitialValueProps {
|
||||||
|
initialLayout?: (`${number}px` | `${number}%` | 'auto' | number)[];
|
||||||
|
split: 'vertical' | 'horizontal';
|
||||||
|
preserveSide: 'left' | 'right';
|
||||||
|
leftPanelMinSize?: number | string;
|
||||||
|
rightPanelMinSize?: number | string;
|
||||||
|
leftPanelMaxSize?: number | string;
|
||||||
|
rightPanelMaxSize?: number | string;
|
||||||
|
containerRef: React.RefObject<HTMLDivElement | null>;
|
||||||
|
mounted: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom hook to calculate the initial value for the AppSplitter component
|
||||||
|
* based on the initial layout and container size constraints
|
||||||
|
*/
|
||||||
|
export const useInitialValue = ({
|
||||||
|
initialLayout,
|
||||||
|
split,
|
||||||
|
preserveSide,
|
||||||
|
leftPanelMinSize = 0,
|
||||||
|
rightPanelMinSize = 0,
|
||||||
|
leftPanelMaxSize,
|
||||||
|
rightPanelMaxSize,
|
||||||
|
containerRef,
|
||||||
|
mounted,
|
||||||
|
}: UseInitialValueProps): number | null => {
|
||||||
|
return useMemo(() => {
|
||||||
|
if (initialLayout) {
|
||||||
|
const [leftValue, rightValue] = initialLayout;
|
||||||
|
const containerSize =
|
||||||
|
split === 'vertical'
|
||||||
|
? (containerRef.current?.offsetWidth ?? 0)
|
||||||
|
: (containerRef.current?.offsetHeight ?? 0);
|
||||||
|
|
||||||
|
if (preserveSide === 'left' && leftValue === 'auto') {
|
||||||
|
return containerSize;
|
||||||
|
}
|
||||||
|
if (preserveSide === 'right' && rightValue === 'auto') {
|
||||||
|
return containerSize;
|
||||||
|
}
|
||||||
|
const preserveValue = preserveSide === 'left' ? leftValue : rightValue;
|
||||||
|
const result = sizeToPixels(preserveValue, containerSize);
|
||||||
|
|
||||||
|
// Check if the result is within min/max bounds
|
||||||
|
if (containerSize > 0) {
|
||||||
|
const minSize =
|
||||||
|
preserveSide === 'left'
|
||||||
|
? sizeToPixels(leftPanelMinSize, containerSize)
|
||||||
|
: sizeToPixels(rightPanelMinSize, containerSize);
|
||||||
|
const maxSize =
|
||||||
|
preserveSide === 'left'
|
||||||
|
? leftPanelMaxSize
|
||||||
|
? sizeToPixels(leftPanelMaxSize, containerSize)
|
||||||
|
: containerSize
|
||||||
|
: rightPanelMaxSize
|
||||||
|
? sizeToPixels(rightPanelMaxSize, containerSize)
|
||||||
|
: containerSize;
|
||||||
|
|
||||||
|
// If the result is outside the min/max bounds, return null to use default value
|
||||||
|
if (result < minSize || result > maxSize) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}, [
|
||||||
|
mounted,
|
||||||
|
initialLayout,
|
||||||
|
split,
|
||||||
|
preserveSide,
|
||||||
|
leftPanelMinSize,
|
||||||
|
rightPanelMinSize,
|
||||||
|
leftPanelMaxSize,
|
||||||
|
rightPanelMaxSize,
|
||||||
|
]);
|
||||||
|
};
|
Loading…
Reference in New Issue