kelt-ui-kit-react 2.0.0 → 2.0.1

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,7 +1,7 @@
1
1
  {
2
2
  "name": "kelt-ui-kit-react",
3
3
  "type": "module",
4
- "version": "2.0.0",
4
+ "version": "2.0.1",
5
5
  "private": false,
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
@@ -1,5 +1,7 @@
1
1
  import { useCallback, useEffect, useRef, useState } from "react";
2
2
  import { createPortal } from "react-dom";
3
+ import { useIsMobile } from "../_core/hooks/useIsMobile";
4
+ import { Card } from "../card/Card";
3
5
  import { TypeInputEnum } from "../form/form.enum";
4
6
  import { Input } from "../form/input/Input";
5
7
  import { Icon } from "../icon/Icon";
@@ -42,6 +44,7 @@ export type DataTableProps<T> = {
42
44
  onRowClassName?: (row: T) => string | undefined;
43
45
  rowStyle?: (row: T) => React.CSSProperties | undefined;
44
46
  rowClassName?: (row: T) => string | undefined;
47
+ mobileCardView?: boolean;
45
48
  };
46
49
  export function DataTable<T>(props: DataTableProps<T>) {
47
50
  const {
@@ -70,8 +73,11 @@ export function DataTable<T>(props: DataTableProps<T>) {
70
73
  onRowClassName,
71
74
  rowStyle,
72
75
  rowClassName,
76
+ mobileCardView = false,
73
77
  } = props;
74
78
 
79
+ const isMobile = useIsMobile();
80
+ const showMobileCardView = mobileCardView && isMobile;
75
81
  const loaderRef = useRef<HTMLDivElement | null>(null);
76
82
  const footerActionRef = useRef<HTMLDivElement | null>(null);
77
83
  const hasAutoFocusedRef = useRef(false);
@@ -150,7 +156,9 @@ export function DataTable<T>(props: DataTableProps<T>) {
150
156
  return;
151
157
  }
152
158
 
153
- const table = document.getElementById(id);
159
+ const table =
160
+ document.getElementById(`${id}-mobile`) ??
161
+ document.getElementById(id);
154
162
  const eligibleColumnIndexes = columns.reduce<number[]>(
155
163
  (indexes, column, idx) => {
156
164
  if (
@@ -232,7 +240,8 @@ export function DataTable<T>(props: DataTableProps<T>) {
232
240
  return;
233
241
  }
234
242
 
235
- const table = document.getElementById(id);
243
+ const table =
244
+ document.getElementById(`${id}-mobile`) ?? document.getElementById(id);
236
245
  if (!table) {
237
246
  return;
238
247
  }
@@ -264,7 +273,7 @@ export function DataTable<T>(props: DataTableProps<T>) {
264
273
  for (let offset = 1; offset <= data.length; offset += 1) {
265
274
  const nextRowIndex = (rowIndex + offset) % data.length;
266
275
  const cell = table.querySelector(
267
- `tbody td[data-row-index="${nextRowIndex}"][data-column-index="${firstEligibleColumnIndex}"]`,
276
+ `[data-row-index="${nextRowIndex}"][data-column-index="${firstEligibleColumnIndex}"]`,
268
277
  );
269
278
 
270
279
  if (!(cell instanceof HTMLElement)) {
@@ -365,9 +374,10 @@ export function DataTable<T>(props: DataTableProps<T>) {
365
374
  return;
366
375
  }
367
376
 
368
- const table = document.getElementById(id);
377
+ const table =
378
+ document.getElementById(`${id}-mobile`) ?? document.getElementById(id);
369
379
  const firstCell = table?.querySelector(
370
- `tbody td[data-row-index="0"][data-column-index="${firstEditableColumnIndex}"]`,
380
+ `[data-row-index="0"][data-column-index="${firstEditableColumnIndex}"]`,
371
381
  );
372
382
 
373
383
  if (!(firstCell instanceof HTMLElement)) {
@@ -462,7 +472,105 @@ export function DataTable<T>(props: DataTableProps<T>) {
462
472
  >
463
473
  {name && <h2 className="mb-2">{name}</h2>}
464
474
  {element}
465
- <table className={`data-table ${className ? className : ""}`} id={id}>
475
+ {showMobileCardView && (
476
+ <div
477
+ id={`${id}-mobile`}
478
+ className={`data-table-mobile-cards ${className ? className : ""}`}
479
+ >
480
+ {loading && <Loader display={loading} />}
481
+ {!loading &&
482
+ data.map((row, rowIndex) => {
483
+ const customStyle = (onRowStyle ?? rowStyle)?.(row);
484
+ const customClass =
485
+ (onRowClassName ?? rowClassName)?.(row) ?? "";
486
+ const rowId = getRowId
487
+ ? getRowId(row)
488
+ : ((row as any).id ?? row);
489
+
490
+ return (
491
+ <Card
492
+ key={rowId as React.Key}
493
+ item={{ title: "" }}
494
+ className={`data-table-mobile-card ${customClass}`.trim()}
495
+ handleClick={onRowClick ? () => onRowClick(row) : undefined}
496
+ >
497
+ <div
498
+ className="data-table-mobile-card__content"
499
+ style={customStyle}
500
+ data-row-index={rowIndex}
501
+ onMouseDown={(event) => {
502
+ if (isInteractiveClick(event.target)) {
503
+ event.stopPropagation();
504
+ }
505
+ }}
506
+ >
507
+ {onSelectionChange && (
508
+ <div className="data-table-mobile-card__field data-table-mobile-card__selection">
509
+ <span className="data-table-mobile-card__label">
510
+ Sélection
511
+ </span>
512
+ <Input
513
+ id={`select-card-${rowIndex}`}
514
+ name={`select-card-${rowIndex}`}
515
+ type={TypeInputEnum.CHECKBOX}
516
+ disabled={
517
+ limitSelect !== undefined &&
518
+ selectedRowIds.size >= limitSelect &&
519
+ !selectedRowIds.has(rowId)
520
+ }
521
+ checked={selectedRowIds.has(rowId)}
522
+ onChange={(event) => toggleRow(event, row)}
523
+ />
524
+ </div>
525
+ )}
526
+ {columns.map((column, columnIndex) => (
527
+ <div
528
+ className="data-table-mobile-card__field"
529
+ key={column.id}
530
+ >
531
+ <span className="data-table-mobile-card__label">
532
+ {column.label}
533
+ </span>
534
+ <DataTableRow
535
+ container="card"
536
+ column={column}
537
+ idx={columnIndex}
538
+ row={row}
539
+ rowIndex={rowIndex}
540
+ onEnterNextColumn={focusNextColumn}
541
+ />
542
+ </div>
543
+ ))}
544
+ {actions.length > 0 && (
545
+ <div className="data-table-mobile-card__actions card-actions">
546
+ {actions.map((action, actionIndex) => (
547
+ <DataTableAction
548
+ key={action.id}
549
+ action={action}
550
+ row={row}
551
+ rowIndex={rowIndex}
552
+ index={actionIndex}
553
+ onFocusNextRow={focusNextRow}
554
+ />
555
+ ))}
556
+ </div>
557
+ )}
558
+ </div>
559
+ </Card>
560
+ );
561
+ })}
562
+ {!loading && data.length === 0 && (
563
+ <div className="data-table-mobile-cards__empty">
564
+ {emptyMessage ?? "Aucune donnée"}
565
+ </div>
566
+ )}
567
+ </div>
568
+ )}
569
+ <table
570
+ className={`data-table ${className ? className : ""}`}
571
+ id={id}
572
+ style={showMobileCardView ? { display: "none" } : undefined}
573
+ >
466
574
  <thead>
467
575
  <tr>
468
576
  {onSelectionChange && (
@@ -62,6 +62,7 @@ export const DataTableView = () => {
62
62
  columns={dataTableColumns}
63
63
  id="datatable-example"
64
64
  name={"DataTable"}
65
+ mobileCardView
65
66
  onSelectionChange={(selectedRows) => {
66
67
  console.log("Selected Rows: ", selectedRows);
67
68
  }}
@@ -47,6 +47,54 @@
47
47
  overflow: auto;
48
48
  position: relative;
49
49
 
50
+ .data-table-mobile-cards {
51
+ display: grid;
52
+ gap: 0.75rem;
53
+
54
+ .data-table-mobile-card {
55
+ width: 100%;
56
+ max-width: none;
57
+ height: auto;
58
+ padding: 0;
59
+ border-radius: 8px;
60
+ }
61
+
62
+ .data-table-mobile-card__content {
63
+ width: 100%;
64
+ min-width: 0;
65
+ }
66
+
67
+ .data-table-mobile-card__field {
68
+ display: grid;
69
+ grid-template-columns: minmax(90px, 35%) minmax(0, 1fr);
70
+ gap: 0.75rem;
71
+ align-items: center;
72
+ padding: 0.625rem 0.75rem;
73
+ border-bottom: 1px solid #e5e5e5;
74
+ }
75
+
76
+ .data-table-mobile-card__label {
77
+ color: var(--tertiary-color);
78
+ font-size: 0.875rem;
79
+ font-weight: 600;
80
+ overflow-wrap: anywhere;
81
+ }
82
+
83
+ .data-table-mobile-card__actions {
84
+ display: flex;
85
+ gap: 0.5rem;
86
+ align-items: center;
87
+ justify-content: flex-end;
88
+ padding: 0.75rem;
89
+ }
90
+
91
+ .data-table-mobile-cards__empty {
92
+ padding: 1rem;
93
+ text-align: center;
94
+ color: #666;
95
+ }
96
+ }
97
+
50
98
  &.data-table-container--with-footer-action {
51
99
  scroll-padding-bottom: 80px;
52
100
  }
@@ -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.css CHANGED
@@ -4,10 +4,12 @@ input {
4
4
  border-radius: 4px;
5
5
  margin: 0.25rem 0 0.25rem 0;
6
6
  min-width: 250px;
7
+
7
8
  &:focus-visible {
8
9
  outline: 1px solid #9696a7;
9
10
  }
10
11
  }
12
+
11
13
  input[type="checkbox"],
12
14
  input[type="radio"] {
13
15
  min-width: auto;
@@ -16,38 +18,46 @@ input[type="radio"] {
16
18
  height: 18px;
17
19
  width: 18px;
18
20
  }
21
+
19
22
  form {
20
23
  &.form-row {
21
24
  display: flex;
22
25
  flex-direction: row;
23
26
  flex-wrap: wrap;
24
27
  align-items: center;
28
+
25
29
  .form-group {
26
30
  margin-right: 1rem;
27
31
  flex: 1;
28
32
  padding: 0;
33
+
29
34
  .input-container {
30
35
  min-width: auto;
36
+
31
37
  input {
32
38
  min-width: auto;
33
39
  }
34
40
  }
35
41
  }
36
42
  }
43
+
37
44
  label {
38
45
  font-weight: 600;
39
46
  font-size: 0.85rem;
40
47
  padding-bottom: 0.15rem;
41
48
  }
49
+
42
50
  .form-group-checkbox {
43
51
  display: flex;
44
52
  flex-direction: row;
45
53
  align-items: center;
46
54
  margin-top: 0.5rem;
55
+
47
56
  label {
48
57
  margin-right: 1rem;
49
58
  padding: 0;
50
59
  }
60
+
51
61
  input {
52
62
  min-width: auto;
53
63
  padding: 0;
@@ -56,9 +66,11 @@ form {
56
66
  width: 18px;
57
67
  }
58
68
  }
69
+
59
70
  button {
60
71
  margin-top: 1rem;
61
72
  }
73
+
62
74
  .form-read-value {
63
75
  width: 100%;
64
76
  min-width: 250px;
@@ -72,8 +84,27 @@ form {
72
84
  line-height: normal;
73
85
  overflow-wrap: anywhere;
74
86
  }
87
+
75
88
  .form-read-value--textarea {
76
89
  min-height: 3.25rem;
77
90
  white-space: pre-wrap;
78
91
  }
79
- }
92
+
93
+ .grid-row {
94
+
95
+ .form-group,
96
+ .input-container,
97
+ .select-container,
98
+ .form-read-value {
99
+ min-width: 0;
100
+ }
101
+
102
+ input:not([type="checkbox"]):not([type="radio"]),
103
+ .form-control-textarea,
104
+ .form-read-value {
105
+ box-sizing: border-box;
106
+ min-width: 0;
107
+ width: 100%;
108
+ }
109
+ }
110
+ }
@@ -10,6 +10,8 @@ type ColStyledProps = {
10
10
  $xl?: GridColumnSpan;
11
11
  };
12
12
  export const ColStyled = styled.div<ColStyledProps>`
13
+ box-sizing: border-box;
14
+ min-width: 0;
13
15
  padding: 0 15px;
14
16
  flex: 0 0 ${({ $xs }) => getColWidth($xs)};
15
17
  max-width: ${({ $xs }) => getColWidth($xs)};
@@ -18,7 +20,7 @@ export const ColStyled = styled.div<ColStyledProps>`
18
20
  ${({ $sm }) =>
19
21
  $sm &&
20
22
  css`
21
- @container (min-width: 576px) {
23
+ @media (min-width: 576px) {
22
24
  flex: 0 0 ${getColWidth($sm)};
23
25
  max-width: ${getColWidth($sm)};
24
26
  }
@@ -27,7 +29,7 @@ export const ColStyled = styled.div<ColStyledProps>`
27
29
  ${({ $md }) =>
28
30
  $md &&
29
31
  css`
30
- @container (min-width: 768px) {
32
+ @media (min-width: 768px) {
31
33
  flex: 0 0 ${getColWidth($md)};
32
34
  max-width: ${getColWidth($md)};
33
35
  }
@@ -36,7 +38,7 @@ export const ColStyled = styled.div<ColStyledProps>`
36
38
  ${({ $lg }) =>
37
39
  $lg &&
38
40
  css`
39
- @container (min-width: 992px) {
41
+ @media (min-width: 992px) {
40
42
  flex: 0 0 ${getColWidth($lg)};
41
43
  max-width: ${getColWidth($lg)};
42
44
  }
@@ -45,7 +47,7 @@ export const ColStyled = styled.div<ColStyledProps>`
45
47
  ${({ $xl }) =>
46
48
  $xl &&
47
49
  css`
48
- @container (min-width: 1200px) {
50
+ @media (min-width: 1200px) {
49
51
  flex: 0 0 ${getColWidth($xl)};
50
52
  max-width: ${getColWidth($xl)};
51
53
  }