import { DemoBox } from "../../src/components/demo/demo-box";
import { FeaturePageHeader } from "../../src/components/docs-page/feature-page-header";

<FeaturePageHeader
    title="Checkboxes"
    subtitle="API for multi-selecting items with checkboxes"
    feature="checkboxes"
/>

```ts jsx
import type { Meta } from "@storybook/react";
import React from "react";
import {
  checkboxesFeature,
  hotkeysCoreFeature,
  selectionFeature,
  syncDataLoaderFeature,
} from "@headless-tree/core";
import { useTree } from "@headless-tree/react";
import cx from "classnames";
import { DemoItem, createDemoData } from "../utils/data";

const meta = {
  title: "React/Checkboxes/General",
  tags: ["feature/checkbox", "checkbox", "homepage"],
} satisfies Meta;

export default meta;

const { syncDataLoader } = createDemoData();

// story-start
export const General = () => {
  const tree = useTree<DemoItem>({
    rootItemId: "root",
    initialState: { expandedItems: ["fruit"], checkedItems: ["banana"] },
    getItemName: (item) => item.getItemData().name,
    isItemFolder: (item) => !!item.getItemData().children,
    dataLoader: syncDataLoader,
    indent: 20,
    features: [
      syncDataLoaderFeature,
      selectionFeature,
      checkboxesFeature,
      hotkeysCoreFeature,
    ],
  });

  return (
    <>
      <div {...tree.getContainerProps()} className="tree">
        {tree.getItems().map((item) => (
          <div className="outeritem" key={item.getId()}>
            <button
              {...item.getProps()}
              style={{ paddingLeft: `${item.getItemMeta().level * 20}px` }}
            >
              <div
                className={cx("treeitem", {
                  focused: item.isFocused(),
                  expanded: item.isExpanded(),
                  selected: item.isSelected(),
                  folder: item.isFolder(),
                })}
              >
                {item.getItemName()}
              </div>
            </button>
            <input type="checkbox" {...item.getCheckboxProps()} />
          </div>
        ))}
      </div>
      <pre>{JSON.stringify(tree.getState().checkedItems)}</pre>
    </>
  );
};
```


The checkboxes feature allows users to multi-select items in a more permanent way than the normal
selection feature, and visualize that state with checkboxes.

To use the feature, add the `checkboxesFeature` to your feature array and render a checkbox component
in each of your tree item renderers with `<input type="checkbox" {...item.getCheckboxProps()} />`.

As with other tree states, you can manage the checkbox state yourself with the config variable `checkedItems`
and the state change handler `setCheckedItems`, or let Headless Tree manage this state for your.

## `propagateCheckedState`

You can configure the behavior of clicking the checkbox on folders with the `propagateCheckedState` option. 
If enabled, toggling the checkbox state of a folder will toggle the state of all its leafs. The folder itself
will not be added to the `checkedItems` state, but its checked state will be inferred from the state of
its leafs, being checked if all leafs are checked, and indeterminate if some leafs are checked
and some are not. You can also force folders to be independently checkable with `canCheckFolders=true`,
though be aware that this will mean that folders will receive an explicit `checked` state and will not
always be directly inferred from their leafs.

:::warning

If you use asynchronous trees with `propagateCheckedState=true`, checking an item will trigger loading
children recursively for all descendants.

:::

Below, you can experiment with the behavior for various combinations of `propagateCheckedState` and `canCheckFolders`:

