Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 84ada72

Browse files
committed
#1949 add different tab modes in PropertyView
1 parent 262a60b commit 84ada72

File tree

1 file changed

+92
-75
lines changed

1 file changed

+92
-75
lines changed

‎client/packages/lowcoder/src/comps/comps/tabs/tabbedContainerComp.tsx

Lines changed: 92 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { NameGenerator } from "comps/utils";
1515
import { ScrollBar, Section, sectionNames } from "lowcoder-design";
1616
import { HintPlaceHolder } from "lowcoder-design";
1717
import _ from "lodash";
18-
import React, {useCallback,useContext, useEffect } from "react";
18+
import React, {useContext, useEffect,useState } from "react";
1919
import styled, { css } from "styled-components";
2020
import { IContainer } from "../containerBase/iContainer";
2121
import { SimpleContainerComp } from "../containerBase/simpleContainerComp";
@@ -34,7 +34,7 @@ import { EditorContext } from "comps/editorState";
3434
import { checkIsMobile } from "util/commonUtils";
3535
import { messageInstance } from "lowcoder-design/src/components/GlobalInstances";
3636
import { BoolControl } from "comps/controls/boolControl";
37-
import { PositionControl } from "comps/controls/dropdownControl";
37+
import { PositionControl,dropdownControl } from "comps/controls/dropdownControl";
3838
import { SliderControl } from "@lowcoder-ee/comps/controls/sliderControl";
3939
import { getBackgroundStyle } from "@lowcoder-ee/util/styleUtils";
4040

@@ -46,6 +46,15 @@ const EVENT_OPTIONS = [
4646
},
4747
] as const;
4848

