@@ -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" ) }