use-roving-index

use-roving-index

roving tabindex 키보드 내비게이션 패턴을 구현하는 훅이에요.

출처: 문서

본문

사용법 (Usage)

use-roving-index는 roving tabindex 키보드 내비게이션 패턴을 구현해요. 포커스 가능한 엘리먼트 그룹에서 하나의 엘리먼트만 tabIndex={0}(Tab 키로 도달 가능)을 가지며, 나머지는 모두 tabIndex={-1}이에요. 화살표 키로 그룹 안의 항목 사이를 이동할 수 있어요.

import { Button, Group } from '@mantine/core';
import { useRovingIndex } from '@mantine/hooks';

const items = ['Bold', 'Italic', 'Underline', 'Strikethrough', 'Code'];

function Demo() {
  const { getItemProps } = useRovingIndex({
    total: items.length,
    orientation: 'horizontal',
    loop: true,
  });

  return (

      {items.map((item, index) => (

          {item}

      ))}

  );
}

방향 (Orientation)

내비게이션에 사용할 화살표 키를 제어하려면 orientation을 설정해요.

  • 'horizontal' (default) – ArrowLeft/ArrowRight
  • 'vertical' – ArrowUp/ArrowDown
  • 'both' – 네 방향 화살표 키 모두
import { Stack, UnstyledButton } from '@mantine/core';
import { useRovingIndex } from '@mantine/hooks';

const items = ['General', 'Account', 'Security', 'Notifications', 'Privacy'];

function Demo() {
  const { getItemProps, focusedIndex } = useRovingIndex({
    total: items.length,
    orientation: 'vertical',
    loop: true,
  });

  return (

      {items.map((item, index) => (

          {item}

      ))}

  );
}

그리드 내비게이션 (Grid navigation)

2D 그리드 내비게이션을 활성화하려면 columns를 설정해요. ArrowLeft/ArrowRight는 한 행 안에서 이동하고, ArrowUp/ArrowDown은 열 위치를 유지한 채 행 사이를 이동해요. 내비게이션은 그리드 경계에서 멈춰요. Ctrl+Home/Ctrl+End로 그리드의 첫/마지막 항목으로 이동하고, Home/End로 현재 행의 첫/마지막 항목으로 이동할 수 있어요.

import { SimpleGrid, UnstyledButton } from '@mantine/core';
import { useRovingIndex } from '@mantine/hooks';

function Demo() {
  const total = 9;
  const columns = 3;

  const { getItemProps, focusedIndex } = useRovingIndex({
    total,
    columns,
  });

  return (

      {Array.from({ length: total }, (_, index) => (

          Cell {index + 1}

      ))}

  );
}

비활성 항목 (Disabled items)

항목을 비활성으로 표시하려면 isItemDisabled 콜백을 사용해요. 비활성 항목은 키보드 내비게이션에서 건너뛰어져요. 처음에 포커스된 항목이 비활성이면, 대신 첫 번째 비활성이 아닌 항목이 포커스를 받아요.

import { Button, Group } from '@mantine/core';
import { useRovingIndex } from '@mantine/hooks';

const items = ['Cut', 'Copy', 'Paste', 'Delete', 'Select All'];
const disabledIndices = new Set([1, 3]);

function Demo() {
  const { getItemProps } = useRovingIndex({
    total: items.length,
    orientation: 'horizontal',
    loop: true,
    isItemDisabled: (index) => disabledIndices.has(index),
  });

  return (

      {items.map((item, index) => (

          {item}

      ))}

  );
}

순환 (Loop)

기본적으로 내비게이션은 경계에서 순환해요(loop가 true). 첫 항목과 마지막 항목에서 멈추려면 loop={false}로 설정해요.

import { useState } from 'react';
import { Button, Checkbox, Group, Stack } from '@mantine/core';
import { useRovingIndex } from '@mantine/hooks';

const items = ['First', 'Second', 'Third', 'Fourth', 'Fifth'];

