@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 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
- function CategoryTreeSelect({ categories, value, onChange, placeholder = "Ch\u1ECDn danh m\u1EE5c", disabled }) {
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
- const newSelected = new Set(value);
8146
- if (newSelected.has(categoryId)) {
8147
- newSelected.delete(categoryId);
8148
- const children = childrenMap.get(categoryId) || [];
8149
- children.forEach((child) => newSelected.delete(child.id));
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
- newSelected.add(categoryId);
8152
- if (category.parent_id) {
8153
- newSelected.add(category.parent_id);
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 = value.includes(category.id);
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 cursor-pointer rounded-md transition-colors",
8169
- "hover:bg-accent",
8170
- // Selected state: subtle bg + square left indicator, avoid left rounding
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
- /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
8195
- "div",
8196
- {
8197
- onClick: () => handleSelect(category.id, category),
8198
- className: "flex items-center gap-2 flex-1",
8199
- children: [
8200
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
8201
- "div",
8202
- {
8203
- className: cn(
8204
- "w-4 h-4 border-2 rounded flex items-center justify-center transition-colors",
8205
- isSelected ? "bg-primary border-primary" : "border-muted-foreground/30"
8206
- ),
8207
- children: isSelected && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_lucide_react19.Check, { className: "w-3 h-3 text-primary-foreground" })
8208
- }
8209
- ),
8210
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: cn("text-sm", isSelected && "font-medium text-primary"), children: category.name })
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 selectedCount = value.length;
8221
- const displayText = selectedCount > 0 ? `\u0110\xE3 ch\u1ECDn ${selectedCount} danh m\u1EE5c` : placeholder;
8222
- return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "relative", children: [
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: parentCategories.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "px-3 py-2 text-sm text-muted-foreground", children: "Kh\xF4ng c\xF3 danh m\u1EE5c n\xE0o" }) : parentCategories.map((cat) => renderCategory(cat)) })
8310
+ children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "p-1", children: renderTreeContent() })
8255
8311
  }
8256
8312
  )
8257
8313
  ] })