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

# Contributing new Frameworks

Headless Tree is designed to be framework-agnostic. It provides a React integration as core framework support,
but it is possible to create integrations for other frameworks as well. This document outlines how to integrate
Headless Tree with custom frameworks, and also how to contribute such integrations back to the project.

The [implementation of the React integration](https://github.com/lukasbach/headless-tree/blob/main/packages/react/src/use-tree.tsx)
can serve as a reference for creating integrations with other frameworks.

# Gist of the integration

To create a Headless Tree instance in your framework integration, you need to:

- Call `createTree` from `@headless-tree/core` once and store that reference
- Call `tree.setMounted(true)` once when the tree is mounted in the DOM
- Call `tree.rebuildTree()` once
- Call `tree.setConfig(prev => ({ ...prev, ...newConfig }))` whenever the config changes
- Call `tree.setMounted(false)` once when the tree is unmounted from the DOM
- Maintain a partial state of the tree for all properties that are not explicitly user-managed via their `config.state` property, and hook that up with:

```ts
const [state, setState] = useState<Partial<TreeState<T>>>({});

tree.setConfig(prev => ({
    ...prev,
    state: {
        ...state,
        ...userConfig.state,
    },
    setState: (state) => {
        setState(state);
        userConfig.setState?.(state);
    },
}))
```

# Remapping props

All prop names generated by features are created as React-compliant prop names. You can remap them
by implementing a custom feature that overwrites prop-related methods, and rewrites their output in
a way that is compatible with your framework. In the following sample, the `aria-label` prop is remapped
to `data-label` for tree item instances. This can be expanded to every prop if you want.

For a new framework integration, a new custom feature needs to be implemented that remaps all props 
of props-generating features to the desired prop names of the new framework. Method calls that
generate props include:

- `itemInstance.getProps()`
- `itemInstance.getCheckboxProps()`
- `itemInstance.getRenameInputProps()`
- `treeInstance.getSearchInputProps()`
- `treeInstance.getContainerProps()`

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

const meta = {
  title: "React/Plugins/Transform Props",
  tags: ["guide/plugin", "basic"],
} satisfies Meta;

export default meta;

// story-start
const customFeature: FeatureImplementation = {
  itemInstance: {
    getProps: ({ prev }) => {
      const { "aria-label": label, ...props } = prev?.() ?? {};
      return { ...props, "data-label": label };
    },
  },
};

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

  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>
  );
};
```

# Integration into the Headless Tree monorepo

The following tasks should be done to integrate a new framework into the Headless Tree monorepo:

- Create a new package in the `packages` folder, e.g. `packages/vue` for a Vue integration, and implement and expose the integration hook
- Write basic unit tests within that package that do a sanity check of the lifecycle and prop renaming
- Create a new package in the `packages` folder, e.g. `examples/sb-vue`, that contains a storybook setup for the new framework integration which includes some sample stories of common use cases. 
- Create a sample app in the `examples` folder, e.g. `examples/vue-app`, that can be used to manually test the integration in a real app environment.

Please discuss your integration proposal in the [HT Discord](https://discord.gg/KuZ6EezzVw) or in a GitHub issue before starting the implementation.
I (Lukas Bach) or other contributors can also help with individual steps of an integration if needed.