import { DemoBox } from "../../src/components/demo/demo-box";

## Installation

To start using Headless Tree, install it to your project as dependency via

```bash
npm install @headless-tree/core @headless-tree/react
```

or

```bash
yarn add @headless-tree/core @headless-tree/react
```

The current main branch is always published to NPM under the `snapshot` tag, so you can use
`npm install @headless-tree/core@snapshot @headless-tree/react@snapshot` to get the latest snapshot deployment
if you want to experiment with the latest features.

:::tip[Headless Tree is available!]

Headless Tree is now available as Beta! If the library provides value to you, leaving [a star
on Github](https://github.com/lukasbach/headless-tree) would help a lot with the visibility of the project.

:::

## Quick Start

The React Bindings of Headless Tree provide a hook `useTree` that creates a tree instance, which you can use
to render your tree however you like. The instance provides methods to get the tree as a flat list of nodes,
that you can easily render with custom logic. It provides accessibility props to make sure that, even if the
tree is not rendered as hierarchical structure, screen readers still read it as such even if it is just rendered
as flat list.

You can also use other frameworks than React. Use the `createTree` method from the core package to create a tree
without a dependency on React. Documentation on that and bindings for other frameworks are planned for the future.

```ts jsx
import type { Meta } from "@storybook/react";
import React from "react";
import {
  hotkeysCoreFeature,
  selectionFeature,
  syncDataLoaderFeature,
} from "@headless-tree/core";
import { useTree } from "@headless-tree/react";
import cn from "classnames";

const meta = {
  title: "React/General/Simple Example",
  tags: ["homepage"],
} satisfies Meta;

export default meta;

// story-start
export const SimpleExample = () => {
  const tree = useTree<string>({
    initialState: { expandedItems: ["folder-1"] },
    rootItemId: "folder",
    getItemName: (item) => item.getItemData(),
    isItemFolder: (item) => !item.getItemData().endsWith("item"),
    dataLoader: {
      getItem: (itemId) => itemId,
      getChildren: (itemId) => [
        `${itemId}-1`,
        `${itemId}-2`,
        `${itemId}-3`,
        `${itemId}-1item`,
        `${itemId}-2item`,
      ],
    },
    indent: 20,
    features: [syncDataLoaderFeature, selectionFeature, hotkeysCoreFeature],
  });

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

The `useTree` hook (or the `createTree` function from the core package, if you are not using the React bindings)
return an instance of TreeInstance. The most important
method on that instance is `getItems()`, which returns a flat list of all nodes in the tree. Those nodes are of type
ItemInstance, which provides methods to interact with
each item and get the necessary props to render it.

When rendering your tree, don't forget to

- Spread the return value of `tree.getContainerProps()` as props into the container component containing your tree items.
- Spread the return value of `item.getProps()` as props into your tree item elements.
- You probably want to indent your items based on their depth, which you can get via `item.getItemMeta().level`.


## Importing Features

The architecture of Headless Tree separates individual features into distinct objects, that need to be manually
imported and added to your tree. They are exported from the `@headless-tree/core` package and need to be added to
the ```features``` property in your tree config. This is done to design a clean architecture, allow customizability
(you can easily write custom plugins that way, or overwrite parts of other plugins), and more importantly, allow
you to only import the features you need. This is especially important for tree-shaking, as you can easily
remove unused features from your bundle.

For example, if you want drag-and-drop support and hotkeys support, import the respective features and add them
to your tree config:

```tsx
import { dragAndDropFeature, hotkeysCoreFeature, syncDataLoaderFeature } from "@headless-tree/core";
import { useTree } from "@headless-tree/react";

useTree({
    // ... other options
    features: [
        syncDataLoaderFeature,
        dragAndDropFeature,
        hotkeysCoreFeature,
    ]
})
```

:::danger[Make sure to import all features you are using]
Which features are imported and which not affects which methods are available on the tree instance and on the
item instances, as well as which config options are respected by Headless Tree. However, the TypeScript Types
always include all possible functions. If you are trying to use a feature which is not working or where methods
are missing during runtime, check if you have included all necessary features in your tree.
:::

All available features are documented with a dedicated guide on how to use them. In the sidebar, you can
find each feature under its respective name in the "Features" category. The "Tree Core" and "Main Feature"
features are special features that are always included and do not need to be imported.

## Types

Headless Tree is written in TypeScript and provides full TypeScript support. The most important types to keep
in mind are

- TreeConfig: The configuration object that you pass to `useTree` or `createTree`.
- TreeInstance: The instance of a tree that you get back from `useTree` or `createTree`.
- ItemInstance: The instance of an item that you get back from the tree instance.
- TreeState: The state of a tree instance, which you can access via `tree.state` (read
  more in the guide on [managing State](https://headless-tree.lukasbach.com/llm/guides/state.md)).

Note that some of the defined functions require that you include the respective feature in your tree config, see
the section above.

## Hooking up your data structure

As you saw in the previous example, we also added the `syncDataLoaderFeature` feature. Then, you can define
how Headless Tree reads your tree with a `dataLoader` property in the tree config.

You need either that or the `asyncDataLoaderFeature` feature to hook up your data structure to Headless Tree.
Note that, when using the `asyncDataLoaderFeature`, you need to provide a `asyncDataLoader` instead, which has
a different interface (async methods instead of sync methods).

- In the `dataLoader.getItem` property, you define a method that returns the payload of an item. The form of the payload
  is up to you, and can be provided as generic type to the `useTree` or `createTree` method.
- In the `dataLoader.getChildren` property, you define a method that returns the IDs of the children of an item.

In addition to the `dataLoader` property, you also need to provide

- the `rootItemId` property
- a `getItemName()` method, which returns the display name of an item
- a `isItemFolder()` method, which returns whether an item is a folder or not.
  An item being a folder means that it can be expanded.

For the latter two, the parameter provided to you is an item instance, so you can use all methods that you also have
available during rendering. Use `item.getItemData()` to get the payload for the item that you provided.

```ts
const tree = useTree<ItemPayload>({
    rootItemId: "root-item",
    getItemName: (item) => item.getItemData().itemName,
    isItemFolder: (item) => item.isFolder,
    dataLoader: {
        getItem: (itemId) => myDataStructure[itemId],
        getChildren: (itemId) => myDataStructure[itemId].childrenIds,
    },
    features: [ syncDataLoaderFeature ],
});
```

In the prior demos, we mostly used `string` as simple payload type. The following demo shows how to use
a more complex payload type, and how to use the `getItemName` and `isItemFolder` methods to get the
display name and folder status of an item.

```ts jsx
import type { Meta } from "@storybook/react";
import React from "react";
import {
  hotkeysCoreFeature,
  selectionFeature,
  syncDataLoaderFeature,
} from "@headless-tree/core";
import { useTree } from "@headless-tree/react";
import cn from "classnames";

const meta = {
  title: "React/General/Item Data Objects",
} satisfies Meta;

export default meta;

// story-start
interface Item {
  name: string;
  children?: string[];
  isFolder?: boolean;
  isRed?: boolean;
}

const items: Record<string, Item> = {
  root: { name: "Root", children: ["folder1", "folder2"], isFolder: true },
  folder1: { name: "Folder 1", children: ["item1", "item2"], isFolder: true },
  folder2: {
    name: "Folder 2 (red)",
    children: ["folder3"],
    isFolder: true,
    isRed: true,
  },
  folder3: { name: "Folder 3", children: ["item3"], isFolder: true },
  item1: { name: "Item 1 (red)", isRed: true },
  item2: { name: "Item 2" },
  item3: { name: "Item 3" },
};

export const ItemDataObjects = () => {
  const tree = useTree<Item>({
    rootItemId: "root",
    getItemName: (item) => item.getItemData().name,
    isItemFolder: (item) => Boolean(item.getItemData().isFolder),
    dataLoader: {
      getItem: (itemId) => items[itemId],
      getChildren: (itemId) => items[itemId].children ?? [],
    },
    indent: 20,
    features: [syncDataLoaderFeature, selectionFeature, hotkeysCoreFeature],
  });

  return (
    <div {...tree.getContainerProps()} className="tree">
      {tree.getItems().map((item) => (
        <button
          {...item.getProps()}
          key={item.getId()}
          style={{
            paddingLeft: `${item.getItemMeta().level * 20}px`,
            color: item.getItemData().isRed ? "red" : undefined,
          }}
        >
          <div
            className={cn("treeitem", {
              focused: item.isFocused(),
              expanded: item.isExpanded(),
              selected: item.isSelected(),
              folder: item.isFolder(),
            })}
          >
            {item.getItemName()}
          </div>
        </button>
      ))}
    </div>
  );
};
```

:::tip

If the library provides value to you, leaving [a star
on Github](https://github.com/lukasbach/headless-tree) would help a lot with the visibility of the project.
If you encounter any bugs or issues, or have feedback about Headless Tree, please don't hesitate to
open [an issue on Github](https://github.com/lukasbach/headless-tree/issues/new/choose), or get involved
in the [discussion on Discord](https://discord.gg/KuZ6EezzVw).

:::

## llms.txt

You can find the documentation as `llm.txt` file available to use with LLM agents:

- [llms.txt](https://headless-tree.lukasbach.com/llms.txt)
- [llms-full.txt](https://headless-tree.lukasbach.com/llms-full.txt)
