@underverse-ui/underverse 0.2.43 → 0.2.45
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/dist/index.cjs +101 -45
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +32 -5
- package/dist/index.d.ts +32 -5
- package/dist/index.js +109 -53
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -8119,9 +8119,27 @@ function OverlayControls({
|
|
|
8119
8119
|
var import_react18 = require("react");
|
|
8120
8120
|
var import_lucide_react19 = require("lucide-react");
|
|
8121
8121
|
var import_jsx_runtime36 = require("react/jsx-runtime");
|
|
8122
|
-
|
|
8122
|
+
var defaultLabels = {
|
|
8123
|
+
emptyText: "No categories",
|
|
8124
|
+
selectedText: (count) => `${count} selected`
|
|
8125
|
+
};
|
|
8126
|
+
function CategoryTreeSelect(props) {
|
|
8127
|
+
const {
|
|
8128
|
+
categories,
|
|
8129
|
+
placeholder = "Select category",
|
|
8130
|
+
disabled,
|
|
8131
|
+
viewOnly = false,
|
|
8132
|
+
defaultExpanded = false,
|
|
8133
|
+
labels,
|
|
8134
|
+
inline = false,
|
|
8135
|
+
onNodeClick,
|
|
8136
|
+
className,
|
|
8137
|
+
singleSelect = false
|
|
8138
|
+
} = props;
|
|
8123
8139
|
const [isOpen, setIsOpen] = (0, import_react18.useState)(false);
|
|
8124
8140
|
const [expandedNodes, setExpandedNodes] = (0, import_react18.useState)(/* @__PURE__ */ new Set());
|
|
8141
|
+
const mergedLabels = { ...defaultLabels, ...labels };
|
|
8142
|
+
const valueArray = singleSelect ? props.value != null ? [props.value] : [] : props.value || [];
|
|
8125
8143
|
const parentCategories = categories.filter((c) => !c.parent_id);
|
|
8126
8144
|
const childrenMap = /* @__PURE__ */ new Map();
|
|
8127
8145
|
categories.forEach((cat) => {
|
|
@@ -8132,6 +8150,12 @@ function CategoryTreeSelect({ categories, value, onChange, placeholder = "Ch\u1E
|
|
|
8132
8150
|
childrenMap.get(cat.parent_id).push(cat);
|
|
8133
8151
|
}
|
|
8134
8152
|
});
|
|
8153
|
+
(0, import_react18.useEffect)(() => {
|
|
8154
|
+
if ((viewOnly || inline) && defaultExpanded) {
|
|
8155
|
+
const allParentIds = categories.filter((c) => childrenMap.has(c.id)).map((c) => c.id);
|
|
8156
|
+
setExpandedNodes(new Set(allParentIds));
|
|
8157
|
+
}
|
|
8158
|
+
}, [viewOnly, inline, defaultExpanded, categories]);
|
|
8135
8159
|
const toggleExpand = (id) => {
|
|
8136
8160
|
const newExpanded = new Set(expandedNodes);
|
|
8137
8161
|
if (newExpanded.has(id)) {
|
|
@@ -8142,43 +8166,54 @@ function CategoryTreeSelect({ categories, value, onChange, placeholder = "Ch\u1E
|
|
|
8142
8166
|
setExpandedNodes(newExpanded);
|
|
8143
8167
|
};
|
|
8144
8168
|
const handleSelect = (categoryId, category) => {
|
|
8145
|
-
|
|
8146
|
-
|
|
8147
|
-
|
|
8148
|
-
|
|
8149
|
-
|
|
8169
|
+
if (viewOnly) return;
|
|
8170
|
+
onNodeClick?.(category);
|
|
8171
|
+
if (!props.onChange) return;
|
|
8172
|
+
if (singleSelect) {
|
|
8173
|
+
const onChange = props.onChange;
|
|
8174
|
+
const currentValue = props.value;
|
|
8175
|
+
if (currentValue === categoryId) {
|
|
8176
|
+
onChange(null);
|
|
8177
|
+
} else {
|
|
8178
|
+
onChange(categoryId);
|
|
8179
|
+
}
|
|
8180
|
+
if (!inline) {
|
|
8181
|
+
setIsOpen(false);
|
|
8182
|
+
}
|
|
8150
8183
|
} else {
|
|
8151
|
-
|
|
8152
|
-
|
|
8153
|
-
|
|
8184
|
+
const onChange = props.onChange;
|
|
8185
|
+
const newSelected = new Set(valueArray);
|
|
8186
|
+
if (newSelected.has(categoryId)) {
|
|
8187
|
+
newSelected.delete(categoryId);
|
|
8188
|
+
const children = childrenMap.get(categoryId) || [];
|
|
8189
|
+
children.forEach((child) => newSelected.delete(child.id));
|
|
8190
|
+
} else {
|
|
8191
|
+
newSelected.add(categoryId);
|
|
8192
|
+
if (category.parent_id) {
|
|
8193
|
+
newSelected.add(category.parent_id);
|
|
8194
|
+
}
|
|
8154
8195
|
}
|
|
8196
|
+
onChange(Array.from(newSelected));
|
|
8155
8197
|
}
|
|
8156
|
-
onChange(Array.from(newSelected));
|
|
8157
8198
|
};
|
|
8158
8199
|
const renderCategory = (category, level = 0) => {
|
|
8159
8200
|
const children = childrenMap.get(category.id) || [];
|
|
8160
8201
|
const hasChildren = children.length > 0;
|
|
8161
8202
|
const isExpanded = expandedNodes.has(category.id);
|
|
8162
|
-
const isSelected =
|
|
8203
|
+
const isSelected = valueArray.includes(category.id);
|
|
8163
8204
|
return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { children: [
|
|
8164
8205
|
/* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
|
|
8165
8206
|
"div",
|
|
8166
8207
|
{
|
|
8167
8208
|
className: cn(
|
|
8168
|
-
"relative flex items-center gap-2 px-3 py-2
|
|
8169
|
-
"hover:bg-accent",
|
|
8170
|
-
// Selected state: subtle bg + square left indicator
|
|
8171
|
-
isSelected && "bg-primary/10 rounded-r-md"
|
|
8209
|
+
"relative flex items-center gap-2 px-3 py-2 rounded-md transition-colors",
|
|
8210
|
+
!viewOnly && "cursor-pointer hover:bg-accent",
|
|
8211
|
+
// Selected state: subtle bg + square left indicator (only in select mode)
|
|
8212
|
+
!viewOnly && isSelected && "bg-primary/10 rounded-r-md"
|
|
8172
8213
|
),
|
|
8173
8214
|
style: { paddingLeft: `${level * 1.5 + 0.75}rem` },
|
|
8174
8215
|
children: [
|
|
8175
|
-
isSelected && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
|
|
8176
|
-
"span",
|
|
8177
|
-
{
|
|
8178
|
-
"aria-hidden": true,
|
|
8179
|
-
className: "absolute left-0 top-0 bottom-0 w-1 bg-primary"
|
|
8180
|
-
}
|
|
8181
|
-
),
|
|
8216
|
+
!viewOnly && isSelected && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { "aria-hidden": true, className: "absolute left-0 top-0 bottom-0 w-1 bg-primary" }),
|
|
8182
8217
|
hasChildren ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
|
|
8183
8218
|
"button",
|
|
8184
8219
|
{
|
|
@@ -8191,25 +8226,39 @@ function CategoryTreeSelect({ categories, value, onChange, placeholder = "Ch\u1E
|
|
|
8191
8226
|
children: isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_lucide_react19.ChevronDown, { className: "w-4 h-4" }) : /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_lucide_react19.ChevronRight, { className: "w-4 h-4" })
|
|
8192
8227
|
}
|
|
8193
8228
|
) : /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: "w-5" }),
|
|
8194
|
-
|
|
8195
|
-
|
|
8196
|
-
{
|
|
8197
|
-
|
|
8198
|
-
|
|
8199
|
-
|
|
8200
|
-
|
|
8201
|
-
|
|
8202
|
-
|
|
8203
|
-
|
|
8204
|
-
|
|
8205
|
-
|
|
8206
|
-
|
|
8207
|
-
|
|
8208
|
-
|
|
8209
|
-
|
|
8210
|
-
|
|
8211
|
-
|
|
8212
|
-
|
|
8229
|
+
viewOnly ? (
|
|
8230
|
+
// View-only mode: just display the name
|
|
8231
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: "text-sm", children: category.name })
|
|
8232
|
+
) : singleSelect ? (
|
|
8233
|
+
// Single select mode: radio-style indicator
|
|
8234
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { onClick: () => handleSelect(category.id, category), className: "flex items-center gap-2 flex-1", children: [
|
|
8235
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
|
|
8236
|
+
"div",
|
|
8237
|
+
{
|
|
8238
|
+
className: cn(
|
|
8239
|
+
"w-4 h-4 border-2 rounded-full flex items-center justify-center transition-colors",
|
|
8240
|
+
isSelected ? "border-primary" : "border-muted-foreground/30"
|
|
8241
|
+
),
|
|
8242
|
+
children: isSelected && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "w-2 h-2 rounded-full bg-primary" })
|
|
8243
|
+
}
|
|
8244
|
+
),
|
|
8245
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: cn("text-sm", isSelected && "font-medium text-primary"), children: category.name })
|
|
8246
|
+
] })
|
|
8247
|
+
) : (
|
|
8248
|
+
// Multi select mode: checkbox-style indicator
|
|
8249
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { onClick: () => handleSelect(category.id, category), className: "flex items-center gap-2 flex-1", children: [
|
|
8250
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
|
|
8251
|
+
"div",
|
|
8252
|
+
{
|
|
8253
|
+
className: cn(
|
|
8254
|
+
"w-4 h-4 border-2 rounded flex items-center justify-center transition-colors",
|
|
8255
|
+
isSelected ? "bg-primary border-primary" : "border-muted-foreground/30"
|
|
8256
|
+
),
|
|
8257
|
+
children: isSelected && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_lucide_react19.Check, { className: "w-3 h-3 text-primary-foreground" })
|
|
8258
|
+
}
|
|
8259
|
+
),
|
|
8260
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: cn("text-sm", isSelected && "font-medium text-primary"), children: category.name })
|
|
8261
|
+
] })
|
|
8213
8262
|
)
|
|
8214
8263
|
]
|
|
8215
8264
|
}
|
|
@@ -8217,9 +8266,16 @@ function CategoryTreeSelect({ categories, value, onChange, placeholder = "Ch\u1E
|
|
|
8217
8266
|
hasChildren && isExpanded && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { children: children.map((child) => renderCategory(child, level + 1)) })
|
|
8218
8267
|
] }, category.id);
|
|
8219
8268
|
};
|
|
8220
|
-
const
|
|
8221
|
-
|
|
8222
|
-
|
|
8269
|
+
const renderTreeContent = () => /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_jsx_runtime36.Fragment, { children: parentCategories.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "px-3 py-2 text-sm text-muted-foreground", children: mergedLabels.emptyText }) : parentCategories.map((cat) => renderCategory(cat)) });
|
|
8270
|
+
if (viewOnly) {
|
|
8271
|
+
return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: cn("rounded-md border bg-background p-2", disabled && "opacity-50", className), children: renderTreeContent() });
|
|
8272
|
+
}
|
|
8273
|
+
if (inline) {
|
|
8274
|
+
return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: cn("rounded-md border bg-background p-2", disabled && "opacity-50 pointer-events-none", className), children: renderTreeContent() });
|
|
8275
|
+
}
|
|
8276
|
+
const selectedCount = valueArray.length;
|
|
8277
|
+
const displayText = singleSelect ? selectedCount > 0 ? categories.find((c) => c.id === valueArray[0])?.name || placeholder : placeholder : selectedCount > 0 ? mergedLabels.selectedText(selectedCount) : placeholder;
|
|
8278
|
+
return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: cn("relative", className), children: [
|
|
8223
8279
|
/* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
|
|
8224
8280
|
"button",
|
|
8225
8281
|
{
|
|
@@ -8251,7 +8307,7 @@ function CategoryTreeSelect({ categories, value, onChange, placeholder = "Ch\u1E
|
|
|
8251
8307
|
"rounded-md border bg-popover text-popover-foreground shadow-md",
|
|
8252
8308
|
"backdrop-blur-sm bg-popover/95 border-border/60"
|
|
8253
8309
|
),
|
|
8254
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "p-1", children:
|
|
8310
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "p-1", children: renderTreeContent() })
|
|
8255
8311
|
}
|
|
8256
8312
|
)
|
|
8257
8313
|
] })
|