@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.d.cts
CHANGED
|
@@ -994,14 +994,41 @@ interface Category {
|
|
|
994
994
|
name: string;
|
|
995
995
|
parent_id?: number | null;
|
|
996
996
|
}
|
|
997
|
-
interface
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
7974
|
-
|
|
7975
|
-
|
|
7976
|
-
|
|
7977
|
-
|
|
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
|
-
|
|
7980
|
-
|
|
7981
|
-
|
|
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 =
|
|
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
|
|
7997
|
-
"hover:bg-accent",
|
|
7998
|
-
// Selected state: subtle bg + square left indicator
|
|
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
|
-
|
|
8023
|
-
|
|
8024
|
-
{
|
|
8025
|
-
|
|
8026
|
-
|
|
8027
|
-
|
|
8028
|
-
|
|
8029
|
-
|
|
8030
|
-
|
|
8031
|
-
|
|
8032
|
-
|
|
8033
|
-
|
|
8034
|
-
|
|
8035
|
-
|
|
8036
|
-
|
|
8037
|
-
|
|
8038
|
-
|
|
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
|
|
8049
|
-
|
|
8050
|
-
|
|
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:
|
|
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
|
|
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
|
-
|
|
10369
|
+
useEffect19(() => {
|
|
10314
10370
|
setIsMounted(true);
|
|
10315
10371
|
}, []);
|
|
10316
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 },
|