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 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)

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