@alexgorbatchev/shadcn 0.0.21 → 0.0.23
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.
|
@@ -4,13 +4,12 @@ import type { IDensityProps } from "../../density/types.js";
|
|
|
4
4
|
type TreeProps<T> = IDensityProps & {
|
|
5
5
|
tree: TreeInstance<T>;
|
|
6
6
|
label: string;
|
|
7
|
+
variant?: "default" | "sidebar";
|
|
7
8
|
renderIcon?: (item: ItemInstance<T>) => ReactNode;
|
|
8
9
|
renderLabel?: (item: ItemInstance<T>) => ReactNode;
|
|
10
|
+
renderItem?: (item: ItemInstance<T>) => ReactNode;
|
|
9
11
|
};
|
|
10
|
-
/**
|
|
11
|
-
declare function Tree<T>({ tree, label, renderIcon, renderLabel, density }: TreeProps<T>): ReactElement;
|
|
12
|
+
/** Headless Tree owns navigation, selection, expansion, and optional pointer/keyboard drag and drop. */
|
|
13
|
+
declare function Tree<T>({ tree, label, variant, renderIcon, renderLabel, renderItem, density, }: TreeProps<T>): ReactElement;
|
|
12
14
|
export { Tree };
|
|
13
15
|
export type { TreeProps };
|
|
14
|
-
export { useTree, AssistiveTreeDescription } from "@headless-tree/react";
|
|
15
|
-
export { syncDataLoaderFeature, asyncDataLoaderFeature, hotkeysCoreFeature, selectionFeature, checkboxesFeature, searchFeature, renamingFeature, expandAllFeature, } from "@headless-tree/core";
|
|
16
|
-
export type { TreeInstance, ItemInstance, TreeConfig } from "@headless-tree/core";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { useRender } from "@base-ui/react/use-render";
|
|
2
|
+
import type { ItemInstance } from "@headless-tree/core";
|
|
3
|
+
import type { ReactElement, ReactNode } from "react";
|
|
4
|
+
import type { IDensityProps } from "../../density/types.js";
|
|
5
|
+
type TreeItemState = {
|
|
6
|
+
slot: "tree-item";
|
|
7
|
+
selected: boolean;
|
|
8
|
+
expanded: boolean;
|
|
9
|
+
dropTarget: boolean;
|
|
10
|
+
density: IDensityProps["density"];
|
|
11
|
+
};
|
|
12
|
+
type TreeItemProps<T> = useRender.ComponentProps<"div", TreeItemState> & IDensityProps & {
|
|
13
|
+
item: ItemInstance<T>;
|
|
14
|
+
startEnhancer?: ReactNode;
|
|
15
|
+
endEnhancer?: ReactNode;
|
|
16
|
+
expandOn?: "click" | "doubleClick";
|
|
17
|
+
isEditing?: boolean;
|
|
18
|
+
isDropTarget?: boolean;
|
|
19
|
+
};
|
|
20
|
+
/** Registered tree row; compose menus with render and place counts/actions in endEnhancer. */
|
|
21
|
+
export declare function TreeItem<T>({ item, children, startEnhancer, endEnhancer, expandOn, isEditing, isDropTarget, density, render, ref, ...props }: TreeItemProps<T>): ReactElement;
|
|
22
|
+
export type { TreeItemProps };
|
|
@@ -1,2 +1,7 @@
|
|
|
1
|
-
export { Tree
|
|
2
|
-
export
|
|
1
|
+
export { Tree } from "./Tree.js";
|
|
2
|
+
export { TreeItem } from "./TreeItem.js";
|
|
3
|
+
export type { TreeProps } from "./Tree.js";
|
|
4
|
+
export type { TreeItemProps } from "./TreeItem.js";
|
|
5
|
+
export { useTree, AssistiveTreeDescription } from "@headless-tree/react";
|
|
6
|
+
export { syncDataLoaderFeature, asyncDataLoaderFeature, hotkeysCoreFeature, selectionFeature, checkboxesFeature, searchFeature, renamingFeature, expandAllFeature, dragAndDropFeature, keyboardDragAndDropFeature, isOrderedDragTarget, } from "@headless-tree/core";
|
|
7
|
+
export type { TreeInstance, ItemInstance, TreeConfig, DragTarget } from "@headless-tree/core";
|
|
@@ -2,8 +2,164 @@ import {
|
|
|
2
2
|
cn2
|
|
3
3
|
} from "../../chunk-cp2m7y7v.js";
|
|
4
4
|
|
|
5
|
-
// src/components/tree/
|
|
5
|
+
// src/components/tree/TreeItem.tsx
|
|
6
|
+
import { mergeProps } from "@base-ui/react/merge-props";
|
|
7
|
+
import { useRender } from "@base-ui/react/use-render";
|
|
6
8
|
import { ChevronRightIcon, FileIcon, FolderIcon, FolderOpenIcon } from "lucide-react";
|
|
9
|
+
import { jsx, jsxs, Fragment } from "react/jsx-runtime";
|
|
10
|
+
function isRowControl(event) {
|
|
11
|
+
return event.target !== event.currentTarget && event.target instanceof Element && Boolean(event.target.closest("button,input,textarea,select,form"));
|
|
12
|
+
}
|
|
13
|
+
function TreeItem({
|
|
14
|
+
item,
|
|
15
|
+
children,
|
|
16
|
+
startEnhancer,
|
|
17
|
+
endEnhancer,
|
|
18
|
+
expandOn = "click",
|
|
19
|
+
isEditing = false,
|
|
20
|
+
isDropTarget = false,
|
|
21
|
+
density,
|
|
22
|
+
render,
|
|
23
|
+
ref,
|
|
24
|
+
...props
|
|
25
|
+
}) {
|
|
26
|
+
const nativeProps = item.getProps();
|
|
27
|
+
const isExpanded = item.isExpanded();
|
|
28
|
+
const isSelected = item.isSelected?.() ?? false;
|
|
29
|
+
const isInsideTarget = item.isUnorderedDragTarget?.() ?? false;
|
|
30
|
+
const toggle = () => {
|
|
31
|
+
if (isExpanded)
|
|
32
|
+
item.collapse();
|
|
33
|
+
else
|
|
34
|
+
item.expand();
|
|
35
|
+
};
|
|
36
|
+
const select = (event) => {
|
|
37
|
+
if (isRowControl(event))
|
|
38
|
+
return;
|
|
39
|
+
item.setFocused();
|
|
40
|
+
if (event.shiftKey)
|
|
41
|
+
item.selectUpTo?.(event.ctrlKey || event.metaKey);
|
|
42
|
+
else if (event.ctrlKey || event.metaKey)
|
|
43
|
+
item.toggleSelect?.();
|
|
44
|
+
else
|
|
45
|
+
item.getTree().setSelectedItems?.([item.getId()]);
|
|
46
|
+
if (!event.shiftKey)
|
|
47
|
+
item.getTree().getDataRef().current.selectUpToAnchorId = item.getId();
|
|
48
|
+
item.primaryAction();
|
|
49
|
+
};
|
|
50
|
+
return useRender({
|
|
51
|
+
defaultTagName: "div",
|
|
52
|
+
render,
|
|
53
|
+
ref: [item.registerElement, ref ?? null],
|
|
54
|
+
props: mergeProps({
|
|
55
|
+
...nativeProps,
|
|
56
|
+
ref: undefined,
|
|
57
|
+
onKeyDown: (event) => {
|
|
58
|
+
if (event.target !== event.currentTarget || event.defaultPrevented || event.key !== "Enter" || event.repeat || item.getTree().getState().dnd)
|
|
59
|
+
return;
|
|
60
|
+
event.preventDefault();
|
|
61
|
+
item.getTree().setSelectedItems?.([item.getId()]);
|
|
62
|
+
item.primaryAction();
|
|
63
|
+
if (expandOn === "doubleClick" && item.isFolder())
|
|
64
|
+
toggle();
|
|
65
|
+
},
|
|
66
|
+
...expandOn === "doubleClick" ? {
|
|
67
|
+
onClick: select,
|
|
68
|
+
onDoubleClick: (event) => {
|
|
69
|
+
if (!isRowControl(event) && item.isFolder())
|
|
70
|
+
toggle();
|
|
71
|
+
}
|
|
72
|
+
} : {},
|
|
73
|
+
...isEditing ? {
|
|
74
|
+
draggable: false,
|
|
75
|
+
onClick: undefined,
|
|
76
|
+
onDoubleClick: undefined,
|
|
77
|
+
onKeyDown: undefined,
|
|
78
|
+
onDragStart: undefined
|
|
79
|
+
} : {},
|
|
80
|
+
style: { paddingInlineStart: `${item.getItemMeta().level * (item.getTree().getConfig().indent ?? 20) + 8}px` },
|
|
81
|
+
className: cn2("relative flex min-h-control min-w-0 cursor-default items-center gap-2 rounded-sm py-1 pr-2 text-sm outline-none hover:bg-accent focus-visible:ring-2 focus-visible:ring-ring", (isSelected || isDropTarget || isInsideTarget) && "bg-accent text-accent-foreground"),
|
|
82
|
+
children: /* @__PURE__ */ jsxs(Fragment, {
|
|
83
|
+
children: [
|
|
84
|
+
item.isFolder() ? /* @__PURE__ */ jsx(ChevronRightIcon, {
|
|
85
|
+
"aria-hidden": "true",
|
|
86
|
+
className: cn2("size-3.5 shrink-0", isExpanded && "rotate-90")
|
|
87
|
+
}) : /* @__PURE__ */ jsx("span", {
|
|
88
|
+
className: "size-3.5 shrink-0"
|
|
89
|
+
}),
|
|
90
|
+
/* @__PURE__ */ jsx("span", {
|
|
91
|
+
"aria-hidden": "true",
|
|
92
|
+
className: "flex shrink-0 [&>svg]:size-4",
|
|
93
|
+
children: startEnhancer ?? (item.isFolder() ? isExpanded ? /* @__PURE__ */ jsx(FolderOpenIcon, {}) : /* @__PURE__ */ jsx(FolderIcon, {}) : /* @__PURE__ */ jsx(FileIcon, {}))
|
|
94
|
+
}),
|
|
95
|
+
/* @__PURE__ */ jsx("div", {
|
|
96
|
+
className: "min-w-0 flex-1 truncate",
|
|
97
|
+
children: children ?? item.getItemName()
|
|
98
|
+
}),
|
|
99
|
+
endEnhancer && /* @__PURE__ */ jsx("div", {
|
|
100
|
+
className: "flex shrink-0 items-center gap-1 text-xs tabular-nums",
|
|
101
|
+
onClick: (event) => event.stopPropagation(),
|
|
102
|
+
onDoubleClick: (event) => event.stopPropagation(),
|
|
103
|
+
onKeyDown: (event) => event.stopPropagation(),
|
|
104
|
+
children: endEnhancer
|
|
105
|
+
})
|
|
106
|
+
]
|
|
107
|
+
})
|
|
108
|
+
}, props),
|
|
109
|
+
state: {
|
|
110
|
+
slot: "tree-item",
|
|
111
|
+
selected: isSelected,
|
|
112
|
+
expanded: isExpanded,
|
|
113
|
+
dropTarget: isInsideTarget || isDropTarget,
|
|
114
|
+
density
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// src/components/tree/Tree.tsx
|
|
120
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
121
|
+
function Tree({
|
|
122
|
+
tree,
|
|
123
|
+
label,
|
|
124
|
+
variant = "default",
|
|
125
|
+
renderIcon,
|
|
126
|
+
renderLabel,
|
|
127
|
+
renderItem,
|
|
128
|
+
density
|
|
129
|
+
}) {
|
|
130
|
+
const container = tree.getContainerProps(label);
|
|
131
|
+
const dragLine = tree.getDragLineData?.();
|
|
132
|
+
const element = tree.getElement();
|
|
133
|
+
return /* @__PURE__ */ jsxs2("div", {
|
|
134
|
+
...container,
|
|
135
|
+
"data-density": density,
|
|
136
|
+
className: cn2("grid min-w-0 gap-1", variant === "default" && "rounded-sm border border-border bg-card p-2"),
|
|
137
|
+
onKeyDown: (event) => {
|
|
138
|
+
if (event.target instanceof Element && event.target.closest("input,textarea,select,form,button,[role=menu]"))
|
|
139
|
+
return;
|
|
140
|
+
container.onKeyDown?.(event);
|
|
141
|
+
},
|
|
142
|
+
children: [
|
|
143
|
+
tree.getItems().map((item) => renderItem ? /* @__PURE__ */ jsx2("div", {
|
|
144
|
+
role: "none",
|
|
145
|
+
children: renderItem(item)
|
|
146
|
+
}, item.getKey()) : /* @__PURE__ */ jsx2(TreeItem, {
|
|
147
|
+
item,
|
|
148
|
+
startEnhancer: renderIcon?.(item),
|
|
149
|
+
density,
|
|
150
|
+
children: renderLabel?.(item)
|
|
151
|
+
}, item.getKey())),
|
|
152
|
+
dragLine && /* @__PURE__ */ jsx2("span", {
|
|
153
|
+
"data-slot": "tree-drop-indicator",
|
|
154
|
+
"aria-hidden": "true",
|
|
155
|
+
className: "pointer-events-none absolute h-0.5 bg-primary",
|
|
156
|
+
style: tree.getDragLineStyle(-1 - (element?.clientTop ?? 0), -(element?.clientLeft ?? 0))
|
|
157
|
+
})
|
|
158
|
+
]
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// src/components/tree/index.ts
|
|
7
163
|
import { useTree, AssistiveTreeDescription } from "@headless-tree/react";
|
|
8
164
|
import {
|
|
9
165
|
syncDataLoaderFeature,
|
|
@@ -13,42 +169,22 @@ import {
|
|
|
13
169
|
checkboxesFeature,
|
|
14
170
|
searchFeature,
|
|
15
171
|
renamingFeature,
|
|
16
|
-
expandAllFeature
|
|
172
|
+
expandAllFeature,
|
|
173
|
+
dragAndDropFeature,
|
|
174
|
+
keyboardDragAndDropFeature,
|
|
175
|
+
isOrderedDragTarget
|
|
17
176
|
} from "@headless-tree/core";
|
|
18
|
-
import { jsx } from "react/jsx-runtime";
|
|
19
|
-
import { createElement } from "react";
|
|
20
|
-
function Tree({ tree, label, renderIcon, renderLabel, density }) {
|
|
21
|
-
return /* @__PURE__ */ jsx("div", {
|
|
22
|
-
...tree.getContainerProps(label),
|
|
23
|
-
"data-density": density,
|
|
24
|
-
className: "grid gap-1 rounded-sm border border-border bg-card p-2",
|
|
25
|
-
children: tree.getItems().map((item) => /* @__PURE__ */ createElement("div", {
|
|
26
|
-
...item.getProps(),
|
|
27
|
-
key: item.getKey(),
|
|
28
|
-
style: { paddingInlineStart: `${item.getItemMeta().level * 20 + 8}px` },
|
|
29
|
-
className: cn2("flex min-h-control cursor-default items-center gap-2 rounded-sm py-1 pr-2 text-sm outline-none hover:bg-accent focus-visible:ring-2 focus-visible:ring-ring", tree.getState().selectedItems?.includes(item.getId()) && "bg-accent text-accent-foreground")
|
|
30
|
-
}, item.isFolder() ? /* @__PURE__ */ jsx(ChevronRightIcon, {
|
|
31
|
-
"aria-hidden": "true",
|
|
32
|
-
className: cn2("size-3.5 shrink-0", item.isExpanded() && "rotate-90")
|
|
33
|
-
}) : /* @__PURE__ */ jsx("span", {
|
|
34
|
-
className: "size-3.5 shrink-0"
|
|
35
|
-
}), /* @__PURE__ */ jsx("span", {
|
|
36
|
-
"aria-hidden": "true",
|
|
37
|
-
className: "flex shrink-0 [&>svg]:size-4",
|
|
38
|
-
children: renderIcon ? renderIcon(item) : item.isFolder() ? item.isExpanded() ? /* @__PURE__ */ jsx(FolderOpenIcon, {}) : /* @__PURE__ */ jsx(FolderIcon, {}) : /* @__PURE__ */ jsx(FileIcon, {})
|
|
39
|
-
}), /* @__PURE__ */ jsx("span", {
|
|
40
|
-
className: "min-w-0 truncate",
|
|
41
|
-
children: renderLabel ? renderLabel(item) : item.getItemName()
|
|
42
|
-
})))
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
177
|
export {
|
|
46
178
|
AssistiveTreeDescription,
|
|
47
179
|
Tree,
|
|
180
|
+
TreeItem,
|
|
48
181
|
asyncDataLoaderFeature,
|
|
49
182
|
checkboxesFeature,
|
|
183
|
+
dragAndDropFeature,
|
|
50
184
|
expandAllFeature,
|
|
51
185
|
hotkeysCoreFeature,
|
|
186
|
+
isOrderedDragTarget,
|
|
187
|
+
keyboardDragAndDropFeature,
|
|
52
188
|
renamingFeature,
|
|
53
189
|
searchFeature,
|
|
54
190
|
selectionFeature,
|
package/dist/index.js
CHANGED
|
@@ -2673,6 +2673,7 @@ function InlineEdit({
|
|
|
2673
2673
|
}) {
|
|
2674
2674
|
const input = useRef3(null);
|
|
2675
2675
|
const isFinished = useRef3(false);
|
|
2676
|
+
const shouldCancel = useRef3(false);
|
|
2676
2677
|
const [isSaving, setSaving] = useState2(false);
|
|
2677
2678
|
const [error, setError] = useState2("");
|
|
2678
2679
|
useEffect2(() => {
|
|
@@ -2712,14 +2713,21 @@ function InlineEdit({
|
|
|
2712
2713
|
disabled: isDisabled || isSaving,
|
|
2713
2714
|
onChange: (event) => onValueChange(event.target.value),
|
|
2714
2715
|
onBlur: () => {
|
|
2715
|
-
|
|
2716
|
+
if (shouldCancel.current && !isFinished.current) {
|
|
2717
|
+
isFinished.current = true;
|
|
2718
|
+
onCancel();
|
|
2719
|
+
} else
|
|
2720
|
+
save();
|
|
2716
2721
|
},
|
|
2717
2722
|
onKeyDown: (event) => {
|
|
2718
2723
|
if (event.key === "Escape") {
|
|
2719
2724
|
event.preventDefault();
|
|
2720
2725
|
event.stopPropagation();
|
|
2721
|
-
|
|
2722
|
-
|
|
2726
|
+
shouldCancel.current = true;
|
|
2727
|
+
event.currentTarget.blur();
|
|
2728
|
+
} else if (event.key === "Enter") {
|
|
2729
|
+
event.preventDefault();
|
|
2730
|
+
event.currentTarget.blur();
|
|
2723
2731
|
}
|
|
2724
2732
|
}
|
|
2725
2733
|
}),
|
|
@@ -8,7 +8,14 @@ Source: src/components/stories/navigation/Tree.stories.tsx
|
|
|
8
8
|
## Public imports
|
|
9
9
|
|
|
10
10
|
```tsx
|
|
11
|
-
import { Tree, useTree, syncDataLoaderFeature, hotkeysCoreFeature, selectionFeature } from "@alexgorbatchev/shadcn/tree";
|
|
11
|
+
import { Tree, TreeItem, useTree, syncDataLoaderFeature, hotkeysCoreFeature, selectionFeature } from "@alexgorbatchev/shadcn/tree";
|
|
12
|
+
import { Button, ContextMenu, ContextMenuTrigger, ContextMenuContent, ContextMenuGroup, ContextMenuItem, InlineEdit } from "@alexgorbatchev/shadcn";
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Supporting imports
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
import { useState } from "react";
|
|
12
19
|
```
|
|
13
20
|
|
|
14
21
|
## Default render
|
|
@@ -26,3 +33,19 @@ Storybook CSF excerpts. Default args and render apply unless overridden. Local h
|
|
|
26
33
|
```tsx
|
|
27
34
|
{}
|
|
28
35
|
```
|
|
36
|
+
|
|
37
|
+
### ComposedRows
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
{
|
|
41
|
+
render: () => <Example isComposed/>
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### EditableRows
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
{
|
|
49
|
+
render: () => <EditableExample />
|
|
50
|
+
}
|
|
51
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alexgorbatchev/shadcn",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.23",
|
|
4
4
|
"description": "Sharp, monospace React components with compact and comfortable densities.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -1043,7 +1043,7 @@
|
|
|
1043
1043
|
"build": "bun scripts/build.ts",
|
|
1044
1044
|
"typecheck": "tsc --noEmit",
|
|
1045
1045
|
"lint": "oxlint --deny-warnings src .storybook scripts && oxfmt --check . && bun run lint:unused",
|
|
1046
|
-
"lint:unused": "
|
|
1046
|
+
"lint:unused": "ts-unused check tsconfig.json",
|
|
1047
1047
|
"storybook": "bun scripts/buildLlmDocs.ts && storybook dev --host 127.0.0.1 -p 6006 --no-open",
|
|
1048
1048
|
"build-storybook": "bun scripts/buildLlmDocs.ts && storybook build",
|
|
1049
1049
|
"prepack": "bun run build",
|
|
@@ -1139,7 +1139,7 @@
|
|
|
1139
1139
|
"storybook": "^10.6.1",
|
|
1140
1140
|
"strip-ansi": "7.2.0",
|
|
1141
1141
|
"tailwindcss": "^4.3.3",
|
|
1142
|
-
"ts-unused": "3.7.
|
|
1142
|
+
"ts-unused": "3.7.1",
|
|
1143
1143
|
"typescript": "^6.0.3",
|
|
1144
1144
|
"vite": "^8.3.4",
|
|
1145
1145
|
"vitest": "^4.1.6",
|