function Demo() {
  const [loop, setLoop] = useState(true);
  const { getItemProps } = useRovingIndex({
    total: items.length,
    orientation: 'horizontal',
    loop,
  });

  return (

       setLoop(event.currentTarget.checked)}
      />

        {items.map((item, index) => (

            {item}

        ))}

  );
}

제어 모드 (Controlled mode)

focusedIndex와 onFocusChange로 포커스된 인덱스를 외부에서 제어할 수 있어요.

import { useState } from 'react';
import { useRovingIndex } from '@mantine/hooks';

function Demo() {
  const [focusedIndex, setFocusedIndex] = useState(0);
  const { getItemProps } = useRovingIndex({
    total: 5,
    focusedIndex,
    onFocusChange: setFocusedIndex,
  });

  // ...
}

activateOnFocus

키보드 내비게이션으로 항목이 포커스를 받을 때 자동으로 클릭하게 하려면 activateOnFocus를 true로 설정해요. 포커스와 선택이 동기화되어야 하는 탭 같은 인터페이스에 유용해요.

import { useRovingIndex } from '@mantine/hooks';

function Demo() {
  const { getItemProps } = useRovingIndex({
    total: 5,
    activateOnFocus: true,
  });

  // ...
}

RTL 지원 (RTL support)

오른쪽에서 왼쪽으로 읽는 레이아웃에서 ArrowLeft/ArrowRight 동작을 바꾸려면 dir="rtl"을 설정해요.

import { useRovingIndex } from '@mantine/hooks';

function Demo() {
  const { getItemProps } = useRovingIndex({
    total: 5,
    dir: 'rtl',
  });

  // ...
}

정의 (Definition)

export interface UseRovingIndexInput {
  /** Total number of items in the group */
  total: number;

  /** Which arrow keys navigate, `'horizontal'` by default */
  orientation?: 'horizontal' | 'vertical' | 'both';

  /** Whether navigation wraps at boundaries, `true` by default */
  loop?: boolean;

  /** Text direction, `'ltr'` by default */
  dir?: 'rtl' | 'ltr';

  /** Whether to click element when it receives focus via keyboard, `false` by default */
  activateOnFocus?: boolean;

  /** Number of columns for grid (2D) navigation. When set, enables grid mode */
  columns?: number;

  /** Controlled focused index */
  focusedIndex?: number;

  /** Initial focused index for uncontrolled mode, first non-disabled item by default */
  initialIndex?: number;

  /** Called when focused index changes */
  onFocusChange?: (index: number) => void;

  /** Function to check if item at given index is disabled, `() => false` by default */
  isItemDisabled?: (index: number) => boolean;
}

export interface UseRovingIndexGetItemPropsInput {
  /** Index of the item in the group */
  index: number;

  /** Called when item is clicked */
  onClick?: React.MouseEventHandler;

  /** Called when keydown event fires on item */
  onKeyDown?: React.KeyboardEventHandler;
}

export interface UseRovingIndexReturnValue {
  /** Get props to spread on each navigable item */
  getItemProps: (options: UseRovingIndexGetItemPropsInput) => {
    tabIndex: 0 | -1;
    onKeyDown: React.KeyboardEventHandler;
    onClick: React.MouseEventHandler;
    ref: React.RefCallback;
  };

  /** Currently focused index */
  focusedIndex: number;

  /** Programmatically set focused index */
  setFocusedIndex: (index: number) => void;
}

function useRovingIndex(input: UseRovingIndexInput): UseRovingIndexReturnValue;

내보내는 타입 (Exported types)

UseRovingIndexInput, UseRovingIndexGetItemPropsInput, UseRovingIndexReturnValue 타입은 @mantine/hooks 패키지에서 내보내져요. 애플리케이션에서 임포트할 수 있어요.

import type {
  UseRovingIndexInput,
  UseRovingIndexGetItemPropsInput,
  UseRovingIndexReturnValue,
} from '@mantine/hooks';

더 알아보기 (Learn more)