extended-dynamic-forms 0.2.0 → 0.3.0

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 (62) hide show
  1. package/README.md +398 -1075
  2. package/dist/WizardForm.d.ts +4 -1
  3. package/dist/events/FormEventContext.d.ts +3 -0
  4. package/dist/events/FormEventHub.d.ts +19 -2
  5. package/dist/index.es.js +21051 -12923
  6. package/dist/index.umd.js +63 -36
  7. package/dist/style.css +1 -1
  8. package/dist/types/index.d.ts +6 -1
  9. package/dist/utils/fieldChangeDetector.d.ts +22 -0
  10. package/dist/widgets/base/index.d.ts +2 -0
  11. package/dist/widgets/base/types.d.ts +135 -0
  12. package/dist/widgets/base/utils.d.ts +63 -0
  13. package/dist/widgets/choice/ChoiceWidget.d.ts +30 -0
  14. package/dist/widgets/choice/ChoiceWidget.types.d.ts +496 -0
  15. package/dist/widgets/choice/examples/ChoiceWidget.example.d.ts +7 -0
  16. package/dist/widgets/choice/hooks/__tests__/useChoiceWidget.test.d.ts +1 -0
  17. package/dist/widgets/choice/hooks/__tests__/useDataSource.test.d.ts +1 -0
  18. package/dist/widgets/choice/hooks/examples/useChoiceWidget.example.d.ts +18 -0
  19. package/dist/widgets/choice/hooks/examples/useDataSourceExample.d.ts +22 -0
  20. package/dist/widgets/choice/hooks/index.d.ts +6 -0
  21. package/dist/widgets/choice/hooks/useChoiceWidget.d.ts +65 -0
  22. package/dist/widgets/choice/hooks/useDataSource.d.ts +69 -0
  23. package/dist/widgets/choice/index.d.ts +59 -0
  24. package/dist/widgets/choice/presenters/CheckboxPresenter.d.ts +70 -0
  25. package/dist/widgets/choice/presenters/DropdownPresenter.d.ts +37 -0
  26. package/dist/widgets/choice/presenters/PresenterStrategy.d.ts +230 -0
  27. package/dist/widgets/choice/presenters/RadioPresenter.d.ts +39 -0
  28. package/dist/widgets/choice/presenters/examples/RadioPresenterExample.d.ts +7 -0
  29. package/dist/widgets/choice/presenters/index.d.ts +20 -0
  30. package/dist/widgets/choice/providers/ApiDataSource.d.ts +241 -0
  31. package/dist/widgets/choice/providers/DataSourceProvider.d.ts +260 -0
  32. package/dist/widgets/choice/providers/StaticDataSource.d.ts +258 -0
  33. package/dist/widgets/choice/providers/WebSocketDataSource.d.ts +310 -0
  34. package/dist/widgets/choice/providers/index.d.ts +12 -0
  35. package/dist/widgets/choice/utils/index.d.ts +1 -0
  36. package/dist/widgets/choice/utils/responseMappers.d.ts +342 -0
  37. package/dist/widgets/date/DateTimeWidget.d.ts +26 -0
  38. package/dist/widgets/date/EnhancedDateWidget.d.ts +26 -0
  39. package/dist/widgets/date/index.d.ts +13 -0
  40. package/dist/widgets/file/FileUploadWidget.d.ts +40 -0
  41. package/dist/widgets/file/index.d.ts +2 -0
  42. package/dist/widgets/index.d.ts +33 -17
  43. package/dist/widgets/input/EmailWidget.d.ts +34 -0
  44. package/dist/widgets/input/NumberWidget.d.ts +39 -0
  45. package/dist/widgets/input/TelephoneWidget.d.ts +34 -0
  46. package/dist/widgets/input/URLWidget.d.ts +34 -0
  47. package/dist/widgets/input/index.d.ts +7 -0
  48. package/dist/widgets/text/PasswordWidget.d.ts +45 -0
  49. package/dist/widgets/text/TextWidget.d.ts +40 -0
  50. package/dist/widgets/text/TextareaWidget.d.ts +38 -0
  51. package/dist/widgets/text/index.d.ts +20 -0
  52. package/package.json +8 -8
  53. package/dist/vanilla-entry.d.ts +0 -204
  54. package/dist/widgets/enhanced/EnhancedWidgets.d.ts +0 -37
  55. package/examples/conditional-v2-example.tsx +0 -64
  56. package/examples/dynamic-array-items-example.tsx +0 -211
  57. package/examples/form-devtools-example.tsx +0 -384
  58. package/examples/global-ui-rules-example.tsx +0 -371
  59. package/examples/vanilla-js/README.md +0 -212
  60. package/examples/vanilla-js/basic-form.html +0 -142
  61. package/examples/vanilla-js/conditional-form.html +0 -159
  62. package/examples/vanilla-js/wizard-form.html +0 -185
