@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.d.cts CHANGED
@@ -994,14 +994,41 @@ interface Category {
994
994
  name: string;
995
995
  parent_id?: number | null;
996
996
  }
997
- interface CategoryTreeSelectProps {
997
+ interface CategoryTreeSelectLabels {
998
+ /** Text shown when no categories available */
999
+ emptyText?: string;
1000
+ /** Text shown when categories are selected, receives count as parameter */
1001
+ selectedText?: (count: number) => string;
1002
+ }
1003
+ interface CategoryTreeSelectBaseProps {
998
1004
  categories: Category[];
999
- value: number[];
1000
- onChange: (selectedIds: number[]) => void;
1001
1005
  placeholder?: string;
1002
1006
  disabled?: boolean;
1003
- }
1004
- declare function CategoryTreeSelect({ categories, value, onChange, placeholder, disabled }: CategoryTreeSelectProps): react_jsx_runtime.JSX.Element;
1007
+ /** When true, renders as a read-only tree view without select functionality */
1008
+ viewOnly?: boolean;
1009
+ /** Default expanded state for all nodes */
1010
+ defaultExpanded?: boolean;
1011
+ /** i18n labels for localization */
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;
1029
+ }
1030
+ type CategoryTreeSelectProps = CategoryTreeSelectMultiProps | CategoryTreeSelectSingleProps;
1031
+ declare function CategoryTreeSelect(props: CategoryTreeSelectProps): react_jsx_runtime.JSX.Element;
1005
1032
 
1006
1033
  type Fit = "cover" | "contain";