```ts jsx
/* eslint-disable jsx-a11y/label-has-associated-control */
import type { Meta } from "@storybook/react";
import React, { useState } from "react";
import {
  checkboxesFeature,
  hotkeysCoreFeature,
  selectionFeature,
  syncDataLoaderFeature,
} from "@headless-tree/core";
import { useTree } from "@headless-tree/react";
import cx from "classnames";
import { DemoItem, createDemoData } from "../utils/data";

const meta = {
  title: "React/Checkboxes/Configurability",
  tags: ["feature/checkbox", "checkbox"],
} satisfies Meta;

export default meta;

const { syncDataLoader } = createDemoData();

// story-start
export const Configurability = () => {
  const [canCheckFolders, setCanCheckFolders] = useState(false);
  const [propagateCheckedState, setPropagateCheckedState] = useState(true);
  const tree = useTree<DemoItem>({
    rootItemId: "root",
    initialState: { expandedItems: ["fruit", "berries"] },
    getItemName: (item) => item.getItemData().name,
    isItemFolder: (item) => !!item.getItemData().children,
    dataLoader: syncDataLoader,
    indent: 20,
    canCheckFolders,
    propagateCheckedState,
    features: [
      syncDataLoaderFeature,
      selectionFeature,
      checkboxesFeature,
      hotkeysCoreFeature,
    ],
  });

  return (
    <>
      <div>
        <label>
          <input
            type="checkbox"
            checked={canCheckFolders}
            onChange={(e) => setCanCheckFolders(e.target.checked)}
          />
          Can check folders
        </label>
        <label>
          <input
            type="checkbox"
            checked={propagateCheckedState}
            onChange={(e) => setPropagateCheckedState(e.target.checked)}
          />
          Propagate checked state
        </label>
      </div>
      <div {...tree.getContainerProps()} className="tree">
        {tree.getItems().map((item) => (
          <div className="outeritem" key={item.getId()}>
            <button
              {...item.getProps()}
              style={{ paddingLeft: `${item.getItemMeta().level * 20}px` }}
            >
              <div
                className={cx("treeitem", {
                  focused: item.isFocused(),
                  expanded: item.isExpanded(),
                  selected: item.isSelected(),
                  folder: item.isFolder(),
                })}
              >
                {item.getItemName()}
              </div>
            </button>
            <input type="checkbox" {...item.getCheckboxProps()} />
          </div>
        ))}
      </div>
      <pre>{JSON.stringify(tree.getState().checkedItems)}</pre>
    </>
  );
};
```

## Asynchronous checkbox propagation

You can also use checkbox propagation with trees that use async data loaders. If a checkbox is clicked, all nested
descendants IDs that are not yet loaded will be loaded via the `getChildren` or `getChildrenWithData` APIs, and
their checkbox state will be updated accordingly. While they are loading, the state variable `loadingCheckPropagationItems`
can be used to render a loading spinner on checkboxes that are loading.

```ts jsx
/* eslint-disable jsx-a11y/label-has-associated-control */
import type { Meta } from "@storybook/react";
import React, { useState } from "react";
import {
  asyncDataLoaderFeature,
  checkboxesFeature,
  hotkeysCoreFeature,
  selectionFeature,
} from "@headless-tree/core";
import { useTree } from "@headless-tree/react";
import cx from "classnames";
import { DemoItem, createDemoData } from "../utils/data";

const meta = {
  title: "React/Checkboxes/Async Configurability",
  tags: ["feature/checkbox", "checkbox"],
} satisfies Meta;

export default meta;

const { asyncDataLoader } = createDemoData();

// story-start
export const AsyncConfigurability = () => {
  const [canCheckFolders, setCanCheckFolders] = useState(false);
  const [propagateCheckedState, setPropagateCheckedState] = useState(true);
  const tree = useTree<DemoItem>({
    rootItemId: "root",
    // initialState: { expandedItems: ["fruit", "berries"] },
    getItemName: (item) => item.getItemData().name,
    isItemFolder: (item) => !!item.getItemData().children,
    dataLoader: asyncDataLoader,
    createLoadingItemData: () => ({ name: "Loading..." }),
    indent: 20,
    canCheckFolders,
    propagateCheckedState,
    features: [
      asyncDataLoaderFeature,
      selectionFeature,
      checkboxesFeature,
      hotkeysCoreFeature,
    ],
  });

  return (
    <>
      <div>
        <label>
          <input
            type="checkbox"
            checked={canCheckFolders}
            onChange={(e) => setCanCheckFolders(e.target.checked)}
          />
          Can check folders
        </label>
        <label>
          <input
            type="checkbox"
            checked={propagateCheckedState}
            onChange={(e) => setPropagateCheckedState(e.target.checked)}
          />
          Propagate checked state
        </label>
      </div>
      <div {...tree.getContainerProps()} className="tree">
        {tree.getItems().map((item) => (
          <div className="outeritem" key={item.getId()}>
            <button
              {...item.getProps()}
              style={{ paddingLeft: `${item.getItemMeta().level * 20}px` }}
            >
              <div
                className={cx("treeitem", {
                  focused: item.isFocused(),
                  expanded: item.isExpanded(),
                  selected: item.isSelected(),
                  folder: item.isFolder(),
                })}
              >
                {item.getItemName()}
              </div>
            </button>
            {!tree
              .getState()
              .loadingCheckPropagationItems.includes(item.getId()) ? (
              <input type="checkbox" {...item.getCheckboxProps()} />
            ) : (
              <>Loading</>
            )}
          </div>
        ))}
      </div>
      <pre>Checked Items: {JSON.stringify(tree.getState().checkedItems)}</pre>
      <pre>
        Loading Item Children:{" "}
        {JSON.stringify(tree.getState().loadingItemChildrens)}
      </pre>
      <pre>
        Loading Item Data: {JSON.stringify(tree.getState().loadingItemData)}
      </pre>
      <pre>
        Loading Checkbox propagation:{" "}
        {JSON.stringify(tree.getState().loadingCheckPropagationItems)}
      </pre>
    </>
  );
};
```