@@ -1,211 +0,0 @@
1
- import React from 'react';
2
- import { ExtendedForm } from '../src/ExtendedForm';
3
- import { RJSFSchema, UiSchema } from '@rjsf/utils';
4
- import {
5
- createDynamicArrayItemsUiSchema,
6
- arrayItemPatterns
7
- } from '../src/conditionals/v2/utils/dynamicArrayItems';
8
-
9
- const schema: RJSFSchema = {
10
- type: 'object',
11
- properties: {
12
- directors: {
13
- type: 'array',
14
- title: 'Company Directors',
15
- items: {
16
- type: 'object',
17
- required: ['name', 'directorType'],
18
- properties: {
19
- name: { type: 'string', title: 'Name' },
20
- directorType: {
21
- type: 'string',
22
- title: 'Director Type',
23
- enum: ['executive', 'non-executive'],
24
- enumNames: ['Executive', 'Non-Executive']
25
- },
26
- executiveRole: {
27
- type: 'string',
28
- title: 'Executive Role',
29
- enum: ['CEO', 'CFO', 'CTO', 'COO', 'Other'],
30
- enumNames: ['CEO', 'CFO', 'CTO', 'COO', 'Other']
31
- },
32
- otherRoleDetails: {
33
- type: 'string',
34
- title: 'Other Role Details'
35
- },
36
- hasShareholding: { type: 'boolean', title: 'Has Shareholding?' },
37
- shareholdingPercentage: {
38
- type: 'number',
39
- title: 'Shareholding %',
40
- minimum: 0,
41
- maximum: 100
42
- }
43
- }
44
- }
45
- }
46
- }
47
- };
48
-
49
- // Base UI schema for all array items
50
- const baseItemUiSchema: UiSchema = {
51
- 'ui:order': ['name', 'directorType', 'executiveRole', 'otherRoleDetails', 'hasShareholding', 'shareholdingPercentage'],
52
- name: {
53
- 'ui:widget': 'text',
54
- 'ui:placeholder': 'Enter director name'
55
- },
56
- directorType: {
57
- 'ui:widget': 'select'
58
- },
59
- executiveRole: {
60
- 'ui:widget': 'select'
61
- },
62
- otherRoleDetails: {
63
- 'ui:widget': 'text',
64
- 'ui:placeholder': 'Specify other role'
65
- },
66
- hasShareholding: {
67
- 'ui:widget': 'checkbox'
68
- },
69
- shareholdingPercentage: {
70
- 'ui:widget': 'updown'
71
- }
72
- };
73
-
74
- // Create dynamic UI schema using the utility
75
- const uiSchema: UiSchema = {
76
- directors: {
77
- items: createDynamicArrayItemsUiSchema(
78
- baseItemUiSchema,
79
- (itemData, index) => {
80
- const conditionalUi: Partial<UiSchema> = {};
81
-
82
- // Three-level conditional dependency chain
83
- // Level 1: Director type determines if executive role is shown
84
- if (itemData?.directorType !== 'executive') {
85
- conditionalUi.executiveRole = { 'ui:widget': 'hidden' };
86
- // Also hide dependent field since parent is hidden
87
- conditionalUi.otherRoleDetails = { 'ui:widget': 'hidden' };
88
- } else {
89
- // Level 2: Executive role determines if other role details is shown
90
- if (itemData?.executiveRole !== 'Other') {
91
- conditionalUi.otherRoleDetails = { 'ui:widget': 'hidden' };
92
- }
93
- }
94
-
95
- // Hide shareholding percentage if not a shareholder
96
- if (!itemData?.hasShareholding) {
97
- conditionalUi.shareholdingPercentage = { 'ui:widget': 'hidden' };
98
- }
99
-
100
- // Add special styling for first director
101
- if (index === 0) {
102
- conditionalUi['ui:classNames'] = 'primary-director';
103
- conditionalUi['ui:title'] = 'Primary Director';
104
- }
105
-
106
- return conditionalUi;
107
- }
108
- )
109
- }
110
- };
111
-
112
- export function DynamicArrayItemsExample() {
113
- const [formData, setFormData] = React.useState({
114
- directors: [
115
- {
116
- name: 'John Doe',
117
- directorType: 'executive',
118
- executiveRole: 'CEO',
119
- hasShareholding: true,
120
- shareholdingPercentage: 51
121
- },
122
- {
123
- name: 'Jane Smith',
124
- directorType: 'executive',
125
- executiveRole: 'Other',
126
- otherRoleDetails: 'Head of Innovation',
127
- hasShareholding: true,
128
- shareholdingPercentage: 30
129
- },
130
- {
131
- name: 'Bob Johnson',
132
- directorType: 'non-executive',
133
- hasShareholding: false
134
- }
135
- ]
136
- });
137
-
138
- return (
139
- <div>
140
- <h1>Dynamic Array Items Example</h1>
141
- <p>
142
- This example demonstrates O(n) performance for array conditionals using
143
- the RJSF fork's dynamic uiSchema.items capability. It showcases:
144
- </p>
145
- <ul>
146
- <li>Three-level conditional dependencies (Director Type → Executive Role → Other Role Details)</li>
147
- <li>Enum-based conditions for more realistic form patterns</li>
148
- <li>The common "Other" option pattern with additional text field</li>
149
- <li>Multiple independent conditionals within each array item</li>
150
- </ul>
151
-
152
- <ExtendedForm
153
- schema={schema}
154
- uiSchema={uiSchema}
155
- formData={formData}
156
- onChange={({ formData }) => setFormData(formData)}
157
- />
158
-
159
- <div style={{ marginTop: '20px' }}>
160
- <h3>Performance Benefits:</h3>
161
- <ul>
162
- <li>Each array item's UI is calculated independently</li>
163
- <li>No wildcard pattern expansion needed</li>
164
- <li>O(n) complexity instead of O(n²)</li>
165
- <li>Scales efficiently with large arrays</li>
166
- </ul>
167
- </div>
168
- </div>
169
- );
170
- }
171
-
172
- // Alternative example using helper patterns
173
- export function DynamicArrayPatternsExample() {
174
- const uiSchemaWithPatterns: UiSchema = {
175
- directors: {
176
- items: createDynamicArrayItemsUiSchema(
177
- baseItemUiSchema,
178
- (itemData, index) => {
179
- // Combine multiple pattern helpers for cleaner code
180
- const hideShareholding = arrayItemPatterns.hideField(
181
- 'shareholdingPercentage',
182
- (data) => !data?.hasShareholding
183
- )(itemData, index);
184
-
185
- const hideExecutiveRole = arrayItemPatterns.hideField(
186
- 'executiveRole',
187
- (data) => data?.directorType !== 'executive'
188
- )(itemData, index);
189
-
190
- const hideOtherRoleDetails = arrayItemPatterns.hideField(
191
- 'otherRoleDetails',
192
- (data) => data?.directorType !== 'executive' || data?.executiveRole !== 'Other'
193
- )(itemData, index);
194
-
195
- return {
196
- ...hideShareholding,
197
- ...hideExecutiveRole,
198
- ...hideOtherRoleDetails
199
- };
200
- }
201
- )
202
- }
203
- };
204
-
205
- return (
206
- <ExtendedForm
207
- schema={schema}
208
- uiSchema={uiSchemaWithPatterns}
209
- />
210
- );
211
- }
@@ -1,384 +0,0 @@
1
- import React, { useState } from 'react';
2
- import Form from '@rjsf/antd';
3
- import { IChangeEvent } from '@rjsf/core';
4
- import { RJSFSchema, UiSchema } from '@rjsf/utils';
5
- import validator from '@rjsf/validator-ajv8';
6
- import {
7
- Card,
8
- Collapse,
9
- Badge,
10
- Tag,
11
- Space,
12
- Switch,
13
- Typography,
14
- Alert,
15
- } from 'antd';
16
- import {
17
- Rule,
18
- UiEffect,
19
- SchemaEffect,
20
- ValidationEffect,
21
- JsonLogicRule,
22
- } from '../src/conditionals/v2/types';
23
- import {
24
- useConditionalUi,
25
- useConditionalSchema,
26
- useConditionalValidation,
27
- } from '../src/conditionals/v2';
28
- import { useFormDevTools } from '../src/hooks/useFormDevTools';
29
-
30
- const { Panel } = Collapse;
31
- const { Text } = Typography;
32
-
33
- // Base schema
34
- const baseSchema: RJSFSchema = {
35
- type: 'object',
36
- title: 'Form with DevTools Example',
37
- properties: {
38
- country: {
39
- type: 'string',
40
- title: 'Country',
41
- enum: ['USA', 'Canada', 'UK', 'Other'],
42
- },
43
- requiresShipping: {
44
- type: 'boolean',
45
- title: 'Requires Shipping?',
46
- },
47
- shippingAddress: {
48
- type: 'object',
49
- title: 'Shipping Address',
50
- properties: {
51
- street: { type: 'string', title: 'Street' },
52
- city: { type: 'string', title: 'City' },
53
- state: { type: 'string', title: 'State' },
54
- zip: { type: 'string', title: 'ZIP Code' },
55
- },
56
- },
57
- maritalStatus: {
58
- type: 'string',
59
- title: 'Marital Status',
60
- enum: ['single', 'married', 'divorced', 'widowed'],
61
- },
62
- spouseName: {
63
- type: 'string',
64
- title: 'Spouse Name',
65
- },
66
- },
67
- };
68
-
69
- // Base UI schema
70
- const baseUiSchema: UiSchema = {
71
- 'ui:order': [
72
- 'country',
73
- 'requiresShipping',
74
- 'shippingAddress',
75
- 'maritalStatus',
76
- 'spouseName',
77
- ],
78
- };
79
-
80
- // UI Rules
81
- const uiRules: Rule<UiEffect>[] = [
82
- {
83
- name: 'Hide shipping address when not required',
84
- condition: { "!": { "var": "requiresShipping" } } as JsonLogicRule,
85
- effect: [
86
- { op: 'add', path: '/shippingAddress/ui:widget', value: 'hidden' },
87
- ],
88
- },
89
- {
90
- name: 'Show state dropdown for US/Canada',
91
- condition: { "or": [{ "==": [{ "var": "country" }, "USA"] }, { "==": [{ "var": "country" }, "Canada"] }] } as JsonLogicRule,
92
- effect: [
93
- {
94
- op: 'add',
95
- path: '/shippingAddress/state/ui:widget',
96
- value: 'select',
97
- },
98
- ],
99
- },
100
- ];
101
-
102
- // Schema Rules
103
- const schemaRules: Rule<SchemaEffect>[] = [
104
- {
105
- name: 'Add spouse fields when married',
106
- condition: { "===": [{ "var": "maritalStatus" }, "married"] } as JsonLogicRule,
107
- effect: [{ op: 'add', path: '/required/-', value: 'spouseName' }],
108
- },
109
- {
110
- name: 'Remove spouse field when not married',
111
- condition: { "!==": [{ "var": "maritalStatus" }, "married"] } as JsonLogicRule,
112
- effect: [{ op: 'remove', path: '/properties/spouseName' }],
113
- },
114
- ];
115
-
116
- // Validation Rules
117
- const validationRules: Rule<ValidationEffect>[] = [
118
- {
119
- name: 'US ZIP code format validation',
120
- effect: {
121
- name: 'US ZIP code format',
122
- isActive: { "and": [{ "==": [{ "var": "country" }, "USA"] }, { "var": "requiresShipping" }] } as JsonLogicRule,
123
- validate: (errors, formData) => {
124
- if (
125
- formData.shippingAddress?.zip &&
126
- !/^\d{5}(-\d{4})?$/.test(formData.shippingAddress.zip)
127
- ) {
128
- if (!errors.shippingAddress) errors.shippingAddress = {};
129
- if (!errors.shippingAddress.zip) errors.shippingAddress.zip = {};
130
- errors.shippingAddress.zip.__errors = [
131
- 'ZIP code must be XXXXX or XXXXX-XXXX format',
132
- ];
133
- }
134
- },
135
- },
136
- },
137
- {
138
- name: 'Canadian postal code validation',
139
- effect: {
140
- name: 'Canadian postal code',
141
- isActive: { "and": [{ "==": [{ "var": "country" }, "Canada"] }, { "var": "requiresShipping" }] } as JsonLogicRule,
142
- validate: (errors, formData) => {
143
- if (
144
- formData.shippingAddress?.zip &&
145
- !/^[A-Z]\d[A-Z] \d[A-Z]\d$/.test(formData.shippingAddress.zip)
146
- ) {
147
- if (!errors.shippingAddress) errors.shippingAddress = {};
148
- if (!errors.shippingAddress.zip) errors.shippingAddress.zip = {};
149
- errors.shippingAddress.zip.__errors = [
150
- 'Postal code must be A1A 1A1 format',
151
- ];
152
- }
153
- },
154
- },
155
- },
156
- ];
157
-
158
- // DevTools Panel Component
159
- const DevToolsPanel: React.FC<{
160
- devTools: ReturnType<typeof useFormDevTools>;
161
- }> = ({ devTools }) => {
162
- const { evaluations, activeRules, hasErrors, errors, summary } = devTools;
163
-
164
- return (
165
- <Card title='Form DevTools' style={{ marginTop: 24 }}>
166
- <Space direction='vertical' style={{ width: '100%' }}>
167
- {/* Summary */}
168
- <Alert
169
- message={
170
- <Space>
171
- <Text>Total Rules: {summary.totalRules}</Text>
172
- <Badge
173
- count={summary.activeCount}
174
- style={{ backgroundColor: '#52c41a' }}
175
- />
176
- <Text>Active</Text>
177
- {hasErrors && (
178
- <>
179
- <Badge count={summary.errorCount} />
180
- <Text>Errors</Text>
181
- </>
182
- )}
183
- </Space>
184
- }
185
- type={hasErrors ? 'warning' : 'info'}
186
- />
187
-
188
- {/* Errors */}
189
- {hasErrors && (
190
- <Alert
191
- message='Rule Evaluation Errors'
192
- description={
193
- <ul>
194
- {errors.map((error, idx) => (
195
- <li key={idx}>
196
- <Text type='danger'>
197
- {error.pillar} Rule "{error.ruleName}": {error.error}
198
- </Text>
199
- </li>
200
- ))}
201
- </ul>
202
- }
203
- type='error'
204
- />
205
- )}
206
-
207
- {/* Rule Details */}
208
- <Collapse defaultActiveKey={['ui']}>
209
- <Panel
210
- header={`UI Rules (${activeRules.ui.length} active)`}
211
- key='ui'
212
- extra={
213
- <Badge
214
- count={activeRules.ui.length}
215
- style={{ backgroundColor: '#52c41a' }}
216
- />
217
- }
218
- >
219
- {evaluations
220
- .filter(e => e.pillar === 'UI')
221
- .map((evaluation, idx) => (
222
- <div key={idx} style={{ marginBottom: 8 }}>
223
- <Tag color={evaluation.active ? 'green' : 'default'}>
224
- {evaluation.active ? '✓' : '○'}
225
- </Tag>
226
- <Text>{evaluation.ruleName}</Text>
227
- {evaluation.error && (
228
- <Text type='danger'> (Error: {evaluation.error})</Text>
229
- )}
230
- </div>
231
- ))}
232
- </Panel>
233
-
234
- <Panel
235
- header={`Schema Rules (${activeRules.schema.length} active)`}
236
- key='schema'
237
- extra={
238
- <Badge
239
- count={activeRules.schema.length}
240
- style={{ backgroundColor: '#52c41a' }}
241
- />
242
- }
243
- >
244
- {evaluations
245
- .filter(e => e.pillar === 'Schema')
246
- .map((evaluation, idx) => (
247
- <div key={idx} style={{ marginBottom: 8 }}>
248
- <Tag color={evaluation.active ? 'green' : 'default'}>
249
- {evaluation.active ? '✓' : '○'}
250
- </Tag>
251
- <Text>{evaluation.ruleName}</Text>
252
- {evaluation.error && (
253
- <Text type='danger'> (Error: {evaluation.error})</Text>
254
- )}
255
- </div>
256
- ))}
257
- </Panel>
258
-
259
- <Panel
260
- header={`Validation Rules (${activeRules.validation.length} active)`}
261
- key='validation'
262
- extra={
263
- <Badge
264
- count={activeRules.validation.length}
265
- style={{ backgroundColor: '#52c41a' }}
266
- />
267
- }
268
- >
269
- {evaluations
270
- .filter(e => e.pillar === 'Validation')
271
- .map((evaluation, idx) => (
272
- <div key={idx} style={{ marginBottom: 8 }}>
273
- <Tag color={evaluation.active ? 'green' : 'default'}>
274
- {evaluation.active ? '✓' : '○'}
275
- </Tag>
276
- <Text>{evaluation.ruleName}</Text>
277
- {evaluation.error && (
278
- <Text type='danger'> (Error: {evaluation.error})</Text>
279
- )}
280
- </div>
281
- ))}
282
- </Panel>
283
- </Collapse>
284
- </Space>
285
- </Card>
286
- );
287
- };
288
-
289
- export default function FormDevToolsExample() {
290
- const [formData, setFormData] = useState({});
291
- const [devToolsEnabled, setDevToolsEnabled] = useState(true);
292
- const [logToConsole, setLogToConsole] = useState(false);
293
- const [captureHistory, setCaptureHistory] = useState(false);
294
-
295
- // Apply conditional logic
296
- const conditionalUiSchema = useConditionalUi(uiRules, baseUiSchema, formData);
297
- const conditionalSchema = useConditionalSchema(
298
- schemaRules,
299
- baseSchema,
300
- formData
301
- );
302
- const customValidate = useConditionalValidation(validationRules, formData);
303
-
304
- // Use DevTools
305
- const devTools = useFormDevTools(
306
- formData,
307
- uiRules,
308
- schemaRules,
309
- validationRules,
310
- {
311
- enabled: devToolsEnabled,
312
- logToConsole,
313
- captureHistory,
314
- historySize: 10,
315
- }
316
- );
317
-
318
- const handleChange = (e: IChangeEvent) => {
319
- setFormData(e.formData);
320
- };
321
-
322
- return (
323
- <div style={{ padding: '24px', maxWidth: '1200px', margin: '0 auto' }}>
324
- <Card title='DevTools Settings' style={{ marginBottom: 24 }}>
325
- <Space>
326
- <label>
327
- <Switch checked={devToolsEnabled} onChange={setDevToolsEnabled} />
328
- <Text style={{ marginLeft: 8 }}>Enable DevTools</Text>
329
- </label>
330
- <label>
331
- <Switch checked={logToConsole} onChange={setLogToConsole} />
332
- <Text style={{ marginLeft: 8 }}>Log to Console</Text>
333
- </label>
334
- <label>
335
- <Switch checked={captureHistory} onChange={setCaptureHistory} />
336
- <Text style={{ marginLeft: 8 }}>Capture History</Text>
337
- </label>
338
- </Space>
339
- </Card>
340
-
341
- <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 24 }}>
342
- <div>
343
- <Form
344
- schema={conditionalSchema}
345
- uiSchema={conditionalUiSchema}
346
- formData={formData}
347
- onChange={handleChange}
348
- validator={validator}
349
- customValidate={customValidate}
350
- />
351
- </div>
352
-
353
- <div>
354
- {devToolsEnabled && <DevToolsPanel devTools={devTools} />}
355
-
356
- {captureHistory && devTools.history.length > 0 && (
357
- <Card title='History' style={{ marginTop: 24 }}>
358
- <Text>History entries: {devTools.history.length}</Text>
359
- <div style={{ maxHeight: 200, overflow: 'auto', marginTop: 8 }}>
360
- {devTools.history.map((entry, idx) => (
361
- <div
362
- key={idx}
363
- style={{
364
- marginBottom: 8,
365
- padding: 8,
366
- background: '#f0f0f0',
367
- }}
368
- >
369
- <Text type='secondary'>
370
- {new Date(entry.timestamp).toLocaleTimeString()}
371
- </Text>
372
- <pre style={{ fontSize: 12, margin: 0 }}>
373
- {JSON.stringify(entry.formData, null, 2)}
374
- </pre>
375
- </div>
376
- ))}
377
- </div>
378
- </Card>
379
- )}
380
- </div>
381
- </div>
382
- </div>
383
- );
384
- }