kelt-ui-kit-react 2.1.0 → 2.1.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.1.0",
4
+ "version": "2.1.1",
5
5
  "private": false,
6
6
  "repository": {
7
7
  "type": "git",
@@ -22,7 +22,6 @@ export type {
22
22
  DataTableDateRangeFilterValue, DataTableFilterState, DataTableSortDirection,
23
23
  DataTableSortState, FilterValue,
24
24
  } from "./dataTable.utils";
25
- const DEFAULT_FILTER_COLUMN_WIDTH = 100;
26
25
 
27
26
  export type DataTableProps<T> = {
28
27
  id: string;
@@ -93,9 +92,7 @@ export function DataTable<T>(props: DataTableProps<T>) {
93
92
  const tableState = useDataTableState(id, groupedColumns, sourceData, onFiltersChange, onSortChange);
94
93
  const { flatColumns: columns, sortedData: data } = tableState;
95
94
  const actionsColumnWidth = getActionsWidth(actions, explicitActionsWidth);
96
- const filterRowRef = useRef<HTMLTableRowElement>(null);
97
95
  const groupRowRef = useRef<HTMLTableRowElement>(null);
98
- const [filterHeight, setFilterHeight] = useState(0);
99
96
  const [groupHeight, setGroupHeight] = useState(0);
100
97
  const hasGroups = groupedColumns.some((column) => column.children?.length);
101
98
  const stickyOffsets = useMemo(() => {
@@ -112,7 +109,6 @@ export function DataTable<T>(props: DataTableProps<T>) {
112
109
 
113
110
  useLayoutEffect(() => {
114
111
  const measure = () => {
115
- setFilterHeight(filterRowRef.current?.getBoundingClientRect().height ?? 0);
116
112
  setGroupHeight(groupRowRef.current?.getBoundingClientRect().height ?? 0);
117
113
  };
118
114
  measure();
@@ -121,10 +117,9 @@ export function DataTable<T>(props: DataTableProps<T>) {
121
117
  return () => window.removeEventListener("resize", measure);
122
118
  }
123
119
  const observer = new ResizeObserver(measure);
124
- if (filterRowRef.current) observer.observe(filterRowRef.current);
125
120
  if (groupRowRef.current) observer.observe(groupRowRef.current);
126
121
  return () => observer.disconnect();
127
- }, [tableState.visibleFilters.size, hasGroups, groupedColumns]);
122
+ }, [hasGroups, groupedColumns]);
128
123
 
129
124
  const isMobile = useIsMobile();
130
125
  const showMobileCardView = mobileCardView && isMobile;
@@ -531,8 +526,8 @@ export function DataTable<T>(props: DataTableProps<T>) {
531
526
  isFilterVisible={tableState.visibleFilters.has(column.name ?? column.id)}
532
527
  onToggleFilter={tableState.toggleFilter} onToggleSort={tableState.toggleSort}
533
528
  sortDirection={tableState.sort?.key === (column.name ?? column.id) ? tableState.sort.direction : null}
529
+ filterControl={tableState.renderFilter(column)}
534
530
  /></tr></thead></table>
535
- {tableState.renderFilter(column)}
536
531
  </div>
537
532
  ))}
538
533
  </div>
@@ -643,29 +638,17 @@ export function DataTable<T>(props: DataTableProps<T>) {
643
638
  {actions.length > 0 && <col style={{ width: actionsColumnWidth }} />}
644
639
  </colgroup>
645
640
  <thead>
646
- {tableState.visibleFilters.size > 0 && <tr className="data-table-filter-row" ref={filterRowRef}>
647
- {onSelectionChange && <th />}
648
- {columns.map((column) => <th className="filter-row-cell" key={column.id}
649
- style={{ position: "sticky", top: 0, zIndex: 14, background: "#f9fbfc",
650
- width: column.width ?? DEFAULT_FILTER_COLUMN_WIDTH,
651
- minWidth: column.width ?? DEFAULT_FILTER_COLUMN_WIDTH,
652
- maxWidth: column.width ?? DEFAULT_FILTER_COLUMN_WIDTH,
653
- ...(column.sticky ? { left: stickyOffsets.get(column.id) } : {}) }}>
654
- {tableState.renderFilter(column)}
655
- </th>)}
656
- {actions.length > 0 && <th />}
657
- </tr>}
658
641
  {hasGroups && <tr ref={groupRowRef} className="data-table-group-row">
659
642
  {onSelectionChange && <th />}
660
643
  {groupedColumns.map((column) => <th key={column.id} colSpan={flattenColumns([column]).length}
661
- style={{ top: filterHeight }}>{column.children?.length ? column.label : ""}</th>)}
644
+ style={{ top: 0 }}>{column.children?.length ? column.label : ""}</th>)}
662
645
  {actions.length > 0 && <th />}
663
646
  </tr>}
664
647
  <tr>
665
648
  {onSelectionChange && (
666
649
  <th
667
650
  className={`select-cell ${hideToggleAll ? "hidden" : ""}`}
668
- style={{ width: "40px", textAlign: "center", top: filterHeight + groupHeight,
651
+ style={{ width: "40px", textAlign: "center", top: groupHeight,
669
652
  ...(stickyOffsets.size > 0 ? { left: 0, zIndex: 12 } : {}) }}
670
653
  >
671
654
  <Input
@@ -686,11 +669,12 @@ export function DataTable<T>(props: DataTableProps<T>) {
686
669
  idx={idx}
687
670
  onColumnClick={onColumnClick}
688
671
  stickyLeft={stickyOffsets.get(column.id)}
689
- stickyTop={filterHeight + groupHeight}
672
+ stickyTop={groupHeight}
690
673
  isFilterVisible={tableState.visibleFilters.has(column.name ?? column.id)}
691
674
  onToggleFilter={tableState.toggleFilter}
692
675
  onToggleSort={tableState.toggleSort}
693
676
  sortDirection={tableState.sort?.key === (column.name ?? column.id) ? tableState.sort.direction : null}
677
+ filterControl={tableState.renderFilter(column)}
694
678
  />
695
679
  ))}
696
680
  {actions.length > 0 && (
@@ -316,6 +316,15 @@
316
316
  flex: 1;
317
317
  }
318
318
  }
319
+ .data-table-filter-popover {
320
+ z-index: 1200;
321
+ padding: 8px;
322
+ box-sizing: border-box;
323
+ border: 1px solid #dce3e7;
324
+ border-radius: 7px;
325
+ background: #fff;
326
+ box-shadow: 0 6px 18px rgba(20, 40, 50, 0.16);
327
+ }
319
328
  .data-table-group-row th {
320
329
  height: 40px;
321
330
  box-sizing: border-box;
@@ -1,3 +1,5 @@
1
+ import { ReactNode, useLayoutEffect, useRef, useState } from "react";
2
+ import { createPortal } from "react-dom";
1
3
  import { dataTableColumnsInterface } from "../dataTable.interface";
2
4
  import { DataTableColumnFilter } from "./dataTableColumnFilter/DataTableColumnFilter";
3
5
  import { DataTableColumnSort } from "./dataTableColumnSort/DataTableColumnSort";
@@ -13,7 +15,62 @@ type DataTableColumnProps = {
13
15
  sortDirection?: DataTableSortDirection | null;
14
16
  stickyLeft?: number;
15
17
  stickyTop?: number;
18
+ filterControl?: ReactNode;
16
19
  };
20
+
21
+ function FilterPopover({
22
+ anchor,
23
+ label,
24
+ children,
25
+ }: {
26
+ anchor: HTMLElement;
27
+ label: string;
28
+ children: ReactNode;
29
+ }) {
30
+ const [style, setStyle] = useState<React.CSSProperties>();
31
+
32
+ useLayoutEffect(() => {
33
+ const updatePosition = () => {
34
+ const rect = anchor.getBoundingClientRect();
35
+ if (!rect.width || !rect.height) {
36
+ setStyle(undefined);
37
+ return;
38
+ }
39
+ const width = Math.min(Math.max(rect.width, 220), window.innerWidth - 24);
40
+ setStyle({
41
+ position: "fixed",
42
+ top: Math.min(rect.bottom + 4, window.innerHeight - 80),
43
+ left: Math.max(12, Math.min(rect.left, window.innerWidth - width - 12)),
44
+ width,
45
+ });
46
+ };
47
+
48
+ updatePosition();
49
+ window.addEventListener("resize", updatePosition);
50
+ window.addEventListener("scroll", updatePosition, true);
51
+ return () => {
52
+ window.removeEventListener("resize", updatePosition);
53
+ window.removeEventListener("scroll", updatePosition, true);
54
+ };
55
+ }, [anchor]);
56
+
57
+ return style && typeof document !== "undefined"
58
+ ? createPortal(
59
+ <div
60
+ className="data-table-filter-popover"
61
+ style={style}
62
+ role="group"
63
+ aria-label={`Filtrer ${label}`}
64
+ onMouseDown={(event) => event.stopPropagation()}
65
+ onClick={(event) => event.stopPropagation()}
66
+ >
67
+ {children}
68
+ </div>,
69
+ document.body,
70
+ )
71
+ : null;
72
+ }
73
+
17
74
  export const DataTableColumn = ({
18
75
  column,
19
76
  idx,
@@ -24,9 +81,12 @@ export const DataTableColumn = ({
24
81
  sortDirection,
25
82
  stickyLeft,
26
83
  stickyTop = 0,
84
+ filterControl,
27
85
  }: DataTableColumnProps) => {
86
+ const headerRef = useRef<HTMLTableCellElement>(null);
28
87
  return (
29
88
  <th
89
+ ref={headerRef}
30
90
  key={`colonne-${column.id}-${idx}`}
31
91
  style={{
32
92
  minWidth: column.minWidth,
@@ -43,6 +103,14 @@ export const DataTableColumn = ({
43
103
  {column.filterable && <DataTableColumnFilter column={column} isFilterVisible={isFilterVisible} onToggleVisibility={onToggleFilter} />}
44
104
  {column.sortable && <DataTableColumnSort column={column} sortDirection={sortDirection} onToggleSort={onToggleSort} />}
45
105
  </div>
106
+ {isFilterVisible && filterControl && headerRef.current && (
107
+ <FilterPopover
108
+ anchor={headerRef.current}
109
+ label={column.label}
110
+ >
111
+ {filterControl}
112
+ </FilterPopover>
113
+ )}
46
114
  </th>
47
115
  );
48
116
  };
@@ -67,7 +67,9 @@ export const DataTableRow = <T,>({
67
67
  column.onChange?.(next, row);
68
68
  commit(next);
69
69
  }}
70
- style={{ minWidth: column.minWidth, maxWidth: column.maxWidth, width: column.width }} />
70
+ style={{ minWidth: column.minWidth, maxWidth: column.maxWidth, width: column.width,
71
+ // le bouton du Select est un bloc : textAlign ne l'aligne pas
72
+ ...(align === "right" ? { marginLeft: "auto" } : align === "center" ? { marginInline: "auto" } : {}) }} />
71
73
  ) : (
72
74
  <input ref={inputRef} type={column.type === "image" ? "text" : column.type}
73
75
  value={enterCommit ? draft : stringValue}
@@ -145,7 +145,7 @@ export const Select = ({
145
145
  }, [showOptions, disabled]);
146
146
 
147
147
  return (
148
- <div id={id} className={`select-container ${className ? className : ""}`}>
148
+ <div id={id} className={`select-container ${hasSelection && placeholder ? "has-selection" : ""} ${className ? className : ""}`}>
149
149
  {label && (
150
150
  <label htmlFor={`select-${id}`} className="select-container-label">
151
151
  {label}
@@ -1,15 +1,6 @@
1
1
  .select-container {
2
2
  position: relative;
3
3
 
4
- &::after,
5
- &::before {
6
- --size: 0.3rem;
7
- content: "";
8
- position: absolute;
9
- right: 1rem;
10
- pointer-events: none;
11
- }
12
-
13
4
  .clear-btn {
14
5
  position: absolute;
15
6
  top: 50%;
@@ -18,22 +9,13 @@
18
9
  align-items: center;
19
10
  z-index: 2;
20
11
  justify-content: center;
21
- right: 10px;
12
+ /* à gauche de la flèche (dessinée à right: 1rem, largeur 0.6rem) */
13
+ right: 2rem;
22
14
  cursor: pointer;
23
15
  }
24
16
 
25
- &::before {
26
- border-left: var(--size) solid transparent;
27
- border-right: var(--size) solid transparent;
28
- border-bottom: var(--size) solid black;
29
- top: 40%;
30
- }
31
-
32
- &::after {
33
- border-left: var(--size) solid transparent;
34
- border-right: var(--size) solid transparent;
35
- border-top: var(--size) solid black;
36
- top: 55%;
17
+ &.has-selection .select-container-button {
18
+ padding-right: 3.5rem;
37
19
  }
38
20
 
39
21
  .select-container-label {
@@ -45,6 +27,7 @@
45
27
 
46
28
  .select-container-button {
47
29
  appearance: none;
30
+ position: relative;
48
31
  width: 100%;
49
32
  font-size: 0.8rem;
50
33
  padding: 0.5rem 1rem;
@@ -60,6 +43,29 @@
60
43
  justify-content: space-between;
61
44
  padding-right: 2rem;
62
45
 
46
+ &::before,
47
+ &::after {
48
+ --size: 0.3rem;
49
+ content: "";
50
+ position: absolute;
51
+ right: 1rem;
52
+ pointer-events: none;
53
+ }
54
+
55
+ &::before {
56
+ border-left: var(--size) solid transparent;
57
+ border-right: var(--size) solid transparent;
58
+ border-bottom: var(--size) solid black;
59
+ top: calc(50% - var(--size));
60
+ }
61
+
62
+ &::after {
63
+ border-left: var(--size) solid transparent;
64
+ border-right: var(--size) solid transparent;
65
+ border-top: var(--size) solid black;
66
+ top: calc(50% + 1px);
67
+ }
68
+
63
69
  .select-container-value {
64
70
  flex: 1;
65
71
  min-width: 0;