@underverse-ui/underverse 0.2.44 → 0.2.46

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.d.cts CHANGED
@@ -1000,20 +1000,35 @@ interface CategoryTreeSelectLabels {
1000
1000
  /** Text shown when categories are selected, receives count as parameter */
1001
1001
  selectedText?: (count: number) => string;
1002
1002
  }
1003
- interface CategoryTreeSelectProps {
1003
+ interface CategoryTreeSelectBaseProps {
1004
1004
  categories: Category[];
1005
- value?: number[];
1006
- onChange?: (selectedIds: number[]) => void;
1007
1005
  placeholder?: string;
1008
1006
  disabled?: boolean;
1009
1007
  /** When true, renders as a read-only tree view without select functionality */
1010
1008
  viewOnly?: boolean;
1011
- /** Default expanded state for all nodes in viewOnly mode */
1009
+ /** Default expanded state for all nodes */
1012
1010
  defaultExpanded?: boolean;
1013
1011
  /** i18n labels for localization */
1014
1012
  labels?: CategoryTreeSelectLabels;
1013
+ /** When true, render tree directly without dropdown trigger */
1014
+ inline?: boolean;
1015
+ /** Callback when a node is clicked (useful for navigation) */
1016
+ onNodeClick?: (node: Category) => void;
1017
+ /** Custom class for the tree container */
1018
+ className?: string;
1019
+ }
1020
+ interface CategoryTreeSelectMultiProps extends CategoryTreeSelectBaseProps {
1021
+ singleSelect?: false;
1022
+ value?: number[];
1023
+ onChange?: (selectedIds: number[]) => void;
1024
+ }
1025
+ interface CategoryTreeSelectSingleProps extends CategoryTreeSelectBaseProps {
1026
+ singleSelect: true;
1027
+ value?: number | null;
1028
+ onChange?: (selectedId: number | null) => void;
1015
1029
  }
1016
- declare function CategoryTreeSelect({ categories, value, onChange, placeholder, disabled, viewOnly, defaultExpanded, labels, }: CategoryTreeSelectProps): react_jsx_runtime.JSX.Element;
1030
+ type CategoryTreeSelectProps = CategoryTreeSelectMultiProps | CategoryTreeSelectSingleProps;
1031
+ declare function CategoryTreeSelect(props: CategoryTreeSelectProps): react_jsx_runtime.JSX.Element;
1017
1032
 
1018
1033
  type Fit = "cover" | "contain";
1019
1034
  interface SmartImageProps {
package/dist/index.d.ts CHANGED
@@ -1000,20 +1000,35 @@ interface CategoryTreeSelectLabels {
1000
1000
  /** Text shown when categories are selected, receives count as parameter */
1001
1001
  selectedText?: (count: number) => string;
1002
1002
  }
1003
- interface CategoryTreeSelectProps {
1003
+ interface CategoryTreeSelectBaseProps {
1004
1004
  categories: Category[];
1005
- value?: number[];
1006
- onChange?: (selectedIds: number[]) => void;
1007
1005
  placeholder?: string;
1008
1006
  disabled?: boolean;
1009
1007
  /** When true, renders as a read-only tree view without select functionality */
1010
1008
  viewOnly?: boolean;
1011
- /** Default expanded state for all nodes in viewOnly mode */
1009
+ /** Default expanded state for all nodes */
1012
1010
  defaultExpanded?: boolean;
1013
1011
  /** i18n labels for localization */
1014
1012
  labels?: CategoryTreeSelectLabels;
1013
+ /** When true, render tree directly without dropdown trigger */
1014
+ inline?: boolean;
1015
+ /** Callback when a node is clicked (useful for navigation) */
1016
+ onNodeClick?: (node: Category) => void;
1017
+ /** Custom class for the tree container */
1018
+ className?: string;
1019
+ }
1020
+ interface CategoryTreeSelectMultiProps extends CategoryTreeSelectBaseProps {
1021
+ singleSelect?: false;
1022
+ value?: number[];
1023
+ onChange?: (selectedIds: number[]) => void;
1024
+ }
1025
+ interface CategoryTreeSelectSingleProps extends CategoryTreeSelectBaseProps {
1026
+ singleSelect: true;
1027
+ value?: number | null;
1028
+ onChange?: (selectedId: number | null) => void;
1015
1029
  }
1016
- declare function CategoryTreeSelect({ categories, value, onChange, placeholder, disabled, viewOnly, defaultExpanded, labels, }: CategoryTreeSelectProps): react_jsx_runtime.JSX.Element;
1030
+ type CategoryTreeSelectProps = CategoryTreeSelectMultiProps | CategoryTreeSelectSingleProps;
1031
+ declare function CategoryTreeSelect(props: CategoryTreeSelectProps): react_jsx_runtime.JSX.Element;
1017
1032
 
1018
1033
  type Fit = "cover" | "contain";
1019
1034
  interface SmartImageProps {
package/dist/index.js CHANGED
@@ -1937,7 +1937,7 @@ Label.displayName = "Label";
1937
1937
  import Image2 from "next/image";
1938
1938
  import React9 from "react";
1939
1939
  import { jsx as jsx11 } from "react/jsx-runtime";
1940
- var DEFAULT_FALLBACK = "/images/products/hoa-hong-do.png";
1940
+ var DEFAULT_FALLBACK = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='400' height='400' viewBox='0 0 400 400'%3E%3Crect fill='%23f3f4f6' width='400' height='400'/%3E%3Cpath fill='%239ca3af' d='M160 150h80v60h-80z'/%3E%3Ccircle fill='%239ca3af' cx='180' cy='130' r='20'/%3E%3Cpath fill='%239ca3af' d='M120 240l60-60 40 40 40-30 60 50v40H120z'/%3E%3C/svg%3E";
1941
1941
  var FAILED_SRCS = /* @__PURE__ */ new Set();
1942
1942
  function SmartImage({
1943
1943
  src,
@@ -7951,19 +7951,23 @@ var defaultLabels = {
7951
7951
  emptyText: "No categories",
7952
7952
  selectedText: (count) => `${count} selected`
7953
7953
  };
7954
- function CategoryTreeSelect({
7955
- categories,
7956
- value = [],
7957
- onChange,
7958
- placeholder = "Select category",
7959
- disabled,
7960
- viewOnly = false,
7961
- defaultExpanded = false,
7962
- labels
7963
- }) {
7954
+ function CategoryTreeSelect(props) {
7955
+ const {
7956
+ categories,
7957
+ placeholder = "Select category",
7958
+ disabled,
7959
+ viewOnly = false,
7960
+ defaultExpanded = false,
7961
+ labels,
7962
+ inline = false,
7963
+ onNodeClick,
7964
+ className,
7965
+ singleSelect = false
7966
+ } = props;
7964
7967
  const [isOpen, setIsOpen] = useState25(false);
7965
7968
  const [expandedNodes, setExpandedNodes] = useState25(/* @__PURE__ */ new Set());
7966
7969
  const mergedLabels = { ...defaultLabels, ...labels };
7970
+ const valueArray = singleSelect ? props.value != null ? [props.value] : [] : props.value || [];
7967
7971
  const parentCategories = categories.filter((c) => !c.parent_id);
7968
7972
  const childrenMap = /* @__PURE__ */ new Map();
7969
7973
  categories.forEach((cat) => {
@@ -7975,11 +7979,11 @@ function CategoryTreeSelect({
7975
7979
  }
7976
7980
  });
7977
7981
  useEffect15(() => {
7978
- if (viewOnly && defaultExpanded) {
7982
+ if ((viewOnly || inline) && defaultExpanded) {
7979
7983
  const allParentIds = categories.filter((c) => childrenMap.has(c.id)).map((c) => c.id);
7980
7984
  setExpandedNodes(new Set(allParentIds));
7981
7985
  }
7982
- }, [viewOnly, defaultExpanded, categories]);
7986
+ }, [viewOnly, inline, defaultExpanded, categories]);
7983
7987
  const toggleExpand = (id) => {
7984
7988
  const newExpanded = new Set(expandedNodes);
7985
7989
  if (newExpanded.has(id)) {
@@ -7990,25 +7994,41 @@ function CategoryTreeSelect({
7990
7994
  setExpandedNodes(newExpanded);
7991
7995
  };
7992
7996
  const handleSelect = (categoryId, category) => {
7993
- if (viewOnly || !onChange) return;
7994
- const newSelected = new Set(value);
7995
- if (newSelected.has(categoryId)) {
7996
- newSelected.delete(categoryId);
7997
- const children = childrenMap.get(categoryId) || [];
7998
- children.forEach((child) => newSelected.delete(child.id));
7997
+ if (viewOnly) return;
7998
+ onNodeClick?.(category);
7999
+ if (!props.onChange) return;
8000
+ if (singleSelect) {
8001
+ const onChange = props.onChange;
8002
+ const currentValue = props.value;
8003
+ if (currentValue === categoryId) {
8004
+ onChange(null);
8005
+ } else {
8006
+ onChange(categoryId);
8007
+ }
8008
+ if (!inline) {
8009
+ setIsOpen(false);
8010
+ }
7999
8011
  } else {
8000
- newSelected.add(categoryId);
8001
- if (category.parent_id) {
8002
- newSelected.add(category.parent_id);
8012
+ const onChange = props.onChange;
8013
+ const newSelected = new Set(valueArray);
8014
+ if (newSelected.has(categoryId)) {
8015
+ newSelected.delete(categoryId);
8016
+ const children = childrenMap.get(categoryId) || [];
8017
+ children.forEach((child) => newSelected.delete(child.id));
8018
+ } else {
8019
+ newSelected.add(categoryId);
8020
+ if (category.parent_id) {
8021
+ newSelected.add(category.parent_id);
8022
+ }
8003
8023
  }
8024
+ onChange(Array.from(newSelected));
8004
8025
  }
8005
- onChange(Array.from(newSelected));
8006
8026
  };
8007
8027
  const renderCategory = (category, level = 0) => {
8008
8028
  const children = childrenMap.get(category.id) || [];
8009
8029
  const hasChildren = children.length > 0;
8010
8030
  const isExpanded = expandedNodes.has(category.id);
8011
- const isSelected = value.includes(category.id);
8031
+ const isSelected = valueArray.includes(category.id);
8012
8032
  return /* @__PURE__ */ jsxs31("div", { children: [
8013
8033
  /* @__PURE__ */ jsxs31(
8014
8034
  "div",
@@ -8037,8 +8057,23 @@ function CategoryTreeSelect({
8037
8057
  viewOnly ? (
8038
8058
  // View-only mode: just display the name
8039
8059
  /* @__PURE__ */ jsx36("span", { className: "text-sm", children: category.name })
8060
+ ) : singleSelect ? (
8061
+ // Single select mode: radio-style indicator
8062
+ /* @__PURE__ */ jsxs31("div", { onClick: () => handleSelect(category.id, category), className: "flex items-center gap-2 flex-1", children: [
8063
+ /* @__PURE__ */ jsx36(
8064
+ "div",
8065
+ {
8066
+ className: cn(
8067
+ "w-4 h-4 border-2 rounded-full flex items-center justify-center transition-colors",
8068
+ isSelected ? "border-primary" : "border-muted-foreground/30"
8069
+ ),
8070
+ children: isSelected && /* @__PURE__ */ jsx36("div", { className: "w-2 h-2 rounded-full bg-primary" })
8071
+ }
8072
+ ),
8073
+ /* @__PURE__ */ jsx36("span", { className: cn("text-sm", isSelected && "font-medium text-primary"), children: category.name })
8074
+ ] })
8040
8075
  ) : (
8041
- // Select mode: clickable with checkbox
8076
+ // Multi select mode: checkbox-style indicator
8042
8077
  /* @__PURE__ */ jsxs31("div", { onClick: () => handleSelect(category.id, category), className: "flex items-center gap-2 flex-1", children: [
8043
8078
  /* @__PURE__ */ jsx36(
8044
8079
  "div",
@@ -8059,12 +8094,16 @@ function CategoryTreeSelect({
8059
8094
  hasChildren && isExpanded && /* @__PURE__ */ jsx36("div", { children: children.map((child) => renderCategory(child, level + 1)) })
8060
8095
  ] }, category.id);
8061
8096
  };
8097
+ const renderTreeContent = () => /* @__PURE__ */ jsx36(Fragment11, { children: parentCategories.length === 0 ? /* @__PURE__ */ jsx36("div", { className: "px-3 py-2 text-sm text-muted-foreground", children: mergedLabels.emptyText }) : parentCategories.map((cat) => renderCategory(cat)) });
8062
8098
  if (viewOnly) {
8063
- return /* @__PURE__ */ jsx36("div", { className: cn("rounded-md border bg-background p-2", disabled && "opacity-50"), children: parentCategories.length === 0 ? /* @__PURE__ */ jsx36("div", { className: "px-3 py-2 text-sm text-muted-foreground", children: mergedLabels.emptyText }) : parentCategories.map((cat) => renderCategory(cat)) });
8099
+ return /* @__PURE__ */ jsx36("div", { className: cn("rounded-md border bg-background p-2", disabled && "opacity-50", className), children: renderTreeContent() });
8100
+ }
8101
+ if (inline) {
8102
+ return /* @__PURE__ */ jsx36("div", { className: cn("rounded-md border bg-background p-2", disabled && "opacity-50 pointer-events-none", className), children: renderTreeContent() });
8064
8103
  }
8065
- const selectedCount = value.length;
8066
- const displayText = selectedCount > 0 ? mergedLabels.selectedText(selectedCount) : placeholder;
8067
- return /* @__PURE__ */ jsxs31("div", { className: "relative", children: [
8104
+ const selectedCount = valueArray.length;
8105
+ const displayText = singleSelect ? selectedCount > 0 ? categories.find((c) => c.id === valueArray[0])?.name || placeholder : placeholder : selectedCount > 0 ? mergedLabels.selectedText(selectedCount) : placeholder;
8106
+ return /* @__PURE__ */ jsxs31("div", { className: cn("relative", className), children: [
8068
8107
  /* @__PURE__ */ jsxs31(
8069
8108
  "button",
8070
8109
  {
@@ -8096,7 +8135,7 @@ function CategoryTreeSelect({
8096
8135
  "rounded-md border bg-popover text-popover-foreground shadow-md",
8097
8136
  "backdrop-blur-sm bg-popover/95 border-border/60"
8098
8137
  ),
8099
- children: /* @__PURE__ */ jsx36("div", { className: "p-1", children: parentCategories.length === 0 ? /* @__PURE__ */ jsx36("div", { className: "px-3 py-2 text-sm text-muted-foreground", children: mergedLabels.emptyText }) : parentCategories.map((cat) => renderCategory(cat)) })
8138
+ children: /* @__PURE__ */ jsx36("div", { className: "p-1", children: renderTreeContent() })
8100
8139
  }
8101
8140
  )
8102
8141
  ] })