Skip to content

Commit 9cac3cf

Browse files
optimise lowcoder-design components
1 parent eae5b3d commit 9cac3cf

File tree

7 files changed

+232
-135
lines changed

7 files changed

+232
-135
lines changed

client/packages/lowcoder-design/src/components/Modal/handler.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import styled, { css } from "styled-components";
2+
import { memo, useMemo } from "react";
23

34
type ResizeHandleAxis = "s" | "w" | "e" | "n" | "sw" | "nw" | "se" | "ne";
45
type ReactRef<T extends HTMLElement> = {
@@ -83,8 +84,11 @@ const ResizeHandle = styled.div<{ $axis: string }>`
8384
${(props) => (["sw", "nw", "se", "ne"].indexOf(props.$axis) >= 0 ? CornerHandle : "")};
8485
`;
8586

86-
const Handle = (axis: ResizeHandleAxis, ref: ReactRef<HTMLDivElement>) => {
87-
return <ResizeHandle ref={ref} $axis={axis} className={`react-resizable-handle`}></ResizeHandle>;
88-
};
87+
// Memoize Handle component
88+
const Handle = memo((axis: ResizeHandleAxis, ref: ReactRef<HTMLDivElement>) => {
89+
return <ResizeHandle ref={ref} $axis={axis} className="react-resizable-handle" />;
90+
});
91+
92+
Handle.displayName = 'Handle';
8993

9094
export default Handle;

client/packages/lowcoder-design/src/components/Modal/index.tsx

Lines changed: 40 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { default as AntdModal, ModalProps as AntdModalProps } from "antd/es/modal";
2-
import { useEffect, useState } from "react";
2+
import { useCallback, useEffect, useMemo, useState } from "react";
33
import { Resizable, ResizeHandle } from "react-resizable";
44
import { useResizeDetector } from "react-resize-detector";
55
import Handle from "./handler";
@@ -39,45 +39,60 @@ export function Modal(props: ModalProps) {
3939

4040
const [width, setWidth] = useState<number>();
4141
const [height, setHeight] = useState<number>();
42+
43+
// Memoize style object
44+
const modalStyles = useMemo(() => ({
45+
body: {
46+
height: height ?? modalHeight,
47+
...styles?.body,
48+
}
49+
}), [height, modalHeight, styles?.body]);
50+
51+
// Memoize event handlers
52+
const handleResizeStart = useCallback((event: React.SyntheticEvent, { node, size, handle }: { node: HTMLElement; size: { width: number; height: number }; handle: ResizeHandle }) => {
53+
props.onResizeStart?.(event, node, size, handle);
54+
}, [props.onResizeStart]);
55+
56+
const handleResize = useCallback((event: React.SyntheticEvent, { node, size, handle }: { node: HTMLElement; size: { width: number; height: number }; handle: ResizeHandle }) => {
57+
setWidth(size.width);
58+
setHeight(size.height);
59+
props.onResize?.(event, node, size, handle);
60+
}, [props.onResize]);
61+
62+
const handleResizeStop = useCallback((event: React.SyntheticEvent, { node, size, handle }: { node: HTMLElement; size: { width: number; height: number }; handle: ResizeHandle }) => {
63+
props.onResizeStop?.(event, node, size, handle);
64+
}, [props.onResizeStop]);
65+
4266
useEffect(() => {
4367
setWidth(undefined);
4468
// eslint-disable-next-line react-hooks/exhaustive-deps
4569
}, [modalWidth]);
70+
4671
useEffect(() => {
4772
setHeight(undefined);
4873
// eslint-disable-next-line react-hooks/exhaustive-deps
4974
}, [modalHeight]);
5075

5176
const { width: detectWidth, height: detectHeight, ref } = useResizeDetector();
52-
// log.info("Modal. modalWidth: ", modalWidth, " width: ", size?.w, " detectWidth: ", detectWidth);
77+
78+
// Memoize Resizable props
79+
const resizableProps = useMemo(() => ({
80+
width: width ?? detectWidth ?? 0,
81+
height: height ?? detectHeight ?? 0,
82+
resizeHandles,
83+
handle: Handle,
84+
onResizeStart: handleResizeStart,
85+
onResize: handleResize,
86+
onResizeStop: handleResizeStop
87+
}), [width, detectWidth, height, detectHeight, resizeHandles, handleResizeStart, handleResize, handleResizeStop]);
88+
5389
return (
5490
<AntdModal
5591
width={width ?? modalWidth}
56-
styles={{
57-
body: {
58-
height: height ?? modalHeight,
59-
...styles?.body,
60-
}
61-
}}
92+
styles={modalStyles}
6293
{...otherProps}
6394
>
64-
<Resizable
65-
width={width ?? detectWidth ?? 0}
66-
height={height ?? detectHeight ?? 0}
67-
resizeHandles={resizeHandles}
68-
handle={Handle}
69-
onResizeStart={(event, { node, size, handle }) =>
70-
props.onResizeStart?.(event, node, size, handle)
71-
}
72-
onResize={(event, { node, size, handle }) => {
73-
setWidth(size.width);
74-
setHeight(size.height);
75-
props.onResize?.(event, node, size, handle);
76-
}}
77-
onResizeStop={(event, { node, size, handle }) =>
78-
props.onResizeStop?.(event, node, size, handle)
79-
}
80-
>
95+
<Resizable {...resizableProps}>
8196
<div ref={ref} style={{ height: "100%" }}>
8297
{children}
8398
</div>

client/packages/lowcoder-design/src/components/ScrollBar.tsx

Lines changed: 36 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from "react";
1+
import React, { useCallback, useMemo } from "react";
22
import SimpleBar from "simplebar-react";
33
import styled from "styled-components";
44
import { DebouncedFunc } from 'lodash'; // Assuming you're using lodash's DebouncedFunc type
@@ -57,7 +57,7 @@ interface IProps {
5757
children: React.ReactNode;
5858
className?: string;
5959
height?: string;
60-
overflow?:string,
60+
overflow?: string,
6161
style?: React.CSSProperties; // Add this line to include a style prop
6262
scrollableNodeProps?: {
6363
onScroll: DebouncedFunc<(e: any) => void>;
@@ -68,7 +68,7 @@ interface IProps {
6868
suffixNode?: React.ReactNode;
6969
}
7070

71-
export const ScrollBar = ({
71+
export const ScrollBar = React.memo(({
7272
className,
7373
children,
7474
style,
@@ -80,31 +80,46 @@ export const ScrollBar = ({
8080
suffixNode,
8181
...otherProps
8282
}: IProps) => {
83-
const height = style?.height ?? '100%';
84-
// You can now use the style prop directly or pass it to SimpleBar
85-
const combinedStyle = { ...style, height }; // Example of combining height with passed style
83+
// Memoize the combined style to prevent unnecessary re-renders
84+
const combinedStyle = useMemo(() => {
85+
const height = style?.height ?? '100%';
86+
return { ...style, height };
87+
}, [style]);
88+
89+
// Memoize the render function to prevent recreation on every render
90+
const renderContent = useCallback(({ scrollableNodeProps, contentNodeProps }: any) => (
91+
<div {...scrollableNodeProps}>
92+
{prefixNode}
93+
<div {...contentNodeProps}>
94+
{children}
95+
</div>
96+
{suffixNode}
97+
</div>
98+
), [prefixNode, children, suffixNode]);
8699

87100
return hideScrollbar ? (
88-
<ScrollBarWrapper className={className}>
101+
<ScrollBarWrapper
102+
className={className}
103+
$hideplaceholder={$hideplaceholder}
104+
$overflow={overflow}
105+
>
89106
{prefixNode}
90107
{children}
91108
{suffixNode}
92109
</ScrollBarWrapper>
93110
) : (
94-
<ScrollBarWrapper className={className}>
95-
<SimpleBar style={combinedStyle} scrollableNodeProps={scrollableNodeProps} {...otherProps}>
96-
{({ scrollableNodeProps, contentNodeProps }) => {
97-
return (
98-
<div {...scrollableNodeProps as any}>
99-
{prefixNode}
100-
<div {...contentNodeProps as any}>
101-
{children}
102-
</div>
103-
{suffixNode}
104-
</div>
105-
);
106-
}}
111+
<ScrollBarWrapper
112+
className={className}
113+
$hideplaceholder={$hideplaceholder}
114+
$overflow={overflow}
115+
>
116+
<SimpleBar
117+
style={combinedStyle}
118+
scrollableNodeProps={scrollableNodeProps}
119+
{...otherProps}
120+
>
121+
{renderContent}
107122
</SimpleBar>
108123
</ScrollBarWrapper>
109124
);
110-
};
125+
});

client/packages/lowcoder-design/src/components/Section.tsx

Lines changed: 36 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { trans } from "i18n/design";
2-
import React, { ReactNode, useContext } from "react";
2+
import React, { ReactNode, useContext, useCallback, useMemo } from "react";
33
import styled from "styled-components";
44
import { ReactComponent as Packup } from "icons/v1/icon-Pack-up.svg";
55
import { labelCss } from "./Label";
@@ -14,6 +14,7 @@ const SectionItem = styled.div<{ $width?: number }>`
1414
border-bottom: none;
1515
}
1616
`;
17+
1718
const SectionLabel = styled.div`
1819
${labelCss};
1920
flex-grow: 1;
@@ -64,6 +65,10 @@ const SectionLabelDiv = styled.div`
6465
}
6566
`;
6667

68+
const ButtonContainer = styled.div`
69+
display: flex;
70+
`;
71+
6772
const ShowChildren = styled.div<{ $show?: string; $noMargin?: boolean }>`
6873
display: ${(props) => props.$show || "none"};
6974
flex-direction: column;
@@ -80,6 +85,7 @@ const TooltipWrapper = styled.span`
8085
white-space: pre-wrap;
8186
color:#fff;
8287
`;
88+
8389
interface ISectionConfig<T> {
8490
name?: string;
8591
open?: boolean;
@@ -109,59 +115,67 @@ export const PropertySectionContext = React.createContext<PropertySectionContext
109115
state: {},
110116
});
111117

112-
export const BaseSection = (props: ISectionConfig<ReactNode>) => {
113-
const { name,hasTooltip } = props;
118+
const TOOLTIP_CONTENT = (
119+
<TooltipWrapper>
120+
Here you can enter the animation type codes. Like bounce, swing or
121+
tada. Read more about all possible codes at:{" "}
122+
<a href="https://animate.style">https://animate.style</a>
123+
</TooltipWrapper>
124+
);
125+
126+
export const BaseSection = React.memo((props: ISectionConfig<ReactNode>) => {
127+
const { name, hasTooltip } = props;
114128
const { compName, state, toggle } = useContext(PropertySectionContext);
115129
const open = props.open !== undefined ? props.open : name ? state[compName]?.[name] !== false : true;
116130

117-
// console.log("open", open, props.open);
118-
119-
const handleToggle = () => {
131+
const handleToggle = useCallback(() => {
120132
if (!name) {
121133
return;
122134
}
123135
toggle(compName, name);
124-
};
136+
}, [name, compName, toggle]);
137+
138+
const tooltipContent = useMemo(() => hasTooltip ? TOOLTIP_CONTENT : null, [hasTooltip]);
139+
140+
const getPopupContainer = useCallback((node: HTMLElement) => {
141+
return (node.closest('.react-grid-item') as HTMLElement) || document.body;
142+
}, []);
125143

126144
return (
127145
<SectionItem $width={props.width} style={props.style}>
128146
{props.name && (
129147
<SectionLabelDiv onClick={handleToggle} className={'section-header'}>
130148
<SectionLabel>{props.name}</SectionLabel>
131-
<div style={{display: 'flex'}}>
149+
<ButtonContainer>
132150
{open && props.additionalButton}
133151
<PackupIcon deg={open ? 'rotate(0deg)' : 'rotate(180deg)'} />
134-
</div>
152+
</ButtonContainer>
135153
</SectionLabelDiv>
136154
)}
137155
<Tooltip
138-
title={
139-
hasTooltip && (
140-
<TooltipWrapper>
141-
Here you can enter the animation type codes. Like bounce, swing or
142-
tada. Read more about all possible codes at:{" "}
143-
<a href="https://animate.style">https://animate.style</a>
144-
</TooltipWrapper>
145-
)
146-
}
156+
title={tooltipContent}
147157
arrow={{
148158
pointAtCenter: true,
149159
}}
150160
placement="top"
151161
color="#2c2c2c"
152-
getPopupContainer={(node: any) => node.closest('.react-grid-item')}
162+
getPopupContainer={getPopupContainer}
153163
>
154164
<ShowChildren $show={open ? 'flex' : 'none'} $noMargin={props.noMargin}>
155165
{props.children}
156166
</ShowChildren>
157167
</Tooltip>
158168
</SectionItem>
159169
);
160-
};
170+
});
161171

162-
export function Section(props: ISectionConfig<ControlNode>) {
172+
BaseSection.displayName = 'BaseSection';
173+
174+
export const Section = React.memo((props: ISectionConfig<ControlNode>) => {
163175
return controlItem({ filterText: props.name, searchChild: true }, <BaseSection {...props} />);
164-
}
176+
});
177+
178+
Section.displayName = 'Section';
165179

166180
// common section names
167181
export const sectionNames = {

client/packages/lowcoder-design/src/components/Tab.tsx

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import styled, { css } from "styled-components";
2-
import React from "react";
2+
import React, { useCallback, useMemo } from "react";
33

44
const HeaderDiv = styled.div`
55
width: 312px;
@@ -79,26 +79,42 @@ interface ITabs {
7979
activeKey: string;
8080
}
8181

82-
const Tabs = (props: ITabs) => {
82+
const Tabs = React.memo((props: ITabs) => {
8383
const { onChange, tabsConfig, activeKey } = props;
84-
const activeTab = tabsConfig.find((c) => c.key === activeKey) || tabsConfig[0];
84+
85+
const activeTab = useMemo(() =>
86+
tabsConfig.find((c) => c.key === activeKey) || tabsConfig[0],
87+
[tabsConfig, activeKey]
88+
);
89+
90+
const handleTabClick = useCallback((key: string) => {
91+
onChange(key);
92+
}, [onChange]);
93+
94+
const renderTab = useCallback((tab: ITabsConfig) => {
95+
const isActive = activeTab.key === tab.key;
96+
return (
97+
<IconAndName
98+
key={tab.key}
99+
onClick={() => handleTabClick(tab.key)}
100+
$isActive={isActive}
101+
>
102+
{tab.icon}
103+
<Text $color={isActive ? "#222222" : "#8b8fa3"}>{tab.title}</Text>
104+
</IconAndName>
105+
);
106+
}, [activeTab.key, handleTabClick]);
85107

86108
return (
87109
<>
88110
<HeaderDiv>
89-
{props.tabsConfig.map((tab) => {
90-
const isActive = activeTab.key === tab.key;
91-
return (
92-
<IconAndName key={tab.key} onClick={() => onChange(tab.key)} $isActive={isActive}>
93-
{tab.icon}
94-
<Text $color={isActive ? "#222222" : "#8b8fa3"}>{tab.title}</Text>
95-
</IconAndName>
96-
);
97-
})}
111+
{tabsConfig.map(renderTab)}
98112
</HeaderDiv>
99113
<ChildDiv>{activeTab.content}</ChildDiv>
100114
</>
101115
);
102-
};
116+
});
117+
118+
Tabs.displayName = 'Tabs';
103119

104120
export { Tabs };

0 commit comments

Comments
 (0)