1007
1034
  interface SmartImageProps {
package/dist/index.d.ts CHANGED
@@ -994,14 +994,41 @@ interface Category {
994
994
  name: string;
995
995
  parent_id?: number | null;
996
996
  }
997
- interface CategoryTreeSelectProps {
997
+ interface CategoryTreeSelectLabels {
998
+ /** Text shown when no categories available */
999
+ emptyText?: string;
1000
+ /** Text shown when categories are selected, receives count as parameter */
1001
+ selectedText?: (count: number) => string;
1002
+ }
1003
+ interface CategoryTreeSelectBaseProps {
998
1004
  categories: Category[];
999
- value: number[];
1000
- onChange: (selectedIds: number[]) => void;
1001
1005
  placeholder?: string;
1002
1006
  disabled?: boolean;
1003
- }
1004
- declare function CategoryTreeSelect({ categories, value, onChange, placeholder, disabled }: CategoryTreeSelectProps): react_jsx_runtime.JSX.Element;
1007
+ /** When true, renders as a read-only tree view without select functionality */
1008
+ viewOnly?: boolean;
1009
+ /** Default expanded state for all nodes */
1010
+ defaultExpanded?: boolean;
1011
+ /** i18n labels for localization */
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;
1029
+ }
1030
+ type CategoryTreeSelectProps = CategoryTreeSelectMultiProps | CategoryTreeSelectSingleProps;
1031
+ declare function CategoryTreeSelect(props: CategoryTreeSelectProps): react_jsx_runtime.JSX.Element;
1005
1032
 
1006
1033
  type Fit = "cover" | "contain";
1007
1034
  interface SmartImageProps {
package/dist/index.js CHANGED
@@ -7944,12 +7944,30 @@ function OverlayControls({
7944
7944
  }
7945
7945
 
7946
7946
  // ../../components/ui/CategoryTreeSelect.tsx
7947
- import { useState as useState25 } from "react";
7947
+ import { useState as useState25, useEffect as useEffect15 } from "react";
7948
7948
  import { ChevronRight as ChevronRight5, ChevronDown as ChevronDown3, Check as Check6 } from "lucide-react";
7949
7949
  import { Fragment as Fragment11, jsx as jsx36, jsxs as jsxs31 } from "react/jsx-runtime";
7950
- function CategoryTreeSelect({ categories, value, onChange, placeholder = "Ch\u1ECDn danh m\u1EE5c", disabled }) {
7950
+ var defaultLabels = {
7951
+ emptyText: "No categories",
7952
+ selectedText: (count) => `${count} selected`
7953
+ };
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;
7951
7967
  const [isOpen, setIsOpen] = useState25(false);
7952
7968
  const [expandedNodes, setExpandedNodes] = useState25(/* @__PURE__ */ new Set());
7969
+ const mergedLabels = { ...defaultLabels, ...labels };
7970
+ const valueArray = singleSelect ? props.value != null ? [props.value] : [] : props.value || [];
7953
7971
  const parentCategories = categories.filter((c) => !c.parent_id);
7954
7972
  const childrenMap = /* @__PURE__ */ new Map();
7955
7973
  categories.forEach((cat) => {
@@ -7960,6 +7978,12 @@ function CategoryTreeSelect({ categories, value, onChange, placeholder = "Ch\u1E
7960
7978
  childrenMap.get(cat.parent_id).push(cat);
7961
7979
  }
7962
7980
  });
7981
+ useEffect15(() => {
7982
+ if ((viewOnly || inline) && defaultExpanded) {
7983
+ const allParentIds = categories.filter((c) => childrenMap.has(c.id)).map((c) => c.id);
7984
+ setExpandedNodes(new Set(allParentIds));
7985
+ }
7986
+ }, [viewOnly, inline, defaultExpanded, categories]);
7963
7987
  const toggleExpand = (id) => {
7964
7988
  const newExpanded = new Set(expandedNodes);
7965
7989
  if (newExpanded.has(id)) {
@@ -7970,43 +7994,54 @@ function CategoryTreeSelect({ categories, value, onChange, placeholder = "Ch\u1E
7970
7994
  setExpandedNodes(newExpanded);
7971
7995
  };
7972
7996
  const handleSelect = (categoryId, category) => {
7973
- const newSelected = new Set(value);
7974
- if (newSelected.has(categoryId)) {
7975
- newSelected.delete(categoryId);
7976
- const children = childrenMap.get(categoryId) || [];
7977
- 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
+ }
7978
8011
  } else {
7979
- newSelected.add(categoryId);
7980
- if (category.parent_id) {
7981
- 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
+ }
7982
8023
  }
8024
+ onChange(Array.from(newSelected));
7983
8025
  }
7984
- onChange(Array.from(newSelected));
7985
8026
  };
7986
8027
  const renderCategory = (category, level = 0) => {
7987
8028
  const children = childrenMap.get(category.id) || [];
7988
8029
  const hasChildren = children.length > 0;
7989
8030
  const isExpanded = expandedNodes.has(category.id);
7990
- const isSelected = value.includes(category.id);
8031
+ const isSelected = valueArray.includes(category.id);
7991
8032
  return /* @__PURE__ */ jsxs31("div", { children: [
7992
8033
  /* @__PURE__ */ jsxs31(
7993
8034
  "div",
7994
8035
  {
7995
8036
  className: cn(
7996
- "relative flex items-center gap-2 px-3 py-2 cursor-pointer rounded-md transition-colors",
7997
- "hover:bg-accent",
7998
- // Selected state: subtle bg + square left indicator, avoid left rounding
7999
- isSelected && "bg-primary/10 rounded-r-md"
8037
+ "relative flex items-center gap-2 px-3 py-2 rounded-md transition-colors",
8038
+ !viewOnly && "cursor-pointer hover:bg-accent",
8039
+ // Selected state: subtle bg + square left indicator (only in select mode)
8040
+ !viewOnly && isSelected && "bg-primary/10 rounded-r-md"
8000
8041
  ),
8001
8042
  style: { paddingLeft: `${level * 1.5 + 0.75}rem` },
8002
8043
  children: [
8003
- isSelected && /* @__PURE__ */ jsx36(
8004
- "span",
8005
- {
8006
- "aria-hidden": true,
8007
- className: "absolute left-0 top-0 bottom-0 w-1 bg-primary"
8008
- }
8009
- ),
8044
+ !viewOnly && isSelected && /* @__PURE__ */ jsx36("span", { "aria-hidden": true, className: "absolute left-0 top-0 bottom-0 w-1 bg-primary" }),
8010
8045
  hasChildren ? /* @__PURE__ */ jsx36(
8011
8046
  "button",
8012
8047
  {
@@ -8019,25 +8054,39 @@ function CategoryTreeSelect({ categories, value, onChange, placeholder = "Ch\u1E
8019
8054
  children: isExpanded ? /* @__PURE__ */ jsx36(ChevronDown3, { className: "w-4 h-4" }) : /* @__PURE__ */ jsx36(ChevronRight5, { className: "w-4 h-4" })
8020
8055
  }
8021
8056
  ) : /* @__PURE__ */ jsx36("span", { className: "w-5" }),
8022
- /* @__PURE__ */ jsxs31(
8023
- "div",
8024
- {
8025
- onClick: () => handleSelect(category.id, category),
8026
- className: "flex items-center gap-2 flex-1",
8027
- children: [
8028
- /* @__PURE__ */ jsx36(
8029
- "div",
8030
- {
8031
- className: cn(
8032
- "w-4 h-4 border-2 rounded flex items-center justify-center transition-colors",
8033
- isSelected ? "bg-primary border-primary" : "border-muted-foreground/30"
8034
- ),
8035
- children: isSelected && /* @__PURE__ */ jsx36(Check6, { className: "w-3 h-3 text-primary-foreground" })
8036
- }
8037
- ),
8038
- /* @__PURE__ */ jsx36("span", { className: cn("text-sm", isSelected && "font-medium text-primary"), children: category.name })
8039
- ]
8040
- }
8057
+ viewOnly ? (
8058
+ // View-only mode: just display the name
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
+ ] })
8075
+ ) : (
8076
+ // Multi select mode: checkbox-style indicator
8077
+ /* @__PURE__ */ jsxs31("div", { onClick: () => handleSelect(category.id, category), className: "flex items-center gap-2 flex-1", children: [
8078
+ /* @__PURE__ */ jsx36(
8079
+ "div",
8080
+ {
8081
+ className: cn(
8082
+ "w-4 h-4 border-2 rounded flex items-center justify-center transition-colors",
8083
+ isSelected ? "bg-primary border-primary" : "border-muted-foreground/30"
8084
+ ),
8085
+ children: isSelected && /* @__PURE__ */ jsx36(Check6, { className: "w-3 h-3 text-primary-foreground" })
8086
+ }
8087
+ ),
8088
+ /* @__PURE__ */ jsx36("span", { className: cn("text-sm", isSelected && "font-medium text-primary"), children: category.name })
8089
+ ] })
8041
8090
  )
8042
8091
  ]
8043
8092
  }
@@ -8045,9 +8094,16 @@ function CategoryTreeSelect({ categories, value, onChange, placeholder = "Ch\u1E
8045
8094
  hasChildren && isExpanded && /* @__PURE__ */ jsx36("div", { children: children.map((child) => renderCategory(child, level + 1)) })
8046
8095
  ] }, category.id);
8047
8096
  };
8048
- const selectedCount = value.length;
8049
- const displayText = selectedCount > 0 ? `\u0110\xE3 ch\u1ECDn ${selectedCount} danh m\u1EE5c` : placeholder;
8050
- return /* @__PURE__ */ jsxs31("div", { className: "relative", children: [
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)) });
8098
+ if (viewOnly) {
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() });
8103
+ }
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: [
8051
8107
  /* @__PURE__ */ jsxs31(
8052
8108
  "button",
8053
8109
  {
@@ -8079,7 +8135,7 @@ function CategoryTreeSelect({ categories, value, onChange, placeholder = "Ch\u1E
8079
8135
  "rounded-md border bg-popover text-popover-foreground shadow-md",
8080
8136
  "backdrop-blur-sm bg-popover/95 border-border/60"
8081
8137
  ),
8082
- 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: "Kh\xF4ng c\xF3 danh m\u1EE5c n\xE0o" }) : parentCategories.map((cat) => renderCategory(cat)) })
8138
+ children: /* @__PURE__ */ jsx36("div", { className: "p-1", children: renderTreeContent() })
8083
8139
  }
8084
8140
  )
8085
8141
  ] })
@@ -10304,16 +10360,16 @@ var Grid_default = Grid;
10304
10360
  import { useMemo as useMemo6, useState as useState33, useRef as useRef14 } from "react";
10305
10361
 
10306
10362
  // ../../components/ui/ChartTooltip.tsx
10307
- import { useEffect as useEffect18, useState as useState32 } from "react";
10363
+ import { useEffect as useEffect19, useState as useState32 } from "react";
10308
10364
  import { createPortal as createPortal10 } from "react-dom";
10309
10365
  import { Fragment as Fragment16, jsx as jsx45, jsxs as jsxs40 } from "react/jsx-runtime";
10310
10366
  function ChartTooltip({ x, y, visible, label, value, color, secondaryLabel, secondaryValue, items, containerRef }) {
10311
10367
  const [isMounted, setIsMounted] = useState32(false);
10312
10368
  const [position, setPosition] = useState32(null);
10313
- useEffect18(() => {
10369
+ useEffect19(() => {
10314
10370
  setIsMounted(true);
10315
10371
  }, []);
10316
- useEffect18(() => {
10372
+ useEffect19(() => {
10317
10373
  if (visible && containerRef?.current) {
10318
10374
  const rect = containerRef.current.getBoundingClientRect();
10319
10375
  setPosition({
@@ -11665,11 +11721,11 @@ function GaugeChart({
11665
11721
  }
11666
11722
 
11667
11723
  // ../../components/ui/ClientOnly.tsx
11668
- import { useEffect as useEffect19, useState as useState38 } from "react";
11724
+ import { useEffect as useEffect20, useState as useState38 } from "react";
11669
11725
  import { Fragment as Fragment19, jsx as jsx53 } from "react/jsx-runtime";
11670
11726
  function ClientOnly({ children, fallback = null }) {
11671
11727
  const [hasMounted, setHasMounted] = useState38(false);
11672
- useEffect19(() => {
11728
+ useEffect20(() => {
11673
11729
  setHasMounted(true);
11674
11730
  }, []);
11675
11731
  if (!hasMounted) {
@@ -12622,7 +12678,7 @@ function AccessDenied({
12622
12678
 
12623
12679
  // ../../components/ui/ThemeToggleHeadless.tsx
12624
12680
  import { Moon, Sun, Monitor } from "lucide-react";
12625
- import { useEffect as useEffect21, useRef as useRef19, useState as useState39 } from "react";
12681
+ import { useEffect as useEffect22, useRef as useRef19, useState as useState39 } from "react";
12626
12682
  import { createPortal as createPortal11 } from "react-dom";
12627
12683
  import { Fragment as Fragment21, jsx as jsx61, jsxs as jsxs55 } from "react/jsx-runtime";
12628
12684
  function ThemeToggleHeadless({
@@ -12635,7 +12691,7 @@ function ThemeToggleHeadless({
12635
12691
  const [mounted, setMounted] = useState39(false);
12636
12692
  const triggerRef = useRef19(null);
12637
12693
  const [dropdownPosition, setDropdownPosition] = useState39(null);
12638
- useEffect21(() => setMounted(true), []);
12694
+ useEffect22(() => setMounted(true), []);
12639
12695
  const themes = [
12640
12696
  { value: "light", label: labels?.light ?? "Light", icon: Sun },
12641
12697
  { value: "dark", label: labels?.dark ?? "Dark", icon: Moon },