kelt-ui-kit-react 2.0.0 → 2.0.2

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.
@@ -7,6 +7,7 @@ type DataTableRowProps = {
7
7
  idx: number;
8
8
  rowIndex: number;
9
9
  row: any;
10
+ container?: "cell" | "card";
10
11
  onEnterNextColumn?: (columnIndex: number, rowIndex: number) => void;
11
12
  };
12
13
  export const DataTableRow = ({
@@ -14,6 +15,7 @@ export const DataTableRow = ({
14
15
  idx,
15
16
  rowIndex,
16
17
  row,
18
+ container = "cell",
17
19
  onEnterNextColumn,
18
20
  }: DataTableRowProps) => {
19
21
  const value = (row as any)[column.id]; // On utilise [column.id] pour accéder aux bonnes propriétés
@@ -24,12 +26,12 @@ export const DataTableRow = ({
24
26
  typeof column.showStepButtons === "function"
25
27
  ? column.showStepButtons(row)
26
28
  : (column.showStepButtons ??
27
- (typeof column.stepControls === "function"
28
- ? column.stepControls(row)
29
- : (column.stepControls ??
30
- (typeof column.withStepControls === "function"
31
- ? column.withStepControls(row)
32
- : column.withStepControls)))),
29
+ (typeof column.stepControls === "function"
30
+ ? column.stepControls(row)
31
+ : (column.stepControls ??
32
+ (typeof column.withStepControls === "function"
33
+ ? column.withStepControls(row)
34
+ : column.withStepControls)))),
33
35
  );
34
36
 
35
37
  const handleStep = (direction: "plus" | "minus", e: React.MouseEvent) => {
@@ -50,8 +52,10 @@ export const DataTableRow = ({
50
52
  }
51
53
  };
52
54
 
55
+ const Container = container === "card" ? "div" : "td";
56
+
53
57
  return (
54
- <td
58
+ <Container
55
59
  className={`${isDisabled ? "disabled" : ""}`}
56
60
  data-column-index={idx}
57
61
  data-row-index={rowIndex}
@@ -93,42 +97,83 @@ export const DataTableRow = ({
93
97
  />
94
98
  </div>
95
99
  ) : (
96
- hasStepButtons ? (
97
- <div
98
- className="data-table-input-number-wrapper"
99
- style={{
100
- display: "inline-flex",
101
- alignItems: "center",
102
- gap: "4px",
103
- justifyContent:
104
- column.align === "right"
105
- ? "flex-end"
106
- : column.align === "center"
107
- ? "center"
108
- : "flex-start",
109
- }}
110
- onClick={(e) => e.stopPropagation()}
111
- >
112
- <button
113
- type="button"
114
- className="data-table-step-btn data-table-step-btn--minus"
115
- disabled={
116
- isDisabled ||
117
- (column.min !== undefined && Number(value) <= column.min)
118
- }
119
- onClick={(e) => handleStep("minus", e)}
100
+ hasStepButtons ? (
101
+ <div
102
+ className="data-table-input-number-wrapper"
103
+ style={{
104
+ display: "inline-flex",
105
+ alignItems: "center",
106
+ gap: "4px",
107
+ justifyContent:
108
+ column.align === "right"
109
+ ? "flex-end"
110
+ : column.align === "center"
111
+ ? "center"
112
+ : "flex-start",
113
+ }}
114
+ onClick={(e) => e.stopPropagation()}
120
115
  >
121
- <Icon classIcon="bi-dash-lg" />
122
- </button>
116
+ <button
117
+ type="button"
118
+ className="data-table-step-btn data-table-step-btn--minus"
119
+ disabled={
120
+ isDisabled ||
121
+ (column.min !== undefined && Number(value) <= column.min)
122
+ }
123
+ onClick={(e) => handleStep("minus", e)}
124
+ >
125
+ <Icon classIcon="bi-dash-lg" />
126
+ </button>
127
+ <input
128
+ type={column.type}
129
+ value={value}
130
+ step={column.step ?? "0.01"}
131
+ min={column.min}
132
+ max={column.max}
133
+ disabled={isDisabled}
134
+ style={{
135
+ textAlign: column.align || "center",
136
+ minWidth: column.minWidth,
137
+ maxWidth: column.maxWidth,
138
+ width: column.width,
139
+ }}
140
+ onClick={(e) => e.stopPropagation()}
141
+ onKeyDown={(e) => {
142
+ if (e.key === "Enter") {
143
+ if (column.onEnter) {
144
+ column.onEnter(e.currentTarget.value, row);
145
+ }
146
+ if (column.enterToNextColumn) {
147
+ e.preventDefault();
148
+ onEnterNextColumn?.(idx, rowIndex);
149
+ }
150
+ }
151
+ }}
152
+ onChange={(e) => {
153
+ if (column.onEdit) {
154
+ column.onEdit(e.target.value, row);
155
+ }
156
+ }}
157
+ />
158
+ <button
159
+ type="button"
160
+ className="data-table-step-btn data-table-step-btn--plus"
161
+ disabled={
162
+ isDisabled ||
163
+ (column.max !== undefined && Number(value) >= column.max)
164
+ }
165
+ onClick={(e) => handleStep("plus", e)}
166
+ >
167
+ <Icon classIcon="bi-plus-lg" />
168
+ </button>
169
+ </div>
170
+ ) : (
123
171
  <input
124
172
  type={column.type}
125
173
  value={value}
126
- step={column.step ?? "0.01"}
127
- min={column.min}
128
- max={column.max}
129
- disabled={isDisabled}
174
+ step="0.01"
130
175
  style={{
131
- textAlign: column.align || "center",
176
+ textAlign: column.align || "left",
132
177
  minWidth: column.minWidth,
133
178
  maxWidth: column.maxWidth,
134
179
  width: column.width,
@@ -151,54 +196,13 @@ export const DataTableRow = ({
151
196
  }
152
197
  }}
153
198
  />
154
- <button
155
- type="button"
156
- className="data-table-step-btn data-table-step-btn--plus"
157
- disabled={
158
- isDisabled ||
159
- (column.max !== undefined && Number(value) >= column.max)
160
- }
161
- onClick={(e) => handleStep("plus", e)}
162
- >
163
- <Icon classIcon="bi-plus-lg" />
164
- </button>
165
- </div>
166
- ) : (
167
- <input
168
- type={column.type}
169
- value={value}
170
- step="0.01"
171
- style={{
172
- textAlign: column.align || "left",
173
- minWidth: column.minWidth,
174
- maxWidth: column.maxWidth,
175
- width: column.width,
176
- }}
177
- onClick={(e) => e.stopPropagation()}
178
- onKeyDown={(e) => {
179
- if (e.key === "Enter") {
180
- if (column.onEnter) {
181
- column.onEnter(e.currentTarget.value, row);
182
- }
183
- if (column.enterToNextColumn) {
184
- e.preventDefault();
185
- onEnterNextColumn?.(idx, rowIndex);
186
- }
187
- }
188
- }}
189
- onChange={(e) => {
190
- if (column.onEdit) {
191
- column.onEdit(e.target.value, row);
192
- }
193
- }}
194
- />
195
- )
199
+ )
196
200
  )
197
201
  ) : column.format ? (
198
202
  column.format(value)
199
203
  ) : (
200
204
  value
201
205
  )}
202
- </td>
206
+ </Container>
203
207
  );
204
208
  };
@@ -39,10 +39,12 @@
39
39
  height: 300px;
40
40
  flex: 1;
41
41
  min-height: 200px;
42
+
42
43
  .expand-header {
43
- background-color: #1d3758;
44
+ background-color: color-mix(in srgb, var(--primary-color), white 5%);
44
45
  color: #fff;
45
46
  }
47
+
46
48
  .expand-header-custom {
47
49
  .expand-header--children {
48
50
  display: flex;
@@ -65,6 +67,7 @@
65
67
  padding: 15px;
66
68
  color: #333;
67
69
  background-color: #fff;
70
+
68
71
  &.expand-header-custom {
69
72
  margin-bottom: 1rem;
70
73
  }
@@ -81,9 +84,11 @@
81
84
  font-weight: 500;
82
85
  }
83
86
  }
87
+
84
88
  & .expand-footer {
85
89
  padding: 15px;
86
90
  }
91
+
87
92
  & .expand-body {
88
93
  display: none;
89
94
  flex: 1;
@@ -94,4 +99,4 @@
94
99
  flex-direction: column;
95
100
  }
96
101
  }
97
- }
102
+ }
package/src/form/Form.tsx CHANGED
@@ -12,6 +12,7 @@ import "./form.css";
12
12
  import {
13
13
  FormInterface,
14
14
  FormMode,
15
+ FormSection,
15
16
  FormValuesInterface,
16
17
  } from "./form.interface";
