use-splitter

use-splitter

크기 조절 가능한 패널(splitter) 레이아웃을 관리하는 훅이에요. 키보드 접근성과 터치 지원을 포함한 리사이즈 핸들 동작을 제공해요.

출처: 문서

본문

사용법 (Usage)

useSplitter 훅에 패널 설정을 넘겨 두 개 이상의 패널을 만들 수 있어요. 각 패널의 defaultSize, min, max 등을 지정할 수 있어요:

import { useState } from 'react';
import { DotsSixVerticalIcon } from '@phosphor-icons/react';
import { Button, Group, Text } from '@mantine/core';
import { useSplitter } from '@mantine/hooks';

function Demo() {
  const [sizes, setSizes] = useState([50, 50]);

  const splitter = useSplitter({
    panels: [
      { defaultSize: 50, min: 20 },
      { defaultSize: 50, min: 20 },
    ],
    sizes,
    onSizeChange: setSizes,
  });

  return (
    <>

          Panel A ({Math.round(splitter.sizes[0] as number)}%)

          Panel B ({Math.round(splitter.sizes[1] as number)}%)

        Current sizes: [{sizes.map((s) => Math.round(s)).join(', ')}]

         setSizes([30, 70])}>30 / 70
         setSizes([50, 50])}>50 / 50
         setSizes([70, 30])}>70 / 30

  );
}

키보드 지원 (Keyboard support)

핸들은 WAI-ARIA Window Splitter 패턴을 따르고 있어요:

Key Action
ArrowLeft/ArrowRight Resize by step (horizontal)
ArrowUp/ArrowDown Resize by step (vertical)
Shift + Arrow Resize by shiftStep
Home Shrink panel before handle to its min
End Grow panel before handle to its max
Enter Toggle collapse of the smaller adjacent collapsible panel

터치 지원 (Touch support)

이 훅은 마우스와 터치를 모두 자동으로 처리하는 Pointer Events API를 사용해요. 터치 드래그가 스크롤로 해석되지 않도록 핸들 엘리먼트에 touch-action: none을 설정해요:

.handle {
  touch-action: none;
}

정의 (Definition)

/** A bare number/`%` is a flexible size, `px`/`rem` is a fixed size */
type SplitterPaneSize = number | `${number}%` | `${number}px` | `${number}rem`;

/** A bare number/`%` is a percentage of the container, `px`/`rem` is resolved to pixels */
type SplitterStep = number | `${number}%` | `${number}px` | `${number}rem`;

interface UseSplitterPanel {
  /** Initial size, a `number`/`%` is flexible, `px`/`rem` is fixed. A bare number is a percentage. */
  defaultSize: SplitterPaneSize;
  /** Minimum size in the same units as `defaultSize`, `0` by default */
  min?: SplitterPaneSize;
  /** Maximum size in the same units as `defaultSize`, no limit by default */
  max?: SplitterPaneSize;
  /** Whether this panel can be collapsed, `false` by default */
  collapsible?: boolean;
  /** Size below which the panel snaps to collapsed, defaults to `min` */
  collapseThreshold?: SplitterPaneSize;
}

/** Panel config resolved to numeric units (percent or pixels) */
interface UseSplitterResolvedPanel {
  defaultSize: number;
  min?: number;
  max?: number;
  collapsible?: boolean;
  collapseThreshold?: number;
}

type UseSplitterRedistributeFn = (input: {
  sizes: number[];
  panels: UseSplitterResolvedPanel[];
  handleIndex: number;
  delta: number;
}) => number[];

interface UseSplitterOptions {
  /** Panel configuration array (minimum 2 panels) */
  panels: UseSplitterPanel[];
  /** Layout direction, `'horizontal'` by default */
  orientation?: 'horizontal' | 'vertical';
  /** Controlled sizes, each value keeps the unit it was declared in */
  sizes?: SplitterPaneSize[];
  /** Called during resize with updated sizes, each value keeps its declared unit */
  onSizeChange?: (sizes: SplitterPaneSize[]) => void;
  /** Called when drag starts */
  onResizeStart?: (handleIndex: number) => void;
  /** Called when drag ends */
  onResizeEnd?: (handleIndex: number, sizes: SplitterPaneSize[]) => void;
  /** Called when a panel collapses or expands */
  onCollapseChange?: (panelIndex: number, collapsed: boolean) => void;
  /** How to borrow space from non-adjacent panels */
  redistribute?: 'nearest' | 'equal' | UseSplitterRedistributeFn;
  /** Keyboard step size, a `number`/`%` is a percentage, `px`/`rem` is pixels, `1` by default */
  step?: SplitterStep;
  /** Shift+arrow step size, a `number`/`%` is a percentage, `px`/`rem` is pixels, `10` by default */
  shiftStep?: SplitterStep;
  /** Text direction for keyboard nav, `'ltr'` by default */
  dir?: 'ltr' | 'rtl';
  /** Enable/disable the hook, `true` by default */
  enabled?: boolean;
}

interface UseSplitterReturnValue {
  /** Ref callback for the container element */
  ref: React.RefCallback<HTMLDivElement>;
  /** Current panel sizes, each value keeps the unit it was declared in */
  sizes: SplitterPaneSize[];
  /** Whether sizes are tracked in pixels because any pane size, `min`, `max`, `step`, `shiftStep`
   * or `collapseThreshold` uses a fixed `px`/`rem` unit */
  pixelMode: boolean;
  /** Which panels are currently collapsed */
  collapsed: boolean[];
  /** Index of handle being dragged, or -1 */
  activeHandle: number;
  /** Get props to spread on each resize handle */
  getHandleProps: (input: { index: number }) => HandleProps;
  /** Programmatically set sizes */
  setSizes: (sizes: SplitterPaneSize[]) => void;
  /** Collapse a panel */
  collapse: (panelIndex: number) => void;
  /** Expand a collapsed panel */
  expand: (panelIndex: number) => void;
  /** Toggle collapse of a panel */
  toggleCollapse: (panelIndex: number) => void;
}

function useSplitter(
  options: UseSplitterOptions
): UseSplitterReturnValue

내보내는 타입 (Exported types)

UseSplitterPanel, UseSplitterOptions, UseSplitterReturnValue, UseSplitterRedistributeInput, UseSplitterRedistributeFn, UseSplitterResolvedPanel, SplitterPaneSize 그리고 SplitterStep 타입은 @mantine/hooks 패키지에서 내보내져요:

import type {
  SplitterPaneSize,
  SplitterStep,
  UseSplitterPanel,
  UseSplitterOptions,
  UseSplitterReturnValue,
  UseSplitterRedistributeInput,
  UseSplitterRedistributeFn,
  UseSplitterResolvedPanel,
} from '@mantine/hooks';

더 알아보기 (Learn more)