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 289ee7a

Browse files
committed
#1949 add tab modes feature
1 parent 84ada72 commit 289ee7a

File tree

2 files changed

+112
-67
lines changed

2 files changed

+112
-67
lines changed

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

Lines changed: 104 additions & 66 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, {useContext, useEffect,useState } from "react";
18+
import React, {useContext, useMemo } from "react";
1919
import styled, { css } from "styled-components";
2020
import { IContainer } from "../containerBase/iContainer";
2121
import { SimpleContainerComp } from "../containerBase/simpleContainerComp";
@@ -47,10 +47,9 @@ const EVENT_OPTIONS = [
4747
] as const;
4848

4949
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" },
50+
{ label: trans("tabbedContainer.tabBehaviorLazy"), value: "lazy" },
51+
{ label: trans("tabbedContainer.tabBehaviorKeepAlive"), value: "keep-alive" },
52+
{ label: trans("tabbedContainer.tabBehaviorDestroy"), value: "destroy" },
5453
] as const;
5554

5655
const TabBehaviorControl = dropdownControl(TAB_BEHAVIOR_OPTIONS, "lazy");
@@ -153,7 +152,8 @@ const StyledTabs = styled(Tabs)<{
153152
$bodyStyle: TabBodyStyleType;
154153
$isMobile?: boolean;
155154
$showHeader?: boolean;
156-
$animationStyle:AnimationStyleType
155+
$animationStyle:AnimationStyleType;
156+
$isDestroyPane?: boolean;
157157
}>`
158158
&.ant-tabs {
159159
height: 100%;
@@ -166,7 +166,6 @@ const StyledTabs = styled(Tabs)<{
166166
167167
.ant-tabs-content {
168168
height: 100%;
169-
170169
}
171170
172171
.ant-tabs-nav {
@@ -183,16 +182,71 @@ const StyledTabs = styled(Tabs)<{
183182
margin-right: -24px;
184183
}
185184
186-
${(props) => props.$style && getStyle(
187-
props.$style,
188-
props.$headerStyle,
189-
props.$bodyStyle,
190-
)}
185+
${(props) =>
186+
props.$style && getStyle(props.$style, props.$headerStyle, props.$bodyStyle)}
187+
188+
/* Conditional styling for all modes except Destroy Inactive Pane */
189+
${(props) => !props.$isDestroyPane && `
190+
.ant-tabs-content-holder { position: relative; }
191+
192+
.ant-tabs-tabpane[aria-hidden="true"],
193+
.ant-tabs-tabpane-hidden {
194+
display: block !important;
195+
visibility: hidden !important;
196+
position: absolute !important;
197+
inset: 0;
198+
pointer-events: none;
199+
}
200+
`}
191201
`;
192202

193203
const ContainerInTab = (props: ContainerBaseProps) => {
204+
return <InnerGrid {...props} emptyRows={15} hintPlaceholder={HintPlaceHolder} />;
205+
};
206+
207+
type TabPaneContentProps = {
208+
autoHeight: boolean;
209+
showVerticalScrollbar: boolean;
210+
paddingWidth: number;
211+
horizontalGridCells: number;
212+
bodyBackground: string;
213+
layoutView: any;
214+
itemsView: any;
215+
positionParamsView: any;
216+
dispatch: DispatchType;
217+
};
218+
219+
const TabPaneContent: React.FC<TabPaneContentProps> = ({
220+
autoHeight,
221+
showVerticalScrollbar,
222+
paddingWidth,
223+
horizontalGridCells,
224+
bodyBackground,
225+
layoutView,
226+
itemsView,
227+
positionParamsView,
228+
dispatch,
229+
}) => {
230+
const gridItems = useMemo(() => gridItemCompToGridItems(itemsView), [itemsView]);
231+
194232
return (
195-
<InnerGrid {...props} emptyRows={15} hintPlaceholder={HintPlaceHolder} />
233+
<BackgroundColorContext.Provider value={bodyBackground}>
234+
<ScrollBar
235+
style={{ height: autoHeight ? "auto" : "100%", margin: "0px", padding: "0px" }}
236+
hideScrollbar={!showVerticalScrollbar}
237+
overflow={autoHeight ? "hidden" : "scroll"}
238+
>
239+
<ContainerInTab
240+
layout={layoutView}
241+
items={gridItems}
242+
horizontalGridCells={horizontalGridCells}
243+
positionParams={positionParamsView}
244+
dispatch={dispatch}
245+
autoHeight={autoHeight}
246+
containerPadding={[paddingWidth, 20]}
247+
/>
248+
</ScrollBar>
249+
</BackgroundColorContext.Provider>
196250
);
197251
};
198252

@@ -212,13 +266,6 @@ const TabbedContainer = (props: TabbedContainerProps) => {
212266
const selectedTab = visibleTabs.find((tab) => tab.key === props.selectedTabKey.value);
213267
const activeKey = selectedTab? selectedTab.key: visibleTabs.length > 0 ? visibleTabs[0].key : undefined;
214268

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]);
222269