## Further customizing checkbox behavior

Similar to other Headless Tree features, you can customize feature behavior by overwriting any of the publicly
exposed methods. Write a custom feature like so:

```ts
const checkboxOverride: FeatureImplementation<DemoItem> = {
  itemInstance: {
    toggleCheckedState: ({ item }) => {
      // Custom logic to toggle the checked state of an item
    },
    
    getCheckedState: ({ tree, item }) => {
      // Custom logic to determine the checked state of an item
      return CheckedState.Indeterminate;
    },
  },
};
```

Then include it in your tree configuration:

```ts
const tree = createTree({
  features: [...otherFeatures, checkboxesFeature, checkboxOverride],
  // other tree config
});
```

You can find some samples below. In the select input below, you can choose between several samples.

```ts jsx
import type { Meta } from "@storybook/react";
import React from "react";
import {
  CheckedState,
  type FeatureImplementation,
  type TreeInstance,
  checkboxesFeature,
  hotkeysCoreFeature,
  selectionFeature,
  syncDataLoaderFeature,
} from "@headless-tree/core";
import { useTree } from "@headless-tree/react";
import cx from "classnames";
import { DemoItem, createDemoData } from "../utils/data";

const meta = {
  title: "React/Checkboxes/Custom Behavior/Checked State As Radio Buttons",
  tags: ["feature/checkbox", "checkbox", "checkbox/customizability"],
} satisfies Meta;

export default meta;

const { syncDataLoader } = createDemoData();

// story-start
const getAllLoadedDescendants = <T,>(
  tree: TreeInstance<T>,
  itemId: string,
): string[] => {
  if (!tree.getConfig().isItemFolder(tree.getItemInstance(itemId))) {
    return [itemId];
  }
  return tree
    .retrieveChildrenIds(itemId)
    .map((child) => getAllLoadedDescendants(tree, child))
    .flat();
};

const checkboxOverride: FeatureImplementation<DemoItem> = {
  itemInstance: {
    toggleCheckedState: ({ item }) => {
      if (item.getCheckedState() === CheckedState.Checked) {
        item.setUnchecked();
      } else {
        // uncheck all siblings
        item
          .getParent()
          ?.getChildren()
          .forEach((child) => child.setUnchecked());

        item.setChecked();
      }
    },
  },
};

export const CheckedStateAsRadioButtons = () => {
  const tree = useTree<DemoItem>({
    rootItemId: "root",
    initialState: {
      expandedItems: ["fruit", "berries", "red"],
      checkedItems: ["fruit", "banana", "berries", "strawberry"],
    },
    getItemName: (item) => item.getItemData().name,
    isItemFolder: (item) => !!item.getItemData().children,
    dataLoader: syncDataLoader,
    canCheckFolders: true,
    propagateCheckedState: false,
    indent: 20,
    features: [
      syncDataLoaderFeature,
      selectionFeature,
      checkboxesFeature,
      hotkeysCoreFeature,
      checkboxOverride,
    ],
  });

  return (
    <>
      <p className="description">
        In this sample, a custom "toggleCheckedState" implementation is used to
        enforce that for each folder, only one child can be checked.
      </p>

      <div {...tree.getContainerProps()} className="tree">
        {tree.getItems().map((item) => (
          <div className="outeritem" key={item.getId()}>
            <button
              {...item.getProps()}
              style={{ paddingLeft: `${item.getItemMeta().level * 20}px` }}
            >
              <div
                className={cx("treeitem", {
                  focused: item.isFocused(),
                  expanded: item.isExpanded(),
                  selected: item.isSelected(),
                  folder: item.isFolder(),
                })}
              >
                {item.getItemName()}
              </div>
            </button>
            <input type="checkbox" {...item.getCheckboxProps()} />
          </div>
        ))}
      </div>

      <pre>{JSON.stringify(tree.getState().checkedItems)}</pre>
    </>
  );
};
```