@ramesesinc/platform-core 0.1.5 → 0.1.8
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/components/action/LookupPage.js +9 -31
- package/dist/components/action/ViewPage.d.ts +2 -0
- package/dist/components/action/ViewPage.js +25 -31
- package/dist/components/common/UIComponent.js +4 -3
- package/dist/components/index.d.ts +1 -1
- package/dist/components/index.js +1 -1
- package/dist/components/table/DataList.js +2 -2
- package/dist/components/view/PopupView.d.ts +13 -0
- package/dist/components/view/PopupView.js +25 -20
- package/dist/core/DataContext.d.ts +7 -4
- package/dist/core/DataContext.js +16 -4
- package/dist/core/Page.js +25 -26
- package/dist/core/PageCache.js +16 -3
- package/dist/core/PageContext.js +90 -18
- package/dist/core/PageViewContext.d.ts +13 -1
- package/dist/core/PageViewContext.js +89 -5
- package/dist/core/PopupContext.d.ts +49 -0
- package/dist/core/PopupContext.js +380 -0
- package/dist/core/RowContext.js +1 -1
- package/dist/core/WindowContext.d.ts +15 -0
- package/dist/core/WindowContext.js +28 -0
- package/dist/core/index.d.ts +16 -0
- package/dist/index.css +25 -7
- package/dist/lib/utils/BeanUtils.js +7 -7
- package/dist/templates/DataListTemplate.js +7 -2
- package/dist/templates/ExplorerTemplate.js +1 -1
- package/package.json +5 -5
- package/dist/components/action/AlertMessage.tsx +0 -38
- package/dist/components/action/Button.tsx +0 -230
- package/dist/components/action/CancelEdit.tsx +0 -40
- package/dist/components/action/DeleteData.tsx +0 -73
- package/dist/components/action/Edit.tsx +0 -40
- package/dist/components/action/LookupPage.tsx +0 -113
- package/dist/components/action/ProcessRunner.tsx +0 -337
- package/dist/components/action/Refresh.tsx +0 -35
- package/dist/components/action/SaveData.tsx +0 -74
- package/dist/components/action/SelectData.tsx +0 -47
- package/dist/components/action/Undo.tsx +0 -50
- package/dist/components/action/UpdateContext.tsx +0 -40
- package/dist/components/action/UpdateData.tsx +0 -49
- package/dist/components/action/ViewBackPage.tsx +0 -46
- package/dist/components/action/ViewPage.tsx +0 -141
- package/dist/components/common/UIComponent.tsx +0 -86
- package/dist/components/common/UIInput.tsx +0 -49
- package/dist/components/common/UIMenu.tsx +0 -91
- package/dist/components/index.ts +0 -51
- package/dist/components/input/CodeEditor.tsx +0 -188
- package/dist/components/input/DateField.tsx +0 -274
- package/dist/components/input/DayPicker.tsx +0 -5
- package/dist/components/input/HtmlCode.tsx +0 -203
- package/dist/components/input/JsonCode.tsx +0 -205
- package/dist/components/input/MonthPicker.tsx +0 -5
- package/dist/components/input/ScriptCode.tsx +0 -195
- package/dist/components/input/Select.tsx +0 -78
- package/dist/components/input/SqlCode.tsx +0 -162
- package/dist/components/input/StringDecision.tsx +0 -64
- package/dist/components/input/Text.tsx +0 -57
- package/dist/components/input/YearPicker.tsx +0 -81
- package/dist/components/list/IconMenu.tsx +0 -115
- package/dist/components/list/TabMenu.tsx +0 -127
- package/dist/components/list/TreeMenu.tsx +0 -279
- package/dist/components/list/TxnTaskList.tsx +0 -198
- package/dist/components/output/Label.tsx +0 -50
- package/dist/components/table/DataList.tsx +0 -820
- package/dist/components/table/DataTable.tsx +0 -572
- package/dist/components/table/ListHandler.ts +0 -276
- package/dist/components/table/TableContext.tsx +0 -122
- package/dist/components/view/ComponentView.tsx +0 -102
- package/dist/components/view/FilterView.tsx +0 -21
- package/dist/components/view/HtmlForm.tsx +0 -176
- package/dist/components/view/HtmlView.tsx +0 -98
- package/dist/components/view/IFrameView.tsx +0 -48
- package/dist/components/view/Modal.tsx +0 -72
- package/dist/components/view/PageView.tsx +0 -131
- package/dist/components/view/PopupView.tsx +0 -160
- package/dist/components/view/RootView.tsx +0 -109
- package/dist/components/view/WizardView.tsx +0 -48
- package/dist/lib/layouts/BorderLayout.tsx +0 -31
- package/dist/lib/layouts/CardLayout.tsx +0 -73
- package/dist/lib/layouts/CenterLayout.tsx +0 -20
- package/dist/lib/layouts/GridLayout.tsx +0 -20
- package/dist/lib/layouts/HPanel.tsx +0 -31
- package/dist/lib/layouts/HorizontalLayout.tsx +0 -29
- package/dist/lib/layouts/MainLayout.tsx +0 -16
- package/dist/lib/layouts/PageLayout.tsx +0 -29
- package/dist/lib/layouts/VPanel.tsx +0 -27
- package/dist/lib/layouts/XLayout.tsx +0 -29
- package/dist/lib/layouts/YLayout.tsx +0 -29
- package/dist/lib/layouts/index.ts +0 -13
- /package/dist/components/action/{UpdateContext.d.ts → UpdateState.d.ts} +0 -0
- /package/dist/components/action/{UpdateContext.js → UpdateState.js} +0 -0
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import clsx from "clsx";
|
|
4
|
-
import { useState } from "react";
|
|
5
|
-
import { twMerge } from "tailwind-merge";
|
|
6
|
-
import DynamicIcon from "../../core/DynamicIcon";
|
|
7
|
-
import { usePageContext } from "../../core/PageContext";
|
|
8
|
-
import { usePageViewContext } from "../../core/PageViewContext";
|
|
9
|
-
import { AbstractComponent } from "../../types/component";
|
|
10
|
-
import useUIMenu, { MenuGroup, MenuItem } from "../common/UIMenu";
|
|
11
|
-
|
|
12
|
-
/* ------------------------------------------------------------------ */
|
|
13
|
-
/* Types */
|
|
14
|
-
|
|
15
|
-
interface IconMenuProps extends AbstractComponent {
|
|
16
|
-
name?: string;
|
|
17
|
-
items?: MenuGroup[];
|
|
18
|
-
data?: Record<string, any>;
|
|
19
|
-
menugroup?: string;
|
|
20
|
-
depends?: string;
|
|
21
|
-
columns?: number;
|
|
22
|
-
size?: "sm" | "md" | "lg";
|
|
23
|
-
style?: string;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/* ------------------------------------------------------------------ */
|
|
27
|
-
/* Size config */
|
|
28
|
-
|
|
29
|
-
const sizeConfig = {
|
|
30
|
-
sm: { tile: "w-20 h-20", icon: 24, label: "text-xs", gap: "gap-2" },
|
|
31
|
-
md: { tile: "w-28 h-28", icon: 32, label: "text-sm", gap: "gap-3" },
|
|
32
|
-
lg: { tile: "w-36 h-36", icon: 40, label: "text-base", gap: "gap-4" },
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
/* ------------------------------------------------------------------ */
|
|
36
|
-
/* Component */
|
|
37
|
-
|
|
38
|
-
const IconMenu = (props: IconMenuProps) => {
|
|
39
|
-
const { name, menugroup, depends, items: itemsProp, data, columns = 5, size = "md", style = "" } = props ?? {};
|
|
40
|
-
const pageContext = usePageContext();
|
|
41
|
-
const pageView = usePageViewContext();
|
|
42
|
-
const contextKey = name ?? depends ?? "";
|
|
43
|
-
const [activeItem, setActiveItem] = useState<string | null>(null);
|
|
44
|
-
|
|
45
|
-
const defaultPopupClass = `bg-white rounded-lg shadow-xl w-[90%] h-[90%] flex flex-col`;
|
|
46
|
-
const finalPopupClass = twMerge(clsx(defaultPopupClass, style));
|
|
47
|
-
|
|
48
|
-
// useUIMenu always returns MenuGroup[]
|
|
49
|
-
const { items } = useUIMenu({ menugroup, items: itemsProp, data });
|
|
50
|
-
|
|
51
|
-
const cfg = sizeConfig[size];
|
|
52
|
-
|
|
53
|
-
/* ---------------------- Events ---------------------- */
|
|
54
|
-
const handleItemClick = (item: MenuItem) => {
|
|
55
|
-
if (!item.page || item.page === activeItem) return;
|
|
56
|
-
if (item.mode == "window") {
|
|
57
|
-
const oldHref = window.location.href;
|
|
58
|
-
const nidx = oldHref.lastIndexOf("/");
|
|
59
|
-
if (nidx > 0) {
|
|
60
|
-
const newUrl = oldHref.substring(0, nidx) + "/" + item.page;
|
|
61
|
-
window.location.href = newUrl;
|
|
62
|
-
}
|
|
63
|
-
} else {
|
|
64
|
-
setActiveItem(item.page);
|
|
65
|
-
if (contextKey && contextKey !== "selectedPage") {
|
|
66
|
-
pageContext?.set(contextKey, item.page);
|
|
67
|
-
} else {
|
|
68
|
-
pageView?.setPage(item.page);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
/* ---------------------- Shared button ---------------------- */
|
|
74
|
-
const renderButton = (item: MenuItem, index: number) => (
|
|
75
|
-
<button
|
|
76
|
-
key={index}
|
|
77
|
-
onClick={() => handleItemClick(item)}
|
|
78
|
-
className={`
|
|
79
|
-
${cfg.tile}
|
|
80
|
-
flex flex-col items-center justify-center gap-2
|
|
81
|
-
rounded-2xl select-none cursor-pointer
|
|
82
|
-
transition-all duration-200 ease-out
|
|
83
|
-
focus:outline-none
|
|
84
|
-
text-gray-600 hover:bg-gray-100 hover:text-gray-900
|
|
85
|
-
`}
|
|
86
|
-
>
|
|
87
|
-
<DynamicIcon icon={item.icon} size={cfg.icon} />
|
|
88
|
-
<span className={`${cfg.label} font-semibold leading-tight text-center px-1 truncate w-full`}>{item.title}</span>
|
|
89
|
-
</button>
|
|
90
|
-
);
|
|
91
|
-
|
|
92
|
-
/* ---------------------- Render ---------------------- */
|
|
93
|
-
return (
|
|
94
|
-
<div className="flex flex-col gap-6 p-4">
|
|
95
|
-
{(items as MenuGroup[]).map((group, groupIndex) => {
|
|
96
|
-
// Flat item — no group structure
|
|
97
|
-
if (!Array.isArray(group.items)) {
|
|
98
|
-
return renderButton(group as unknown as MenuItem, groupIndex);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// Grouped with category title
|
|
102
|
-
return (
|
|
103
|
-
<div key={groupIndex} className="flex flex-col gap-2">
|
|
104
|
-
{group.title && <span className="text-xs font-semibold text-gray-400 uppercase tracking-widest px-1">{group.title}</span>}
|
|
105
|
-
<div className={`grid ${cfg.gap} justify-items-center`} style={{ gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))` }}>
|
|
106
|
-
{group.items.map((item, index) => renderButton(item, index))}
|
|
107
|
-
</div>
|
|
108
|
-
</div>
|
|
109
|
-
);
|
|
110
|
-
})}
|
|
111
|
-
</div>
|
|
112
|
-
);
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
export default IconMenu;
|
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
import { useEffect, useState } from "react";
|
|
2
|
-
import { usePageContext } from "../../core/PageContext";
|
|
3
|
-
import { usePageViewContext } from "../../core/PageViewContext";
|
|
4
|
-
import useDependHandler from "../../core/UIDependHandler";
|
|
5
|
-
import HPanel from "../../layouts/HPanel";
|
|
6
|
-
import VPanel from "../../layouts/VPanel";
|
|
7
|
-
|
|
8
|
-
type TabMenuProps = {
|
|
9
|
-
name?: string;
|
|
10
|
-
depends?: string;
|
|
11
|
-
items?: Record<string, any>[];
|
|
12
|
-
data?: Record<string, any>;
|
|
13
|
-
menugroup?: string;
|
|
14
|
-
orientation?: "horizontal" | "vertical";
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
type TabMenuItemProps = {
|
|
18
|
-
id: string;
|
|
19
|
-
title: string;
|
|
20
|
-
key: string;
|
|
21
|
-
component?: string;
|
|
22
|
-
attr?: Record<string, any>;
|
|
23
|
-
index: number;
|
|
24
|
-
selected?: boolean;
|
|
25
|
-
onClick: (item: Record<string, any>) => void;
|
|
26
|
-
[key: string]: any;
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
const TabMenu = (props: TabMenuProps) => {
|
|
30
|
-
const { name, depends, items = [], data, menugroup, orientation = "horizontal" } = props ?? {};
|
|
31
|
-
const [selectedIndex, setSelectedIndex] = useState(-1);
|
|
32
|
-
const [refreshKey, setRefreshKey] = useState(0);
|
|
33
|
-
const pageContext = usePageContext();
|
|
34
|
-
const pageView = usePageViewContext();
|
|
35
|
-
const contextKey = name ?? depends ?? "";
|
|
36
|
-
|
|
37
|
-
const resolvedItems = items.map((item: any, index: number) => ({
|
|
38
|
-
...item,
|
|
39
|
-
key: item.id ?? Math.random().toString(36).substring(2, 15),
|
|
40
|
-
index,
|
|
41
|
-
}));
|
|
42
|
-
|
|
43
|
-
const onRefresh = () => setRefreshKey((k) => k + 1);
|
|
44
|
-
useDependHandler({ name: depends, onRefresh });
|
|
45
|
-
|
|
46
|
-
const handleClick = (item: Record<string, any>) => {
|
|
47
|
-
setSelectedIndex(item.index);
|
|
48
|
-
|
|
49
|
-
if (item.page != null && item.page.trim() !== "") {
|
|
50
|
-
pageView.setPage(item.page);
|
|
51
|
-
} else if (contextKey != null && contextKey.trim() !== "") {
|
|
52
|
-
const selItem = { ...item };
|
|
53
|
-
delete selItem.id;
|
|
54
|
-
delete selItem.title;
|
|
55
|
-
delete selItem.index;
|
|
56
|
-
delete selItem.key;
|
|
57
|
-
delete selItem.selected;
|
|
58
|
-
delete selItem.onClick;
|
|
59
|
-
pageContext.set(contextKey, selItem); // ← sets "selectedTask" = "new_application_list"
|
|
60
|
-
}
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
useEffect(() => {
|
|
64
|
-
if (resolvedItems != null && resolvedItems.length > 0 && selectedIndex === -1) {
|
|
65
|
-
handleClick(resolvedItems[0]);
|
|
66
|
-
}
|
|
67
|
-
}, [resolvedItems]);
|
|
68
|
-
|
|
69
|
-
const render = () => {
|
|
70
|
-
if (resolvedItems.length === 0) return null;
|
|
71
|
-
|
|
72
|
-
const elems = resolvedItems.map((it) => {
|
|
73
|
-
const item = it as unknown as TabMenuItemProps;
|
|
74
|
-
item.selected = item.index === selectedIndex;
|
|
75
|
-
return <TabMenuItem {...item} key={item.key} onClick={handleClick} />;
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
if (orientation === "vertical") {
|
|
79
|
-
return (
|
|
80
|
-
<div className="border-r border-gray-200">
|
|
81
|
-
<VPanel gap={25}>{elems}</VPanel>
|
|
82
|
-
</div>
|
|
83
|
-
);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
return (
|
|
87
|
-
<div className="w-full">
|
|
88
|
-
<HPanel gap={25}>{elems}</HPanel>
|
|
89
|
-
</div>
|
|
90
|
-
);
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
return <div>{render()}</div>;
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
export default TabMenu;
|
|
97
|
-
|
|
98
|
-
/* ------------------------------------------------------------------ */
|
|
99
|
-
/* TabMenuItem */
|
|
100
|
-
|
|
101
|
-
const TabMenuItem = (props: TabMenuItemProps) => {
|
|
102
|
-
const { title, onClick } = props ?? {};
|
|
103
|
-
|
|
104
|
-
const handleClick = () => onClick(props);
|
|
105
|
-
|
|
106
|
-
const render = () => {
|
|
107
|
-
if (props.selected) {
|
|
108
|
-
return (
|
|
109
|
-
<div onClick={handleClick} className="relative px-4 py-3 cursor-pointer text-sm transition-all duration-200 text-gray-900 font-semibold">
|
|
110
|
-
{title}
|
|
111
|
-
<span className="absolute bottom-0 left-0 w-full h-[2px] rounded-full bg-gray-900" />
|
|
112
|
-
</div>
|
|
113
|
-
);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
return (
|
|
117
|
-
<div
|
|
118
|
-
onClick={handleClick}
|
|
119
|
-
className="relative px-4 py-3 cursor-pointer text-sm transition-all duration-200 text-gray-500 font-normal hover:text-gray-600"
|
|
120
|
-
>
|
|
121
|
-
{title}
|
|
122
|
-
</div>
|
|
123
|
-
);
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
return <div onClick={handleClick}>{render()}</div>;
|
|
127
|
-
};
|
|
@@ -1,279 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { ChevronDown } from "lucide-react";
|
|
4
|
-
import { useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
5
|
-
import DynamicIcon from "../../core/DynamicIcon";
|
|
6
|
-
import { usePageContext } from "../../core/PageContext";
|
|
7
|
-
import { usePageViewContext } from "../../core/PageViewContext";
|
|
8
|
-
import { AbstractComponent } from "../../types/component";
|
|
9
|
-
import useUIMenu, { MenuGroup, MenuItem } from "../common/UIMenu";
|
|
10
|
-
|
|
11
|
-
/* ------------------------------------------------------------------ */
|
|
12
|
-
/* Local type */
|
|
13
|
-
type TreeMenuGroup = MenuGroup & { isDropdown?: boolean };
|
|
14
|
-
|
|
15
|
-
/* ------------------------------------------------------------------ */
|
|
16
|
-
/* Helpers */
|
|
17
|
-
|
|
18
|
-
const buildItems = (raw: any[]): TreeMenuGroup[] => {
|
|
19
|
-
const mapped: TreeMenuGroup[] = [];
|
|
20
|
-
|
|
21
|
-
for (const entry of raw) {
|
|
22
|
-
if (Array.isArray(entry.items)) {
|
|
23
|
-
mapped.push({
|
|
24
|
-
...entry,
|
|
25
|
-
isDropdown: entry.isDropdown ?? true,
|
|
26
|
-
items: buildItems(entry.items),
|
|
27
|
-
});
|
|
28
|
-
} else {
|
|
29
|
-
const lastGroup = mapped[mapped.length - 1];
|
|
30
|
-
if (lastGroup && lastGroup.title === "" && !lastGroup.isDropdown) {
|
|
31
|
-
lastGroup.items.push(entry);
|
|
32
|
-
} else {
|
|
33
|
-
mapped.push({ title: "", icon: undefined, isDropdown: false, items: [entry] });
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
return mapped;
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
const findFirstItem = (groups: TreeMenuGroup[]): MenuItem | null => {
|
|
42
|
-
for (const group of groups) {
|
|
43
|
-
for (const item of group.items) {
|
|
44
|
-
if (Array.isArray((item as any).items)) {
|
|
45
|
-
const found = findFirstItem([item as any as TreeMenuGroup]);
|
|
46
|
-
if (found) return found;
|
|
47
|
-
} else if (item.page) return item;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
return null;
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
const findGroupPath = (groups: TreeMenuGroup[], targetPage: string | null, depth = 0): string[] => {
|
|
54
|
-
for (let i = 0; i < groups.length; i++) {
|
|
55
|
-
const group = groups[i];
|
|
56
|
-
const key = `${depth}-${i}`;
|
|
57
|
-
|
|
58
|
-
for (const item of group.items) {
|
|
59
|
-
if (Array.isArray((item as any).items)) {
|
|
60
|
-
const path = findGroupPath([item as any as TreeMenuGroup], targetPage, depth + 1);
|
|
61
|
-
if (path.length) return [key, ...path];
|
|
62
|
-
} else if (item.page === targetPage) return [key];
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
return [];
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
const findItemByPage = (groups: TreeMenuGroup[], targetPage: string | null): MenuItem | null => {
|
|
69
|
-
for (const group of groups) {
|
|
70
|
-
for (const item of group.items) {
|
|
71
|
-
if (Array.isArray((item as any).items)) {
|
|
72
|
-
const found = findItemByPage([item as any as TreeMenuGroup], targetPage);
|
|
73
|
-
if (found) return found;
|
|
74
|
-
} else {
|
|
75
|
-
// console.log("treemenu findItemByPage", targetPage, item.page);
|
|
76
|
-
if (item.page === targetPage) return item;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
return null;
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
const isActiveItemInTree = (groups: TreeMenuGroup[], page: string | null): boolean =>
|
|
84
|
-
groups.some((group) =>
|
|
85
|
-
group.items.some((item) => (Array.isArray((item as any).items) ? isActiveItemInTree([item as any as TreeMenuGroup], page) : item.page === page)),
|
|
86
|
-
);
|
|
87
|
-
|
|
88
|
-
/* ------------------------------------------------------------------ */
|
|
89
|
-
/* Component */
|
|
90
|
-
|
|
91
|
-
interface TreeMenuProps extends AbstractComponent {
|
|
92
|
-
name?: string;
|
|
93
|
-
items?: TreeMenuGroup[] | MenuItem[];
|
|
94
|
-
data?: Record<string, any>;
|
|
95
|
-
menugroup?: string;
|
|
96
|
-
depends?: string;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const TreeMenu = (props: TreeMenuProps) => {
|
|
100
|
-
const { name = "selectedPage", menugroup, depends, items: itemsProp, data } = props ?? {};
|
|
101
|
-
|
|
102
|
-
const pageContext = usePageContext();
|
|
103
|
-
const pageView = usePageViewContext();
|
|
104
|
-
|
|
105
|
-
const contextKey = name ?? depends ?? "";
|
|
106
|
-
|
|
107
|
-
const [openGroups, setOpenGroups] = useState<string[]>([]);
|
|
108
|
-
const [activeItem, setActiveItem] = useState<Record<string, any>>({});
|
|
109
|
-
const [items, setItems] = useState<TreeMenuGroup[]>([]);
|
|
110
|
-
const initializedRef = useRef(false);
|
|
111
|
-
|
|
112
|
-
const { items: rawItems } = useUIMenu({
|
|
113
|
-
items: itemsProp as Record<string, any>[],
|
|
114
|
-
data,
|
|
115
|
-
menugroup,
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
useLayoutEffect(() => {
|
|
119
|
-
const { path = "" } = pageView.getOriginalLocationInfo() ?? {};
|
|
120
|
-
const [, ...anchors] = path.split("#");
|
|
121
|
-
const [selectedPath] = anchors;
|
|
122
|
-
pageContext.set(contextKey, selectedPath);
|
|
123
|
-
}, []);
|
|
124
|
-
|
|
125
|
-
/* ---------------------- Sync active item from page view ---------------------- */
|
|
126
|
-
useLayoutEffect(() => {
|
|
127
|
-
if (items.length === 0 || !pageView.getSelectedPage()) return;
|
|
128
|
-
if (initializedRef.current) return;
|
|
129
|
-
|
|
130
|
-
const item = findItemByPage(items, pageView.getSelectedPage());
|
|
131
|
-
if (item) setActiveItem(item);
|
|
132
|
-
}, [items, pageView.getSelectedPage()]);
|
|
133
|
-
|
|
134
|
-
/* ---------------------- Build items ---------------------- */
|
|
135
|
-
useEffect(() => {
|
|
136
|
-
if (!Array.isArray(rawItems) || rawItems.length === 0) return;
|
|
137
|
-
setItems(buildItems(rawItems as any[]));
|
|
138
|
-
}, [rawItems]);
|
|
139
|
-
|
|
140
|
-
/* ---------------------- Auto-select first item on init ---------------------- */
|
|
141
|
-
useEffect(() => {
|
|
142
|
-
if (initializedRef.current || items.length === 0) return;
|
|
143
|
-
|
|
144
|
-
const firstItem = findFirstItem(items);
|
|
145
|
-
if (!firstItem?.page) return;
|
|
146
|
-
|
|
147
|
-
let selectedPage = pageContext.get(contextKey);
|
|
148
|
-
// console.log("treemenu selected page from context", contextKey, selectedPage);
|
|
149
|
-
if (selectedPage == null && contextKey === "selectedPage") {
|
|
150
|
-
selectedPage = pageView.getSelectedPage();
|
|
151
|
-
// console.log("treemenu selected page fallback from context", contextKey, selectedPage);
|
|
152
|
-
}
|
|
153
|
-
// console.log("treemenu selected page final", contextKey, selectedPage);
|
|
154
|
-
if (selectedPage != null) {
|
|
155
|
-
const selectedItem = findItemByPage(items, selectedPage);
|
|
156
|
-
// console.log("treemenu findItemByPage", selectedPage, selectedItem);
|
|
157
|
-
if (selectedItem) {
|
|
158
|
-
setOpenGroups(findGroupPath(items, selectedPage));
|
|
159
|
-
setActiveItem(selectedItem);
|
|
160
|
-
} else {
|
|
161
|
-
setOpenGroups(findGroupPath(items, firstItem.page));
|
|
162
|
-
setActiveItem(firstItem);
|
|
163
|
-
}
|
|
164
|
-
} else {
|
|
165
|
-
setOpenGroups(findGroupPath(items, firstItem.page));
|
|
166
|
-
setActiveItem(firstItem);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
initializedRef.current = true;
|
|
170
|
-
}, [items, contextKey]);
|
|
171
|
-
|
|
172
|
-
/* ---------------------- Sync page context on active item change ---------------------- */
|
|
173
|
-
useEffect(() => {
|
|
174
|
-
const { mode, page } = (activeItem ?? {}) as Record<string, any>;
|
|
175
|
-
if (!page) return;
|
|
176
|
-
|
|
177
|
-
const idx = ["window", "popup"].indexOf(String(mode).toLowerCase());
|
|
178
|
-
if (idx >= 0) {
|
|
179
|
-
pageView.setPage(page, { mode });
|
|
180
|
-
} else {
|
|
181
|
-
pageView.setSelectedPage(page);
|
|
182
|
-
pageContext.set(contextKey, page);
|
|
183
|
-
}
|
|
184
|
-
}, [activeItem]);
|
|
185
|
-
|
|
186
|
-
/* ---------------------- Auto-open group containing active item ---------------------- */
|
|
187
|
-
useEffect(() => {
|
|
188
|
-
if (!activeItem?.page || items.length === 0) return;
|
|
189
|
-
|
|
190
|
-
const path = findGroupPath(items, activeItem.page);
|
|
191
|
-
if (path.length) {
|
|
192
|
-
setOpenGroups((prev) => Array.from(new Set([...prev, ...path])));
|
|
193
|
-
}
|
|
194
|
-
}, [activeItem, items]);
|
|
195
|
-
|
|
196
|
-
/* ---------------------- Helpers ---------------------- */
|
|
197
|
-
const toggleGroup = (key: string) => setOpenGroups((prev) => (prev.includes(key) ? prev.filter((k) => k !== key) : [...prev, key]));
|
|
198
|
-
|
|
199
|
-
const isGroupOpen = (key: string) => openGroups.includes(key);
|
|
200
|
-
|
|
201
|
-
const calculateHeight = (items: any[], depth: number): number => {
|
|
202
|
-
return items.reduce((total: number, item: any, idx: number) => {
|
|
203
|
-
if (Array.isArray(item.items)) {
|
|
204
|
-
const key = `${depth + 1}-${idx}`;
|
|
205
|
-
return total + 36 + (isGroupOpen(key) ? calculateHeight(item.items, depth + 1) : 0);
|
|
206
|
-
}
|
|
207
|
-
return total + 36;
|
|
208
|
-
}, 0);
|
|
209
|
-
};
|
|
210
|
-
|
|
211
|
-
/* ---------------------- Events ---------------------- */
|
|
212
|
-
const handleSubItemClick = (item: MenuItem) => {
|
|
213
|
-
if (!item.page || item.page === activeItem.page) return;
|
|
214
|
-
setActiveItem(item);
|
|
215
|
-
};
|
|
216
|
-
|
|
217
|
-
/* ---------------------- Recursive render ---------------------- */
|
|
218
|
-
const renderGroup = (group: TreeMenuGroup, index: number, depth: number = 0): React.ReactNode => {
|
|
219
|
-
const paddingLeft = depth * 12;
|
|
220
|
-
const key = `${depth}-${index}`;
|
|
221
|
-
|
|
222
|
-
return (
|
|
223
|
-
<div key={key}>
|
|
224
|
-
{group.title && (
|
|
225
|
-
<div
|
|
226
|
-
onClick={() => group.isDropdown && toggleGroup(key)}
|
|
227
|
-
className="flex items-center justify-between px-3 py-2 cursor-pointer rounded-md"
|
|
228
|
-
style={{ paddingLeft: `${12 + paddingLeft}px` }}
|
|
229
|
-
>
|
|
230
|
-
<div className="flex items-center gap-2 h-6">
|
|
231
|
-
<div className="w-5 h-5 flex items-center justify-center">
|
|
232
|
-
<DynamicIcon icon={group.icon} size={18} />
|
|
233
|
-
</div>
|
|
234
|
-
<span className="text-sm font-medium truncate">{group.title}</span>
|
|
235
|
-
</div>
|
|
236
|
-
{group.isDropdown && <ChevronDown size={16} className={`transition-transform duration-200 ${isGroupOpen(key) ? "rotate-90" : ""}`} />}
|
|
237
|
-
</div>
|
|
238
|
-
)}
|
|
239
|
-
|
|
240
|
-
<div
|
|
241
|
-
style={{
|
|
242
|
-
maxHeight: !group.title || isGroupOpen(key) ? `${calculateHeight(group.items, depth)}px` : "0px",
|
|
243
|
-
}}
|
|
244
|
-
className="overflow-hidden transition-[max-height] duration-300"
|
|
245
|
-
>
|
|
246
|
-
{group.items.map((item: any, idx: number) => {
|
|
247
|
-
if (Array.isArray(item.items)) {
|
|
248
|
-
return renderGroup(item as TreeMenuGroup, idx, depth + 1);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
const isActive = item.page === activeItem.page;
|
|
252
|
-
const itemPadding = group.title ? `${24 + paddingLeft}px` : `${12 + paddingLeft}px`;
|
|
253
|
-
|
|
254
|
-
return (
|
|
255
|
-
<div
|
|
256
|
-
key={idx}
|
|
257
|
-
onClick={() => handleSubItemClick(item)}
|
|
258
|
-
className={`py-2 text-sm cursor-pointer rounded-md flex items-center gap-2 hover:bg-[#d3e3fd] ${
|
|
259
|
-
isActive ? "bg-[#e8f0fe] text-blue-700 font-medium" : ""
|
|
260
|
-
}`}
|
|
261
|
-
style={{ paddingLeft: itemPadding }}
|
|
262
|
-
>
|
|
263
|
-
<div className="w-5 h-5 flex items-center justify-center">
|
|
264
|
-
<DynamicIcon icon={item.icon} size={16} />
|
|
265
|
-
</div>
|
|
266
|
-
<span className="text-sm truncate">{item.title}</span>
|
|
267
|
-
</div>
|
|
268
|
-
);
|
|
269
|
-
})}
|
|
270
|
-
</div>
|
|
271
|
-
</div>
|
|
272
|
-
);
|
|
273
|
-
};
|
|
274
|
-
|
|
275
|
-
/* ---------------------- Render ---------------------- */
|
|
276
|
-
return <div className="flex flex-col gap-1 px-4 py-3 h-full overflow-y-auto">{items.map((group, index) => renderGroup(group, index, 0))}</div>;
|
|
277
|
-
};
|
|
278
|
-
|
|
279
|
-
export default TreeMenu;
|