kelt-ui-kit-react 1.9.7 → 1.9.8

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": "1.9.7",
4
+ "version": "1.9.8",
5
5
  "private": false,
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
@@ -13,6 +13,7 @@ export const Button = ({
13
13
  classIcon,
14
14
  positionicon = 1,
15
15
  disabled = false,
16
+ style,
16
17
  }: ButtonInterface) => {
17
18
  const handleClick = (e: React.MouseEvent) => {
18
19
  e.stopPropagation();
@@ -28,6 +29,7 @@ export const Button = ({
28
29
  type="submit"
29
30
  onClick={(e) => handleClick(e)}
30
31
  className={className}
32
+ style={style}
31
33
  >
32
34
  {badge && <Badge {...badge} />}
33
35
  {classIcon && positionicon == 1 && (
@@ -11,6 +11,7 @@ export interface ButtonInterface {
11
11
  positionicon?: number;
12
12
  disabled?: boolean;
13
13
  sizeIcon?: IconSizeEnum;
14
+ style?: React.CSSProperties;
14
15
  }
15
16
  export interface buttonActionInterface {
16
17
  icon?: string;
@@ -21,6 +21,7 @@ export type DataTableProps<T> = {
21
21
  element?: JSX.Element;
22
22
  data: T[];
23
23
  emptyMessage?: string;
24
+ actionsColumnWidth?: number;
24
25
  actions?: dataTableActionsInterface<T>[];
25
26
  footerAction?: (props: {
26
27
  close: () => void;
@@ -37,6 +38,10 @@ export type DataTableProps<T> = {
37
38
  hideToggleAll?: boolean;
38
39
  limitSelect?: number;
39
40
  getRowId?: (row: T) => string | number;
41
+ onRowStyle?: (row: T) => React.CSSProperties | undefined;
42
+ onRowClassName?: (row: T) => string | undefined;
43
+ rowStyle?: (row: T) => React.CSSProperties | undefined;
44
+ rowClassName?: (row: T) => string | undefined;
40
45
  };
41
46
  export function DataTable<T>(props: DataTableProps<T>) {
42
47
  const {
@@ -46,6 +51,7 @@ export function DataTable<T>(props: DataTableProps<T>) {
46
51
  children,
47
52
  element,
48
53
  data = [],
54
+ actionsColumnWidth = 200,
49
55
  actions = [],
50
56
  footerAction,
51
57
  columns = [],
@@ -60,6 +66,10 @@ export function DataTable<T>(props: DataTableProps<T>) {
60
66
  hideToggleAll = false,
61
67
  limitSelect = undefined,
62
68
  getRowId,
69
+ onRowStyle,
70
+ onRowClassName,
71
+ rowStyle,
72
+ rowClassName,
63
73
  } = props;
64
74
 
65
75
  const loaderRef = useRef<HTMLDivElement | null>(null);
@@ -175,7 +185,8 @@ export function DataTable<T>(props: DataTableProps<T>) {
175
185
  );
176
186
 
177
187
  const currentCellIndex = editableCells.findIndex(
178
- (cell) => cell.rowIndex === rowIndex && cell.columnIndex === columnIndex,
188
+ (cell) =>
189
+ cell.rowIndex === rowIndex && cell.columnIndex === columnIndex,
179
190
  );
180
191
 
181
192
  if (currentCellIndex === -1) {
@@ -197,7 +208,8 @@ export function DataTable<T>(props: DataTableProps<T>) {
197
208
  continue;
198
209
  }
199
210
 
200
- const focusableElement = cell.querySelector<HTMLElement>(focusableSelector);
211
+ const focusableElement =
212
+ cell.querySelector<HTMLElement>(focusableSelector);
201
213
 
202
214
  if (focusableElement) {
203
215
  nextInput = focusableElement;
@@ -214,6 +226,69 @@ export function DataTable<T>(props: DataTableProps<T>) {
214
226
  [columns, data, focusEditableElement, id],
215
227
  );
216
228
 
229
+ const focusNextRow = useCallback(
230
+ (rowIndex: number) => {
231
+ if (typeof document === "undefined") {
232
+ return;
233
+ }
234
+
235
+ const table = document.getElementById(id);
236
+ if (!table) {
237
+ return;
238
+ }
239
+
240
+ const eligibleColumnIndexes = columns.reduce<number[]>(
241
+ (indexes, column, idx) => {
242
+ if (
243
+ column.editable &&
244
+ column.type !== "select" &&
245
+ column.type !== "image"
246
+ ) {
247
+ indexes.push(idx);
248
+ }
249
+
250
+ return indexes;
251
+ },
252
+ [],
253
+ );
254
+
255
+ if (eligibleColumnIndexes.length === 0) {
256
+ return;
257
+ }
258
+
259
+ const firstEligibleColumnIndex = eligibleColumnIndexes[0];
260
+ const focusableSelector =
261
+ 'input:not([disabled]), textarea:not([disabled]), [contenteditable="true"]';
262
+ let nextInput: HTMLElement | null = null;
263
+
264
+ for (let offset = 1; offset <= data.length; offset += 1) {
265
+ const nextRowIndex = (rowIndex + offset) % data.length;
266
+ const cell = table.querySelector(
267
+ `tbody td[data-row-index="${nextRowIndex}"][data-column-index="${firstEligibleColumnIndex}"]`,
268
+ );
269
+
270
+ if (!(cell instanceof HTMLElement)) {
271
+ continue;
272
+ }
273
+
274
+ const focusableElement =
275
+ cell.querySelector<HTMLElement>(focusableSelector);
276
+
277
+ if (focusableElement) {
278
+ nextInput = focusableElement;
279
+ break;
280
+ }
281
+ }
282
+
283
+ if (!nextInput) {
284
+ return;
285
+ }
286
+
287
+ focusEditableElement(nextInput);
288
+ },
289
+ [columns, data.length, focusEditableElement, id],
290
+ );
291
+
217
292
  const handleClickRow = useCallback(
218
293
  (e: React.MouseEvent, row: T) => {
219
294
  e.stopPropagation();
@@ -309,7 +384,14 @@ export function DataTable<T>(props: DataTableProps<T>) {
309
384
 
310
385
  hasAutoFocusedRef.current = true;
311
386
  focusEditableElement(firstInput);
312
- }, [autoFocusFirstEditable, columns, data.length, focusEditableElement, id, loading]);
387
+ }, [
388
+ autoFocusFirstEditable,
389
+ columns,
390
+ data.length,
391
+ focusEditableElement,
392
+ id,
393
+ loading,
394
+ ]);
313
395
  useEffect(() => {
314
396
  if (!showFooterAction) {
315
397
  setFooterActionOffset(0);
@@ -373,7 +455,9 @@ export function DataTable<T>(props: DataTableProps<T>) {
373
455
  <div
374
456
  className={`data-table-container ${showFooterAction ? "data-table-container--with-footer-action" : ""}`}
375
457
  style={
376
- showFooterAction ? { paddingBottom: `${footerActionOffset}px` } : undefined
458
+ showFooterAction
459
+ ? { paddingBottom: `${footerActionOffset}px` }
460
+ : undefined
377
461
  }
378
462
  >
379
463
  {name && <h2 className="mb-2">{name}</h2>}
@@ -408,7 +492,8 @@ export function DataTable<T>(props: DataTableProps<T>) {
408
492
  {actions.length > 0 && (
409
493
  <th
410
494
  style={{
411
- width: "200px",
495
+ width: `${actionsColumnWidth}px`,
496
+ minWidth: `${actionsColumnWidth}px`,
412
497
  }}
413
498
  align="right"
414
499
  key={"actions"}
@@ -432,66 +517,81 @@ export function DataTable<T>(props: DataTableProps<T>) {
432
517
  <></>
433
518
  )}
434
519
  {!loading &&
435
- data.map((row, rowIndex) => (
436
- <tr
437
- key={rowIndex}
438
- className={rowIndex % 2 === 0 ? "even" : "odd"}
439
- data-row-index={rowIndex}
440
- onClick={(e) => handleClickRow?.(e, row)}
441
- >
442
- {onSelectionChange && (
443
- <td
444
- className="select-cell"
445
- style={{ textAlign: "center" }}
446
- onClick={(e) => {
447
- e.stopPropagation();
448
- setIsFooterActionVisible(true);
449
- }}
450
- >
451
- <Input
452
- id={`select-row-${rowIndex}`}
453
- name={`select-row-${rowIndex}`}
454
- type={TypeInputEnum.CHECKBOX}
455
- disabled={
456
- limitSelect !== undefined &&
457
- selectedRowIds.size >= limitSelect &&
458
- !selectedRowIds.has(
520
+ data.map((row, rowIndex) => {
521
+ const customStyle = (onRowStyle ?? rowStyle)?.(row);
522
+ const customClass = (onRowClassName ?? rowClassName)?.(row) ?? "";
523
+
524
+ return (
525
+ <tr
526
+ key={rowIndex}
527
+ className={`${rowIndex % 2 === 0 ? "even" : "odd"} ${customClass}`.trim()}
528
+ style={customStyle}
529
+ data-row-index={rowIndex}
530
+ onClick={(e) => handleClickRow?.(e, row)}
531
+ >
532
+ {onSelectionChange && (
533
+ <td
534
+ className="select-cell"
535
+ style={{ textAlign: "center" }}
536
+ onClick={(e) => {
537
+ e.stopPropagation();
538
+ setIsFooterActionVisible(true);
539
+ }}
540
+ >
541
+ <Input
542
+ id={`select-row-${rowIndex}`}
543
+ name={`select-row-${rowIndex}`}
544
+ type={TypeInputEnum.CHECKBOX}
545
+ disabled={
546
+ limitSelect !== undefined &&
547
+ selectedRowIds.size >= limitSelect &&
548
+ !selectedRowIds.has(
549
+ getRowId ? getRowId(row) : ((row as any).id ?? row),
550
+ )
551
+ }
552
+ checked={selectedRowIds.has(
459
553
  getRowId ? getRowId(row) : ((row as any).id ?? row),
460
- )
461
- }
462
- checked={selectedRowIds.has(
463
- getRowId ? getRowId(row) : ((row as any).id ?? row),
464
- )}
465
- onChange={(e) => toggleRow(e, row)}
554
+ )}
555
+ onChange={(e) => toggleRow(e, row)}
556
+ />
557
+ </td>
558
+ )}
559
+ {columns.map((column, idx) => (
560
+ <DataTableRow
561
+ column={column}
562
+ idx={idx}
563
+ row={row}
564
+ rowIndex={rowIndex}
565
+ key={idx}
566
+ onEnterNextColumn={focusNextColumn}
466
567
  />
467
- </td>
468
- )}
469
- {columns.map((column, idx) => (
470
- <DataTableRow
471
- column={column}
472
- idx={idx}
473
- row={row}
474
- rowIndex={rowIndex}
475
- key={idx}
476
- onEnterNextColumn={focusNextColumn}
477
- />
478
- ))}
479
- {actions.length > 0 && (
480
- <td align="right" onClick={(e) => e.stopPropagation()}>
481
- <div className="d-flex align-items-center justify-content-end">
482
- {actions.map((action, index) => (
483
- <DataTableAction
484
- key={index}
485
- action={action}
486
- row={row}
487
- index={index}
488
- />
489
- ))}
490
- </div>
491
- </td>
492
- )}
493
- </tr>
494
- ))}
568
+ ))}
569
+ {actions.length > 0 && (
570
+ <td
571
+ style={{
572
+ width: `${actionsColumnWidth}px`,
573
+ minWidth: `${actionsColumnWidth}px`,
574
+ }}
575
+ align="right"
576
+ onClick={(e) => e.stopPropagation()}
577
+ >
578
+ <div className="d-flex align-items-center justify-content-end">
579
+ {actions.map((action, index) => (
580
+ <DataTableAction
581
+ key={index}
582
+ action={action}
583
+ row={row}
584
+ rowIndex={rowIndex}
585
+ index={index}
586
+ onFocusNextRow={focusNextRow}
587
+ />
588
+ ))}
589
+ </div>
590
+ </td>
591
+ )}
592
+ </tr>
593
+ );
594
+ })}
495
595
  {!loading && data.length === 0 && (
496
596
  <DataTableLoading
497
597
  columns={columns}
@@ -106,6 +106,55 @@
106
106
  tr.odd {
107
107
  background-color: #f5f5f5;
108
108
  }
109
+
110
+ .data-table-input-number-wrapper {
111
+ display: inline-flex;
112
+ align-items: center;
113
+ gap: 4px;
114
+
115
+ button.data-table-step-btn {
116
+ min-width: 28px !important;
117
+ width: 28px !important;
118
+ height: 28px !important;
119
+ max-height: 28px !important;
120
+ padding: 0 !important;
121
+ display: inline-flex !important;
122
+ align-items: center !important;
123
+ justify-content: center !important;
124
+ border-radius: 4px;
125
+ border: 1px solid #dcdcdc;
126
+ background-color: #f8f9fa;
127
+ color: #333;
128
+ font-size: 14px;
129
+ line-height: 1;
130
+ cursor: pointer;
131
+ transition: background-color 0.15s, border-color 0.15s;
132
+
133
+ &:hover:not(:disabled) {
134
+ background-color: #e9ecef;
135
+ border-color: #adb5bd;
136
+ }
137
+
138
+ &:active:not(:disabled) {
139
+ background-color: #dee2e6;
140
+ }
141
+
142
+ &:disabled {
143
+ opacity: 0.4;
144
+ cursor: not-allowed;
145
+ background-color: #f8f9fa;
146
+ }
147
+
148
+ .bi {
149
+ font-size: 13px;
150
+ line-height: 1;
151
+ }
152
+ }
153
+
154
+ input {
155
+ margin: 0;
156
+ }
157
+ }
109
158
  }
110
159
  }
111
160
  }
@@ -15,21 +15,31 @@ export interface dataTableColumnsInterface<T> {
15
15
  enterToNextColumn?: boolean;
16
16
  options?: { label: string; value: string }[];
17
17
  onEdit?: (value: any, row: T) => void;
18
+ onEnter?: (value: any, row: T) => void;
18
19
  onDisable?: (row: T) => boolean;
19
20
  onRowStyle?: (row: T) => React.CSSProperties;
20
21
  type: HTMLInputTypeAttribute | "select" | "image";
21
22
  imageAlt?: (row: T) => string;
22
23
  imageStyle?: React.CSSProperties;
23
24
  imageSrc?: (row: T) => string;
25
+ showStepButtons?: boolean | ((row: T) => boolean);
26
+ stepControls?: boolean | ((row: T) => boolean);
27
+ withStepControls?: boolean | ((row: T) => boolean);
28
+ step?: number;
29
+ min?: number;
30
+ max?: number;
24
31
  }
25
32
  export interface dataTableActionsInterface<T> {
26
33
  id: string;
27
34
  label: string;
28
35
  type: "button" | "link" | "icon";
29
36
  classIcon?: string;
30
- onClick: (row: T) => void;
37
+ onClick: (row: T, rowIndex?: number) => void;
31
38
  shouldDisplay?: (row: T) => boolean;
32
39
  disabled?: boolean;
33
40
  labelButton?: (row: T) => string;
34
41
  visible?: boolean;
42
+ width?: string | number;
43
+ minWidth?: string | number;
44
+ focusNextRow?: boolean;
35
45
  }
@@ -4,12 +4,16 @@ import { dataTableActionsInterface } from "../dataTable.interface";
4
4
  type DataTableActionProps = {
5
5
  action: dataTableActionsInterface<any>;
6
6
  row: any;
7
+ rowIndex?: number;
7
8
  index?: number;
9
+ onFocusNextRow?: (rowIndex: number) => void;
8
10
  };
9
11
  export const DataTableAction = ({
10
12
  action,
11
13
  row,
14
+ rowIndex,
12
15
  index,
16
+ onFocusNextRow,
13
17
  }: DataTableActionProps) => {
14
18
  let title = action.label;
15
19
  const display = action.shouldDisplay ? action.shouldDisplay(row) : true;
@@ -17,14 +21,27 @@ export const DataTableAction = ({
17
21
  if (action.labelButton) {
18
22
  title = action.labelButton ? action.labelButton(row) : action.label;
19
23
  }
24
+
25
+ const handleClick = (e?: React.MouseEvent) => {
26
+ e?.stopPropagation();
27
+ action.onClick(row, rowIndex);
28
+ if (action.focusNextRow && rowIndex !== undefined) {
29
+ onFocusNextRow?.(rowIndex);
30
+ }
31
+ };
32
+
20
33
  return (
21
34
  <>
22
35
  {action.type === "button" && (
23
36
  <Button
24
37
  title={title}
25
38
  key={`button-action-${action.id}-${index}`}
26
- onClick={() => action.onClick(row)}
39
+ onClick={handleClick}
27
40
  disabled={action.disabled}
41
+ style={{
42
+ minWidth: action.minWidth,
43
+ width: action.width,
44
+ }}
28
45
  className={`data-table-action button--tertiary mr-2 w-100 ${
29
46
  action.classIcon ? action.classIcon : ""
30
47
  }`}
@@ -34,7 +51,11 @@ export const DataTableAction = ({
34
51
  <div
35
52
  key={`icon-action-${action.id}-${index}`}
36
53
  className={`mr-2 `}
37
- onClick={() => action.onClick(row)}
54
+ onClick={handleClick}
55
+ style={{
56
+ minWidth: action.minWidth,
57
+ width: action.width,
58
+ }}
38
59
  >
39
60
  <Icon classIcon={action.classIcon ?? ""} key={action.id} />
40
61
  </div>
@@ -20,6 +20,36 @@ export const DataTableRow = ({
20
20
  const style = column.onRowStyle ? column.onRowStyle(row) : {};
21
21
  const isDisabled = column.onDisable?.(row) ?? false;
22
22
 
23
+ const hasStepButtons = Boolean(
24
+ typeof column.showStepButtons === "function"
25
+ ? column.showStepButtons(row)
26
+ : (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)))),
33
+ );
34
+
35
+ const handleStep = (direction: "plus" | "minus", e: React.MouseEvent) => {
36
+ e.stopPropagation();
37
+ if (isDisabled) return;
38
+ const currentNum = Number(value) || 0;
39
+ const step = column.step ?? 1;
40
+ let newNum = direction === "plus" ? currentNum + step : currentNum - step;
41
+ if (column.min !== undefined && newNum < column.min) {
42
+ newNum = column.min;
43
+ }
44
+ if (column.max !== undefined && newNum > column.max) {
45
+ newNum = column.max;
46
+ }
47
+ newNum = Math.round(newNum * 1000) / 1000;
48
+ if (column.onEdit) {
49
+ column.onEdit(newNum, row);
50
+ }
51
+ };
52
+
23
53
  return (
24
54
  <td
25
55
  className={`${isDisabled ? "disabled" : ""}`}
@@ -62,6 +92,77 @@ export const DataTableRow = ({
62
92
  }}
63
93
  />
64
94
  </div>
95
+ ) : (
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)}
120
+ >
121
+ <Icon classIcon="bi-dash-lg" />
122
+ </button>
123
+ <input
124
+ type={column.type}
125
+ value={value}
126
+ step={column.step ?? "0.01"}
127
+ min={column.min}
128
+ max={column.max}
129
+ disabled={isDisabled}
130
+ style={{
131
+ textAlign: column.align || "center",
132
+ minWidth: column.minWidth,
133
+ maxWidth: column.maxWidth,
134
+ width: column.width,
135
+ }}
136
+ onClick={(e) => e.stopPropagation()}
137
+ onKeyDown={(e) => {
138
+ if (e.key === "Enter") {
139
+ if (column.onEnter) {
140
+ column.onEnter(e.currentTarget.value, row);
141
+ }
142
+ if (column.enterToNextColumn) {
143
+ e.preventDefault();
144
+ onEnterNextColumn?.(idx, rowIndex);
145
+ }
146
+ }
147
+ }}
148
+ onChange={(e) => {
149
+ if (column.onEdit) {
150
+ column.onEdit(e.target.value, row);
151
+ }
152
+ }}
153
+ />
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>
65
166
  ) : (
66
167
  <input
67
168
  type={column.type}
@@ -75,9 +176,14 @@ export const DataTableRow = ({
75
176
  }}
76
177
  onClick={(e) => e.stopPropagation()}
77
178
  onKeyDown={(e) => {
78
- if (e.key === "Enter" && column.enterToNextColumn) {
79
- e.preventDefault();
80
- onEnterNextColumn?.(idx, rowIndex);
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
+ }
81
187
  }
82
188
  }}
83
189
  onChange={(e) => {
@@ -87,6 +193,7 @@ export const DataTableRow = ({
87
193
  }}
88
194
  />
89
195
  )
196
+ )
90
197
  ) : column.format ? (
91
198
  column.format(value)
92
199
  ) : (