tee3apps-cms-sdk-react 0.0.5 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/README.md +244 -2
  2. package/dist/index.d.ts +189 -1
  3. package/dist/index.js +3 -3
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +3 -3
  6. package/dist/index.mjs.map +1 -1
  7. package/package.json +1 -1
  8. package/src/App.css +37 -0
  9. package/src/Page.tsx +5 -2
  10. package/src/PageComponents/Visual-Components/GroupProductComponent.tsx +182 -318
  11. package/src/PageComponents/Visual-Components/RichTextComponent.tsx +28 -0
  12. package/src/PageFormComponents/BooleanField.tsx +56 -0
  13. package/src/PageFormComponents/BoxRenderer.tsx +229 -0
  14. package/src/PageFormComponents/Button.tsx +127 -0
  15. package/src/PageFormComponents/DateField.tsx +46 -0
  16. package/src/PageFormComponents/ImageComponent.tsx +163 -0
  17. package/src/PageFormComponents/InputField.tsx +62 -0
  18. package/src/PageFormComponents/NumberField.tsx +56 -0
  19. package/src/PageFormComponents/PageForm.css +213 -0
  20. package/src/PageFormComponents/PageForm.tsx +267 -0
  21. package/src/PageFormComponents/RadioField.tsx +59 -0
  22. package/src/PageFormComponents/RowComponent.tsx +82 -0
  23. package/src/PageFormComponents/SelectField.tsx +183 -0
  24. package/src/PageFormComponents/Styles/BooleanField.css +56 -0
  25. package/src/PageFormComponents/Styles/DateField.css +44 -0
  26. package/src/PageFormComponents/Styles/InputField.css +50 -0
  27. package/src/PageFormComponents/Styles/NumberField.css +57 -0
  28. package/src/PageFormComponents/Styles/RadioField.css +66 -0
  29. package/src/PageFormComponents/Styles/SelectField.css +264 -0
  30. package/src/PageFormComponents/TextComponent.tsx +45 -0
  31. package/src/index.ts +2 -2
@@ -0,0 +1,56 @@
1
+ import React from 'react';
2
+ import type { ComponentProps } from '../types';
3
+ import './Styles/NumberField.css'; // Import the external CSS
4
+
5
+ interface NumberFieldProps {
6
+ props: ComponentProps;
7
+ value?: any;
8
+ onChange?: (code: string, value: any) => void;
9
+ }
10
+
11
+ const NumberField: React.FC<NumberFieldProps> = ({ props, value = '', onChange }) => {
12
+ const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
13
+ if (onChange) {
14
+ const code = props.code || `numberfield_${props.name?.all || 'field'}`;
15
+ onChange(code, e.target.value);
16
+ }
17
+ };
18
+
19
+ return (
20
+ <div className="number-field">
21
+ <label className="number-field__label">
22
+ {props.name?.all || 'Number Field'}
23
+ {props.required && <span className="number-field__required">*</span>}
24
+ </label>
25
+
26
+ <div className="number-field__input-wrapper">
27
+ <input
28
+ type="number"
29
+ value={value || ''}
30
+ onChange={handleChange}
31
+ min={props.min}
32
+ max={props.max}
33
+ step={props.step}
34
+ required={props.required}
35
+ className="number-field__input"
36
+ />
37
+
38
+ {props.suffix && (
39
+ <span className="number-field__suffix">
40
+ {props.suffix}
41
+ </span>
42
+ )}
43
+ </div>
44
+
45
+ {props.helperText && (
46
+ <p className="number-field__helper-text">{props.helperText}</p>
47
+ )}
48
+
49
+ <div className="number-field__status">
50
+ Range: {props.min}-{props.max} • Step: {props.step}
51
+ </div>
52
+ </div>
53
+ );
54
+ };
55
+
56
+ export default NumberField;
@@ -0,0 +1,213 @@
1
+ .page-form {
2
+ min-height: 100vh;
3
+ background: linear-gradient(to bottom right, #f9fafb, #f3f4f6); /* from-gray-50 to-gray-100 */
4
+ padding-top: 2rem;
5
+ padding-bottom: 2rem;
6
+ }
7
+
8
+ .page-form__container {
9
+ max-width: 112rem; /* max-w-7xl (1120px) */
10
+ margin-left: auto;
11
+ margin-right: auto;
12
+ padding-left: 1rem;
13
+ padding-right: 1rem;
14
+ }
15
+
16
+ @media (min-width: 640px) {
17
+ .page-form__container {
18
+ padding-left: 1.5rem; /* sm:px-6 */
19
+ padding-right: 1.5rem;
20
+ }
21
+ }
22
+
23
+ @media (min-width: 1024px) {
24
+ .page-form__container {
25
+ padding-left: 2rem; /* lg:px-8 */
26
+ padding-right: 2rem;
27
+ }
28
+ }
29
+
30
+ .page-form__footer {
31
+ margin-top: 3rem; /* mt-12 */
32
+ text-align: center;
33
+ }
34
+
35
+ .page-form__status {
36
+ display: inline-flex;
37
+ align-items: center;
38
+ gap: 0.5rem; /* gap-2 */
39
+ padding: 0.5rem 1rem; /* px-4 py-2 */
40
+ background-color: #fff; /* bg-white */
41
+ border-radius: 9999px; /* rounded-full */
42
+ box-shadow: 0 1px 2px rgb(0 0 0 / 0.05); /* shadow-sm */
43
+ border: 1px solid #e5e7eb; /* border-gray-200 */
44
+ }
45
+
46
+ .page-form__status-indicator {
47
+ width: 0.5rem; /* w-2 */
48
+ height: 0.5rem; /* h-2 */
49
+ background-color: #10b981; /* green-500 */
50
+ border-radius: 9999px; /* rounded-full */
51
+ animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
52
+ }
53
+
54
+ .page-form__status-text {
55
+ font-size: 0.875rem; /* text-sm */
56
+ color: #4b5563; /* gray-600 */
57
+ }
58
+
59
+ /* pulse animation */
60
+ @keyframes pulse {
61
+ 0%, 100% {
62
+ opacity: 1;
63
+ }
64
+ 50% {
65
+ opacity: 0.4;
66
+ }
67
+ }
68
+
69
+ /* Toast Notification Styles */
70
+ .toast-container {
71
+ position: fixed;
72
+ top: 20px;
73
+ right: 20px;
74
+ z-index: 10000;
75
+ display: flex;
76
+ flex-direction: column;
77
+ gap: 12px;
78
+ max-width: 400px;
79
+ width: calc(100% - 40px);
80
+ pointer-events: none;
81
+ }
82
+
83
+ @media (max-width: 480px) {
84
+ .toast-container {
85
+ top: 10px;
86
+ right: 10px;
87
+ left: 10px;
88
+ width: calc(100% - 20px);
89
+ max-width: none;
90
+ }
91
+ }
92
+
93
+ .toast {
94
+ display: flex;
95
+ align-items: flex-start;
96
+ gap: 12px;
97
+ padding: 16px;
98
+ background-color: #ffffff;
99
+ border-radius: 8px;
100
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
101
+ border-left: 4px solid;
102
+ cursor: pointer;
103
+ pointer-events: all;
104
+ animation: slideInRight 0.3s ease-out, fadeOut 0.3s ease-out 4.7s forwards;
105
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
106
+ }
107
+
108
+ .toast:hover {
109
+ transform: translateX(-4px);
110
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
111
+ }
112
+
113
+ .toast--error {
114
+ border-left-color: #ef4444;
115
+ background-color: #fef2f2;
116
+ }
117
+
118
+ .toast--error .toast__icon {
119
+ color: #ef4444;
120
+ }
121
+
122
+ .toast--success {
123
+ border-left-color: #10b981;
124
+ background-color: #f0fdf4;
125
+ }
126
+
127
+ .toast--success .toast__icon {
128
+ color: #10b981;
129
+ }
130
+
131
+ .toast--info {
132
+ border-left-color: #3b82f6;
133
+ background-color: #eff6ff;
134
+ }
135
+
136
+ .toast--info .toast__icon {
137
+ color: #3b82f6;
138
+ }
139
+
140
+ .toast--warning {
141
+ border-left-color: #f59e0b;
142
+ background-color: #fffbeb;
143
+ }
144
+
145
+ .toast--warning .toast__icon {
146
+ color: #f59e0b;
147
+ }
148
+
149
+ .toast__icon {
150
+ flex-shrink: 0;
151
+ display: flex;
152
+ align-items: center;
153
+ justify-content: center;
154
+ width: 20px;
155
+ height: 20px;
156
+ }
157
+
158
+ .toast__message {
159
+ flex: 1;
160
+ font-size: 14px;
161
+ line-height: 1.5;
162
+ color: #1f2937;
163
+ font-weight: 500;
164
+ word-wrap: break-word;
165
+ overflow-wrap: break-word;
166
+ }
167
+
168
+ .toast__close {
169
+ flex-shrink: 0;
170
+ background: none;
171
+ border: none;
172
+ padding: 4px;
173
+ cursor: pointer;
174
+ color: #6b7280;
175
+ display: flex;
176
+ align-items: center;
177
+ justify-content: center;
178
+ border-radius: 4px;
179
+ transition: background-color 0.2s ease, color 0.2s ease;
180
+ margin-left: 4px;
181
+ }
182
+
183
+ .toast__close:hover {
184
+ background-color: rgba(0, 0, 0, 0.05);
185
+ color: #1f2937;
186
+ }
187
+
188
+ .toast__close:active {
189
+ background-color: rgba(0, 0, 0, 0.1);
190
+ }
191
+
192
+ /* Animations */
193
+ @keyframes slideInRight {
194
+ from {
195
+ transform: translateX(100%);
196
+ opacity: 0;
197
+ }
198
+ to {
199
+ transform: translateX(0);
200
+ opacity: 1;
201
+ }
202
+ }
203
+
204
+ @keyframes fadeOut {
205
+ from {
206
+ opacity: 1;
207
+ transform: translateX(0);
208
+ }
209
+ to {
210
+ opacity: 0;
211
+ transform: translateX(100%);
212
+ }
213
+ }
@@ -0,0 +1,267 @@
1
+ import React, { useState, useCallback, useEffect } from 'react';
2
+ import type { Row } from '../types';
3
+ import RowComponent from './RowComponent';
4
+ import './PageForm.css'; // Import the new CSS file
5
+
6
+ interface PageFormProps {
7
+ jsonData: Row[];
8
+ onSubmit?: (data: Record<string, any>) => void;
9
+ }
10
+
11
+ interface Toast {
12
+ id: string;
13
+ message: string;
14
+ type: 'error' | 'success' | 'info' | 'warning';
15
+ }
16
+
17
+ const PageForm: React.FC<PageFormProps> = ({ jsonData, onSubmit }) => {
18
+ // Form state to store all field values
19
+ const [formValues, setFormValues] = useState<Record<string, any>>({});
20
+ const [deviceMode, setDeviceMode] = useState<string>('web');
21
+ const [validationErrors, setValidationErrors] = useState<Record<string, boolean>>({});
22
+ const [toasts, setToasts] = useState<Toast[]>([]);
23
+
24
+ // Function to determine device mode based on screen width
25
+ const getDeviceMode = (width: number) => {
26
+ if (width <= 480) return 'mobileapp';
27
+ if (width <= 768) return 'mobileweb';
28
+ if (width <= 1024) return 'tablet';
29
+ return 'web';
30
+ };
31
+
32
+ // Function to get field code from component
33
+ const getFieldCode = (component: any) => {
34
+ return component.props.code || `${component.name.toLowerCase()}_${component.props.name?.all || 'field'}`;
35
+ };
36
+
37
+ // Function to get field name from component
38
+ const getFieldName = (component: any) => {
39
+ return component.props.name?.all || component.name;
40
+ };
41
+
42
+ // Function to create a mapping of field codes to field names
43
+ const getFieldCodeToNameMap = useCallback(() => {
44
+ const codeToNameMap: Record<string, string> = {};
45
+
46
+ jsonData.forEach((row) => {
47
+ row.columns?.forEach((box) => {
48
+ box.components?.forEach((component) => {
49
+ // Check if component is a form field
50
+ const fieldTypes = ['DateField', 'NumberField', 'SelectField', 'RadioField', 'InputField', 'BooleanField'];
51
+ if (fieldTypes.includes(component.name)) {
52
+ const code = getFieldCode(component);
53
+ const name = getFieldName(component);
54
+ codeToNameMap[code] = name;
55
+ }
56
+ });
57
+ });
58
+ });
59
+
60
+ return codeToNameMap;
61
+ }, [jsonData]);
62
+
63
+ // Function to collect all required fields from jsonData
64
+ const getRequiredFields = useCallback(() => {
65
+ const requiredFields: Record<string, { code: string; name: string }> = {};
66
+
67
+ jsonData.forEach((row) => {
68
+ row.columns?.forEach((box) => {
69
+ box.components?.forEach((component) => {
70
+ // Check if component is a form field and is required
71
+ const fieldTypes = ['DateField', 'NumberField', 'SelectField', 'RadioField', 'InputField', 'BooleanField'];
72
+ if (fieldTypes.includes(component.name) && component.props.required) {
73
+ const code = getFieldCode(component);
74
+ const name = getFieldName(component);
75
+ requiredFields[code] = { code, name };
76
+ }
77
+ });
78
+ });
79
+ });
80
+
81
+ return requiredFields;
82
+ }, [jsonData]);
83
+
84
+ // Function to transform form values from code-based to name-based
85
+ const transformFormValuesToNames = useCallback(() => {
86
+ const codeToNameMap = getFieldCodeToNameMap();
87
+ const transformedData: Record<string, any> = {};
88
+
89
+ Object.keys(formValues).forEach((code) => {
90
+ const fieldName = codeToNameMap[code];
91
+ if (fieldName) {
92
+ transformedData[fieldName] = formValues[code];
93
+ } else {
94
+ // If no mapping found, keep the code as key
95
+ transformedData[code] = formValues[code];
96
+ }
97
+ });
98
+
99
+ return transformedData;
100
+ }, [formValues, getFieldCodeToNameMap]);
101
+
102
+ // Effect to handle window resize
103
+ useEffect(() => {
104
+ const handleResize = () => {
105
+ const width = window.innerWidth;
106
+ setDeviceMode(getDeviceMode(width));
107
+ };
108
+
109
+ // Call once on mount
110
+ handleResize();
111
+
112
+ window.addEventListener('resize', handleResize);
113
+ return () => {
114
+ window.removeEventListener('resize', handleResize);
115
+ };
116
+ }, []);
117
+
118
+ // Handler to update form values
119
+ const handleFieldChange = useCallback((code: string, value: any) => {
120
+ setFormValues(prev => ({
121
+ ...prev,
122
+ [code]: value
123
+ }));
124
+ // Clear validation error for this field when user types
125
+ if (validationErrors[code]) {
126
+ setValidationErrors(prev => {
127
+ const newErrors = { ...prev };
128
+ delete newErrors[code];
129
+ return newErrors;
130
+ });
131
+ }
132
+ }, [validationErrors]);
133
+
134
+ // Function to validate required fields
135
+ const validateForm = useCallback(() => {
136
+ const requiredFields = getRequiredFields();
137
+ const errors: Record<string, boolean> = {};
138
+
139
+ Object.keys(requiredFields).forEach((code) => {
140
+ const value = formValues[code];
141
+ // Check if value is empty, null, undefined, empty array, or empty string
142
+ if (
143
+ value === undefined ||
144
+ value === null ||
145
+ value === '' ||
146
+ (Array.isArray(value) && value.length === 0)
147
+ ) {
148
+ errors[code] = true;
149
+ }
150
+ });
151
+
152
+ setValidationErrors(errors);
153
+ return { isValid: Object.keys(errors).length === 0, errors };
154
+ }, [formValues, getRequiredFields]);
155
+
156
+ // Function to show toast notification
157
+ const showToast = useCallback((message: string, type: Toast['type'] = 'error') => {
158
+ const id = Date.now().toString();
159
+ const newToast: Toast = { id, message, type };
160
+
161
+ setToasts(prev => [...prev, newToast]);
162
+
163
+ // Auto remove toast after 5 seconds
164
+ setTimeout(() => {
165
+ setToasts(prev => prev.filter(toast => toast.id !== id));
166
+ }, 5000);
167
+ }, []);
168
+
169
+ // Function to remove toast
170
+ const removeToast = useCallback((id: string) => {
171
+ setToasts(prev => prev.filter(toast => toast.id !== id));
172
+ }, []);
173
+
174
+ // Single method to handle form submission - validates, transforms, and sends data to parent
175
+ const handleFormSubmit = useCallback(() => {
176
+ // Validate form and get validation result
177
+ const { isValid, errors } = validateForm();
178
+
179
+ if (isValid) {
180
+ // Transform form values to use field names instead of codes
181
+ const transformedData = transformFormValuesToNames();
182
+
183
+ // Send transformed data back to parent component
184
+ if (onSubmit) {
185
+ onSubmit(transformedData);
186
+ }
187
+ } else {
188
+ // If validation fails, show error message with field names
189
+ const requiredFields = getRequiredFields();
190
+ const errorFields = Object.keys(errors)
191
+ .map(code => requiredFields[code]?.name || code)
192
+ .join(', ');
193
+ showToast(`Please fill in all required fields: ${errorFields}`, 'error');
194
+ }
195
+ }, [formValues, validateForm, getRequiredFields, transformFormValuesToNames, onSubmit, showToast]);
196
+
197
+ return (
198
+ <div className="page-form">
199
+ <div className="page-form__container">
200
+ {/* Render each row */}
201
+ {jsonData.map((row, rowIndex) => (
202
+ <RowComponent
203
+ key={rowIndex}
204
+ row={row}
205
+ deviceMode={deviceMode}
206
+ formValues={formValues}
207
+ validationErrors={validationErrors}
208
+ onFieldChange={handleFieldChange}
209
+ onFormSubmit={handleFormSubmit}
210
+ />
211
+ ))}
212
+
213
+ {/* Footer */}
214
+
215
+ </div>
216
+
217
+ {/* Toast Container */}
218
+ <div className="toast-container">
219
+ {toasts.map((toast) => (
220
+ <div
221
+ key={toast.id}
222
+ className={`toast toast--${toast.type}`}
223
+ onClick={() => removeToast(toast.id)}
224
+ >
225
+ <div className="toast__icon">
226
+ {toast.type === 'error' && (
227
+ <svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor">
228
+ <path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clipRule="evenodd" />
229
+ </svg>
230
+ )}
231
+ {toast.type === 'success' && (
232
+ <svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor">
233
+ <path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
234
+ </svg>
235
+ )}
236
+ {toast.type === 'info' && (
237
+ <svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor">
238
+ <path fillRule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clipRule="evenodd" />
239
+ </svg>
240
+ )}
241
+ {toast.type === 'warning' && (
242
+ <svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor">
243
+ <path fillRule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clipRule="evenodd" />
244
+ </svg>
245
+ )}
246
+ </div>
247
+ <div className="toast__message">{toast.message}</div>
248
+ <button
249
+ className="toast__close"
250
+ onClick={(e) => {
251
+ e.stopPropagation();
252
+ removeToast(toast.id);
253
+ }}
254
+ aria-label="Close"
255
+ >
256
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
257
+ <path d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z"/>
258
+ </svg>
259
+ </button>
260
+ </div>
261
+ ))}
262
+ </div>
263
+ </div>
264
+ );
265
+ };
266
+
267
+ export default PageForm;
@@ -0,0 +1,59 @@
1
+ import React from 'react';
2
+ import type { ComponentProps } from '../types';
3
+ import './Styles/RadioField.css'; // Import external stylesheet
4
+
5
+ interface RadioFieldProps {
6
+ props: ComponentProps;
7
+ value?: any;
8
+ onChange?: (code: string, value: any) => void;
9
+ }
10
+
11
+ const RadioField: React.FC<RadioFieldProps> = ({ props, value = '', onChange }) => {
12
+ const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
13
+ if (onChange) {
14
+ const code = props.code || `radiofield_${props.name?.all || 'field'}`;
15
+ onChange(code, e.target.value);
16
+ }
17
+ };
18
+
19
+ return (
20
+ <div className="radio-field">
21
+ <label className="radio-field__label">
22
+ {props.name?.all || 'Radio Field'}
23
+ {props.required && <span className="radio-field__required">*</span>}
24
+ </label>
25
+
26
+ <div className="radio-field__options">
27
+ {props.lov?.length === 0 ? (
28
+ <div className="radio-field__empty">No options available</div>
29
+ ) : (
30
+ props.lov?.map((item) => (
31
+ <label
32
+ key={item.id}
33
+ className="radio-field__option"
34
+ >
35
+ <input
36
+ type="radio"
37
+ name={props.code}
38
+ value={item.code}
39
+ checked={value === item.code}
40
+ onChange={handleChange}
41
+ className="radio-field__input"
42
+ required={props.required}
43
+ />
44
+ <span className="radio-field__option-label">{item.name}</span>
45
+ </label>
46
+ ))
47
+ )}
48
+ </div>
49
+
50
+ {props.helperText && (
51
+ <p className="radio-field__helper-text">{props.helperText}</p>
52
+ )}
53
+
54
+
55
+ </div>
56
+ );
57
+ };
58
+
59
+ export default RadioField;
@@ -0,0 +1,82 @@
1
+ import React from 'react';
2
+ import BoxRenderer from './BoxRenderer';
3
+ import type { Row } from '../types';
4
+
5
+ interface RowComponentProps {
6
+ row: Row;
7
+ deviceMode?: string;
8
+ formValues?: Record<string, any>;
9
+ validationErrors?: Record<string, boolean>;
10
+ onFieldChange?: (code: string, value: any) => void;
11
+ onFormSubmit?: () => void;
12
+ }
13
+
14
+ const RowComponent: React.FC<RowComponentProps> = ({
15
+ row,
16
+ deviceMode = 'web',
17
+ formValues = {},
18
+ validationErrors = {},
19
+ onFieldChange,
20
+ onFormSubmit
21
+ }) => {
22
+ const getCurrentMode = () => {
23
+ switch(deviceMode) {
24
+ case 'mobileweb':
25
+ return row.props.mode.mobileweb || {};
26
+ case 'mobileapp':
27
+ return row.props.mode.mobileapp || {};
28
+ case 'tablet':
29
+ return row.props.mode.tablet || {};
30
+ case 'web':
31
+ default:
32
+ return row.props.mode.web;
33
+ }
34
+ };
35
+
36
+ const currentMode = getCurrentMode();
37
+
38
+ return (
39
+ <div
40
+ style={{
41
+ minHeight: row.props.minHeight,
42
+ backgroundColor: row.props.bgColor,
43
+ backgroundImage: row.props.bgImage ? `url(${row.props.bgImage})` : 'none',
44
+ backgroundSize: row.props.bgsize,
45
+ backgroundPosition: 'center',
46
+ width: '100%',
47
+ marginBottom: '2px',
48
+ border: '1px solid #e0e0e0',
49
+ borderRadius: '4px',
50
+ boxSizing: 'border-box',
51
+ overflow: 'hidden',
52
+ position: 'relative',
53
+ display: currentMode.isVisible !== false ? 'block' : 'none',
54
+ }}
55
+ >
56
+ {/* Row Content */}
57
+ <div style={{
58
+ width: '100%',
59
+ display: 'block',
60
+ position: 'relative',
61
+ padding: '5px',
62
+ boxSizing: 'border-box'
63
+ }}>
64
+ {row.columns.map((box, boxIndex) => (
65
+ <BoxRenderer
66
+ key={boxIndex}
67
+ box={box}
68
+ deviceMode={deviceMode}
69
+ formValues={formValues}
70
+ validationErrors={validationErrors}
71
+ onFieldChange={onFieldChange}
72
+ onFormSubmit={onFormSubmit}
73
+ />
74
+ ))}
75
+ {/* Clear float after all children */}
76
+ <div style={{ clear: 'both' }} />
77
+ </div>
78
+ </div>
79
+ );
80
+ };
81
+
82
+ export default RowComponent;