kelt-ui-kit-react 2.1.0 → 2.1.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.
Files changed (32) hide show
  1. package/dist/button/buttonImport/ButtonImport.d.ts +13 -0
  2. package/dist/button/buttonImport/ButtonImport.view.d.ts +1 -0
  3. package/dist/dataTable/dataTable.interface.d.ts +2 -0
  4. package/dist/dataTable/dataTable.utils.d.ts +3 -0
  5. package/dist/dataTable/dataTableActions/DataTableActionMenu.d.ts +9 -0
  6. package/dist/dataTable/dataTableColumn/DataTableColumn.d.ts +3 -1
  7. package/dist/index.d.ts +1 -0
  8. package/dist/index.js +2622 -2419
  9. package/dist/style.css +1 -1
  10. package/package.json +1 -1
  11. package/src/App.css +953 -161
  12. package/src/App.menu.tsx +8 -0
  13. package/src/button/buttonFile/ButtonFile.tsx +4 -1
  14. package/src/button/buttonImport/ButtonImport.tsx +99 -0
  15. package/src/button/buttonImport/ButtonImport.view.tsx +17 -0
  16. package/src/button/buttonImport/buttonImport.css +38 -0
  17. package/src/dataTable/DataTable.tsx +24 -25
  18. package/src/dataTable/DataTable.view.tsx +13 -0
  19. package/src/dataTable/dataTable.css +51 -3
  20. package/src/dataTable/dataTable.interface.ts +2 -0
  21. package/src/dataTable/dataTable.utils.ts +15 -2
  22. package/src/dataTable/dataTableActions/DataTableAction.tsx +2 -1
  23. package/src/dataTable/dataTableActions/DataTableActionMenu.tsx +95 -0
  24. package/src/dataTable/dataTableColumn/DataTableColumn.tsx +68 -0
  25. package/src/dataTable/dataTableRow/DataTableRow.tsx +3 -1
  26. package/src/docs/catalog.ts +1 -0
  27. package/src/index.ts +1 -0
  28. package/src/select/Select.tsx +1 -1
  29. package/src/select/select.css +28 -22
  30. package/src/tabs/tabs.css +7 -1
  31. package/tests/dataTable.test.mjs +9 -0
  32. package/tests/documentation.test.mjs +1 -1
@@ -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}
@@ -5,6 +5,7 @@ export const catalog: Record<string, { category: Category; description: string;
5
5
  "/buttons": { category: "Actions", icon: "cursor", description: "Des actions claires, avec icones, badges et menus contextuels." },
6
6
  "/action": { category: "Actions", icon: "lightning", description: "Une action compacte et ses interactions." },
7
7
  "/button-file": { category: "Actions", icon: "upload", description: "Selectionner un fichier depuis un bouton dedie." },
8
+ "/button-import": { category: "Actions", icon: "file-earmark-arrow-up", description: "Ouvrir une modal pour choisir un fichier puis valider l'import." },
8
9
  "/form": { category: "Formulaires", icon: "ui-checks", description: "Composer des formulaires dynamiques, sections et champs responsives." },
9
10
  "/address-search": { category: "Formulaires", icon: "geo-alt", description: "Rechercher une adresse et exploiter ses donnees geographiques." },
10
11
  "/select": { category: "Formulaires", icon: "chevron-expand", description: "Choisir une valeur parmi des options." },
package/src/index.ts CHANGED
@@ -26,6 +26,7 @@ export { Badge } from "./badge/Badge";
26
26
  export { Button } from "./button/Button";
27
27
  export { ButtonActions } from "./button/buttonActions/ButtonActions";
28
28
  export { ButtonFile } from "./button/buttonFile/ButtonFile";
29
+ export { ButtonImport } from "./button/buttonImport/ButtonImport";
29
30
  export { ButtonFilters } from "./button/buttonFilters/ButtonFilters";
30
31
  export { Card } from "./card/Card";
31
32
  export { Carousel } from "./carousel/Carousel";
@@ -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;
package/src/tabs/tabs.css CHANGED
@@ -27,4 +27,10 @@
27
27
  }
28
28
  }
29
29
  }
30
- }
30
+ }
31
+
32
+ /* Un panel inactif (attribut hidden) ne doit jamais occuper de place,
33
+ même si un panelClassName lui donne un display (flex, grid...) */
34
+ .tabs-root [role="tabpanel"][hidden] {
35
+ display: none !important;
36
+ }
@@ -50,6 +50,15 @@ test("action widths preserve Hub sums and Kelt explicit column widths", () => {
50
50
  assert.equal(utils.getActionsWidth([]), 200);
51
51
  });
52
52
 
53
+ test("menu actions are grouped behind a single three-dots toggle", () => {
54
+ assert.equal(utils.getActionsWidth([{ width: 48 }, { width: 100, display: "menu" }]), 48 + utils.ACTION_MENU_WIDTH);
55
+ assert.equal(utils.getActionsWidth([{ display: "menu" }, { display: "menu" }]), utils.ACTION_MENU_WIDTH);
56
+ assert.equal(utils.getActionsWidth([{ type: "button" }, { display: "menu" }]), 200);
57
+ assert.equal(utils.isActionDisplayed({ visible: false }, {}), false);
58
+ assert.equal(utils.isActionDisplayed({ shouldDisplay: (row) => row.id === 2 }, { id: 1 }), false);
59
+ assert.equal(utils.isActionDisplayed({}, { id: 1 }), true);
60
+ });
61
+
53
62
  test("sorting is stable, immutable, and supports Hub names and dates", () => {
54
63
  const rows = [{ name: "Dossier 10", date: "2026-10-12" }, { name: "Dossier 2", date: "2026-01-02" }];
55
64
  const columns = [{ id: "name", name: "dossier", type: "text" }, { id: "date", type: "date" }];
@@ -27,7 +27,7 @@ test("every registered demo has generated API documentation and its exact source
27
27
  ts.forEachChild(node, visit);
28
28
  }
29
29
  visit(source);
30
- assert.equal(routes.length, 30);
30
+ assert.equal(routes.length, 31);
31
31
  assert.deepEqual(Object.keys(docs).sort(), routes.sort());
32
32
  for (const route of routes) {
33
33
  assert.ok(docs[route].components.length > 0, route);