@@ -21,7 +21,7 @@ import { NameGenerator } from "comps/utils";
21
21
import { Section , controlItem , sectionNames } from "lowcoder-design" ;
22
22
import { HintPlaceHolder } from "lowcoder-design" ;
23
23
import _ from "lodash" ;
24
- import React , { useEffect } from "react" ;
24
+ import React , { useRef , useState , useEffect } from "react" ;
25
25
import styled from "styled-components" ;
26
26
import { IContainer } from "../containerBase/iContainer" ;
27
27
import { SimpleContainerComp } from "../containerBase/simpleContainerComp" ;
@@ -44,11 +44,14 @@ import { disabledPropertyView, hiddenPropertyView } from "comps/utils/propertyUt
44
44
import { DisabledContext } from "comps/generators/uiCompBuilder" ;
45
45
import SliderControl from "@lowcoder-ee/comps/controls/sliderControl" ;
46
46
import { getBackgroundStyle } from "@lowcoder-ee/util/styleUtils" ;
47
+ import { useScreenInfo } from "../../hooks/screenInfoComp" ;
48
+
47
49
48
50
const RowWrapper = styled ( Row ) < {
49
51
$style : ResponsiveLayoutRowStyleType ;
50
52
$animationStyle : AnimationStyleType ;
51
- $showScrollbar :boolean
53
+ $showScrollbar : boolean ;
54
+ $columnCount : number ;
52
55
} > `
53
56
${ ( props ) => props . $animationStyle }
54
57
height: 100%;
@@ -57,26 +60,40 @@ const RowWrapper = styled(Row)<{
57
60
border-color: ${ ( props ) => props . $style ?. border } ;
58
61
border-style: ${ ( props ) => props . $style ?. borderStyle } ;
59
62
padding: ${ ( props ) => props . $style . padding } ;
60
- rotate: ${ props => props . $style . rotation }
63
+ rotate: ${ ( props ) => props . $style . rotation } ;
61
64
overflow: ${ ( props ) => ( props . $showScrollbar ? 'auto' : 'hidden' ) } ;
62
- ::-webkit-scrollbar {
65
+ display: flex;
66
+ flex-wrap: wrap; // Ensure columns wrap properly when rowBreak = true
67
+ ::-webkit-scrollbar {
63
68
display: ${ ( props ) => ( props . $showScrollbar ? 'block' : 'none' ) } ;
64
- }
69
+ }
65
70
${ props => getBackgroundStyle ( props . $style ) }
71
+
72
+ --columns: ${ ( props ) => props . $columnCount || 3 } ;
66
73
` ;
67
74
68
75
const ColWrapper = styled ( Col ) < {
69
- $style : ResponsiveLayoutColStyleType | undefined ,
70
- $minWidth ?: string ,
71
- $matchColumnsHeight : boolean ,
76
+ $style : ResponsiveLayoutColStyleType | undefined ;
77
+ $minWidth ?: string ;
78
+ $matchColumnsHeight : boolean ;
79
+ $rowBreak : boolean ;
72
80
} > `
73
81
display: flex;
74
82
flex-direction: column;
75
- flex-basis: ${ ( props ) => props . $minWidth } ;
76
- max-width: ${ ( props ) => props . $minWidth } ;
83
+ flex-grow: 1;
84
+
85
+ // When rowBreak is true, columns are stretched evenly based on configured number
86
+ // When rowBreak is false, they stay at minWidth but break only if necessary
87
+ flex-basis: ${ ( props ) =>
88
+ props . $rowBreak
89
+ ? `calc(100% / var(--columns))` // Force exact column distribution
90
+ : `clamp(${ props . $minWidth } , 100% / var(--columns), 100%)` } ; // MinWidth respected
91
+
92
+ min-width: ${ ( props ) => props . $minWidth } ; // Ensure minWidth is respected
93
+ max-width: 100%; // Prevent more columns than allowed
77
94
78
95
> div {
79
- height: ${ ( props ) => props . $matchColumnsHeight ? ' 100%' : ' auto' } ;
96
+ height: ${ ( props ) => ( props . $matchColumnsHeight ? " 100%" : " auto" ) } ;
80
97
border-radius: ${ ( props ) => props . $style ?. radius } ;
81
98
border-width: ${ ( props ) => props . $style ?. borderWidth } px;
82
99
border-color: ${ ( props ) => props . $style ?. border } ;
@@ -87,6 +104,8 @@ const ColWrapper = styled(Col)<{
87
104
}
88
105
` ;
89
106
107
+
108
+
90
109
const childrenMap = {
91
110
disabled : BoolCodeControl ,
92
111
columns : ColumnOptionControl ,
@@ -96,7 +115,8 @@ const childrenMap = {
96
115
} ) ,
97
116
horizontalGridCells : SliderControl ,
98
117
autoHeight : AutoHeightControl ,
99
- rowBreak : withDefault ( BoolControl , false ) ,
118
+ rowBreak : withDefault ( BoolControl , true ) ,
119
+ useComponentWidth : withDefault ( BoolControl , true ) ,
100
120
matchColumnsHeight : withDefault ( BoolControl , true ) ,
101
121
style : styleControl ( ResponsiveLayoutRowStyle , 'style' ) ,
102
122
columnStyle : styleControl ( ResponsiveLayoutColStyle , 'columnStyle' ) ,
@@ -127,13 +147,17 @@ const ColumnContainer = (props: ColumnContainerProps) => {
127
147
) ;
128
148
} ;
129
149
130
-
131
150
const ResponsiveLayout = ( props : ResponsiveLayoutProps ) => {
151
+ const screenInfo = useScreenInfo ( ) ;
152
+ const containerRef = useRef < HTMLDivElement | null > ( null ) ;
153
+ const [ componentWidth , setComponentWidth ] = useState < number | null > ( null ) ;
154
+
132
155
let {
133
156
columns,
134
157
containers,
135
158
dispatch,
136
159
rowBreak,
160
+ useComponentWidth,
137
161
matchColumnsHeight,
138
162
style,
139
163
columnStyle,
@@ -148,33 +172,84 @@ const ResponsiveLayout = (props: ResponsiveLayoutProps) => {
148
172
autoHeight
149
173
} = props ;
150
174
175
+ // Ensure screenInfo is initialized properly
176
+ const safeScreenInfo = screenInfo && screenInfo . width
177
+ ? screenInfo
178
+ : { width : window . innerWidth , height : window . innerHeight , deviceType : "desktop" } ;
179
+
180
+ // Get device type based on width
181
+ const getDeviceType = ( width : number ) => {
182
+ if ( width < 768 ) return "mobile" ;
183
+ if ( width < 1024 ) return "tablet" ;
184
+ return "desktop" ;
185
+ } ;
186
+
187
+ // Observe component width dynamically
188
+ useEffect ( ( ) => {
189
+ if ( ! containerRef . current ) return ;
190
+
191
+ const resizeObserver = new ResizeObserver ( ( entries ) => {
192
+ for ( let entry of entries ) {
193
+ setComponentWidth ( entry . contentRect . width ) ;
194
+ }
195
+ } ) ;
196
+
197
+ resizeObserver . observe ( containerRef . current ) ;
198
+ return ( ) => resizeObserver . disconnect ( ) ;
199
+ } , [ ] ) ;
200
+
201
+ const totalColumns = columns . length ;
202
+
203
+ // Decide between screen width or component width
204
+ const effectiveWidth = useComponentWidth ? componentWidth ?? safeScreenInfo . width : safeScreenInfo . width ;
205
+ const effectiveDeviceType = useComponentWidth ? getDeviceType ( effectiveWidth || 1000 ) : safeScreenInfo . deviceType ;
206
+
207
+ // Get columns per row based on device type
208
+ let configuredColumnsPerRow = effectiveDeviceType === "mobile"
209
+ ? columnPerRowSM
210
+ : effectiveDeviceType === "tablet"
211
+ ? columnPerRowMD
212
+ : columnPerRowLG ;
213
+
214
+ // Calculate max columns that fit based on minWidth
215
+ let maxColumnsThatFit = componentWidth
216
+ ? Math . floor ( componentWidth / Math . max ( ...columns . map ( ( col ) => parseFloat ( col . minWidth || "0" ) ) ) )
217
+ : configuredColumnsPerRow ;
218
+
219
+ // Determine actual number of columns
220
+ let numberOfColumns = rowBreak ? configuredColumnsPerRow : Math . min ( maxColumnsThatFit , totalColumns ) ;
221
+
151
222
return (
152
223
< BackgroundColorContext . Provider value = { props . style . background } >
153
224
< DisabledContext . Provider value = { props . disabled } >
154
- < div style = { { padding : style . margin , height : ' 100%' } } >
225
+ < div ref = { containerRef } style = { { padding : style . margin , height : " 100%" } } >
155
226
< RowWrapper
156
- $style = { style }
227
+ $style = { { ... style } }
157
228
$animationStyle = { animationStyle }
158
229
$showScrollbar = { mainScrollbar }
230
+ $columnCount = { numberOfColumns }
159
231
wrap = { rowBreak }
160
232
gutter = { [ horizontalSpacing , verticalSpacing ] }
161
233
>
162
- { columns . map ( column => {
234
+ { columns . map ( ( column ) => {
163
235
const id = String ( column . id ) ;
164
236
const childDispatch = wrapDispatch ( wrapDispatch ( dispatch , "containers" ) , id ) ;
165
- if ( ! containers [ id ] ) return null
237
+ if ( ! containers [ id ] ) return null ;
166
238
const containerProps = containers [ id ] . children ;
167
- const noOfColumns = columns . length ;
239
+
240
+ const calculatedWidth = 100 / numberOfColumns ;
241
+
168
242
return (
169
243
< ColWrapper
170
244
key = { id }
171
- lg = { 24 / ( noOfColumns < columnPerRowLG ? noOfColumns : columnPerRowLG ) }
172
- md = { 24 / ( noOfColumns < columnPerRowMD ? noOfColumns : columnPerRowMD ) }
173
- sm = { 24 / ( noOfColumns < columnPerRowSM ? noOfColumns : columnPerRowSM ) }
174
- xs = { 24 / ( noOfColumns < columnPerRowSM ? noOfColumns : columnPerRowSM ) }
245
+ lg = { rowBreak ? 24 / numberOfColumns : undefined }
246
+ md = { rowBreak ? 24 / numberOfColumns : undefined }
247
+ sm = { rowBreak ? 24 / numberOfColumns : undefined }
248
+ xs = { rowBreak ? 24 / numberOfColumns : undefined }
175
249
$style = { props . columnStyle }
176
- $minWidth = { column . minWidth }
250
+ $minWidth = { ` ${ calculatedWidth } px` }
177
251
$matchColumnsHeight = { matchColumnsHeight }
252
+ $rowBreak = { rowBreak }
178
253
>
179
254
< ColumnContainer
180
255
layout = { containerProps . layout . getView ( ) }
@@ -186,16 +261,16 @@ const ResponsiveLayout = (props: ResponsiveLayoutProps) => {
186
261
style = { columnStyle }
187
262
/>
188
263
</ ColWrapper >
189
- )
190
- } )
191
- }
264
+ ) ;
265
+ } ) }
192
266
</ RowWrapper >
193
267
</ div >
194
- </ DisabledContext . Provider >
268
+ </ DisabledContext . Provider >
195
269
</ BackgroundColorContext . Provider >
196
270
) ;
197
271
} ;
198
272
273
+
199
274
export const ResponsiveLayoutBaseComp = ( function ( ) {
200
275
return new UICompBuilder ( childrenMap , ( props , dispatch ) => {
201
276
return (
@@ -234,6 +309,10 @@ export const ResponsiveLayoutBaseComp = (function () {
234
309
{ children . rowBreak . propertyView ( {
235
310
label : trans ( "responsiveLayout.rowBreak" )
236
311
} ) }
312
+ { children . useComponentWidth . propertyView ( {
313
+ label : trans ( "responsiveLayout.useComponentWidth" ) ,
314
+ tooltip : trans ( "responsiveLayout.useComponentWidthDesc" )
315
+ } ) }
237
316
{ controlItem ( { } , (
238
317
< div style = { { marginTop : '8px' } } >
239
318
{ trans ( "responsiveLayout.columnsPerRow" ) }
0 commit comments