17
18
  import { Input } from "./input/Input";
@@ -20,6 +21,7 @@ import { TextArea } from "./textArea/TextArea";
20
21
  import { Col } from "../grid/col/Col";
21
22
  import { Row } from "../grid/row/Row";
22
23
  import { TypeInputEnum } from "./form.enum";
24
+ import { AddressSearch } from "../addressSearch/AddressSearch";
23
25
 
24
26
  const euroFormatter = new Intl.NumberFormat("fr-FR", {
25
27
  style: "currency",
@@ -48,7 +50,8 @@ export type FormProps<
48
50
  hideSubmit?: boolean;
49
51
  autoComplete?: string;
50
52
  children?: React.ReactNode;
51
- initialForm: FormInterface[];
53
+ initialForm?: FormInterface[];
54
+ sections?: FormSection[];
52
55
  disabledSubmit?: boolean;
53
56
  mode?: FormMode;
54
57
  onSubmit?: (values: FormValuesInterface<T>) => void;
@@ -59,6 +62,7 @@ export const DynamicForm = forwardRef(
59
62
  <T extends { [key: string]: string | number | boolean | Date | string[] }>(
60
63
  {
61
64
  initialForm,
65
+ sections,
62
66
  title,
63
67
  onSubmit,
64
68
  id,
@@ -79,11 +83,17 @@ export const DynamicForm = forwardRef(
79
83
  setBtnDisabledSubmit?: (disabled: boolean) => void;
80
84
  }>
81
85
  ) => {
86
+ const formFields = sections
87
+ ? sections.reduce<FormInterface[]>(
88
+ (fields, section) => fields.concat(section.fields),
89
+ [],
90
+ )
91
+ : (initialForm ?? []);
82
92
  const [btnDisabledSubmit, setBtnDisabledSubmit] = useState(disabledSubmit);
83
93
  const [values, setValues] = useState<FormValuesInterface<T>>(() => {
84
94
  const initialValues: FormValuesInterface<T> =
85
95
  {} as FormValuesInterface<T>;
86
- initialForm.forEach((field) => {
96
+ formFields.forEach((field) => {
87
97
  (initialValues as any)[field.name] = field.value ?? "";
88
98
  });
89
99
  return initialValues;
@@ -164,7 +174,7 @@ export const DynamicForm = forwardRef(
164
174
  setValues(() => {
165
175
  const resetValues: FormValuesInterface<T> =
166
176
  {} as FormValuesInterface<T>;
167
- initialForm.forEach((field) => {
177
+ formFields.forEach((field) => {
168
178
  (resetValues as any)[field.name] = field.value ?? "";
169
179
  });
170
180
  return resetValues;
@@ -179,7 +189,7 @@ export const DynamicForm = forwardRef(
179
189
  }, []);
180
190
 
181
191
  const focusInitialElement = () => {
182
- const focusedField = initialForm.find((field) => field.focus);
192
+ const focusedField = formFields.find((field) => field.focus);
183
193
  if (focusedField && inputRefs.current[focusedField.name]) {
184
194
  setTimeout(() => {
185
195
  inputRefs.current[focusedField.name]?.select();
@@ -198,6 +208,17 @@ export const DynamicForm = forwardRef(
198
208
  }));
199
209
  };
200
210
 
211
+ const updateValues = useCallback(
212
+ (updatedValues: FormValuesInterface<T>) => {
213
+ setValues((previousValues) => {
214
+ const nextValues = { ...previousValues, ...updatedValues };
215
+ onChange?.(nextValues);
216
+ return nextValues;
217
+ });
218
+ },
219
+ [onChange],
220
+ );
221
+
201
222
  useImperativeHandle(ref, () => ({
202
223
  resetForm,
203
224
  clearInput,
@@ -207,8 +228,6 @@ export const DynamicForm = forwardRef(
207
228
  setBtnDisabledSubmit,
208
229
  }));
209
230
 
210
- const gridEnabled = initialForm.some((field) => field.col !== undefined);
211
- const FieldsContainer = gridEnabled ? Row : React.Fragment;
212
231
  const getReadValue = (field: FormInterface): React.ReactNode => {
213
232
  const value = values[field.name];
214
233
 
@@ -253,15 +272,13 @@ export const DynamicForm = forwardRef(
253
272
  : String(value);
254
273
  };
255
274
 
256
- return (
257
- <form
258
- autoComplete={autoComplete ?? "off"}
259
- onSubmit={handleSubmit}
260
- className={className ?? ""}
261
- >
262
- <div className="form-content">
263
- <FieldsContainer>
264
- {initialForm.map((v, key) => {
275
+ const renderFields = (fields: FormInterface[]) => {
276
+ const gridEnabled = fields.some((field) => field.col !== undefined);
277
+ const FieldsContainer = gridEnabled ? Row : React.Fragment;
278
+
279
+ return (
280
+ <FieldsContainer>
281
+ {fields.map((v, key) => {
265
282
  const required = v.onRequired
266
283
  ? v.onRequired(v.value)
267
284
  : v.required ?? false;
@@ -270,102 +287,150 @@ export const DynamicForm = forwardRef(
270
287
  : v.disabled ?? false;
271
288
 
272
289
  return (
273
- <FormFieldContainer key={key} field={v} gridEnabled={gridEnabled}>
290
+ <FormFieldContainer
291
+ key={v.name || key}
292
+ field={v}
293
+ gridEnabled={gridEnabled}
294
+ >
274
295
  <div
275
- className={`d-flex flex-column ${
276
- v.className ?? ""
277
- } form-group form-group-${v.type}`}
296
+ className={`d-flex flex-column ${v.className ?? ""
297
+ } form-group form-group-${v.type}`}
278
298
  >
279
- {v.label && <label>{v.label}</label>}
280
- {v.icon && <Icon classIcon={v.icon} />}
299
+ {v.label && <label>{v.label}</label>}
300
+ {v.icon && <Icon classIcon={v.icon} />}
281
301
 
282
- {mode === "read" ? (
283
- <div
284
- className={`form-read-value ${
285
- v.type === TypeInputEnum.TEXTAREA
302
+ {mode === "read" ? (
303
+ <div
304
+ className={`form-read-value ${v.type === TypeInputEnum.TEXTAREA
286
305
  ? "form-read-value--textarea"
287
306
  : ""
288
- }`}
289
- >
290
- {getReadValue(v)}
291
- </div>
292
- ) : v.type === "select" ? (
293
- <Select
294
- id={v.id ?? v.name}
295
- label={undefined}
296
- name={v.name}
297
- options={v.options ?? []}
298
- disabled={disabled}
299
- multiple={v.multiple}
300
- value={values[v.name] as any}
301
- required={required}
302
- defaultValue={v.value as string | string[]}
303
- placeholder={v.placeholder}
304
- onChangeMultiple={(newValue) =>
305
- handleChangeSelect(newValue, v.name, v)
306
- }
307
- onChange={(newValue) =>
308
- handleChangeSelect(newValue, v.name, v)
309
- }
310
- />
311
- ) : v.type === "textarea" ? (
312
- <TextArea
313
- id={v.id ?? v.name}
314
- name={v.name}
315
- className="textarea-input"
316
- placeholder={v.placeholder ?? ""}
317
- disabled={disabled}
318
- required={required}
319
- value={values[v.name] as string}
320
- onChange={(value, name) =>
321
- handleChangeTextArea(name, value, v)
322
- }
323
- />
324
- ) : v.type === TypeInputEnum.PRICE ? (
325
- <InputPrice
326
- id={v.id ?? v.name}
327
- ref={(el) => (inputRefs.current[v.name] = el)}
328
- name={v.name}
329
- placeholder={v.placeholder ?? ""}
330
- autoComplete={v.autoComplete}
331
- tabIndex={0}
332
- disabled={disabled}
333
- autoFocus={v.focus ?? false}
334
- required={required}
335
- value={String(values[v.name] ?? "")}
336
- onChange={(value) =>
337
- handleChangeTextArea(v.name, value, v)
338
- }
339
- />
340
- ) : (
341
- <Input
342
- id={v.id ?? v.name}
343
- ref={(el) => (inputRefs.current[v.name] = el)}
344
- name={v.name}
345
- type={v.type}
346
- placeholder={v.placeholder ?? ""}
347
- autoComplete={v.autoComplete}
348
- tabIndex={0}
349
- disabled={disabled}
350
- autoFocus={v.focus ?? false}
351
- required={required}
352
- step={v.step ?? "0.01"}
353
- value={values[v.name] as string | number}
354
- checked={
355
- v.type === "checkbox" ? !!values[v.name] : undefined
356
- }
357
- onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
358
- handleChangeInput(e, v)
359
- }
360
- onInvalid={v.onInvalid ? v.onInvalid : undefined}
361
- />
362
- )}
307
+ }`}
308
+ >
309
+ {getReadValue(v)}
310
+ </div>
311
+ ) : v.type === TypeInputEnum.ADDRESS ? (
312
+ <AddressSearch
313
+ id={v.id ?? v.name}
314
+ label=""
315
+ placeholder={v.placeholder}
316
+ value={String(values[v.name] ?? "")}
317
+ disabled={disabled}
318
+ minimumQueryLength={v.addressSearch?.minimumQueryLength}
319
+ debounceTime={v.addressSearch?.debounceTime}
320
+ limit={v.addressSearch?.limit}
321
+ provider={v.addressSearch?.provider}
322
+ allowManualEntry
323
+ manualEntryLabel={v.addressSearch?.manualEntryLabel}
324
+ onChange={(value) =>
325
+ updateValues({ [v.name]: value } as FormValuesInterface<T>)
326
+ }
327
+ onSelect={(address) => {
328
+ const mappedValues =
329
+ v.addressSearch?.mapAddress?.(address) ?? {};
330
+ updateValues(mappedValues as FormValuesInterface<T>);
331
+ v.addressSearch?.onSelect?.(address);
332
+ }}
333
+ />
334
+ ) : v.type === "select" ? (
335
+ <Select
336
+ id={v.id ?? v.name}
337
+ label={undefined}
338
+ name={v.name}
339
+ options={v.options ?? []}
340
+ disabled={disabled}
341
+ multiple={v.multiple}
342
+ value={values[v.name] as any}
343
+ required={required}
344
+ defaultValue={v.value as string | string[]}
345
+ placeholder={v.placeholder}
346
+ onChangeMultiple={(newValue) =>
347
+ handleChangeSelect(newValue, v.name, v)
348
+ }
349
+ onChange={(newValue) =>
350
+ handleChangeSelect(newValue, v.name, v)
351
+ }
352
+ />
353
+ ) : v.type === "textarea" ? (
354
+ <TextArea
355
+ id={v.id ?? v.name}
356
+ name={v.name}
357
+ className="textarea-input"
358
+ placeholder={v.placeholder ?? ""}
359
+ disabled={disabled}
360
+ required={required}
361
+ value={values[v.name] as string}
362
+ onChange={(value, name) =>
363
+ handleChangeTextArea(name, value, v)
364
+ }
365
+ />
366
+ ) : v.type === TypeInputEnum.PRICE ? (
367
+ <InputPrice
368
+ id={v.id ?? v.name}
369
+ ref={(el) => (inputRefs.current[v.name] = el)}
370
+ name={v.name}
371
+ placeholder={v.placeholder ?? ""}
372
+ autoComplete={v.autoComplete}
373
+ tabIndex={0}
374
+ disabled={disabled}
375
+ autoFocus={v.focus ?? false}
376
+ required={required}
377
+ value={String(values[v.name] ?? "")}
378
+ onChange={(value) =>
379
+ handleChangeTextArea(v.name, value, v)
380
+ }
381
+ />
382
+ ) : (
383
+ <Input
384
+ id={v.id ?? v.name}
385
+ ref={(el) => (inputRefs.current[v.name] = el)}
386
+ name={v.name}
387
+ type={v.type}
388
+ placeholder={v.placeholder ?? ""}
389
+ autoComplete={v.autoComplete}
390
+ tabIndex={0}
391
+ disabled={disabled}
392
+ autoFocus={v.focus ?? false}
393
+ required={required}
394
+ step={v.step ?? "0.01"}
395
+ value={values[v.name] as string | number}
396
+ checked={
397
+ v.type === "checkbox" ? !!values[v.name] : undefined
398
+ }
399
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
400
+ handleChangeInput(e, v)
401
+ }
402
+ onInvalid={v.onInvalid ? v.onInvalid : undefined}
403
+ />
404
+ )}
363
405
  </div>
364
406
  </FormFieldContainer>
365
407
  );
366
408
  })}
409
+ </FieldsContainer>
410
+ );
411
+ };
412
+
413
+ return (
414
+ <form
415
+ autoComplete={autoComplete ?? "off"}
416
+ onSubmit={handleSubmit}
417
+ className={className ?? ""}
418
+ >
419
+ <div className="form-content">
420
+ {sections
421
+ ? sections.map((section, index) => (
422
+ <section
423
+ className={`form-section ${section.className ?? ""}`}
424
+ key={index}
425
+ >
426
+ {section.title && (
427
+ <h3 className="form-section-title">{section.title}</h3>
428
+ )}
429
+ {renderFields(section.fields)}
430
+ </section>
431
+ ))
432
+ : renderFields(initialForm ?? [])}
367
433
  {children}
368
- </FieldsContainer>
369
434
  </div>
370
435
  <div className="d-flex justify-content-end form-footer">
371
436
  {mode === "edit" && !hideSubmit && (