1
- import { MultiCompBuilder , withViewFn } from "comps/generators" ;
1
+ import { MultiCompBuilder , withDefault , withViewFn } from "comps/generators" ;
2
2
import { trans } from "i18n" ;
3
- import { Section } from "components/Section" ;
3
+ import { Section , sectionNames } from "components/Section" ;
4
4
import { manualOptionsControl } from "comps/controls/optionsControl" ;
5
- import { BoolCodeControl , StringControl } from "comps/controls/codeControl" ;
5
+ import { BoolCodeControl , StringControl , jsonControl } from "comps/controls/codeControl" ;
6
6
import { IconControl } from "comps/controls/iconControl" ;
7
7
import styled from "styled-components" ;
8
- import React , { Suspense , useContext , useState } from "react" ;
8
+ import React , { Suspense , useContext , useEffect , useMemo , useState } from "react" ;
9
9
import { registerLayoutMap } from "comps/comps/uiComp" ;
10
10
import { AppSelectComp } from "comps/comps/layout/appSelectComp" ;
11
11
import { NameAndExposingInfo } from "comps/utils/exposingTypes" ;
12
- import { ConstructorToComp } from "lowcoder-core" ;
12
+ import { ConstructorToComp , ConstructorToDataType } from "lowcoder-core" ;
13
13
import { CanvasContainer } from "comps/comps/gridLayoutComp/canvasView" ;
14
14
import { CanvasContainerID } from "constants/domLocators" ;
15
15
import { EditorContainer , EmptyContent } from "pages/common/styledComponent" ;
16
16
import { Layers } from "constants/Layers" ;
17
17
import { ExternalEditorContext } from "util/context/ExternalEditorContext" ;
18
18
import { default as Skeleton } from "antd/es/skeleton" ;
19
19
import { hiddenPropertyView } from "comps/utils/propertyUtils" ;
20
+ import { dropdownControl } from "@lowcoder-ee/comps/controls/dropdownControl" ;
21
+ import { DataOption , DataOptionType , ModeOptions , menuItemStyleOptions , mobileNavJsonMenuItems } from "./navLayoutConstants" ;
22
+ import { styleControl } from "@lowcoder-ee/comps/controls/styleControl" ;
23
+ import { NavLayoutItemActiveStyle , NavLayoutItemActiveStyleType , NavLayoutItemHoverStyle , NavLayoutItemHoverStyleType , NavLayoutItemStyle , NavLayoutItemStyleType , NavLayoutStyle , NavLayoutStyleType , defaultTheme } from "@lowcoder-ee/comps/controls/styleControlConstants" ;
24
+ import Segmented from "antd/es/segmented" ;
25
+ import { controlItem } from "components/control" ;
26
+ import { check } from "@lowcoder-ee/util/convertUtils" ;
27
+ import { JSONObject } from "@lowcoder-ee/util/jsonTypes" ;
28
+ import { isEmpty } from "lodash" ;
29
+ import { ThemeContext } from "@lowcoder-ee/comps/utils/themeContext" ;
20
30
21
31
const TabBar = React . lazy ( ( ) => import ( "antd-mobile/es/components/tab-bar" ) ) ;
22
32
const TabBarItem = React . lazy ( ( ) =>
@@ -43,9 +53,12 @@ const TabLayoutViewContainer = styled.div`
43
53
height: calc(100% - ${ TabBarHeight } px);
44
54
` ;
45
55
46
- const TabBarWrapper = styled . div < { $readOnly : boolean } > `
56
+ const TabBarWrapper = styled . div < {
57
+ $readOnly : boolean ,
58
+ $canvasBg : string ,
59
+ } > `
47
60
max-width: inherit;
48
- background: white ;
61
+ background: ${ ( props ) => ( props . $canvasBg ) } ;
49
62
margin: 0 auto;
50
63
position: fixed;
51
64
bottom: 0;
@@ -61,6 +74,65 @@ const TabBarWrapper = styled.div<{ $readOnly: boolean }>`
61
74
}
62
75
` ;
63
76
77
+ const StyledTabBar = styled ( TabBar ) < {
78
+ $tabStyle : NavLayoutStyleType ,
79
+ $tabItemStyle : NavLayoutItemStyleType ,
80
+ $tabItemHoverStyle : NavLayoutItemHoverStyleType ,
81
+ $tabItemActiveStyle : NavLayoutItemActiveStyleType ,
82
+ } > `
83
+ width: ${ ( props ) => `calc(100% - ${ props . $tabStyle . margin } - ${ props . $tabStyle . margin } )` } ;
84
+ border: ${ ( props ) => props . $tabStyle . border } ;
85
+ background: ${ ( props ) => props . $tabStyle . background } ;
86
+ border-radius: ${ ( props ) => props . $tabStyle . radius } ;
87
+ margin: ${ ( props ) => props . $tabStyle . margin } ;
88
+ padding: ${ ( props ) => props . $tabStyle . padding } ;
89
+
90
+ .adm-tab-bar-item:not(:last-child) {
91
+ border-right: ${ ( props ) => props . $tabStyle . border } ;
92
+ }
93
+ .adm-tab-bar-item-icon, .adm-tab-bar-item-title {
94
+ color: ${ ( props ) => props . $tabStyle . text } ;
95
+ }
96
+
97
+ .adm-tab-bar-item {
98
+ background-color: ${ ( props ) => props . $tabItemStyle ?. background } ;
99
+ color: ${ ( props ) => props . $tabItemStyle ?. text } ;
100
+ border-radius: ${ ( props ) => props . $tabItemStyle ?. radius } !important;
101
+ border: ${ ( props ) => `1px solid ${ props . $tabItemStyle ?. border } ` } ;
102
+ margin: ${ ( props ) => props . $tabItemStyle ?. margin } ;
103
+ padding: ${ ( props ) => props . $tabItemStyle ?. padding } ;
104
+ }
105
+
106
+ .adm-tab-bar-item:hover {
107
+ background-color: ${ ( props ) => props . $tabItemHoverStyle ?. background } !important;
108
+ color: ${ ( props ) => props . $tabItemHoverStyle ?. text } !important;
109
+ border: ${ ( props ) => `1px solid ${ props . $tabItemHoverStyle ?. border } ` } ;
110
+ }
111
+
112
+ .adm-tab-bar-item.adm-tab-bar-item-active {
113
+ background-color: ${ ( props ) => props . $tabItemActiveStyle . background } ;
114
+ // border: ${ ( props ) => `1px solid ${ props . $tabItemActiveStyle . border } ` } ;
115
+ .adm-tab-bar-item-icon, .adm-tab-bar-item-title {
116
+ color: ${ ( props ) => props . $tabItemActiveStyle . text } ;
117
+ }
118
+ }
119
+ ` ;
120
+
121
+ const defaultStyle = {
122
+ radius : '0px' ,
123
+ margin : '0px' ,
124
+ padding : '0px' ,
125
+ }
126
+
127
+ type MenuItemStyleOptionValue = "normal" | "hover" | "active" ;
128
+
129
+ type JsonItemNode = {
130
+ label : string ;
131
+ hidden ?: boolean ;
132
+ icon ?: any ;
133
+ app ?: JSONObject ,
134
+ }
135
+
64
136
type TabBarProps = {
65
137
tabs : Array < {
66
138
title : string ;
@@ -70,27 +142,56 @@ type TabBarProps = {
70
142
selectedKey : string ;
71
143
onChange : ( key : string ) => void ;
72
144
readOnly : boolean ;
145
+ canvasBg : string ;
146
+ tabStyle : NavLayoutStyleType ;
147
+ tabItemStyle : NavLayoutItemStyleType ;
148
+ tabItemHoverStyle : NavLayoutItemHoverStyleType ;
149
+ tabItemActiveStyle : NavLayoutItemActiveStyleType ;
73
150
} ;
74
151
152
+ function checkDataNodes ( value : any , key ?: string ) : JsonItemNode [ ] | undefined {
153
+ return check ( value , [ "array" , "undefined" ] , key , ( node , k ) => {
154
+ check ( node , [ "object" ] , k ) ;
155
+ check ( node [ "label" ] , [ "string" ] , "label" ) ;
156
+ check ( node [ "hidden" ] , [ "boolean" , "undefined" ] , "hidden" ) ;
157
+ check ( node [ "icon" ] , [ "string" , "undefined" ] , "icon" ) ;
158
+ check ( node [ "app" ] , [ "object" , "undefined" ] , "app" ) ;
159
+ return node ;
160
+ } ) ;
161
+ }
162
+
163
+ function convertTreeData ( data : any ) {
164
+ return data === "" ? [ ] : checkDataNodes ( data ) ?? [ ] ;
165
+ }
166
+
75
167
function TabBarView ( props : TabBarProps ) {
168
+ const {
169
+ canvasBg, tabStyle, tabItemStyle, tabItemHoverStyle, tabItemActiveStyle,
170
+ } = props ;
76
171
return (
77
172
< Suspense fallback = { < Skeleton /> } >
78
- < TabBarWrapper $readOnly = { props . readOnly } >
79
- < TabBar
173
+ < TabBarWrapper
174
+ $readOnly = { props . readOnly }
175
+ $canvasBg = { canvasBg }
176
+ >
177
+ < StyledTabBar
80
178
onChange = { ( key : string ) => {
81
179
if ( key ) {
82
180
props . onChange ( key ) ;
83
181
}
84
182
} }
85
- style = { { width : "100%" } }
86
183
activeKey = { props . selectedKey }
184
+ $tabStyle = { tabStyle }
185
+ $tabItemStyle = { tabItemStyle }
186
+ $tabItemHoverStyle = { tabItemHoverStyle }
187
+ $tabItemActiveStyle = { tabItemActiveStyle }
87
188
>
88
189
{ props . tabs . map ( ( tab ) => {
89
190
return (
90
191
< TabBarItem key = { tab . key } icon = { tab . icon } title = { tab . title } />
91
192
) ;
92
193
} ) }
93
- </ TabBar >
194
+ </ StyledTabBar >
94
195
</ TabBarWrapper >
95
196
</ Suspense >
96
197
) ;
@@ -126,6 +227,8 @@ const TabOptionComp = (function () {
126
227
127
228
let MobileTabLayoutTmp = ( function ( ) {
128
229
const childrenMap = {
230
+ dataOptionType : dropdownControl ( DataOptionType , DataOption . Manual ) ,
231
+ jsonItems : jsonControl < JsonItemNode [ ] > ( convertTreeData , mobileNavJsonMenuItems ) ,
129
232
tabs : manualOptionsControl ( TabOptionComp , {
130
233
initOptions : [
131
234
{
@@ -142,17 +245,64 @@ let MobileTabLayoutTmp = (function () {
142
245
} ,
143
246
] ,
144
247
} ) ,
248
+ jsonTabs : manualOptionsControl ( TabOptionComp , {
249
+ initOptions : [ ] ,
250
+ } ) ,
251
+ backgroundImage : withDefault ( StringControl , "" ) ,
252
+ navStyle : withDefault ( styleControl ( NavLayoutStyle ) , defaultStyle ) ,
253
+ navItemStyle : withDefault ( styleControl ( NavLayoutItemStyle ) , defaultStyle ) ,
254
+ navItemHoverStyle : withDefault ( styleControl ( NavLayoutItemHoverStyle ) , { } ) ,
255
+ navItemActiveStyle : withDefault ( styleControl ( NavLayoutItemActiveStyle ) , { } ) ,
145
256
} ;
146
257
return new MultiCompBuilder ( childrenMap , ( props ) => {
147
258
return null ;
148
259
} )
149
260
. setPropertyViewFn ( ( children ) => {
261
+ const [ styleSegment , setStyleSegment ] = useState ( 'normal' )
150
262
return (
151
- < >
263
+ < div style = { { overflowY : 'auto' } } >
152
264
< Section name = { trans ( "aggregation.tabBar" ) } >
153
- { children . tabs . propertyView ( { } ) }
265
+ { children . dataOptionType . propertyView ( {
266
+ radioButton : true ,
267
+ type : "oneline" ,
268
+ } ) }
269
+ {
270
+ children . dataOptionType . getView ( ) === DataOption . Manual
271
+ ? children . tabs . propertyView ( { } )
272
+ : children . jsonItems . propertyView ( {
273
+ label : "Json Data" ,
274
+ } )
275
+ }
154
276
</ Section >
155
- </ >
277
+ < Section name = { sectionNames . layout } >
278
+ { children . backgroundImage . propertyView ( {
279
+ label : `Background Image` ,
280
+ placeholder : 'https://temp.im/350x400' ,
281
+ } ) }
282
+ </ Section >
283
+ < Section name = { trans ( "navLayout.navStyle" ) } >
284
+ { children . navStyle . getPropertyView ( ) }
285
+ </ Section >
286
+ < Section name = { trans ( "navLayout.navItemStyle" ) } >
287
+ { controlItem ( { } , (
288
+ < Segmented
289
+ block
290
+ options = { menuItemStyleOptions }
291
+ value = { styleSegment }
292
+ onChange = { ( k ) => setStyleSegment ( k as MenuItemStyleOptionValue ) }
293
+ />
294
+ ) ) }
295
+ { styleSegment === 'normal' && (
296
+ children . navItemStyle . getPropertyView ( )
297
+ ) }
298
+ { styleSegment === 'hover' && (
299
+ children . navItemHoverStyle . getPropertyView ( )
300
+ ) }
301
+ { styleSegment === 'active' && (
302
+ children . navItemActiveStyle . getPropertyView ( )
303
+ ) }
304
+ </ Section >
305
+ </ div >
156
306
) ;
157
307
} )
158
308
. build ( ) ;
@@ -161,20 +311,54 @@ let MobileTabLayoutTmp = (function () {
161
311
MobileTabLayoutTmp = withViewFn ( MobileTabLayoutTmp , ( comp ) => {
162
312
const [ tabIndex , setTabIndex ] = useState ( 0 ) ;
163
313
const { readOnly } = useContext ( ExternalEditorContext ) ;
164
- const tabViews = (
165
- comp . children . tabs . children . manual . getView ( ) as unknown as Array <
166
- ConstructorToComp < typeof TabOptionComp >
167
- >
168
- ) . filter ( ( tab ) => ! tab . children . hidden . getView ( ) ) ;
169
- const currentTab = tabViews [ tabIndex ] ;
170
- const appView = ( currentTab &&
171
- currentTab . children . app . getAppId ( ) &&
172
- currentTab . children . app . getView ( ) ) || (
173
- < EmptyContent
174
- text = { readOnly ? "" : trans ( "aggregation.emptyTabTooltip" ) }
175
- style = { { height : "100%" , backgroundColor : "white" } }
176
- />
177
- ) ;
314
+ const navStyle = comp . children . navStyle . getView ( ) ;
315
+ const navItemStyle = comp . children . navItemStyle . getView ( ) ;
316
+ const navItemHoverStyle = comp . children . navItemHoverStyle . getView ( ) ;
317
+ const navItemActiveStyle = comp . children . navItemActiveStyle . getView ( ) ;
318
+ const backgroundImage = comp . children . backgroundImage . getView ( ) ;
319
+ const jsonItems = comp . children . jsonItems . getView ( ) ;
320
+ const dataOptionType = comp . children . dataOptionType . getView ( ) ;
321
+ const bgColor = ( useContext ( ThemeContext ) ?. theme || defaultTheme ) . canvas ;
322
+
323
+ useEffect ( ( ) => {
324
+ comp . children . jsonTabs . dispatchChangeValueAction ( {
325
+ manual : jsonItems as unknown as Array < ConstructorToDataType < typeof TabOptionComp > >
326
+ } ) ;
327
+ } , [ jsonItems ] ) ;
328
+
329
+ const tabViews = useMemo ( ( ) => {
330
+ if ( dataOptionType === DataOption . Manual ) {
331
+ return ( comp . children . tabs . children . manual . getView ( ) as unknown as Array <
332
+ ConstructorToComp < typeof TabOptionComp >
333
+ >
334
+ ) . filter ( ( tab ) => ! tab . children . hidden . getView ( ) ) ;
335
+ }
336
+ if ( dataOptionType === DataOption . Json ) {
337
+ return ( comp . children . jsonTabs . children . manual . getView ( ) as unknown as Array <
338
+ ConstructorToComp < typeof TabOptionComp >
339
+ >
340
+ ) . filter ( ( tab ) => ! tab . children . hidden . getView ( ) ) ;
341
+ }
342
+ return [ ] ;
343
+ } , [ dataOptionType , jsonItems , comp . children . tabs , comp . children . jsonTabs ] )
344
+
345
+ const appView = useMemo ( ( ) => {
346
+ const currentTab = tabViews [ tabIndex ] ;
347
+
348
+ return ( currentTab &&
349
+ currentTab . children . app . getAppId ( ) &&
350
+ currentTab . children . app . getView ( ) ) || (
351
+ < EmptyContent
352
+ text = { readOnly ? "" : trans ( "aggregation.emptyTabTooltip" ) }
353
+ style = { { height : "100%" , backgroundColor : "white" } }
354
+ />
355
+ )
356
+ } , [ tabIndex , tabViews , dataOptionType ] ) ;
357
+
358
+ let backgroundStyle = navStyle . background ;
359
+ if ( ! isEmpty ( backgroundImage ) ) {
360
+ backgroundStyle = `center / cover url('${ backgroundImage } ') no-repeat, ${ backgroundStyle } ` ;
361
+ }
178
362
179
363
const tabBarView = (
180
364
< TabBarView
@@ -188,11 +372,21 @@ MobileTabLayoutTmp = withViewFn(MobileTabLayoutTmp, (comp) => {
188
372
selectedKey = { tabIndex + "" }
189
373
onChange = { ( key ) => setTabIndex ( Number ( key ) ) }
190
374
readOnly = { ! ! readOnly }
375
+ canvasBg = { bgColor }
376
+ tabStyle = { {
377
+ border : `1px solid ${ navStyle . border } ` ,
378
+ radius : navStyle . radius ,
379
+ text : navStyle . text ,
380
+ margin : navStyle . margin ,
381
+ padding : navStyle . padding ,
382
+ background : backgroundStyle ,
383
+ } }
384
+ tabItemStyle = { navItemStyle }
385
+ tabItemHoverStyle = { navItemHoverStyle }
386
+ tabItemActiveStyle = { navItemActiveStyle }
191
387
/>
192
388
) ;
193
389
194
- //console.log("appView", appView);
195
-
196
390
if ( readOnly ) {
197
391
return (
198
392
< TabLayoutViewContainer >
0 commit comments