import { useRef, useCallback, useState, useMemo } from "react";
import { ArgsTable, Story, Canvas, Meta } from "@storybook/addon-docs";
import useGridKeyboardNavigation from "../useGridKeyboardNavigation";
import { action } from "@storybook/addon-actions";
import cx from "classnames";
import { range } from "lodash-es";
import "./useGridKeyboardNavigation.stories.scss";
import Button from "../../../../src/components/Button/Button";

<Meta title="Hooks/useGridKeyboardNavigation" />

# useGridKeyboardNavigation

- [Overview](#overview)
- [Usage](#usage)
- [Arguments](#arguments)
- [Returns](#returns)
- [Feedback](#feedback)

## Overview

Used for accessible keyboard navigation. Useful for components rendering items that can be navigated and selected with a keyboard.

export const ELEMENT_WIDTH_PX = 72;
export const PADDING_PX = 24;

export const ON_CLICK = action("item selected");

<Canvas>
  <Story name="Overview">
    {() => {
      const ref = useRef(null);
      const disabledIndexes = [2, 4, 6];
      const [itemsCount, setItemsCount] = useState(15);
      const [numberOfItemsInLine, setNumberOfItemsInLine] = useState(4);
      const width = useMemo(() => numberOfItemsInLine * ELEMENT_WIDTH_PX + 2 * PADDING_PX, [numberOfItemsInLine]);
      const items = useMemo(() => range(itemsCount).map(num => `${num}.`), [itemsCount]);
      const getItemByIndex = useCallback(index => items[index], [items]);
      const { activeIndex, onSelectionAction } = useGridKeyboardNavigation({
        ref,
        numberOfItemsInLine,
        itemsCount,
        getItemByIndex,
        onItemClicked: ON_CLICK,
        disabledIndexes
      });
      const onClickByIndex = useCallback(index => () => onSelectionAction(index), [onSelectionAction]);
      return (
        <div>
          <div className="use-grid-keyboard-nav-comp-wrapper" style={{ width }} ref={ref} tabIndex={-1}>
            {items.map((item, index) => (
              <Button
                key={item}
                disabled={disabledIndexes.includes(index)}
                onClick={onClickByIndex(index)}
                kind={Button.kinds.SECONDARY}
                className={cx("use-grid-keyboard-nav-item", { "active-item": index === activeIndex })}
              >
                {item}
              </Button>
            ))}
          </div>
          <div className="use-grid-keyboard-nav-controls">
            <div>
              Items count:{" "}
              <input value={itemsCount} onChange={e => setItemsCount(e.target.value)} type="number" min={1} />
            </div>
            <div>
              Number of items in line:{" "}
              <input
                value={numberOfItemsInLine}
                onChange={e => setNumberOfItemsInLine(e.target.value)}
                type="number"
                min={1}
              />
            </div>
          </div>
        </div>
      );
    }}
  </Story>
</Canvas>

## Usage

<UsageGuidelines guidelines={["Use this hook when you want to add keyboard navigation to a grid-like component."]} />

## Arguments

<FunctionArguments>
  <FunctionArgument name="options" type="Object">
    <FunctionArgument
      name="ref"
      type="React.MutableRefObject<HTMLElement>"
      description={
        <>
          A React{" "}
          <a href="https://reactjs.org/docs/hooks-reference.html#useref" target="_blank" rel="noopener noreferrer">
            ref
          </a>{" "}
          object. The reference for the component that listens to keyboard. <br />
          <b>Important:</b> the referred element must have a <code>tabIndex={-1}</code> for the focus to work properly.
        </>
      }
      required
    />
    <FunctionArgument name="itemsCount" type="Number" description="The number of items." required />
    <FunctionArgument
      name="numberOfItemsInLine"
      type="Number"
      description="The number of items on each line of the grid."
      required
    />
    <FunctionArgument
      name="onItemClicked"
      type="(item, index) => void"
      description="The callback for selecting an item. It will be called when an active item is selected, for example with 'Enter'."
      required
    />
    <FunctionArgument
      name="getItemByIndex"
      type="(index) => item"
      description="A function which gets an index as a param, and returns the item on that index."
    />
    <FunctionArgument
      name="focusOnMount"
      type="boolean"
      description="If true, the referenced element will be focused when mounted."
    />
    <FunctionArgument
      name="focusItemIndexOnMount"
      type="number"
      description={
        <>
          Optional item index to focus when mounted. Only works with <code>options.focusOnMount</code>.
        </>
      }
    />
    <FunctionArgument
      name="disabledIndexes"
      type="number[]"
      description="Optional array of disabled indices, which will be skipped while navigating."
    />
  </FunctionArgument>
</FunctionArguments>

## Returns

<FunctionArguments>
  <FunctionArgument name="result" type="Object">
    <FunctionArgument name="activeIndex" type="number" description="The index of the currently active item." />
    <FunctionArgument
      name="onSelectionAction"
      type="(itemIndex) => void"
      description={
        <>
          A wrapper around the passed <code>onItemClicked</code> function. Use it as the handler for selecting items
          (e.g. <code>onClick</code>)
        </>
      }
    />
    <FunctionArgument
      name="isInitialActiveState"
      type="boolean"
      description={
        <>
          If true, the currently active element was due to an initial mounting index option. See{" "}
          <code>options.focusItemIndexOnMount</code>.
        </>
      }
    />
  </FunctionArgument>
</FunctionArguments>