223270
const editorState = useContext(EditorContext);
224271
const maxWidth = editorState.getAppSettings().maxWidth;
@@ -229,7 +276,7 @@ const TabbedContainer = (props: TabbedContainerProps) => {
229276
const tabItems = visibleTabs.map((tab) => {
230277
const id = String(tab.id);
231278
const childDispatch = wrapDispatch(wrapDispatch(dispatch, "containers"), id);
232-
const containerProps = containers[id].children;
279+
const containerChildren = containers[id].children;
233280
const hasIcon = tab.icon.props.value;
234281

235282
const label = (
@@ -240,50 +287,25 @@ const TabbedContainer = (props: TabbedContainerProps) => {
240287
</>
241288
);
242289

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 (
266-
<BackgroundColorContext.Provider value={bodyStyle.background}>
267-
<ScrollBar style={{ height: props.autoHeight ? "auto" : "100%", margin: "0px", padding: "0px" }} hideScrollbar={!props.showVerticalScrollbar} overflow={props.autoHeight ? 'hidden':'scroll'}>
268-
<ContainerInTab
269-
layout={containerProps.layout.getView()}
270-
items={gridItemCompToGridItems(containerProps.items.getView())}
271-
horizontalGridCells={horizontalGridCells}
272-
positionParams={containerProps.positionParams.getView()}
273-
dispatch={childDispatch}
274-
autoHeight={props.autoHeight}
275-
containerPadding={[paddingWidth, 20]}
276-
/>
277-
</ScrollBar>
278-
</BackgroundColorContext.Provider>
279-
);
280-
};
290+
const forceRender = tabBehavior === "keep-alive";
281291

282292
return {
283293
label,
284294
key: tab.key,
285-
forceRender, // true only for keep-alive
286-
children: renderTabContent(),
295+
forceRender,
296+
children: (
297+
<TabPaneContent
298+
autoHeight={props.autoHeight}
299+
showVerticalScrollbar={props.showVerticalScrollbar}
300+
paddingWidth={paddingWidth}
301+
horizontalGridCells={horizontalGridCells}
302+
bodyBackground={bodyStyle.background}
303+
layoutView={containerChildren.layout.getView()}
304+
itemsView={containerChildren.items.getView()}
305+
positionParamsView={containerChildren.positionParams.getView()}
306+
dispatch={childDispatch}
307+
/>
308+
),
287309
};
288310
});
289311

@@ -299,13 +321,11 @@ const TabbedContainer = (props: TabbedContainerProps) => {
299321
$headerStyle={headerStyle}
300322
$bodyStyle={bodyStyle}
301323
$showHeader={showHeader}
324+
$isDestroyPane={tabBehavior === "destroy"}
302325
onChange={(key) => {
303326
if (key !== props.selectedTabKey.value) {
304327
props.selectedTabKey.onChange(key);
305328
props.onEvent("change");
306-
if (tabBehavior === "lazy") {
307-
setLoadedTabs((prev: Set<string>) => new Set([...prev, key]));
308-
}
309329
}
310330
}}
311331
animated
@@ -344,7 +364,25 @@ export const TabbedContainerBaseComp = (function () {
344364
{disabledPropertyView(children)}
345365
{hiddenPropertyView(children)}
346366
{children.showHeader.propertyView({ label: trans("tabbedContainer.showTabs") })}
347-
{children.tabBehavior.propertyView({ label: "Tab Behavior" })}
367+
{children.tabBehavior.propertyView({
368+
label: trans("tabbedContainer.tabBehavior"),
369+
tooltip: (
370+
<div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
371+
<div>
372+
<b>{trans("tabbedContainer.tabBehaviorLazy")}:</b>
373+
&nbsp;{trans("tabbedContainer.tabBehaviorLazyTooltip")}
374+
</div>
375+
<div>
376+
<b>{trans("tabbedContainer.tabBehaviorKeepAlive")}:</b>
377+
&nbsp;{trans("tabbedContainer.tabBehaviorKeepAliveTooltip")}
378+
</div>
379+
<div>
380+
<b>{trans("tabbedContainer.tabBehaviorDestroy")}:</b>
381+
&nbsp;{trans("tabbedContainer.tabBehaviorDestroyTooltip")}
382+
</div>
383+
</div>
384+
),
385+
})}
348386
</Section>
349387
)}
350388

@@ -435,6 +473,7 @@ class TabbedContainerImplComp extends TabbedContainerBaseComp implements IContai
435473
return this;
436474
}
437475
}
476+
438477
let newInstance = super.reduce(action);
439478
if (action.type === CompActionTypes.UPDATE_NODES_V2) {
440479
// Need eval to get the value in StringControl
@@ -489,4 +528,3 @@ export const TabbedContainerComp = withExposingConfigs(TabbedContainerImplComp,
489528
new NameConfig("selectedTabKey", trans("tabbedContainer.selectedTabKeyDesc")),
490529
NameConfigHidden,
491530
]);
492-

‎client/packages/lowcoder/src/i18n/locales/en.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3102,7 +3102,14 @@ export const en = {
31023102
"gutter" : "Gap",
31033103
"gutterTooltip" : "The distance between tabs in px",
31043104
"tabsCentered" : "Centered Tabs",
3105-
"destroyInactiveTab": "Destroy Inactive TabPane"
3105+
"destroyInactiveTab": "Destroy Inactive TabPane",
3106+
"tabBehavior": "Tab Behavior",
3107+
"tabBehaviorLazy": "Lazy",
3108+
"tabBehaviorKeepAlive": "Keep Alive",
3109+
"tabBehaviorDestroy": "Destroy Inactive",
3110+
"tabBehaviorLazyTooltip": "Render tabs only when they are first activated. Hidden tabs are not rendered until selected.",
3111+
"tabBehaviorKeepAliveTooltip": "Keep all tab contents mounted and initialized. Hidden tabs remain mounted but are visually hidden.",
3112+
"tabBehaviorDestroyTooltip": "Unmount contents of inactive tabs to free resources. Hidden tabs are destroyed until selected again."
31063113
},
31073114
"formComp": {
31083115
"containerPlaceholder": "Drag Components from the Right Pane or",

0 commit comments

Comments
(0)

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