Skip to content

Commit 628c91d

Browse files
authored
Merge pull request #471 from easyops-cn/steve/sidebar
fix(): support space nav in sidebar
2 parents 7360dc5 + d0afa42 commit 628c91d

File tree

10 files changed

+365
-114
lines changed

10 files changed

+365
-114
lines changed

bricks/advanced/src/jsx.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ declare global {
7474
showSorterTooltip?: boolean | object;
7575
sortDirections?: ("descend" | "ascend" | null)[];
7676
render?: (data: {
77-
rowData: object;
77+
rowData: Record<string, any>;
7878
cellData: any;
7979
}) => React.ReactNode;
8080
headerBrick?: {

bricks/ai-portal/src/elevo-sidebar/ChatHistory.tsx

Lines changed: 3 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ import React, {
66
useMemo,
77
useRef,
88
useState,
9-
type MutableRefObject,
10-
type PropsWithChildren,
119
} from "react";
1210
import classNames from "classnames";
1311
import {
@@ -20,7 +18,7 @@ import type {
2018
SimpleActionType,
2119
} from "@next-bricks/basic/mini-actions";
2220
import type { GeneralIconProps } from "@next-bricks/icons/general-icon";
23-
import { isEqual, throttle } from "lodash";
21+
import { isEqual } from "lodash";
2422
import { K, t } from "./i18n.js";
2523
import {
2624
WrappedIcon,
@@ -31,8 +29,9 @@ import {
3129
import { DONE_STATES } from "../shared/constants.js";
3230
import { parseTemplate } from "../shared/parseTemplate.js";
3331
import type { ConversationState } from "../shared/interfaces.js";
34-
import type { SidebarLink } from "./index.js";
32+
import type { SidebarLink } from "./interfaces.js";
3533
import { NavLink } from "./NavLink.js";
34+
import { SectionTitle } from "./SectionTitle.js";
3635

3736
const ADD_ICON: GeneralIconProps = {
3837
lib: "fa",
@@ -514,57 +513,3 @@ export function LowLevelChatHistory(
514513
</div>
515514
);
516515
}
517-
518-
interface SectionTitleProps {
519-
rootRef: MutableRefObject<HTMLDivElement | null>;
520-
title: string;
521-
collapsed: boolean;
522-
onToggle: () => void;
523-
}
524-
525-
function SectionTitle({
526-
rootRef,
527-
title,
528-
collapsed,
529-
children,
530-
onToggle,
531-
}: PropsWithChildren<SectionTitleProps>) {
532-
const ref = useRef<HTMLDivElement | null>(null);
533-
const [stickyActive, setStickyActive] = useState(false);
534-
535-
useEffect(() => {
536-
if (collapsed) {
537-
setStickyActive(false);
538-
return;
539-
}
540-
const parent = rootRef.current;
541-
const element = ref.current;
542-
const sibling = element?.nextElementSibling as HTMLElement | null;
543-
if (!parent || !element || !sibling) {
544-
return;
545-
}
546-
const onScroll = throttle(() => {
547-
const rect = element.getBoundingClientRect();
548-
const siblingRect = sibling.getBoundingClientRect();
549-
const diff = siblingRect.top - rect.top - rect.height;
550-
setStickyActive(diff < 1);
551-
}, 100);
552-
parent.addEventListener("scroll", onScroll);
553-
return () => {
554-
parent.removeEventListener("scroll", onScroll);
555-
};
556-
}, [collapsed, rootRef]);
557-
558-
return (
559-
<div
560-
className={classNames("section-title", { sticky: stickyActive })}
561-
ref={ref}
562-
>
563-
<div className="section-label" onClick={onToggle}>
564-
{title}
565-
<WrappedIcon lib="fa" icon="angle-down" />
566-
</div>
567-
{children}
568-
</div>
569-
);
570-
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import React, {
2+
useEffect,
3+
useRef,
4+
useState,
5+
type MutableRefObject,
6+
type PropsWithChildren,
7+
} from "react";
8+
import { throttle } from "lodash";
9+
import classNames from "classnames";
10+
import { WrappedIcon } from "./bricks.js";
11+
12+
export interface SectionTitleProps {
13+
rootRef: MutableRefObject<HTMLDivElement | null>;
14+
title: string;
15+
collapsed: boolean;
16+
onToggle: () => void;
17+
}
18+
19+
export function SectionTitle({
20+
rootRef,
21+
title,
22+
collapsed,
23+
children,
24+
onToggle,
25+
}: PropsWithChildren<SectionTitleProps>) {
26+
const ref = useRef<HTMLDivElement | null>(null);
27+
const [stickyActive, setStickyActive] = useState(false);
28+
29+
useEffect(() => {
30+
if (collapsed) {
31+
setStickyActive(false);
32+
return;
33+
}
34+
const parent = rootRef.current;
35+
const element = ref.current;
36+
const sibling = element?.nextElementSibling as HTMLElement | null;
37+
if (!parent || !element || !sibling) {
38+
return;
39+
}
40+
const onScroll = throttle(() => {
41+
const rect = element.getBoundingClientRect();
42+
const siblingRect = sibling.getBoundingClientRect();
43+
const diff = siblingRect.top - rect.top - rect.height;
44+
setStickyActive(diff < 1);
45+
}, 100);
46+
parent.addEventListener("scroll", onScroll);
47+
return () => {
48+
parent.removeEventListener("scroll", onScroll);
49+
};
50+
}, [collapsed, rootRef]);
51+
52+
return (
53+
<div
54+
className={classNames("section-title", { sticky: stickyActive })}
55+
ref={ref}
56+
>
57+
<div className="section-label" onClick={onToggle}>
58+
{title}
59+
<WrappedIcon lib="fa" icon="angle-down" />
60+
</div>
61+
{children}
62+
</div>
63+
);
64+
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import React, { useRef, useState } from "react";
2+
import classNames from "classnames";
3+
import { SectionTitle } from "./SectionTitle.js";
4+
import { K, t } from "./i18n.js";
5+
import { WrappedIcon, WrappedLink } from "./bricks.js";
6+
import { NavLink } from "./NavLink.js";
7+
import type { SidebarLink } from "./interfaces.js";
8+
9+
export interface SpaceNavProps {
10+
returnUrl: string;
11+
spaceDetail: {
12+
instanceId: string;
13+
name: string;
14+
};
15+
spaceObjects?: SidebarLink[];
16+
spaceServiceflows?: SidebarLink[];
17+
spaceLinks?: SidebarLink[];
18+
}
19+
20+
export function SpaceNav({
21+
returnUrl,
22+
spaceDetail,
23+
spaceObjects,
24+
spaceServiceflows,
25+
spaceLinks,
26+
}: SpaceNavProps) {
27+
const rootRef = useRef<HTMLDivElement | null>(null);
28+
const [objectsCollapsed, setObjectsCollapsed] = useState(false);
29+
const [serviceflowsCollapsed, setServiceflowsCollapsed] = useState(false);
30+
31+
return (
32+
<>
33+
<WrappedLink url={returnUrl} className="return-link">
34+
<div className="heading">
35+
<div className="title">{spaceDetail?.name}</div>
36+
<div className="sub-title">{t(K.COLLABORATION_SPACES)}</div>
37+
</div>
38+
<WrappedIcon className="icon" lib="lucide" icon="arrow-left" />
39+
</WrappedLink>
40+
<div className="divider" />
41+
<div className="history" ref={rootRef}>
42+
{spaceObjects?.length ? (
43+
<div
44+
className={classNames("section", { collapsed: objectsCollapsed })}
45+
>
46+
<SectionTitle
47+
rootRef={rootRef}
48+
title={t(K.BUSINESS_OBJECTS)}
49+
collapsed={objectsCollapsed}
50+
onToggle={() => setObjectsCollapsed((prev) => !prev)}
51+
/>
52+
<ul className="items">
53+
{spaceObjects.map((obj, index) => (
54+
<li key={index}>
55+
<NavLink
56+
url={obj.url}
57+
activeIncludes={obj.activeIncludes}
58+
render={({ active }) => (
59+
<WrappedLink
60+
className={classNames("item", { active })}
61+
url={obj.url}
62+
>
63+
<div className="item-title">{obj.title}</div>
64+
</WrappedLink>
65+
)}
66+
/>
67+
</li>
68+
))}
69+
</ul>
70+
</div>
71+
) : null}
72+
{spaceServiceflows?.length ? (
73+
<div
74+
className={classNames("section", {
75+
collapsed: serviceflowsCollapsed,
76+
})}
77+
>
78+
<SectionTitle
79+
rootRef={rootRef}
80+
title={t(K.SERVICEFLOWS)}
81+
collapsed={serviceflowsCollapsed}
82+
onToggle={() => setServiceflowsCollapsed((prev) => !prev)}
83+
/>
84+
<ul className="items">
85+
{spaceServiceflows.map((obj, index) => (
86+
<li key={index}>
87+
<NavLink
88+
url={obj.url}
89+
activeIncludes={obj.activeIncludes}
90+
render={({ active }) => (
91+
<WrappedLink
92+
className={classNames("item", { active })}
93+
url={obj.url}
94+
>
95+
<div className="item-title">{obj.title}</div>
96+
</WrappedLink>
97+
)}
98+
/>
99+
</li>
100+
))}
101+
</ul>
102+
</div>
103+
) : null}
104+
{spaceLinks?.length ? (
105+
<div className="space-links">
106+
{spaceLinks.map((link, index) => (
107+
<NavLink
108+
key={index}
109+
url={link.url}
110+
activeIncludes={link.activeIncludes}
111+
render={({ active }) => (
112+
<WrappedLink
113+
key={index}
114+
className={classNames("link", { active })}
115+
url={link.url}
116+
>
117+
<WrappedIcon className="icon" {...link.icon} />
118+
<span className="title">{link.title}</span>
119+
</WrappedLink>
120+
)}
121+
/>
122+
))}
123+
</div>
124+
) : null}
125+
</div>
126+
</>
127+
);
128+
}

bricks/ai-portal/src/elevo-sidebar/i18n.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ export enum K {
1515
MOVE_TO_PROJECT = "MOVE_TO_PROJECT",
1616
UNTITLED = "UNTITLED",
1717
UNNAMED = "UNNAMED",
18+
BUSINESS_OBJECTS = "BUSINESS_OBJECTS",
19+
SERVICEFLOWS = "SERVICEFLOWS",
20+
COLLABORATION_SPACES = "COLLABORATION_SPACES",
1821
}
1922

2023
const en: Locale = {
@@ -32,6 +35,9 @@ const en: Locale = {
3235
[K.MOVE_TO_PROJECT]: "Move to project",
3336
[K.UNTITLED]: "Untitled",
3437
[K.UNNAMED]: "Unnamed",
38+
[K.BUSINESS_OBJECTS]: "Business objects",
39+
[K.SERVICEFLOWS]: "Serviceflows",
40+
[K.COLLABORATION_SPACES]: "Collaboration spaces",
3541
};
3642

3743
const zh: Locale = {
@@ -49,6 +55,9 @@ const zh: Locale = {
4955
[K.MOVE_TO_PROJECT]: "移动到项目",
5056
[K.UNTITLED]: "无标题",
5157
[K.UNNAMED]: "未命名",
58+
[K.BUSINESS_OBJECTS]: "业务对象",
59+
[K.SERVICEFLOWS]: "业务流",
60+
[K.COLLABORATION_SPACES]: "协作空间",
5261
};
5362

5463
export const NS = "bricks/ai-portal/elevo-sidebar";

0 commit comments

Comments
 (0)