shadcn-treeview 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +174 -0
- package/dist/index.cjs +184 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +114 -0
- package/dist/index.d.ts +114 -0
- package/dist/index.js +157 -0
- package/dist/index.js.map +1 -0
- package/package.json +77 -0
package/README.md
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# shadcn-treeview
|
|
2
|
+
|
|
3
|
+
A lightweight, accessible, and customizable tree view component for React. Built on top of `react-accessible-treeview` with Shadcn UI styling.
|
|
4
|
+
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Accessible** - Built on `react-accessible-treeview` with full keyboard navigation and ARIA support
|
|
10
|
+
- **Customizable** - Flexible styling with Tailwind CSS classes
|
|
11
|
+
- **Lightweight** - Minimal dependencies
|
|
12
|
+
- **TypeScript** - Full TypeScript support with exported types
|
|
13
|
+
- **Inline Editing** - Built-in support for renaming items
|
|
14
|
+
- **Multi-select** - Support for single and multi-selection modes
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
### Shadcn CLI (Recommended)
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npx shadcn@latest add https://shadcn-treeview.achromatic.dev/registry/tree-view.json
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Package Manager
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install shadcn-treeview
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Quick Start
|
|
31
|
+
|
|
32
|
+
### 1. Import the components
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
import { TreeView, TreeViewItem, flattenTree } from "shadcn-treeview";
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### 2. Create your tree data
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
const data = flattenTree({
|
|
42
|
+
name: "Project",
|
|
43
|
+
children: [
|
|
44
|
+
{
|
|
45
|
+
name: "src",
|
|
46
|
+
children: [
|
|
47
|
+
{ name: "components", children: [{ name: "tree-view.tsx" }] },
|
|
48
|
+
{ name: "app.tsx" },
|
|
49
|
+
{ name: "index.tsx" }
|
|
50
|
+
]
|
|
51
|
+
},
|
|
52
|
+
{ name: "package.json" },
|
|
53
|
+
{ name: "README.md" }
|
|
54
|
+
]
|
|
55
|
+
});
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### 3. Render the tree
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
<TreeView
|
|
62
|
+
data={data}
|
|
63
|
+
aria-label="File tree"
|
|
64
|
+
className="w-64 rounded border p-2"
|
|
65
|
+
nodeRenderer={({
|
|
66
|
+
element,
|
|
67
|
+
isBranch,
|
|
68
|
+
isExpanded,
|
|
69
|
+
isSelected,
|
|
70
|
+
getNodeProps,
|
|
71
|
+
level
|
|
72
|
+
}) => (
|
|
73
|
+
<TreeViewItem
|
|
74
|
+
{...getNodeProps()}
|
|
75
|
+
name={element.name}
|
|
76
|
+
isBranch={isBranch}
|
|
77
|
+
isExpanded={isExpanded}
|
|
78
|
+
isSelected={isSelected}
|
|
79
|
+
level={level}
|
|
80
|
+
indentation={16}
|
|
81
|
+
icon={isBranch ? <FolderIcon /> : <FileIcon />}
|
|
82
|
+
/>
|
|
83
|
+
)}
|
|
84
|
+
/>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## API Reference
|
|
88
|
+
|
|
89
|
+
### TreeView
|
|
90
|
+
|
|
91
|
+
The main container component. Accepts all props from `react-accessible-treeview`.
|
|
92
|
+
|
|
93
|
+
| Prop | Type | Description |
|
|
94
|
+
|------|------|-------------|
|
|
95
|
+
| `data` | `INode[]` | Flattened tree data (use `flattenTree` helper) |
|
|
96
|
+
| `nodeRenderer` | `(props: INodeRendererProps) => ReactNode` | Render function for each node |
|
|
97
|
+
| `multiSelect` | `boolean` | Enable multi-selection |
|
|
98
|
+
| `togglableSelect` | `boolean` | Allow toggling selection |
|
|
99
|
+
| `clickAction` | `"EXCLUSIVE_SELECT" \| "SELECT" \| "FOCUS"` | Action on click |
|
|
100
|
+
|
|
101
|
+
### TreeViewItem
|
|
102
|
+
|
|
103
|
+
The component for rendering individual tree items.
|
|
104
|
+
|
|
105
|
+
| Prop | Type | Default | Description |
|
|
106
|
+
|------|------|---------|-------------|
|
|
107
|
+
| `name` | `string` | Required | Display name of the item |
|
|
108
|
+
| `level` | `number` | Required | Nesting level (1-based) |
|
|
109
|
+
| `isBranch` | `boolean` | `false` | Whether item has children |
|
|
110
|
+
| `isExpanded` | `boolean` | `false` | Whether item is expanded |
|
|
111
|
+
| `isSelected` | `boolean` | `false` | Whether item is selected |
|
|
112
|
+
| `indentation` | `number` | `16` | Base indentation in pixels |
|
|
113
|
+
| `levelIndentation` | `number` | `48` | Indentation per level |
|
|
114
|
+
| `icon` | `ReactNode` | - | Icon to display |
|
|
115
|
+
| `isEditing` | `boolean` | `false` | Enable edit mode |
|
|
116
|
+
| `onEditSubmit` | `(value: string) => void` | - | Edit submit callback |
|
|
117
|
+
| `isLoading` | `boolean` | `false` | Show loading state |
|
|
118
|
+
| `expandIcon` | `ReactNode` | - | Custom expand icon |
|
|
119
|
+
| `loadingIcon` | `ReactNode` | - | Custom loading icon |
|
|
120
|
+
|
|
121
|
+
## Examples
|
|
122
|
+
|
|
123
|
+
### Multi-select
|
|
124
|
+
|
|
125
|
+
```tsx
|
|
126
|
+
<TreeView
|
|
127
|
+
data={data}
|
|
128
|
+
multiSelect
|
|
129
|
+
togglableSelect
|
|
130
|
+
clickAction="EXCLUSIVE_SELECT"
|
|
131
|
+
onSelect={(props) => console.log("Selected:", props)}
|
|
132
|
+
nodeRenderer={/* ... */}
|
|
133
|
+
/>
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### With Context Menu
|
|
137
|
+
|
|
138
|
+
```tsx
|
|
139
|
+
<ContextMenu>
|
|
140
|
+
<ContextMenuTrigger asChild>
|
|
141
|
+
<TreeViewItem {...props} />
|
|
142
|
+
</ContextMenuTrigger>
|
|
143
|
+
<ContextMenuContent>
|
|
144
|
+
<ContextMenuItem>Rename</ContextMenuItem>
|
|
145
|
+
<ContextMenuItem>Delete</ContextMenuItem>
|
|
146
|
+
</ContextMenuContent>
|
|
147
|
+
</ContextMenu>
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Inline Editing
|
|
151
|
+
|
|
152
|
+
```tsx
|
|
153
|
+
<TreeViewItem
|
|
154
|
+
name={element.name}
|
|
155
|
+
isEditing={element.metadata?.isEditing}
|
|
156
|
+
onEditSubmit={(newName) => {
|
|
157
|
+
// Update tree data with new name
|
|
158
|
+
updateNodeName(element.id, newName);
|
|
159
|
+
}}
|
|
160
|
+
{...otherProps}
|
|
161
|
+
/>
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Documentation
|
|
165
|
+
|
|
166
|
+
For full documentation, visit [shadcn-treeview.achromatic.dev](https://shadcn-treeview.achromatic.dev).
|
|
167
|
+
|
|
168
|
+
## License
|
|
169
|
+
|
|
170
|
+
MIT
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
Maintained by [Achromatic](https://achromatic.dev)
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var TreeViewPrimitive = require('react-accessible-treeview');
|
|
4
|
+
var React = require('react');
|
|
5
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
6
|
+
|
|
7
|
+
function _interopNamespace(e) {
|
|
8
|
+
if (e && e.__esModule) return e;
|
|
9
|
+
var n = Object.create(null);
|
|
10
|
+
if (e) {
|
|
11
|
+
Object.keys(e).forEach(function (k) {
|
|
12
|
+
if (k !== 'default') {
|
|
13
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
14
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
15
|
+
enumerable: true,
|
|
16
|
+
get: function () { return e[k]; }
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
n.default = e;
|
|
22
|
+
return Object.freeze(n);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
var TreeViewPrimitive__namespace = /*#__PURE__*/_interopNamespace(TreeViewPrimitive);
|
|
26
|
+
var React__namespace = /*#__PURE__*/_interopNamespace(React);
|
|
27
|
+
|
|
28
|
+
// src/utils.ts
|
|
29
|
+
function cn(...inputs) {
|
|
30
|
+
return inputs.flat().filter((x) => typeof x === "string" && x.length > 0).join(" ");
|
|
31
|
+
}
|
|
32
|
+
var TreeView = TreeViewPrimitive__namespace.default;
|
|
33
|
+
var TreeViewItem = React__namespace.forwardRef(
|
|
34
|
+
({
|
|
35
|
+
level = 1,
|
|
36
|
+
isExpanded = false,
|
|
37
|
+
isBranch = false,
|
|
38
|
+
isSelected = false,
|
|
39
|
+
isLoading = false,
|
|
40
|
+
indentation = 16,
|
|
41
|
+
levelIndentation = 48,
|
|
42
|
+
name = "",
|
|
43
|
+
icon,
|
|
44
|
+
isEditing = false,
|
|
45
|
+
onEditSubmit,
|
|
46
|
+
expandIcon,
|
|
47
|
+
loadingIcon,
|
|
48
|
+
className,
|
|
49
|
+
style,
|
|
50
|
+
...props
|
|
51
|
+
}, ref) => {
|
|
52
|
+
const [localValueState, setLocalValueState] = React__namespace.useState(name);
|
|
53
|
+
const inputRef = React__namespace.useRef(null);
|
|
54
|
+
React__namespace.useEffect(() => {
|
|
55
|
+
const handleClickOutside = (event) => {
|
|
56
|
+
if (!inputRef.current?.contains(event.target)) {
|
|
57
|
+
onEditSubmit?.(localValueState);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
if (isEditing) {
|
|
61
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
62
|
+
}
|
|
63
|
+
return () => {
|
|
64
|
+
if (isEditing) {
|
|
65
|
+
document.removeEventListener("mousedown", handleClickOutside);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}, [isEditing, localValueState, onEditSubmit]);
|
|
69
|
+
React__namespace.useEffect(() => {
|
|
70
|
+
if (isEditing) {
|
|
71
|
+
inputRef.current?.focus();
|
|
72
|
+
}
|
|
73
|
+
}, [isEditing]);
|
|
74
|
+
const handleSubmit = (e) => {
|
|
75
|
+
e.preventDefault();
|
|
76
|
+
onEditSubmit?.(localValueState);
|
|
77
|
+
};
|
|
78
|
+
const paddingLeft = level === 1 && !isBranch ? indentation : levelIndentation * (level - 1) + indentation;
|
|
79
|
+
const defaultExpandIcon = /* @__PURE__ */ jsxRuntime.jsx(
|
|
80
|
+
"svg",
|
|
81
|
+
{
|
|
82
|
+
"aria-hidden": "true",
|
|
83
|
+
className: "text-muted-foreground transition-transform duration-200 group-aria-expanded:rotate-90",
|
|
84
|
+
width: "14",
|
|
85
|
+
height: "14",
|
|
86
|
+
viewBox: "0 0 24 24",
|
|
87
|
+
fill: "none",
|
|
88
|
+
stroke: "currentColor",
|
|
89
|
+
strokeWidth: "2",
|
|
90
|
+
strokeLinecap: "round",
|
|
91
|
+
strokeLinejoin: "round",
|
|
92
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m9 18 6-6-6-6" })
|
|
93
|
+
}
|
|
94
|
+
);
|
|
95
|
+
const defaultLoadingIcon = /* @__PURE__ */ jsxRuntime.jsx(
|
|
96
|
+
"svg",
|
|
97
|
+
{
|
|
98
|
+
"aria-hidden": "true",
|
|
99
|
+
className: "animate-spin text-muted-foreground",
|
|
100
|
+
width: "14",
|
|
101
|
+
height: "14",
|
|
102
|
+
viewBox: "0 0 24 24",
|
|
103
|
+
fill: "none",
|
|
104
|
+
stroke: "currentColor",
|
|
105
|
+
strokeWidth: "2",
|
|
106
|
+
strokeLinecap: "round",
|
|
107
|
+
strokeLinejoin: "round",
|
|
108
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" })
|
|
109
|
+
}
|
|
110
|
+
);
|
|
111
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
112
|
+
"div",
|
|
113
|
+
{
|
|
114
|
+
ref,
|
|
115
|
+
"aria-expanded": !isEditing && isExpanded,
|
|
116
|
+
className: cn(
|
|
117
|
+
"group relative flex h-8 cursor-pointer select-none items-center gap-3 text-sm text-muted-foreground transition-colors hover:bg-muted",
|
|
118
|
+
isSelected && "!bg-muted text-foreground",
|
|
119
|
+
className
|
|
120
|
+
),
|
|
121
|
+
style: {
|
|
122
|
+
paddingLeft,
|
|
123
|
+
...style
|
|
124
|
+
},
|
|
125
|
+
"data-treeview-is-branch": isBranch,
|
|
126
|
+
"data-treeview-level": level,
|
|
127
|
+
...props,
|
|
128
|
+
children: [
|
|
129
|
+
level > 1 && /* @__PURE__ */ jsxRuntime.jsx(
|
|
130
|
+
"div",
|
|
131
|
+
{
|
|
132
|
+
style: {
|
|
133
|
+
left: (levelIndentation / 2 + 4) * (level - 1) + indentation
|
|
134
|
+
},
|
|
135
|
+
className: "absolute h-full w-px group-data-[treeview-is-branch=false]:border"
|
|
136
|
+
}
|
|
137
|
+
),
|
|
138
|
+
isSelected && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute left-0 h-full w-0.5 bg-foreground" }),
|
|
139
|
+
isBranch && (isLoading ? loadingIcon ?? defaultLoadingIcon : expandIcon ?? defaultExpandIcon),
|
|
140
|
+
icon,
|
|
141
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
142
|
+
"span",
|
|
143
|
+
{
|
|
144
|
+
className: cn("truncate text-sm", isEditing && "hidden"),
|
|
145
|
+
title: name,
|
|
146
|
+
children: name
|
|
147
|
+
}
|
|
148
|
+
),
|
|
149
|
+
isEditing && /* @__PURE__ */ jsxRuntime.jsx("form", { onSubmit: handleSubmit, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
150
|
+
"input",
|
|
151
|
+
{
|
|
152
|
+
ref: inputRef,
|
|
153
|
+
onChange: (e) => {
|
|
154
|
+
setLocalValueState(e.target.value);
|
|
155
|
+
},
|
|
156
|
+
onKeyDownCapture: (e) => {
|
|
157
|
+
if (e.key === "Enter") {
|
|
158
|
+
onEditSubmit?.(localValueState);
|
|
159
|
+
} else if (e.key === "Escape") {
|
|
160
|
+
setLocalValueState(name);
|
|
161
|
+
onEditSubmit?.(name);
|
|
162
|
+
} else {
|
|
163
|
+
e.stopPropagation();
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
className: "block h-7 w-full rounded border bg-background px-2 py-1 text-sm focus:outline-none focus:ring-1 focus:ring-ring",
|
|
167
|
+
value: localValueState
|
|
168
|
+
}
|
|
169
|
+
) })
|
|
170
|
+
]
|
|
171
|
+
}
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
);
|
|
175
|
+
TreeViewItem.displayName = "TreeViewItem";
|
|
176
|
+
|
|
177
|
+
Object.defineProperty(exports, "flattenTree", {
|
|
178
|
+
enumerable: true,
|
|
179
|
+
get: function () { return TreeViewPrimitive.flattenTree; }
|
|
180
|
+
});
|
|
181
|
+
exports.TreeView = TreeView;
|
|
182
|
+
exports.TreeViewItem = TreeViewItem;
|
|
183
|
+
//# sourceMappingURL=index.cjs.map
|
|
184
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils.ts","../src/tree-view.tsx"],"names":["TreeViewPrimitive","React","jsx","jsxs"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { ClassValue } from \"clsx\";\n\n/**\n * Utility function to merge class names\n * This is a minimal implementation that doesn't require clsx/tailwind-merge as dependencies\n */\nexport function cn(...inputs: ClassValue[]): string {\n\treturn inputs\n\t\t.flat()\n\t\t.filter((x) => typeof x === \"string\" && x.length > 0)\n\t\t.join(\" \");\n}\n","\"use client\";\n\nimport * as React from \"react\";\nimport * as TreeViewPrimitive from \"react-accessible-treeview\";\n\nimport { cn } from \"./utils\";\n\n/**\n * TreeView component props - extends the underlying react-accessible-treeview props\n */\nexport type TreeViewProps = React.ComponentPropsWithoutRef<\n\ttypeof TreeViewPrimitive.default\n>;\n\n/**\n * TreeView component - a wrapper around react-accessible-treeview\n *\n * @example\n * ```tsx\n * import { TreeView, TreeViewItem, flattenTree } from \"shadcn-treeview\";\n *\n * const data = flattenTree({\n * name: \"Root\",\n * children: [{ name: \"Child 1\" }, { name: \"Child 2\" }]\n * });\n *\n * <TreeView\n * data={data}\n * nodeRenderer={({ element, getNodeProps, level, isBranch, isExpanded, isSelected }) => (\n * <TreeViewItem\n * {...getNodeProps()}\n * name={element.name}\n * level={level}\n * isBranch={isBranch}\n * isExpanded={isExpanded}\n * isSelected={isSelected}\n * indentation={16}\n * />\n * )}\n * />\n * ```\n */\nconst TreeView = TreeViewPrimitive.default;\n\n/**\n * Props for the TreeViewItem component\n */\nexport type TreeViewItemProps = React.ComponentPropsWithoutRef<\"div\"> & {\n\t/** The nesting level of the item (1-based) */\n\tlevel: number;\n\t/** Whether this item is expanded (only applicable for branches) */\n\tisExpanded?: boolean;\n\t/** Whether this item is a branch (has children) */\n\tisBranch?: boolean;\n\t/** Whether this item is currently selected */\n\tisSelected?: boolean;\n\t/** Base indentation in pixels */\n\tindentation?: number;\n\t/** Indentation per level in pixels */\n\tlevelIndentation?: number;\n\t/** The display name of the item */\n\tname: string;\n\t/** Optional icon to display before the name */\n\ticon?: React.ReactNode;\n\t/** Whether the item is in editing mode */\n\tisEditing?: boolean;\n\t/** Callback when editing is submitted */\n\tonEditSubmit?: (value: string) => void;\n\t/** Whether the item is in a loading state */\n\tisLoading?: boolean;\n\t/** Custom expand/collapse icon */\n\texpandIcon?: React.ReactNode;\n\t/** Custom loading icon */\n\tloadingIcon?: React.ReactNode;\n};\n\n/**\n * TreeViewItem component - renders a single item in the tree\n *\n * @example\n * ```tsx\n * <TreeViewItem\n * name=\"My File\"\n * level={1}\n * isBranch={false}\n * isSelected={false}\n * indentation={16}\n * icon={<FileIcon className=\"h-4 w-4\" />}\n * />\n * ```\n */\nconst TreeViewItem = React.forwardRef<HTMLDivElement, TreeViewItemProps>(\n\t(\n\t\t{\n\t\t\tlevel = 1,\n\t\t\tisExpanded = false,\n\t\t\tisBranch = false,\n\t\t\tisSelected = false,\n\t\t\tisLoading = false,\n\t\t\tindentation = 16,\n\t\t\tlevelIndentation = 48,\n\t\t\tname = \"\",\n\t\t\ticon,\n\t\t\tisEditing = false,\n\t\t\tonEditSubmit,\n\t\t\texpandIcon,\n\t\t\tloadingIcon,\n\t\t\tclassName,\n\t\t\tstyle,\n\t\t\t...props\n\t\t},\n\t\tref,\n\t) => {\n\t\tconst [localValueState, setLocalValueState] = React.useState(name);\n\t\tconst inputRef = React.useRef<HTMLInputElement>(null);\n\n\t\tReact.useEffect(() => {\n\t\t\tconst handleClickOutside = (event: MouseEvent) => {\n\t\t\t\tif (!inputRef.current?.contains(event.target as Node)) {\n\t\t\t\t\tonEditSubmit?.(localValueState);\n\t\t\t\t}\n\t\t\t};\n\t\t\tif (isEditing) {\n\t\t\t\tdocument.addEventListener(\"mousedown\", handleClickOutside);\n\t\t\t}\n\t\t\treturn () => {\n\t\t\t\tif (isEditing) {\n\t\t\t\t\tdocument.removeEventListener(\"mousedown\", handleClickOutside);\n\t\t\t\t}\n\t\t\t};\n\t\t}, [isEditing, localValueState, onEditSubmit]);\n\n\t\tReact.useEffect(() => {\n\t\t\tif (isEditing) {\n\t\t\t\tinputRef.current?.focus();\n\t\t\t}\n\t\t}, [isEditing]);\n\n\t\tconst handleSubmit = (e: React.FormEvent<HTMLFormElement>): void => {\n\t\t\te.preventDefault();\n\t\t\tonEditSubmit?.(localValueState);\n\t\t};\n\n\t\tconst paddingLeft =\n\t\t\tlevel === 1 && !isBranch\n\t\t\t\t? indentation\n\t\t\t\t: levelIndentation * (level - 1) + indentation;\n\n\t\tconst defaultExpandIcon = (\n\t\t\t<svg\n\t\t\t\taria-hidden=\"true\"\n\t\t\t\tclassName=\"text-muted-foreground transition-transform duration-200 group-aria-expanded:rotate-90\"\n\t\t\t\twidth=\"14\"\n\t\t\t\theight=\"14\"\n\t\t\t\tviewBox=\"0 0 24 24\"\n\t\t\t\tfill=\"none\"\n\t\t\t\tstroke=\"currentColor\"\n\t\t\t\tstrokeWidth=\"2\"\n\t\t\t\tstrokeLinecap=\"round\"\n\t\t\t\tstrokeLinejoin=\"round\"\n\t\t\t>\n\t\t\t\t<path d=\"m9 18 6-6-6-6\" />\n\t\t\t</svg>\n\t\t);\n\n\t\tconst defaultLoadingIcon = (\n\t\t\t<svg\n\t\t\t\taria-hidden=\"true\"\n\t\t\t\tclassName=\"animate-spin text-muted-foreground\"\n\t\t\t\twidth=\"14\"\n\t\t\t\theight=\"14\"\n\t\t\t\tviewBox=\"0 0 24 24\"\n\t\t\t\tfill=\"none\"\n\t\t\t\tstroke=\"currentColor\"\n\t\t\t\tstrokeWidth=\"2\"\n\t\t\t\tstrokeLinecap=\"round\"\n\t\t\t\tstrokeLinejoin=\"round\"\n\t\t\t>\n\t\t\t\t<path d=\"M21 12a9 9 0 1 1-6.219-8.56\" />\n\t\t\t</svg>\n\t\t);\n\n\t\treturn (\n\t\t\t<div\n\t\t\t\tref={ref}\n\t\t\t\taria-expanded={!isEditing && isExpanded}\n\t\t\t\tclassName={cn(\n\t\t\t\t\t\"group relative flex h-8 cursor-pointer select-none items-center gap-3 text-sm text-muted-foreground transition-colors hover:bg-muted\",\n\t\t\t\t\tisSelected && \"!bg-muted text-foreground\",\n\t\t\t\t\tclassName,\n\t\t\t\t)}\n\t\t\t\tstyle={{\n\t\t\t\t\tpaddingLeft,\n\t\t\t\t\t...style,\n\t\t\t\t}}\n\t\t\t\tdata-treeview-is-branch={isBranch}\n\t\t\t\tdata-treeview-level={level}\n\t\t\t\t{...props}\n\t\t\t>\n\t\t\t\t{level > 1 && (\n\t\t\t\t\t<div\n\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\tleft: (levelIndentation / 2 + 4) * (level - 1) + indentation,\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tclassName=\"absolute h-full w-px group-data-[treeview-is-branch=false]:border\"\n\t\t\t\t\t/>\n\t\t\t\t)}\n\t\t\t\t{isSelected && (\n\t\t\t\t\t<div className=\"absolute left-0 h-full w-0.5 bg-foreground\" />\n\t\t\t\t)}\n\t\t\t\t{isBranch &&\n\t\t\t\t\t(isLoading\n\t\t\t\t\t\t? (loadingIcon ?? defaultLoadingIcon)\n\t\t\t\t\t\t: (expandIcon ?? defaultExpandIcon))}\n\t\t\t\t{icon}\n\t\t\t\t<span\n\t\t\t\t\tclassName={cn(\"truncate text-sm\", isEditing && \"hidden\")}\n\t\t\t\t\ttitle={name}\n\t\t\t\t>\n\t\t\t\t\t{name}\n\t\t\t\t</span>\n\t\t\t\t{isEditing && (\n\t\t\t\t\t<form onSubmit={handleSubmit}>\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\tref={inputRef}\n\t\t\t\t\t\t\tonChange={(e) => {\n\t\t\t\t\t\t\t\tsetLocalValueState(e.target.value);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tonKeyDownCapture={(e) => {\n\t\t\t\t\t\t\t\tif (e.key === \"Enter\") {\n\t\t\t\t\t\t\t\t\tonEditSubmit?.(localValueState);\n\t\t\t\t\t\t\t\t} else if (e.key === \"Escape\") {\n\t\t\t\t\t\t\t\t\tsetLocalValueState(name);\n\t\t\t\t\t\t\t\t\tonEditSubmit?.(name);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\te.stopPropagation();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tclassName=\"block h-7 w-full rounded border bg-background px-2 py-1 text-sm focus:outline-none focus:ring-1 focus:ring-ring\"\n\t\t\t\t\t\t\tvalue={localValueState}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</form>\n\t\t\t\t)}\n\t\t\t</div>\n\t\t);\n\t},\n);\nTreeViewItem.displayName = \"TreeViewItem\";\n\nexport { TreeView, TreeViewItem };\n"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import * as TreeViewPrimitive from 'react-accessible-treeview';
|
|
2
|
+
export { INode, INodeRendererProps, ITreeViewOnExpandProps, ITreeViewOnLoadDataProps, ITreeViewOnSelectProps, ITreeViewProps, flattenTree } from 'react-accessible-treeview';
|
|
3
|
+
import * as react_accessible_treeview_dist_TreeView_utils from 'react-accessible-treeview/dist/TreeView/utils';
|
|
4
|
+
import * as React from 'react';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* TreeView component props - extends the underlying react-accessible-treeview props
|
|
8
|
+
*/
|
|
9
|
+
type TreeViewProps = React.ComponentPropsWithoutRef<typeof TreeViewPrimitive.default>;
|
|
10
|
+
/**
|
|
11
|
+
* TreeView component - a wrapper around react-accessible-treeview
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```tsx
|
|
15
|
+
* import { TreeView, TreeViewItem, flattenTree } from "shadcn-treeview";
|
|
16
|
+
*
|
|
17
|
+
* const data = flattenTree({
|
|
18
|
+
* name: "Root",
|
|
19
|
+
* children: [{ name: "Child 1" }, { name: "Child 2" }]
|
|
20
|
+
* });
|
|
21
|
+
*
|
|
22
|
+
* <TreeView
|
|
23
|
+
* data={data}
|
|
24
|
+
* nodeRenderer={({ element, getNodeProps, level, isBranch, isExpanded, isSelected }) => (
|
|
25
|
+
* <TreeViewItem
|
|
26
|
+
* {...getNodeProps()}
|
|
27
|
+
* name={element.name}
|
|
28
|
+
* level={level}
|
|
29
|
+
* isBranch={isBranch}
|
|
30
|
+
* isExpanded={isExpanded}
|
|
31
|
+
* isSelected={isSelected}
|
|
32
|
+
* indentation={16}
|
|
33
|
+
* />
|
|
34
|
+
* )}
|
|
35
|
+
* />
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
declare const TreeView: React.ForwardRefExoticComponent<TreeViewPrimitive.ITreeViewProps<react_accessible_treeview_dist_TreeView_utils.IFlatMetadata> & React.RefAttributes<HTMLUListElement>>;
|
|
39
|
+
/**
|
|
40
|
+
* Props for the TreeViewItem component
|
|
41
|
+
*/
|
|
42
|
+
type TreeViewItemProps = React.ComponentPropsWithoutRef<"div"> & {
|
|
43
|
+
/** The nesting level of the item (1-based) */
|
|
44
|
+
level: number;
|
|
45
|
+
/** Whether this item is expanded (only applicable for branches) */
|
|
46
|
+
isExpanded?: boolean;
|
|
47
|
+
/** Whether this item is a branch (has children) */
|
|
48
|
+
isBranch?: boolean;
|
|
49
|
+
/** Whether this item is currently selected */
|
|
50
|
+
isSelected?: boolean;
|
|
51
|
+
/** Base indentation in pixels */
|
|
52
|
+
indentation?: number;
|
|
53
|
+
/** Indentation per level in pixels */
|
|
54
|
+
levelIndentation?: number;
|
|
55
|
+
/** The display name of the item */
|
|
56
|
+
name: string;
|
|
57
|
+
/** Optional icon to display before the name */
|
|
58
|
+
icon?: React.ReactNode;
|
|
59
|
+
/** Whether the item is in editing mode */
|
|
60
|
+
isEditing?: boolean;
|
|
61
|
+
/** Callback when editing is submitted */
|
|
62
|
+
onEditSubmit?: (value: string) => void;
|
|
63
|
+
/** Whether the item is in a loading state */
|
|
64
|
+
isLoading?: boolean;
|
|
65
|
+
/** Custom expand/collapse icon */
|
|
66
|
+
expandIcon?: React.ReactNode;
|
|
67
|
+
/** Custom loading icon */
|
|
68
|
+
loadingIcon?: React.ReactNode;
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* TreeViewItem component - renders a single item in the tree
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```tsx
|
|
75
|
+
* <TreeViewItem
|
|
76
|
+
* name="My File"
|
|
77
|
+
* level={1}
|
|
78
|
+
* isBranch={false}
|
|
79
|
+
* isSelected={false}
|
|
80
|
+
* indentation={16}
|
|
81
|
+
* icon={<FileIcon className="h-4 w-4" />}
|
|
82
|
+
* />
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
declare const TreeViewItem: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & {
|
|
86
|
+
/** The nesting level of the item (1-based) */
|
|
87
|
+
level: number;
|
|
88
|
+
/** Whether this item is expanded (only applicable for branches) */
|
|
89
|
+
isExpanded?: boolean;
|
|
90
|
+
/** Whether this item is a branch (has children) */
|
|
91
|
+
isBranch?: boolean;
|
|
92
|
+
/** Whether this item is currently selected */
|
|
93
|
+
isSelected?: boolean;
|
|
94
|
+
/** Base indentation in pixels */
|
|
95
|
+
indentation?: number;
|
|
96
|
+
/** Indentation per level in pixels */
|
|
97
|
+
levelIndentation?: number;
|
|
98
|
+
/** The display name of the item */
|
|
99
|
+
name: string;
|
|
100
|
+
/** Optional icon to display before the name */
|
|
101
|
+
icon?: React.ReactNode;
|
|
102
|
+
/** Whether the item is in editing mode */
|
|
103
|
+
isEditing?: boolean;
|
|
104
|
+
/** Callback when editing is submitted */
|
|
105
|
+
onEditSubmit?: (value: string) => void;
|
|
106
|
+
/** Whether the item is in a loading state */
|
|
107
|
+
isLoading?: boolean;
|
|
108
|
+
/** Custom expand/collapse icon */
|
|
109
|
+
expandIcon?: React.ReactNode;
|
|
110
|
+
/** Custom loading icon */
|
|
111
|
+
loadingIcon?: React.ReactNode;
|
|
112
|
+
} & React.RefAttributes<HTMLDivElement>>;
|
|
113
|
+
|
|
114
|
+
export { TreeView, TreeViewItem, type TreeViewItemProps, type TreeViewProps };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import * as TreeViewPrimitive from 'react-accessible-treeview';
|
|
2
|
+
export { INode, INodeRendererProps, ITreeViewOnExpandProps, ITreeViewOnLoadDataProps, ITreeViewOnSelectProps, ITreeViewProps, flattenTree } from 'react-accessible-treeview';
|
|
3
|
+
import * as react_accessible_treeview_dist_TreeView_utils from 'react-accessible-treeview/dist/TreeView/utils';
|
|
4
|
+
import * as React from 'react';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* TreeView component props - extends the underlying react-accessible-treeview props
|
|
8
|
+
*/
|
|
9
|
+
type TreeViewProps = React.ComponentPropsWithoutRef<typeof TreeViewPrimitive.default>;
|
|
10
|
+
/**
|
|
11
|
+
* TreeView component - a wrapper around react-accessible-treeview
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```tsx
|
|
15
|
+
* import { TreeView, TreeViewItem, flattenTree } from "shadcn-treeview";
|
|
16
|
+
*
|
|
17
|
+
* const data = flattenTree({
|
|
18
|
+
* name: "Root",
|
|
19
|
+
* children: [{ name: "Child 1" }, { name: "Child 2" }]
|
|
20
|
+
* });
|
|
21
|
+
*
|
|
22
|
+
* <TreeView
|
|
23
|
+
* data={data}
|
|
24
|
+
* nodeRenderer={({ element, getNodeProps, level, isBranch, isExpanded, isSelected }) => (
|
|
25
|
+
* <TreeViewItem
|
|
26
|
+
* {...getNodeProps()}
|
|
27
|
+
* name={element.name}
|
|
28
|
+
* level={level}
|
|
29
|
+
* isBranch={isBranch}
|
|
30
|
+
* isExpanded={isExpanded}
|
|
31
|
+
* isSelected={isSelected}
|
|
32
|
+
* indentation={16}
|
|
33
|
+
* />
|
|
34
|
+
* )}
|
|
35
|
+
* />
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
declare const TreeView: React.ForwardRefExoticComponent<TreeViewPrimitive.ITreeViewProps<react_accessible_treeview_dist_TreeView_utils.IFlatMetadata> & React.RefAttributes<HTMLUListElement>>;
|
|
39
|
+
/**
|
|
40
|
+
* Props for the TreeViewItem component
|
|
41
|
+
*/
|
|
42
|
+
type TreeViewItemProps = React.ComponentPropsWithoutRef<"div"> & {
|
|
43
|
+
/** The nesting level of the item (1-based) */
|
|
44
|
+
level: number;
|
|
45
|
+
/** Whether this item is expanded (only applicable for branches) */
|
|
46
|
+
isExpanded?: boolean;
|
|
47
|
+
/** Whether this item is a branch (has children) */
|
|
48
|
+
isBranch?: boolean;
|
|
49
|
+
/** Whether this item is currently selected */
|
|
50
|
+
isSelected?: boolean;
|
|
51
|
+
/** Base indentation in pixels */
|
|
52
|
+
indentation?: number;
|
|
53
|
+
/** Indentation per level in pixels */
|
|
54
|
+
levelIndentation?: number;
|
|
55
|
+
/** The display name of the item */
|
|
56
|
+
name: string;
|
|
57
|
+
/** Optional icon to display before the name */
|
|
58
|
+
icon?: React.ReactNode;
|
|
59
|
+
/** Whether the item is in editing mode */
|
|
60
|
+
isEditing?: boolean;
|
|
61
|
+
/** Callback when editing is submitted */
|
|
62
|
+
onEditSubmit?: (value: string) => void;
|
|
63
|
+
/** Whether the item is in a loading state */
|
|
64
|
+
isLoading?: boolean;
|
|
65
|
+
/** Custom expand/collapse icon */
|
|
66
|
+
expandIcon?: React.ReactNode;
|
|
67
|
+
/** Custom loading icon */
|
|
68
|
+
loadingIcon?: React.ReactNode;
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* TreeViewItem component - renders a single item in the tree
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```tsx
|
|
75
|
+
* <TreeViewItem
|
|
76
|
+
* name="My File"
|
|
77
|
+
* level={1}
|
|
78
|
+
* isBranch={false}
|
|
79
|
+
* isSelected={false}
|
|
80
|
+
* indentation={16}
|
|
81
|
+
* icon={<FileIcon className="h-4 w-4" />}
|
|
82
|
+
* />
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
declare const TreeViewItem: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & {
|
|
86
|
+
/** The nesting level of the item (1-based) */
|
|
87
|
+
level: number;
|
|
88
|
+
/** Whether this item is expanded (only applicable for branches) */
|
|
89
|
+
isExpanded?: boolean;
|
|
90
|
+
/** Whether this item is a branch (has children) */
|
|
91
|
+
isBranch?: boolean;
|
|
92
|
+
/** Whether this item is currently selected */
|
|
93
|
+
isSelected?: boolean;
|
|
94
|
+
/** Base indentation in pixels */
|
|
95
|
+
indentation?: number;
|
|
96
|
+
/** Indentation per level in pixels */
|
|
97
|
+
levelIndentation?: number;
|
|
98
|
+
/** The display name of the item */
|
|
99
|
+
name: string;
|
|
100
|
+
/** Optional icon to display before the name */
|
|
101
|
+
icon?: React.ReactNode;
|
|
102
|
+
/** Whether the item is in editing mode */
|
|
103
|
+
isEditing?: boolean;
|
|
104
|
+
/** Callback when editing is submitted */
|
|
105
|
+
onEditSubmit?: (value: string) => void;
|
|
106
|
+
/** Whether the item is in a loading state */
|
|
107
|
+
isLoading?: boolean;
|
|
108
|
+
/** Custom expand/collapse icon */
|
|
109
|
+
expandIcon?: React.ReactNode;
|
|
110
|
+
/** Custom loading icon */
|
|
111
|
+
loadingIcon?: React.ReactNode;
|
|
112
|
+
} & React.RefAttributes<HTMLDivElement>>;
|
|
113
|
+
|
|
114
|
+
export { TreeView, TreeViewItem, type TreeViewItemProps, type TreeViewProps };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import * as TreeViewPrimitive from 'react-accessible-treeview';
|
|
2
|
+
export { flattenTree } from 'react-accessible-treeview';
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
5
|
+
|
|
6
|
+
// src/utils.ts
|
|
7
|
+
function cn(...inputs) {
|
|
8
|
+
return inputs.flat().filter((x) => typeof x === "string" && x.length > 0).join(" ");
|
|
9
|
+
}
|
|
10
|
+
var TreeView = TreeViewPrimitive.default;
|
|
11
|
+
var TreeViewItem = React.forwardRef(
|
|
12
|
+
({
|
|
13
|
+
level = 1,
|
|
14
|
+
isExpanded = false,
|
|
15
|
+
isBranch = false,
|
|
16
|
+
isSelected = false,
|
|
17
|
+
isLoading = false,
|
|
18
|
+
indentation = 16,
|
|
19
|
+
levelIndentation = 48,
|
|
20
|
+
name = "",
|
|
21
|
+
icon,
|
|
22
|
+
isEditing = false,
|
|
23
|
+
onEditSubmit,
|
|
24
|
+
expandIcon,
|
|
25
|
+
loadingIcon,
|
|
26
|
+
className,
|
|
27
|
+
style,
|
|
28
|
+
...props
|
|
29
|
+
}, ref) => {
|
|
30
|
+
const [localValueState, setLocalValueState] = React.useState(name);
|
|
31
|
+
const inputRef = React.useRef(null);
|
|
32
|
+
React.useEffect(() => {
|
|
33
|
+
const handleClickOutside = (event) => {
|
|
34
|
+
if (!inputRef.current?.contains(event.target)) {
|
|
35
|
+
onEditSubmit?.(localValueState);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
if (isEditing) {
|
|
39
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
40
|
+
}
|
|
41
|
+
return () => {
|
|
42
|
+
if (isEditing) {
|
|
43
|
+
document.removeEventListener("mousedown", handleClickOutside);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}, [isEditing, localValueState, onEditSubmit]);
|
|
47
|
+
React.useEffect(() => {
|
|
48
|
+
if (isEditing) {
|
|
49
|
+
inputRef.current?.focus();
|
|
50
|
+
}
|
|
51
|
+
}, [isEditing]);
|
|
52
|
+
const handleSubmit = (e) => {
|
|
53
|
+
e.preventDefault();
|
|
54
|
+
onEditSubmit?.(localValueState);
|
|
55
|
+
};
|
|
56
|
+
const paddingLeft = level === 1 && !isBranch ? indentation : levelIndentation * (level - 1) + indentation;
|
|
57
|
+
const defaultExpandIcon = /* @__PURE__ */ jsx(
|
|
58
|
+
"svg",
|
|
59
|
+
{
|
|
60
|
+
"aria-hidden": "true",
|
|
61
|
+
className: "text-muted-foreground transition-transform duration-200 group-aria-expanded:rotate-90",
|
|
62
|
+
width: "14",
|
|
63
|
+
height: "14",
|
|
64
|
+
viewBox: "0 0 24 24",
|
|
65
|
+
fill: "none",
|
|
66
|
+
stroke: "currentColor",
|
|
67
|
+
strokeWidth: "2",
|
|
68
|
+
strokeLinecap: "round",
|
|
69
|
+
strokeLinejoin: "round",
|
|
70
|
+
children: /* @__PURE__ */ jsx("path", { d: "m9 18 6-6-6-6" })
|
|
71
|
+
}
|
|
72
|
+
);
|
|
73
|
+
const defaultLoadingIcon = /* @__PURE__ */ jsx(
|
|
74
|
+
"svg",
|
|
75
|
+
{
|
|
76
|
+
"aria-hidden": "true",
|
|
77
|
+
className: "animate-spin text-muted-foreground",
|
|
78
|
+
width: "14",
|
|
79
|
+
height: "14",
|
|
80
|
+
viewBox: "0 0 24 24",
|
|
81
|
+
fill: "none",
|
|
82
|
+
stroke: "currentColor",
|
|
83
|
+
strokeWidth: "2",
|
|
84
|
+
strokeLinecap: "round",
|
|
85
|
+
strokeLinejoin: "round",
|
|
86
|
+
children: /* @__PURE__ */ jsx("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" })
|
|
87
|
+
}
|
|
88
|
+
);
|
|
89
|
+
return /* @__PURE__ */ jsxs(
|
|
90
|
+
"div",
|
|
91
|
+
{
|
|
92
|
+
ref,
|
|
93
|
+
"aria-expanded": !isEditing && isExpanded,
|
|
94
|
+
className: cn(
|
|
95
|
+
"group relative flex h-8 cursor-pointer select-none items-center gap-3 text-sm text-muted-foreground transition-colors hover:bg-muted",
|
|
96
|
+
isSelected && "!bg-muted text-foreground",
|
|
97
|
+
className
|
|
98
|
+
),
|
|
99
|
+
style: {
|
|
100
|
+
paddingLeft,
|
|
101
|
+
...style
|
|
102
|
+
},
|
|
103
|
+
"data-treeview-is-branch": isBranch,
|
|
104
|
+
"data-treeview-level": level,
|
|
105
|
+
...props,
|
|
106
|
+
children: [
|
|
107
|
+
level > 1 && /* @__PURE__ */ jsx(
|
|
108
|
+
"div",
|
|
109
|
+
{
|
|
110
|
+
style: {
|
|
111
|
+
left: (levelIndentation / 2 + 4) * (level - 1) + indentation
|
|
112
|
+
},
|
|
113
|
+
className: "absolute h-full w-px group-data-[treeview-is-branch=false]:border"
|
|
114
|
+
}
|
|
115
|
+
),
|
|
116
|
+
isSelected && /* @__PURE__ */ jsx("div", { className: "absolute left-0 h-full w-0.5 bg-foreground" }),
|
|
117
|
+
isBranch && (isLoading ? loadingIcon ?? defaultLoadingIcon : expandIcon ?? defaultExpandIcon),
|
|
118
|
+
icon,
|
|
119
|
+
/* @__PURE__ */ jsx(
|
|
120
|
+
"span",
|
|
121
|
+
{
|
|
122
|
+
className: cn("truncate text-sm", isEditing && "hidden"),
|
|
123
|
+
title: name,
|
|
124
|
+
children: name
|
|
125
|
+
}
|
|
126
|
+
),
|
|
127
|
+
isEditing && /* @__PURE__ */ jsx("form", { onSubmit: handleSubmit, children: /* @__PURE__ */ jsx(
|
|
128
|
+
"input",
|
|
129
|
+
{
|
|
130
|
+
ref: inputRef,
|
|
131
|
+
onChange: (e) => {
|
|
132
|
+
setLocalValueState(e.target.value);
|
|
133
|
+
},
|
|
134
|
+
onKeyDownCapture: (e) => {
|
|
135
|
+
if (e.key === "Enter") {
|
|
136
|
+
onEditSubmit?.(localValueState);
|
|
137
|
+
} else if (e.key === "Escape") {
|
|
138
|
+
setLocalValueState(name);
|
|
139
|
+
onEditSubmit?.(name);
|
|
140
|
+
} else {
|
|
141
|
+
e.stopPropagation();
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
className: "block h-7 w-full rounded border bg-background px-2 py-1 text-sm focus:outline-none focus:ring-1 focus:ring-ring",
|
|
145
|
+
value: localValueState
|
|
146
|
+
}
|
|
147
|
+
) })
|
|
148
|
+
]
|
|
149
|
+
}
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
);
|
|
153
|
+
TreeViewItem.displayName = "TreeViewItem";
|
|
154
|
+
|
|
155
|
+
export { TreeView, TreeViewItem };
|
|
156
|
+
//# sourceMappingURL=index.js.map
|
|
157
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils.ts","../src/tree-view.tsx"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { ClassValue } from \"clsx\";\n\n/**\n * Utility function to merge class names\n * This is a minimal implementation that doesn't require clsx/tailwind-merge as dependencies\n */\nexport function cn(...inputs: ClassValue[]): string {\n\treturn inputs\n\t\t.flat()\n\t\t.filter((x) => typeof x === \"string\" && x.length > 0)\n\t\t.join(\" \");\n}\n","\"use client\";\n\nimport * as React from \"react\";\nimport * as TreeViewPrimitive from \"react-accessible-treeview\";\n\nimport { cn } from \"./utils\";\n\n/**\n * TreeView component props - extends the underlying react-accessible-treeview props\n */\nexport type TreeViewProps = React.ComponentPropsWithoutRef<\n\ttypeof TreeViewPrimitive.default\n>;\n\n/**\n * TreeView component - a wrapper around react-accessible-treeview\n *\n * @example\n * ```tsx\n * import { TreeView, TreeViewItem, flattenTree } from \"shadcn-treeview\";\n *\n * const data = flattenTree({\n * name: \"Root\",\n * children: [{ name: \"Child 1\" }, { name: \"Child 2\" }]\n * });\n *\n * <TreeView\n * data={data}\n * nodeRenderer={({ element, getNodeProps, level, isBranch, isExpanded, isSelected }) => (\n * <TreeViewItem\n * {...getNodeProps()}\n * name={element.name}\n * level={level}\n * isBranch={isBranch}\n * isExpanded={isExpanded}\n * isSelected={isSelected}\n * indentation={16}\n * />\n * )}\n * />\n * ```\n */\nconst TreeView = TreeViewPrimitive.default;\n\n/**\n * Props for the TreeViewItem component\n */\nexport type TreeViewItemProps = React.ComponentPropsWithoutRef<\"div\"> & {\n\t/** The nesting level of the item (1-based) */\n\tlevel: number;\n\t/** Whether this item is expanded (only applicable for branches) */\n\tisExpanded?: boolean;\n\t/** Whether this item is a branch (has children) */\n\tisBranch?: boolean;\n\t/** Whether this item is currently selected */\n\tisSelected?: boolean;\n\t/** Base indentation in pixels */\n\tindentation?: number;\n\t/** Indentation per level in pixels */\n\tlevelIndentation?: number;\n\t/** The display name of the item */\n\tname: string;\n\t/** Optional icon to display before the name */\n\ticon?: React.ReactNode;\n\t/** Whether the item is in editing mode */\n\tisEditing?: boolean;\n\t/** Callback when editing is submitted */\n\tonEditSubmit?: (value: string) => void;\n\t/** Whether the item is in a loading state */\n\tisLoading?: boolean;\n\t/** Custom expand/collapse icon */\n\texpandIcon?: React.ReactNode;\n\t/** Custom loading icon */\n\tloadingIcon?: React.ReactNode;\n};\n\n/**\n * TreeViewItem component - renders a single item in the tree\n *\n * @example\n * ```tsx\n * <TreeViewItem\n * name=\"My File\"\n * level={1}\n * isBranch={false}\n * isSelected={false}\n * indentation={16}\n * icon={<FileIcon className=\"h-4 w-4\" />}\n * />\n * ```\n */\nconst TreeViewItem = React.forwardRef<HTMLDivElement, TreeViewItemProps>(\n\t(\n\t\t{\n\t\t\tlevel = 1,\n\t\t\tisExpanded = false,\n\t\t\tisBranch = false,\n\t\t\tisSelected = false,\n\t\t\tisLoading = false,\n\t\t\tindentation = 16,\n\t\t\tlevelIndentation = 48,\n\t\t\tname = \"\",\n\t\t\ticon,\n\t\t\tisEditing = false,\n\t\t\tonEditSubmit,\n\t\t\texpandIcon,\n\t\t\tloadingIcon,\n\t\t\tclassName,\n\t\t\tstyle,\n\t\t\t...props\n\t\t},\n\t\tref,\n\t) => {\n\t\tconst [localValueState, setLocalValueState] = React.useState(name);\n\t\tconst inputRef = React.useRef<HTMLInputElement>(null);\n\n\t\tReact.useEffect(() => {\n\t\t\tconst handleClickOutside = (event: MouseEvent) => {\n\t\t\t\tif (!inputRef.current?.contains(event.target as Node)) {\n\t\t\t\t\tonEditSubmit?.(localValueState);\n\t\t\t\t}\n\t\t\t};\n\t\t\tif (isEditing) {\n\t\t\t\tdocument.addEventListener(\"mousedown\", handleClickOutside);\n\t\t\t}\n\t\t\treturn () => {\n\t\t\t\tif (isEditing) {\n\t\t\t\t\tdocument.removeEventListener(\"mousedown\", handleClickOutside);\n\t\t\t\t}\n\t\t\t};\n\t\t}, [isEditing, localValueState, onEditSubmit]);\n\n\t\tReact.useEffect(() => {\n\t\t\tif (isEditing) {\n\t\t\t\tinputRef.current?.focus();\n\t\t\t}\n\t\t}, [isEditing]);\n\n\t\tconst handleSubmit = (e: React.FormEvent<HTMLFormElement>): void => {\n\t\t\te.preventDefault();\n\t\t\tonEditSubmit?.(localValueState);\n\t\t};\n\n\t\tconst paddingLeft =\n\t\t\tlevel === 1 && !isBranch\n\t\t\t\t? indentation\n\t\t\t\t: levelIndentation * (level - 1) + indentation;\n\n\t\tconst defaultExpandIcon = (\n\t\t\t<svg\n\t\t\t\taria-hidden=\"true\"\n\t\t\t\tclassName=\"text-muted-foreground transition-transform duration-200 group-aria-expanded:rotate-90\"\n\t\t\t\twidth=\"14\"\n\t\t\t\theight=\"14\"\n\t\t\t\tviewBox=\"0 0 24 24\"\n\t\t\t\tfill=\"none\"\n\t\t\t\tstroke=\"currentColor\"\n\t\t\t\tstrokeWidth=\"2\"\n\t\t\t\tstrokeLinecap=\"round\"\n\t\t\t\tstrokeLinejoin=\"round\"\n\t\t\t>\n\t\t\t\t<path d=\"m9 18 6-6-6-6\" />\n\t\t\t</svg>\n\t\t);\n\n\t\tconst defaultLoadingIcon = (\n\t\t\t<svg\n\t\t\t\taria-hidden=\"true\"\n\t\t\t\tclassName=\"animate-spin text-muted-foreground\"\n\t\t\t\twidth=\"14\"\n\t\t\t\theight=\"14\"\n\t\t\t\tviewBox=\"0 0 24 24\"\n\t\t\t\tfill=\"none\"\n\t\t\t\tstroke=\"currentColor\"\n\t\t\t\tstrokeWidth=\"2\"\n\t\t\t\tstrokeLinecap=\"round\"\n\t\t\t\tstrokeLinejoin=\"round\"\n\t\t\t>\n\t\t\t\t<path d=\"M21 12a9 9 0 1 1-6.219-8.56\" />\n\t\t\t</svg>\n\t\t);\n\n\t\treturn (\n\t\t\t<div\n\t\t\t\tref={ref}\n\t\t\t\taria-expanded={!isEditing && isExpanded}\n\t\t\t\tclassName={cn(\n\t\t\t\t\t\"group relative flex h-8 cursor-pointer select-none items-center gap-3 text-sm text-muted-foreground transition-colors hover:bg-muted\",\n\t\t\t\t\tisSelected && \"!bg-muted text-foreground\",\n\t\t\t\t\tclassName,\n\t\t\t\t)}\n\t\t\t\tstyle={{\n\t\t\t\t\tpaddingLeft,\n\t\t\t\t\t...style,\n\t\t\t\t}}\n\t\t\t\tdata-treeview-is-branch={isBranch}\n\t\t\t\tdata-treeview-level={level}\n\t\t\t\t{...props}\n\t\t\t>\n\t\t\t\t{level > 1 && (\n\t\t\t\t\t<div\n\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\tleft: (levelIndentation / 2 + 4) * (level - 1) + indentation,\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tclassName=\"absolute h-full w-px group-data-[treeview-is-branch=false]:border\"\n\t\t\t\t\t/>\n\t\t\t\t)}\n\t\t\t\t{isSelected && (\n\t\t\t\t\t<div className=\"absolute left-0 h-full w-0.5 bg-foreground\" />\n\t\t\t\t)}\n\t\t\t\t{isBranch &&\n\t\t\t\t\t(isLoading\n\t\t\t\t\t\t? (loadingIcon ?? defaultLoadingIcon)\n\t\t\t\t\t\t: (expandIcon ?? defaultExpandIcon))}\n\t\t\t\t{icon}\n\t\t\t\t<span\n\t\t\t\t\tclassName={cn(\"truncate text-sm\", isEditing && \"hidden\")}\n\t\t\t\t\ttitle={name}\n\t\t\t\t>\n\t\t\t\t\t{name}\n\t\t\t\t</span>\n\t\t\t\t{isEditing && (\n\t\t\t\t\t<form onSubmit={handleSubmit}>\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\tref={inputRef}\n\t\t\t\t\t\t\tonChange={(e) => {\n\t\t\t\t\t\t\t\tsetLocalValueState(e.target.value);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tonKeyDownCapture={(e) => {\n\t\t\t\t\t\t\t\tif (e.key === \"Enter\") {\n\t\t\t\t\t\t\t\t\tonEditSubmit?.(localValueState);\n\t\t\t\t\t\t\t\t} else if (e.key === \"Escape\") {\n\t\t\t\t\t\t\t\t\tsetLocalValueState(name);\n\t\t\t\t\t\t\t\t\tonEditSubmit?.(name);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\te.stopPropagation();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tclassName=\"block h-7 w-full rounded border bg-background px-2 py-1 text-sm focus:outline-none focus:ring-1 focus:ring-ring\"\n\t\t\t\t\t\t\tvalue={localValueState}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</form>\n\t\t\t\t)}\n\t\t\t</div>\n\t\t);\n\t},\n);\nTreeViewItem.displayName = \"TreeViewItem\";\n\nexport { TreeView, TreeViewItem };\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "shadcn-treeview",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Accessible tree view component for React with Shadcn UI styling. Built on react-accessible-treeview.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.cjs",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"import": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"default": "./dist/index.js"
|
|
15
|
+
},
|
|
16
|
+
"require": {
|
|
17
|
+
"types": "./dist/index.d.cts",
|
|
18
|
+
"default": "./dist/index.cjs"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist",
|
|
24
|
+
"README.md"
|
|
25
|
+
],
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsup",
|
|
28
|
+
"dev": "tsup --watch",
|
|
29
|
+
"typecheck": "tsc --noEmit",
|
|
30
|
+
"lint": "biome check",
|
|
31
|
+
"lint:fix": "biome check --fix",
|
|
32
|
+
"test": "vitest run",
|
|
33
|
+
"prepublishOnly": "npm run build"
|
|
34
|
+
},
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"react": ">=18.0.0",
|
|
37
|
+
"react-dom": ">=18.0.0"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"react-accessible-treeview": "^2.10.0"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@biomejs/biome": "^2.3.11",
|
|
44
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
45
|
+
"@testing-library/react": "^16.3.0",
|
|
46
|
+
"@types/react": "^19.0.0",
|
|
47
|
+
"@types/react-dom": "^19.0.0",
|
|
48
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
49
|
+
"jsdom": "^27.4.0",
|
|
50
|
+
"react": "^19.0.0",
|
|
51
|
+
"react-dom": "^19.0.0",
|
|
52
|
+
"tsup": "^8.5.0",
|
|
53
|
+
"typescript": "^5.8.0",
|
|
54
|
+
"vitest": "^3.2.0"
|
|
55
|
+
},
|
|
56
|
+
"keywords": [
|
|
57
|
+
"react",
|
|
58
|
+
"treeview",
|
|
59
|
+
"tree",
|
|
60
|
+
"accessible",
|
|
61
|
+
"a11y",
|
|
62
|
+
"shadcn-ui",
|
|
63
|
+
"shadcn",
|
|
64
|
+
"file-tree",
|
|
65
|
+
"hierarchy",
|
|
66
|
+
"typescript",
|
|
67
|
+
"hooks"
|
|
68
|
+
],
|
|
69
|
+
"repository": {
|
|
70
|
+
"type": "git",
|
|
71
|
+
"url": "git+https://github.com/achromaticlabs/shadcn-treeview.git"
|
|
72
|
+
},
|
|
73
|
+
"homepage": "https://shadcn-treeview.achromatic.dev",
|
|
74
|
+
"bugs": {
|
|
75
|
+
"url": "https://github.com/achromaticlabs/shadcn-treeview/issues"
|
|
76
|
+
}
|
|
77
|
+
}
|