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.
- package/dist/button/buttonImport/ButtonImport.d.ts +13 -0
- package/dist/button/buttonImport/ButtonImport.view.d.ts +1 -0
- package/dist/dataTable/dataTable.interface.d.ts +2 -0
- package/dist/dataTable/dataTable.utils.d.ts +3 -0
- package/dist/dataTable/dataTableActions/DataTableActionMenu.d.ts +9 -0
- package/dist/dataTable/dataTableColumn/DataTableColumn.d.ts +3 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2622 -2419
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/App.css +953 -161
- package/src/App.menu.tsx +8 -0
- package/src/button/buttonFile/ButtonFile.tsx +4 -1
- package/src/button/buttonImport/ButtonImport.tsx +99 -0
- package/src/button/buttonImport/ButtonImport.view.tsx +17 -0
- package/src/button/buttonImport/buttonImport.css +38 -0
- package/src/dataTable/DataTable.tsx +24 -25
- package/src/dataTable/DataTable.view.tsx +13 -0
- package/src/dataTable/dataTable.css +51 -3
- package/src/dataTable/dataTable.interface.ts +2 -0
- package/src/dataTable/dataTable.utils.ts +15 -2
- package/src/dataTable/dataTableActions/DataTableAction.tsx +2 -1
- package/src/dataTable/dataTableActions/DataTableActionMenu.tsx +95 -0
- package/src/dataTable/dataTableColumn/DataTableColumn.tsx +68 -0
- package/src/dataTable/dataTableRow/DataTableRow.tsx +3 -1
- package/src/docs/catalog.ts +1 -0
- package/src/index.ts +1 -0
- package/src/select/Select.tsx +1 -1
- package/src/select/select.css +28 -22
- package/src/tabs/tabs.css +7 -1
- package/tests/dataTable.test.mjs +9 -0
- 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}
|
package/src/docs/catalog.ts
CHANGED
|
@@ -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";
|
package/src/select/Select.tsx
CHANGED
|
@@ -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}
|
package/src/select/select.css
CHANGED
|
@@ -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:
|
|
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
|
-
|
|
26
|
-
|
|
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
package/tests/dataTable.test.mjs
CHANGED
|
@@ -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
|
+
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);
|