Skip to content

Commit 26f4dd3

Browse files
Merge pull request #1993 from iamfaran/feat/1949-tabs
[Feat]: TAB Component Different Behaviours
2 parents 060a20e + 289ee7a commit 26f4dd3

File tree

2 files changed

+157
-95
lines changed

2 files changed

+157
-95
lines changed

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

Lines changed: 149 additions & 94 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, useMemo } 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,14 @@ const EVENT_OPTIONS = [
4646
},
4747
] as const;
4848

49+
const TAB_BEHAVIOR_OPTIONS = [
50+
{ label: trans("tabbedContainer.tabBehaviorLazy"), value: "lazy" },
51+
{ label: trans("tabbedContainer.tabBehaviorKeepAlive"), value: "keep-alive" },
52+
{ label: trans("tabbedContainer.tabBehaviorDestroy"), value: "destroy" },
53+
] as const;
54+
55+
const TabBehaviorControl = dropdownControl(TAB_BEHAVIOR_OPTIONS, "lazy");
56+
4957
const childrenMap = {
5058
tabs: TabsOptionControl,
5159
selectedTabKey: stringExposingStateControl("key", "Tab1"),
@@ -61,7 +69,7 @@ const childrenMap = {
6169
onEvent: eventHandlerControl(EVENT_OPTIONS),
6270
disabled: BoolCodeControl,
6371
showHeader: withDefault(BoolControl, true),
64-
destroyInactiveTab: withDefault(BoolControl, false),
72+
tabBehavior: withDefault(TabBehaviorControl, "lazy"),
6573
style: styleControl(TabContainerStyle , 'style'),
6674
headerStyle: styleControl(ContainerHeaderStyle , 'headerStyle'),
6775
bodyStyle: styleControl(TabBodyStyle , 'bodyStyle'),
@@ -72,7 +80,7 @@ const childrenMap = {
7280

7381
type ViewProps = RecordConstructorToView<typeof childrenMap>;
7482
type TabbedContainerProps = ViewProps & { dispatch: DispatchType };
75-
83+
7684
const getStyle = (
7785
style: TabContainerStyleType,
7886
headerStyle: ContainerHeaderStyleType,
@@ -138,13 +146,14 @@ const getStyle = (
138146
`;
139147
};
140148

141-
const StyledTabs = styled(Tabs)<{
149+
const StyledTabs = styled(Tabs)<{
142150
$style: TabContainerStyleType;
143151
$headerStyle: ContainerHeaderStyleType;
144152
$bodyStyle: TabBodyStyleType;
145-
$isMobile?: boolean;
153+
$isMobile?: boolean;
146154
$showHeader?: boolean;
147-
$animationStyle:AnimationStyleType
155+
$animationStyle:AnimationStyleType;
156+
$isDestroyPane?: boolean;
148157
}>`
149158
&.ant-tabs {
150159
height: 100%;
@@ -157,13 +166,11 @@ const StyledTabs = styled(Tabs)<{
157166
158167
.ant-tabs-content {
159168
height: 100%;
160-
// margin-top: -16px;
161169
}
162170
163171
.ant-tabs-nav {
164172
display: ${(props) => (props.$showHeader ? "block" : "none")};
165173
padding: 0 ${(props) => (props.$isMobile ? 16 : 24)}px;
166-
// background: white;
167174
margin: 0px;
168175
}
169176
@@ -175,16 +182,71 @@ const StyledTabs = styled(Tabs)<{
175182
margin-right: -24px;
176183
}
177184
178-
${(props) => props.$style && getStyle(
179-
props.$style,
180-
props.$headerStyle,
181-
props.$bodyStyle,
182-
)}
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+
`}
183201
`;
184202

185203
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+
186232
return (
187-
<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>
188250
);
189251
};
190252

@@ -197,27 +259,13 @@ const TabbedContainer = (props: TabbedContainerProps) => {
197259
headerStyle,
198260
bodyStyle,
199261
horizontalGridCells,
200-
destroyInactiveTab,
262+
tabBehavior,
201263
} = props;
202264

203265
const visibleTabs = tabs.filter((tab) => !tab.hidden);
204266
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-
);
267+
const activeKey = selectedTab? selectedTab.key: visibleTabs.length > 0 ? visibleTabs[0].key : undefined;
268+
221269

222270
const editorState = useContext(EditorContext);
223271
const maxWidth = editorState.getAppSettings().maxWidth;
@@ -228,73 +276,69 @@ const TabbedContainer = (props: TabbedContainerProps) => {
228276
const tabItems = visibleTabs.map((tab) => {
229277
const id = String(tab.id);
230278
const childDispatch = wrapDispatch(wrapDispatch(dispatch, "containers"), id);
231-
const containerProps = containers[id].children;
279+
const containerChildren = containers[id].children;
232280
const hasIcon = tab.icon.props.value;
281+
233282
const label = (
234283
<>
235-
{tab.iconPosition === "left" && hasIcon && (
236-
<span style={{ marginRight: "4px" }}>{tab.icon}</span>
237-
)}
284+
{tab.iconPosition === "left" && hasIcon && <span style={{ marginRight: 4 }}>{tab.icon}</span>}
238285
{tab.label}
239-
{tab.iconPosition === "right" && hasIcon && (
240-
<span style={{ marginLeft: "4px" }}>{tab.icon}</span>
241-
)}
286+
{tab.iconPosition === "right" && hasIcon && <span style={{ marginLeft: 4 }}>{tab.icon}</span>}
242287
</>
243288
);
289+
290+
const forceRender = tabBehavior === "keep-alive";
291+
244292
return {
245293
label,
246-
key: tab.key,
247-
forceRender: !destroyInactiveTab,
248-
destroyInactiveTab: destroyInactiveTab,
294+
key: tab.key,
295+
forceRender,
249296
children: (
250-
<BackgroundColorContext.Provider value={bodyStyle.background}>
251-
<ScrollBar style={{ height: props.autoHeight ? "auto" : "100%", margin: "0px", padding: "0px" }} hideScrollbar={!props.showVerticalScrollbar} overflow={props.autoHeight ? 'hidden':'scroll'}>
252-
<ContainerInTab
253-
layout={containerProps.layout.getView()}
254-
items={gridItemCompToGridItems(containerProps.items.getView())}
255-
horizontalGridCells={horizontalGridCells}
256-
positionParams={containerProps.positionParams.getView()}
257-
dispatch={childDispatch}
258-
autoHeight={props.autoHeight}
259-
containerPadding={[paddingWidth, 20]}
260-
/>
261-
</ScrollBar>
262-
</BackgroundColorContext.Provider>
263-
)
264-
}
265-
})
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+
),
309+
};
310+
});
266311

267312
return (
268313
<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>
314+
<BackgroundColorContext.Provider value={headerStyle.headerBackground}>
315+
<StyledTabs
316+
destroyOnHidden={tabBehavior === "destroy"}
317+
$animationStyle={props.animationStyle}
318+
tabPosition={props.placement}
319+
activeKey={activeKey}
320+
$style={style}
321+
$headerStyle={headerStyle}
322+
$bodyStyle={bodyStyle}
323+
$showHeader={showHeader}
324+
$isDestroyPane={tabBehavior === "destroy"}
325+
onChange={(key) => {
326+
if (key !== props.selectedTabKey.value) {
327+
props.selectedTabKey.onChange(key);
328+
props.onEvent("change");
329+
}
330+
}}
331+
animated
332+
$isMobile={isMobile}
333+
items={tabItems}
334+
tabBarGutter={props.tabsGutter}
335+
centered={props.tabsCentered}
336+
/>
337+
</BackgroundColorContext.Provider>
338+
</div>
294339
);
295340
};
296341

297-
298342
export const TabbedContainerBaseComp = (function () {
299343
return new UICompBuilder(childrenMap, (props, dispatch) => {
300344
return (
@@ -313,14 +357,32 @@ export const TabbedContainerBaseComp = (function () {
313357
})}
314358
{children.selectedTabKey.propertyView({ label: trans("prop.defaultValue") })}
315359
</Section>
316-
360+
317361
{["logic", "both"].includes(useContext(EditorContext).editorModeStatus) && (
318362
<Section name={sectionNames.interaction}>
319363
{children.onEvent.getPropertyView()}
320364
{disabledPropertyView(children)}
321365
{hiddenPropertyView(children)}
322366
{children.showHeader.propertyView({ label: trans("tabbedContainer.showTabs") })}
323-
{children.destroyInactiveTab.propertyView({ label: trans("tabbedContainer.destroyInactiveTab") })}
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+
})}
324386
</Section>
325387
)}
326388

@@ -371,21 +433,18 @@ class TabbedContainerImplComp extends TabbedContainerBaseComp implements IContai
371433
const actions: CompAction[] = [];
372434
Object.keys(containers).forEach((id) => {
373435
if (!ids.has(id)) {
374-
// log.debug("syncContainers delete. ids=", ids, " id=", id);
375436
actions.push(wrapChildAction("containers", wrapChildAction(id, deleteCompAction())));
376437
}
377438
});
378439
// new
379440
ids.forEach((id) => {
380441
if (!containers.hasOwnProperty(id)) {
381-
// log.debug("syncContainers new containers: ", containers, " id: ", id);
382442
actions.push(
383443
wrapChildAction("containers", addMapChildAction(id, { layout: {}, items: {} }))
384444
);
385445
}
386446
});
387447

388-
// log.debug("syncContainers. actions: ", actions);
389448
let instance = this;
390449
actions.forEach((action) => {
391450
instance = instance.reduce(action);
@@ -414,13 +473,12 @@ class TabbedContainerImplComp extends TabbedContainerBaseComp implements IContai
414473
return this;
415474
}
416475
}
417-
// log.debug("before super reduce. action: ", action);
476+
418477
let newInstance = super.reduce(action);
419478
if (action.type === CompActionTypes.UPDATE_NODES_V2) {
420479
// Need eval to get the value in StringControl
421480
newInstance = newInstance.syncContainers();
422481
}
423-
// log.debug("reduce. instance: ", this, " newInstance: ", newInstance);
424482
return newInstance;
425483
}
426484

@@ -464,12 +522,9 @@ class TabbedContainerImplComp extends TabbedContainerBaseComp implements IContai
464522
override autoHeight(): boolean {
465523
return this.children.autoHeight.getView();
466524
}
467-
468-
469525
}
470526

471527
export const TabbedContainerComp = withExposingConfigs(TabbedContainerImplComp, [
472528
new NameConfig("selectedTabKey", trans("tabbedContainer.selectedTabKeyDesc")),
473529
NameConfigHidden,
474530
]);
475-

0 commit comments

Comments
 (0)