49+
const TAB_BEHAVIOR_OPTIONS = [
50+
{ label: "Lazy Loading", value: "lazy" },
51+
{ label: "Remember State", value: "remember" },
52+
{ label: "Destroy Inactive", value: "destroy" },
53+
{ label: "Keep Alive (render all)", value: "keep-alive" },
54+
] as const;
55+
56+
const TabBehaviorControl = dropdownControl(TAB_BEHAVIOR_OPTIONS, "lazy");
57+
4958
const childrenMap = {
5059
tabs: TabsOptionControl,
5160
selectedTabKey: stringExposingStateControl("key", "Tab1"),
@@ -61,7 +70,7 @@ const childrenMap = {
6170
onEvent: eventHandlerControl(EVENT_OPTIONS),
6271
disabled: BoolCodeControl,
6372
showHeader: withDefault(BoolControl, true),
64-
destroyInactiveTab: withDefault(BoolControl,false),
73+
tabBehavior: withDefault(TabBehaviorControl,"lazy"),
6574
style: styleControl(TabContainerStyle , 'style'),
6675
headerStyle: styleControl(ContainerHeaderStyle , 'headerStyle'),
6776
bodyStyle: styleControl(TabBodyStyle , 'bodyStyle'),
@@ -72,7 +81,7 @@ const childrenMap = {
7281

7382
type ViewProps = RecordConstructorToView<typeof childrenMap>;
7483
type TabbedContainerProps = ViewProps & { dispatch: DispatchType };
75-
84+
7685
const getStyle = (
7786
style: TabContainerStyleType,
7887
headerStyle: ContainerHeaderStyleType,
@@ -138,11 +147,11 @@ const getStyle = (
138147
`;
139148
};
140149

141-
const StyledTabs = styled(Tabs)<{
150+
const StyledTabs = styled(Tabs)<{
142151
$style: TabContainerStyleType;
143152
$headerStyle: ContainerHeaderStyleType;
144153
$bodyStyle: TabBodyStyleType;
145-
$isMobile?: boolean;
154+
$isMobile?: boolean;
146155
$showHeader?: boolean;
147156
$animationStyle:AnimationStyleType
148157
}>`
@@ -157,13 +166,12 @@ const StyledTabs = styled(Tabs)<{
157166
158167
.ant-tabs-content {
159168
height: 100%;
160-
// margin-top: -16px;
169+
161170
}
162171
163172
.ant-tabs-nav {
164173
display: ${(props) => (props.$showHeader ? "block" : "none")};
165174
padding: 0 ${(props) => (props.$isMobile ? 16 : 24)}px;
166-
// background: white;
167175
margin: 0px;
168176
}
169177
@@ -197,27 +205,20 @@ const TabbedContainer = (props: TabbedContainerProps) => {
197205
headerStyle,
198206
bodyStyle,
199207
horizontalGridCells,
200-
destroyInactiveTab,
208+
tabBehavior,
201209
} = props;
202210

203211
const visibleTabs = tabs.filter((tab) => !tab.hidden);
204212
const selectedTab = visibleTabs.find((tab) => tab.key === props.selectedTabKey.value);
205-
const activeKey = selectedTab
206-
? selectedTab.key
207-
: visibleTabs.length > 0
208-
? visibleTabs[0].key
209-
: undefined;
210-
211-
const onTabClick = useCallback(
212-
(key: string, event: React.KeyboardEvent<Element> | React.MouseEvent<Element, MouseEvent>) => {
213-
// log.debug("onTabClick. event: ", event);
214-
const target = event.target;
215-
(target as any).parentNode.click
216-
? (target as any).parentNode.click()
217-
: (target as any).parentNode.parentNode.click();
218-
},
219-
[]
220-
);
213+
const activeKey = selectedTab? selectedTab.key: visibleTabs.length > 0 ? visibleTabs[0].key : undefined;
214+
215+
// Placeholder-based lazy loading — only for "lazy" mode
216+
const [loadedTabs, setLoadedTabs] = useState<Set<string>>(new Set());
217+
useEffect(() => {
218+
if (tabBehavior === "lazy" && activeKey) {
219+
setLoadedTabs((prev: Set<string>) => new Set([...prev, activeKey]));
220+
}
221+
}, [tabBehavior, activeKey]);
221222

222223
const editorState = useContext(EditorContext);
223224
const maxWidth = editorState.getAppSettings().maxWidth;
@@ -230,23 +231,38 @@ const TabbedContainer = (props: TabbedContainerProps) => {
230231
const childDispatch = wrapDispatch(wrapDispatch(dispatch, "containers"), id);
231232
const containerProps = containers[id].children;
232233
const hasIcon = tab.icon.props.value;
234+
233235
const label = (
234236
<>
235-
{tab.iconPosition === "left" && hasIcon && (
236-
<span style={{ marginRight: "4px" }}>{tab.icon}</span>
237-
)}
237+
{tab.iconPosition === "left" && hasIcon && <span style={{ marginRight: 4 }}>{tab.icon}</span>}
238238
{tab.label}
239-
{tab.iconPosition === "right" && hasIcon && (
240-
<span style={{ marginLeft: "4px" }}>{tab.icon}</span>
241-
)}
239+
{tab.iconPosition === "right" && hasIcon && <span style={{ marginLeft: 4 }}>{tab.icon}</span>}
242240
</>
243241
);
244-
return {
245-
label,
246-
key: tab.key,
247-
forceRender: !destroyInactiveTab,
248-
destroyInactiveTab: destroyInactiveTab,
249-
children: (
242+
243+
// Item-level forceRender mapping
244+
const forceRender: boolean = tabBehavior === "keep-alive";
245+
246+
// Render content (placeholder only for "lazy" & not yet opened)
247+
const renderTabContent = () => {
248+
if (tabBehavior === "lazy" && !loadedTabs.has(tab.key)) {
249+
return (
250+
<div
251+
style={{
252+
display: "flex",
253+
justifyContent: "center",
254+
alignItems: "center",
255+
height: "200px",
256+
color: "#999",
257+
fontSize: "14px",
258+
}}
259+
>
260+
Click to load tab content
261+
</div>
262+
);
263+
}
264+
265+
return (
250266
<BackgroundColorContext.Provider value={bodyStyle.background}>
251267
<ScrollBar style={{ height: props.autoHeight ? "auto" : "100%", margin: "0px", padding: "0px" }} hideScrollbar={!props.showVerticalScrollbar} overflow={props.autoHeight ? 'hidden':'scroll'}>
252268
<ContainerInTab
@@ -260,41 +276,49 @@ const TabbedContainer = (props: TabbedContainerProps) => {
260276
/>
261277
</ScrollBar>
262278
</BackgroundColorContext.Provider>
263-
)
264-
}
265-
})
279+
);
280+
};
281+
282+
return {
283+
label,
284+
key: tab.key,
285+
forceRender, // true only for keep-alive
286+
children: renderTabContent(),
287+
};
288+
});
266289

267290
return (
268291
<div style={{padding: props.style.margin, height: props.autoHeight ? "auto" : "100%"}}>
269-
<BackgroundColorContext.Provider value={headerStyle.headerBackground}>
270-
<StyledTabs
271-
$animationStyle={props.animationStyle}
272-
tabPosition={props.placement}
273-
activeKey={activeKey}
274-
$style={style}
275-
$headerStyle={headerStyle}
276-
$bodyStyle={bodyStyle}
277-
$showHeader={showHeader}
278-
onChange={(key) => {
279-
if (key !== props.selectedTabKey.value) {
280-
props.selectedTabKey.onChange(key);
281-
props.onEvent("change");
282-
}
283-
}}
284-
// onTabClick={onTabClick}
285-
animated
286-
$isMobile={isMobile}
287-
items={tabItems}
288-
tabBarGutter={props.tabsGutter}
289-
centered={props.tabsCentered}
290-
>
291-
</StyledTabs>
292-
</BackgroundColorContext.Provider>
293-
</div>
292+
<BackgroundColorContext.Provider value={headerStyle.headerBackground}>
293+
<StyledTabs
294+
destroyOnHidden={tabBehavior === "destroy"}
295+
$animationStyle={props.animationStyle}
296+
tabPosition={props.placement}
297+
activeKey={activeKey}
298+
$style={style}
299+
$headerStyle={headerStyle}
300+
$bodyStyle={bodyStyle}
301+
$showHeader={showHeader}
302+
onChange={(key) => {
303+
if (key !== props.selectedTabKey.value) {
304+
props.selectedTabKey.onChange(key);
305+
props.onEvent("change");
306+
if (tabBehavior === "lazy") {
307+
setLoadedTabs((prev: Set<string>) => new Set([...prev, key]));
308+
}
309+
}
310+
}}
311+
animated
312+
$isMobile={isMobile}
313+
items={tabItems}
314+
tabBarGutter={props.tabsGutter}
315+
centered={props.tabsCentered}
316+
/>
317+
</BackgroundColorContext.Provider>
318+
</div>
294319
);
295320
};
296321

297-
298322
export const TabbedContainerBaseComp = (function () {
299323
return new UICompBuilder(childrenMap, (props, dispatch) => {
300324
return (
@@ -313,14 +337,14 @@ export const TabbedContainerBaseComp = (function () {
313337
})}
314338
{children.selectedTabKey.propertyView({ label: trans("prop.defaultValue") })}
315339
</Section>
316-
340+
317341
{["logic", "both"].includes(useContext(EditorContext).editorModeStatus) && (
318342
<Section name={sectionNames.interaction}>
319343
{children.onEvent.getPropertyView()}
320344
{disabledPropertyView(children)}
321345
{hiddenPropertyView(children)}
322346
{children.showHeader.propertyView({ label: trans("tabbedContainer.showTabs") })}
323-
{children.destroyInactiveTab.propertyView({ label: trans("tabbedContainer.destroyInactiveTab") })}
347+
{children.tabBehavior.propertyView({ label: "Tab Behavior" })}
324348
</Section>
325349
)}
326350

@@ -371,21 +395,18 @@ class TabbedContainerImplComp extends TabbedContainerBaseComp implements IContai
371395
const actions: CompAction[] = [];
372396
Object.keys(containers).forEach((id) => {
373397
if (!ids.has(id)) {
374-
// log.debug("syncContainers delete. ids=", ids, " id=", id);
375398
actions.push(wrapChildAction("containers", wrapChildAction(id, deleteCompAction())));
376399
}
377400
});
378401
// new
379402
ids.forEach((id) => {
380403
if (!containers.hasOwnProperty(id)) {
381-
// log.debug("syncContainers new containers: ", containers, " id: ", id);
382404
actions.push(
383405
wrapChildAction("containers", addMapChildAction(id, { layout: {}, items: {} }))
384406
);
385407
}
386408
});
387409

388-
// log.debug("syncContainers. actions: ", actions);
389410
let instance = this;
390411
actions.forEach((action) => {
391412
instance = instance.reduce(action);
@@ -414,13 +435,11 @@ class TabbedContainerImplComp extends TabbedContainerBaseComp implements IContai
414435
return this;
415436
}
416437
}
417-
// log.debug("before super reduce. action: ", action);
418438
let newInstance = super.reduce(action);
419439
if (action.type === CompActionTypes.UPDATE_NODES_V2) {
420440
// Need eval to get the value in StringControl
421441
newInstance = newInstance.syncContainers();
422442
}
423-
// log.debug("reduce. instance: ", this, " newInstance: ", newInstance);
424443
return newInstance;
425444
}
426445

@@ -464,8 +483,6 @@ class TabbedContainerImplComp extends TabbedContainerBaseComp implements IContai
464483
override autoHeight(): boolean {
465484
return this.children.autoHeight.getView();
466485
}
467-
468-
469486
}
470487

471488
export const TabbedContainerComp = withExposingConfigs(TabbedContainerImplComp, [

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /