tee3apps-cms-sdk-react 0.0.9 → 0.0.11

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tee3apps-cms-sdk-react",
3
- "version": "0.0.9",
3
+ "version": "0.0.11",
4
4
  "description": "Uses JSON to dynamically generate and render UI pages in a website",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -9,6 +9,7 @@ import InputField from './InputField';
9
9
  import RadioField from './RadioField';
10
10
  import SelectField from './SelectField';
11
11
  import ImageComponent from './ImageComponent';
12
+ import TermsAndCondition from './TermsAndCondition';
12
13
 
13
14
  interface BoxRendererProps {
14
15
  box: Box;
@@ -19,6 +20,9 @@ interface BoxRendererProps {
19
20
  validationErrors?: Record<string, boolean>;
20
21
  onFieldChange?: (code: string, value: any) => void;
21
22
  onFormSubmit?: () => void;
23
+ onFormReset?: () => void;
24
+ onFormCancel?: () => void;
25
+ isSubmitting?: boolean;
22
26
  }
23
27
 
24
28
  const BoxRenderer: React.FC<BoxRendererProps> = ({
@@ -29,7 +33,10 @@ const BoxRenderer: React.FC<BoxRendererProps> = ({
29
33
  formValues = {},
30
34
  validationErrors = {},
31
35
  onFieldChange,
32
- onFormSubmit
36
+ onFormSubmit,
37
+ onFormReset,
38
+ onFormCancel,
39
+ isSubmitting = false
33
40
  }) => {
34
41
  // Get colspan based on device mode
35
42
  const getColspan = () => {
@@ -211,8 +218,35 @@ const BoxRenderer: React.FC<BoxRendererProps> = ({
211
218
  );
212
219
  } else if (component.name === 'TextComponent') {
213
220
  return <TextComponent key={index} props={component.props} />;
221
+ } else if (component.name === 'TermsAndCondition' ) {
222
+ const code = getFieldCode(component);
223
+ const hasError = validationErrors[code];
224
+ return (
225
+ <div key={index} style={{ width: '100%' }}>
226
+ <TermsAndCondition
227
+ props={component.props}
228
+ value={formValues[code] ?? component.props.checked ?? false}
229
+ onChange={onFieldChange}
230
+ />
231
+ {hasError && (component.props.isRequired || component.props.required) && (
232
+ <div style={{ color: 'red', fontSize: '12px', marginTop: '4px' }}>
233
+ This field is required
234
+ </div>
235
+ )}
236
+ </div>
237
+ );
214
238
  } else if (component.name === 'ButtonField') {
215
- return <Button key={index} props={component.props} onFormSubmit={onFormSubmit} />;
239
+ return (
240
+ <Button
241
+ key={index}
242
+ props={component.props}
243
+ onFormSubmit={onFormSubmit}
244
+ onFormReset={onFormReset}
245
+ onFormCancel={onFormCancel}
246
+ isSubmitting={isSubmitting}
247
+ deviceMode={deviceMode}
248
+ />
249
+ );
216
250
  } else {
217
251
  return (
218
252
  <div key={index} className="mb-4 p-4 bg-gray-100 rounded-md border-2 border-dashed border-gray-300">
@@ -20,13 +20,43 @@ interface ButtonModeProps {
20
20
  interface ButtonProps {
21
21
  props: ComponentProps;
22
22
  onFormSubmit?: () => void;
23
+ onFormReset?: () => void;
24
+ onFormCancel?: () => void;
25
+ isSubmitting?: boolean;
26
+ deviceMode?: string;
23
27
  }
24
28
 
25
- const Button: React.FC<ButtonProps> = ({ props, onFormSubmit }) => {
29
+ const Button: React.FC<ButtonProps> = ({
30
+ props,
31
+ onFormSubmit,
32
+ onFormReset,
33
+ onFormCancel,
34
+ isSubmitting = false,
35
+ deviceMode = 'web'
36
+ }) => {
26
37
  // Get device-specific mode properties with proper typing
27
38
  const getCurrentMode = (): ButtonModeProps => {
39
+ let modeProps: ButtonModeProps | undefined;
40
+
41
+ // Get mode based on deviceMode
42
+ switch (deviceMode) {
43
+ case 'mobileweb':
44
+ modeProps = props.mode?.mobileweb as ButtonModeProps;
45
+ break;
46
+ case 'mobileapp':
47
+ modeProps = props.mode?.mobileapp as ButtonModeProps;
48
+ break;
49
+ case 'tablet':
50
+ modeProps = props.mode?.tablet as ButtonModeProps;
51
+ break;
52
+ case 'web':
53
+ default:
54
+ modeProps = props.mode?.web as ButtonModeProps;
55
+ break;
56
+ }
57
+
28
58
  // Default to web mode if not specified
29
- return (props.mode?.web as ButtonModeProps) || {
59
+ return modeProps || {
30
60
  radius: '4px',
31
61
  bgColor: '#3498db',
32
62
  textstyle: {
@@ -54,28 +84,61 @@ const Button: React.FC<ButtonProps> = ({ props, onFormSubmit }) => {
54
84
  }
55
85
  };
56
86
 
57
- // Handle button click
58
- const handleClick = () => {
59
- // You can add button click logic here
60
- console.log('Button clicked:', props.name?.all || 'Button');
87
+ // Handle button click based on button type
88
+ const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
89
+ // Prevent default form submission behavior
90
+ e.preventDefault();
91
+ e.stopPropagation();
61
92
 
62
- // Trigger form submission if handler is provided
63
- if (onFormSubmit) {
64
- onFormSubmit();
93
+ const buttonType = props.buttonType || 'submit';
94
+
95
+ // Handle different button types
96
+ switch (buttonType) {
97
+ case 'submit':
98
+ // Trigger form submission if handler is provided
99
+ if (onFormSubmit) {
100
+ onFormSubmit();
101
+ }
102
+ break;
103
+ case 'reset':
104
+ // Trigger form reset if handler is provided
105
+ if (onFormReset) {
106
+ onFormReset();
107
+ }
108
+ break;
109
+ case 'cancel':
110
+ // Trigger form cancel if handler is provided
111
+ if (onFormCancel) {
112
+ onFormCancel();
113
+ }
114
+ break;
115
+ default:
116
+ // Default to submit behavior
117
+ if (onFormSubmit) {
118
+ onFormSubmit();
119
+ }
120
+ break;
65
121
  }
66
122
 
67
- // If there's a link configured, handle it
68
- if (props.linktype === 'EXTERNAL' && props.link?.url) {
123
+ // If there's a link configured, handle it (only for non-submit buttons or when not submitting)
124
+ if (props.linktype === 'EXTERNAL' && props.link?.url && buttonType !== 'submit') {
69
125
  window.open(props.link.url, props.link.target || '_blank');
70
126
  }
71
127
  };
72
128
 
73
- // Get button text - handle both string and {all: string} formats
129
+ // Get button text - handle both string and {all: string, locale: string} formats
74
130
  const getButtonText = () => {
75
131
  if (typeof props.text === 'string') {
76
132
  return props.text;
77
- } else if (props.text?.all) {
78
- return props.text.all;
133
+ } else if (props.text && typeof props.text === 'object') {
134
+ // Try to get locale-specific text first, then fall back to 'all'
135
+ // You can extend this to detect current locale
136
+ const locale = 'en-IN'; // Default locale, can be made dynamic
137
+ if (props.text[locale]) {
138
+ return props.text[locale];
139
+ } else if (props.text.all) {
140
+ return props.text.all;
141
+ }
79
142
  } else if (props.name?.all) {
80
143
  return props.name.all;
81
144
  }
@@ -86,7 +149,8 @@ const Button: React.FC<ButtonProps> = ({ props, onFormSubmit }) => {
86
149
  <div className="mb-6">
87
150
  <button
88
151
  onClick={handleClick}
89
- disabled={props.disabled}
152
+ type="button"
153
+ disabled={props.disabled || (isSubmitting && props.buttonType === 'submit')}
90
154
  style={{
91
155
  backgroundColor: currentMode.bgColor || '#3498db',
92
156
  color: textStyle.fontColor,
@@ -98,25 +162,25 @@ const Button: React.FC<ButtonProps> = ({ props, onFormSubmit }) => {
98
162
  borderRadius: currentMode.radius || '4px',
99
163
  padding: '10px 20px',
100
164
  border: 'none',
101
- cursor: props.disabled ? 'not-allowed' : 'pointer',
102
- opacity: props.disabled ? 0.6 : 1,
165
+ cursor: (props.disabled || (isSubmitting && props.buttonType === 'submit')) ? 'not-allowed' : 'pointer',
166
+ opacity: (props.disabled || (isSubmitting && props.buttonType === 'submit')) ? 0.6 : 1,
103
167
  transition: 'all 0.2s ease-in-out',
104
168
  minWidth: '120px'
105
169
  }}
106
170
  onMouseEnter={(e) => {
107
- if (!props.disabled) {
171
+ if (!props.disabled && !(isSubmitting && props.buttonType === 'submit')) {
108
172
  e.currentTarget.style.opacity = '0.8';
109
173
  e.currentTarget.style.transform = 'translateY(-1px)';
110
174
  }
111
175
  }}
112
176
  onMouseLeave={(e) => {
113
- if (!props.disabled) {
177
+ if (!props.disabled && !(isSubmitting && props.buttonType === 'submit')) {
114
178
  e.currentTarget.style.opacity = '1';
115
179
  e.currentTarget.style.transform = 'translateY(0)';
116
180
  }
117
181
  }}
118
182
  >
119
- {getButtonText()}
183
+ {(isSubmitting && props.buttonType === 'submit') ? 'Submitting...' : getButtonText()}
120
184
  </button>
121
185
 
122
186
 
@@ -10,13 +10,36 @@ interface InputFieldProps {
10
10
 
11
11
  const InputField: React.FC<InputFieldProps> = ({ props, value = '', onChange }) => {
12
12
  const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
13
- const newValue = e.target.value;
13
+ let newValue = e.target.value;
14
+
15
+ // Handle postal code formatting
16
+ if (props.format === 'Postalcode' && !props.textArea) {
17
+ // Remove non-alphanumeric characters for postal code
18
+ newValue = newValue.replace(/[^a-zA-Z0-9\s-]/g, '');
19
+
20
+ // Apply formatting based on postaltype
21
+ if (props.postaltype === 'State') {
22
+ // US postal code format: 12345 or 12345-6789
23
+ newValue = newValue.replace(/(\d{5})(\d{0,4})/, (match, p1, p2) => {
24
+ return p2 ? `${p1}-${p2}` : p1;
25
+ });
26
+ }
27
+ }
28
+
14
29
  if (onChange) {
15
30
  const code = props.code || `inputfield_${props.name?.all || 'field'}`;
16
31
  onChange(code, newValue);
17
32
  }
18
33
  };
19
34
 
35
+ // Determine input type based on format
36
+ const getInputType = () => {
37
+ if (props.format === 'Postalcode') {
38
+ return 'text'; // Use text to allow formatting
39
+ }
40
+ return 'text';
41
+ };
42
+
20
43
  return (
21
44
  <div className="input-field">
22
45
  <label className="input-field__label">
@@ -28,6 +51,7 @@ const InputField: React.FC<InputFieldProps> = ({ props, value = '', onChange })
28
51
  <textarea
29
52
  value={value || ''}
30
53
  onChange={handleChange}
54
+ placeholder={props.helperText || ''}
31
55
  minLength={props.minLength}
32
56
  maxLength={props.maxLength}
33
57
  rows={4}
@@ -36,9 +60,10 @@ const InputField: React.FC<InputFieldProps> = ({ props, value = '', onChange })
36
60
  />
37
61
  ) : (
38
62
  <input
39
- type="text"
63
+ type={getInputType()}
40
64
  value={value || ''}
41
65
  onChange={handleChange}
66
+ placeholder={props.helperText || ''}
42
67
  minLength={props.minLength}
43
68
  maxLength={props.maxLength}
44
69
  required={props.required}
@@ -46,10 +71,6 @@ const InputField: React.FC<InputFieldProps> = ({ props, value = '', onChange })
46
71
  />
47
72
  )}
48
73
 
49
- {props.helperText && (
50
- <p className="input-field__helper-text">{props.helperText}</p>
51
- )}
52
-
53
74
  <div className="input-field__status">
54
75
  {props.format && `Format: ${props.format}`}
55
76
  {props.postaltype && ` • Type: ${props.postaltype}`}
@@ -16,6 +16,11 @@ const NumberField: React.FC<NumberFieldProps> = ({ props, value = '', onChange }
16
16
  }
17
17
  };
18
18
 
19
+ // Parse HTML content safely
20
+ const createMarkup = (html: string) => {
21
+ return { __html: html };
22
+ };
23
+
19
24
  return (
20
25
  <div className="number-field">
21
26
  <label className="number-field__label">
@@ -32,6 +37,7 @@ const NumberField: React.FC<NumberFieldProps> = ({ props, value = '', onChange }
32
37
  max={props.max}
33
38
  step={props.step}
34
39
  required={props.required}
40
+ placeholder={props.helperText || ''}
35
41
  className="number-field__input"
36
42
  />
37
43
 
@@ -42,12 +48,19 @@ const NumberField: React.FC<NumberFieldProps> = ({ props, value = '', onChange }
42
48
  )}
43
49
  </div>
44
50
 
45
- {props.helperText && (
46
- <p className="number-field__helper-text">{props.helperText}</p>
51
+ {props.termsandcondition?.all && (
52
+ <div
53
+ className="number-field__terms"
54
+ dangerouslySetInnerHTML={createMarkup(props.termsandcondition.all)}
55
+ />
47
56
  )}
48
57
 
49
58
  <div className="number-field__status">
50
- Range: {props.min}-{props.max} Step: {props.step}
59
+ {props.onText && props.offText ? (
60
+ <span>{props.onText} / {props.offText}</span>
61
+ ) : (
62
+ <span>Range: {props.min}-{props.max} • Step: {props.step}</span>
63
+ )}
51
64
  </div>
52
65
  </div>
53
66
  );
@@ -6,6 +6,9 @@ import './PageForm.css'; // Import the new CSS file
6
6
  interface PageFormProps {
7
7
  jsonData: Row[];
8
8
  onSubmit?: (data: Record<string, any>) => void;
9
+ isUrl?: boolean;
10
+ method?: 'POST' | 'GET' | 'post' | 'get';
11
+ url?: string;
9
12
  }
10
13
 
11
14
  interface Toast {
@@ -14,12 +17,13 @@ interface Toast {
14
17
  type: 'error' | 'success' | 'info' | 'warning';
15
18
  }
16
19
 
17
- const PageForm: React.FC<PageFormProps> = ({ jsonData, onSubmit }) => {
20
+ const PageForm: React.FC<PageFormProps> = ({ jsonData, onSubmit, isUrl = false, method = 'POST', url }) => {
18
21
  // Form state to store all field values
19
22
  const [formValues, setFormValues] = useState<Record<string, any>>({});
20
23
  const [deviceMode, setDeviceMode] = useState<string>('web');
21
24
  const [validationErrors, setValidationErrors] = useState<Record<string, boolean>>({});
22
25
  const [toasts, setToasts] = useState<Toast[]>([]);
26
+ const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
23
27
 
24
28
  // Function to determine device mode based on screen width
25
29
  const getDeviceMode = (width: number) => {
@@ -47,7 +51,7 @@ const PageForm: React.FC<PageFormProps> = ({ jsonData, onSubmit }) => {
47
51
  row.columns?.forEach((box) => {
48
52
  box.components?.forEach((component) => {
49
53
  // Check if component is a form field
50
- const fieldTypes = ['DateField', 'NumberField', 'SelectField', 'RadioField', 'InputField', 'BooleanField'];
54
+ const fieldTypes = ['DateField', 'NumberField', 'SelectField', 'RadioField', 'InputField', 'BooleanField', 'TermsAndCondition', 'TERMSANDCONDITION'];
51
55
  if (fieldTypes.includes(component.name)) {
52
56
  const code = getFieldCode(component);
53
57
  const name = getFieldName(component);
@@ -68,8 +72,8 @@ const PageForm: React.FC<PageFormProps> = ({ jsonData, onSubmit }) => {
68
72
  row.columns?.forEach((box) => {
69
73
  box.components?.forEach((component) => {
70
74
  // 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) {
75
+ const fieldTypes = ['DateField', 'NumberField', 'SelectField', 'RadioField', 'InputField', 'BooleanField', 'TermsAndCondition', 'TERMSANDCONDITION'];
76
+ if (fieldTypes.includes(component.name) && (component.props.required || component.props.isRequired)) {
73
77
  const code = getFieldCode(component);
74
78
  const name = getFieldName(component);
75
79
  requiredFields[code] = { code, name };
@@ -138,11 +142,12 @@ const PageForm: React.FC<PageFormProps> = ({ jsonData, onSubmit }) => {
138
142
 
139
143
  Object.keys(requiredFields).forEach((code) => {
140
144
  const value = formValues[code];
141
- // Check if value is empty, null, undefined, empty array, or empty string
145
+ // Check if value is empty, null, undefined, empty array, empty string, or false (for checkboxes)
142
146
  if (
143
147
  value === undefined ||
144
148
  value === null ||
145
149
  value === '' ||
150
+ value === false ||
146
151
  (Array.isArray(value) && value.length === 0)
147
152
  ) {
148
153
  errors[code] = true;
@@ -171,28 +176,123 @@ const PageForm: React.FC<PageFormProps> = ({ jsonData, onSubmit }) => {
171
176
  setToasts(prev => prev.filter(toast => toast.id !== id));
172
177
  }, []);
173
178
 
174
- // Single method to handle form submission - validates, transforms, and sends data to parent
175
- const handleFormSubmit = useCallback(() => {
179
+ // Handler to reset form values
180
+ const handleFormReset = useCallback(() => {
181
+ setFormValues({});
182
+ setValidationErrors({});
183
+ showToast('Form has been reset', 'info');
184
+ }, [showToast]);
185
+
186
+ // Handler to cancel form (same as reset for now, but can be customized)
187
+ const handleFormCancel = useCallback(() => {
188
+ setFormValues({});
189
+ setValidationErrors({});
190
+ showToast('Form has been cancelled', 'info');
191
+ }, [showToast]);
192
+
193
+ // Single method to handle form submission - validates, transforms, and sends data to parent or API
194
+ const handleFormSubmit = useCallback(async () => {
176
195
  // Validate form and get validation result
177
196
  const { isValid, errors } = validateForm();
178
197
 
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 {
198
+ if (!isValid) {
188
199
  // If validation fails, show error message with field names
189
200
  const requiredFields = getRequiredFields();
190
201
  const errorFields = Object.keys(errors)
191
202
  .map(code => requiredFields[code]?.name || code)
192
203
  .join(', ');
193
204
  showToast(`Please fill in all required fields: ${errorFields}`, 'error');
205
+ return;
206
+ }
207
+
208
+ // Transform form values to use field names instead of codes
209
+ const transformedData = transformFormValuesToNames();
210
+
211
+ // Check if we should send to URL or use onSubmit callback
212
+ // If URL is provided, automatically use it for API submission
213
+ if (url && url.trim() !== '') {
214
+ // Send data to API URL
215
+ setIsSubmitting(true);
216
+ try {
217
+ const httpMethod = method.toUpperCase();
218
+ const requestOptions: RequestInit = {
219
+ method: httpMethod,
220
+ headers: {
221
+ 'Content-Type': 'application/json',
222
+ },
223
+ };
224
+
225
+ // For GET requests, append data as query parameters
226
+ // For POST requests, send data in the body
227
+ let requestUrl = url.trim();
228
+ if (httpMethod === 'GET') {
229
+ const queryParams = new URLSearchParams();
230
+ Object.keys(transformedData).forEach((key) => {
231
+ const value = transformedData[key];
232
+ if (value !== null && value !== undefined && value !== '') {
233
+ queryParams.append(key, String(value));
234
+ }
235
+ });
236
+ const queryString = queryParams.toString();
237
+ requestUrl = queryString ? `${requestUrl}?${queryString}` : requestUrl;
238
+ } else {
239
+ // POST, PUT, PATCH, etc.
240
+ requestOptions.body = JSON.stringify(transformedData);
241
+ }
242
+
243
+ const response = await fetch(requestUrl, requestOptions);
244
+
245
+ // Read response as text first (can only read body once)
246
+ const responseText = await response.text().catch(() => '');
247
+
248
+ if (!response.ok) {
249
+ const errorText = responseText || response.statusText || `HTTP ${response.status}`;
250
+ throw new Error(`HTTP error! status: ${response.status}, message: ${errorText}`);
251
+ }
252
+
253
+ // Try to parse as JSON, fallback to text if not JSON
254
+ let responseData;
255
+ const contentType = response.headers.get('content-type');
256
+ if (contentType && contentType.includes('application/json') && responseText) {
257
+ try {
258
+ responseData = JSON.parse(responseText);
259
+ } catch {
260
+ // If JSON parsing fails, use text as message
261
+ responseData = { message: responseText };
262
+ }
263
+ } else {
264
+ // Not JSON or empty response, use text as message
265
+ responseData = responseText ? { message: responseText } : { message: 'Success' };
266
+ }
267
+
268
+ setIsSubmitting(false);
269
+ showToast('Form submitted successfully!', 'success');
270
+
271
+ // Also call onSubmit if provided, passing the response data
272
+ if (onSubmit) {
273
+ onSubmit(responseData || transformedData);
274
+ }
275
+ } catch (error) {
276
+ setIsSubmitting(false);
277
+ const errorMessage = error instanceof Error ? error.message : 'Failed to submit form';
278
+ console.error('Form submission error:', error);
279
+ showToast(`Error submitting form: ${errorMessage}`, 'error');
280
+
281
+ // Still call onSubmit with error data if provided
282
+ if (onSubmit) {
283
+ onSubmit({ error: errorMessage, data: transformedData });
284
+ }
285
+ }
286
+ } else {
287
+ // Send transformed data back to parent component via onSubmit callback
288
+ if (onSubmit) {
289
+ onSubmit(transformedData);
290
+ } else {
291
+ // If neither URL nor onSubmit is provided, show a warning
292
+ showToast('No submission handler configured. Please provide either a URL or onSubmit callback.', 'warning');
293
+ }
194
294
  }
195
- }, [formValues, validateForm, getRequiredFields, transformFormValuesToNames, onSubmit, showToast]);
295
+ }, [formValues, validateForm, getRequiredFields, transformFormValuesToNames, onSubmit, showToast, isUrl, method, url]);
196
296
 
197
297
  return (
198
298
  <div className="page-form">
@@ -207,6 +307,9 @@ const PageForm: React.FC<PageFormProps> = ({ jsonData, onSubmit }) => {
207
307
  validationErrors={validationErrors}
208
308
  onFieldChange={handleFieldChange}
209
309
  onFormSubmit={handleFormSubmit}
310
+ onFormReset={handleFormReset}
311
+ onFormCancel={handleFormCancel}
312
+ isSubmitting={isSubmitting}
210
313
  />
211
314
  ))}
212
315
 
@@ -20,6 +20,9 @@ const RadioField: React.FC<RadioFieldProps> = ({ props, value = '', onChange })
20
20
  <div className="radio-field">
21
21
  <label className="radio-field__label">
22
22
  {props.name?.all || 'Radio Field'}
23
+ {props.helperText && (
24
+ <span className="radio-field__placeholder"> ({props.helperText})</span>
25
+ )}
23
26
  {props.required && <span className="radio-field__required">*</span>}
24
27
  </label>
25
28
 
@@ -46,12 +49,6 @@ const RadioField: React.FC<RadioFieldProps> = ({ props, value = '', onChange })
46
49
  ))
47
50
  )}
48
51
  </div>
49
-
50
- {props.helperText && (
51
- <p className="radio-field__helper-text">{props.helperText}</p>
52
- )}
53
-
54
-
55
52
  </div>
56
53
  );
57
54
  };
@@ -9,6 +9,9 @@ interface RowComponentProps {
9
9
  validationErrors?: Record<string, boolean>;
10
10
  onFieldChange?: (code: string, value: any) => void;
11
11
  onFormSubmit?: () => void;
12
+ onFormReset?: () => void;
13
+ onFormCancel?: () => void;
14
+ isSubmitting?: boolean;
12
15
  }
13
16
 
14
17
  const RowComponent: React.FC<RowComponentProps> = ({
@@ -17,7 +20,10 @@ const RowComponent: React.FC<RowComponentProps> = ({
17
20
  formValues = {},
18
21
  validationErrors = {},
19
22
  onFieldChange,
20
- onFormSubmit
23
+ onFormSubmit,
24
+ onFormReset,
25
+ onFormCancel,
26
+ isSubmitting = false
21
27
  }) => {
22
28
  const getCurrentMode = () => {
23
29
  switch(deviceMode) {
@@ -70,6 +76,9 @@ const RowComponent: React.FC<RowComponentProps> = ({
70
76
  validationErrors={validationErrors}
71
77
  onFieldChange={onFieldChange}
72
78
  onFormSubmit={onFormSubmit}
79
+ onFormReset={onFormReset}
80
+ onFormCancel={onFormCancel}
81
+ isSubmitting={isSubmitting}
73
82
  />
74
83
  ))}
75
84
  {/* Clear float after all children */}
@@ -37,6 +37,12 @@
37
37
  resize: vertical;
38
38
  }
39
39
 
40
+ .input-field__input::placeholder,
41
+ .input-field__textarea::placeholder {
42
+ color: #9ca3af; /* gray-400 */
43
+ opacity: 1;
44
+ }
45
+
40
46
  .input-field__helper-text {
41
47
  margin-top: 0.25rem;
42
48
  font-size: 0.75rem;
@@ -50,6 +50,22 @@
50
50
  color: #6b7280; /* gray-500 */
51
51
  }
52
52
 
53
+ .number-field__terms {
54
+ margin-top: 0.25rem;
55
+ font-size: 0.75rem;
56
+ color: #4b5563; /* gray-600 */
57
+ line-height: 1.5;
58
+ }
59
+
60
+ .number-field__terms p {
61
+ margin: 0;
62
+ }
63
+
64
+ .number-field__input::placeholder {
65
+ color: #9ca3af; /* gray-400 */
66
+ opacity: 1;
67
+ }
68
+
53
69
  .number-field__status {
54
70
  margin-top: 0.25rem;
55
71
  font-size: 0.75rem;
@@ -10,6 +10,12 @@
10
10
  margin-bottom: 0.5rem;
11
11
  }
12
12
 
13
+ .radio-field__placeholder {
14
+ color: #9ca3af; /* gray-400 */
15
+ font-weight: 400;
16
+ font-style: italic;
17
+ }
18
+
13
19
  .radio-field__required {
14
20
  color: #ef4444; /* red-500 */
15
21
  margin-left: 0.25rem;