kelt-ui-kit-react 2.0.3 → 2.0.4
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/README.md +26 -0
- package/dist/addressSearch/AddressSearch.d.ts +20 -0
- package/dist/addressSearch/AddressSearch.view.d.ts +1 -0
- package/dist/badge/Badge.d.ts +1 -3
- package/dist/button/Button.d.ts +1 -1
- package/dist/button/button.interface.d.ts +2 -1
- package/dist/card/Card.d.ts +4 -1
- package/dist/dataTable/DataTable.d.ts +14 -17
- package/dist/dataTable/dataTable.interface.d.ts +19 -24
- package/dist/dataTable/dataTableActions/DataTableAction.d.ts +5 -3
- package/dist/dataTable/dataTableColumn/DataTableColumn.d.ts +7 -13
- package/dist/dataTable/dataTableLoading/DataTableLoading.d.ts +3 -1
- package/dist/dataTable/dataTableRow/DataTableRow.d.ts +6 -5
- package/dist/expands/Expands.d.ts +1 -0
- package/dist/expands/expand/expand.d.ts +3 -2
- package/dist/form/Form.d.ts +10 -2
- package/dist/form/form.enum.d.ts +3 -2
- package/dist/form/form.interface.d.ts +22 -3
- package/dist/form/input/Input.d.ts +0 -3
- package/dist/form/inputNumber/InputNumber.d.ts +2 -0
- package/dist/geocoding/GeocodingService.d.ts +13 -0
- package/dist/geocoding/geocoding.interface.d.ts +20 -0
- package/dist/grid/Grid.d.ts +5 -2
- package/dist/grid/col/Col.d.ts +3 -6
- package/dist/grid/col/colStyled/ColStyled.d.ts +8 -5
- package/dist/grid/grid.interface.d.ts +20 -5
- package/dist/grid/row/Row.d.ts +7 -0
- package/dist/header/Header.d.ts +1 -1
- package/dist/header/header.interface.d.ts +2 -1
- package/dist/icon/Icon.d.ts +1 -1
- package/dist/icon/icon.interface.d.ts +0 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +3724 -3628
- package/dist/menus/menu/Menu.d.ts +1 -0
- package/dist/select/Select.d.ts +2 -1
- package/dist/style.css +1 -1
- package/dist/tabs/Tabs.d.ts +1 -2
- package/dist/toaster/store/useToasterStore.d.ts +1 -0
- package/index.html +1 -2
- package/package.json +1 -1
- package/src/App.menu.tsx +8 -0
- package/src/addressSearch/AddressSearch.tsx +246 -0
- package/src/addressSearch/AddressSearch.view.tsx +52 -0
- package/src/addressSearch/addressSearch.css +133 -0
- package/src/badge/Badge.tsx +1 -9
- package/src/badge/badge.css +0 -4
- package/src/button/Button.tsx +3 -1
- package/src/button/button.css +2 -2
- package/src/button/button.interface.tsx +2 -1
- package/src/card/Card.tsx +37 -11
- package/src/card/card.css +7 -28
- package/src/card/cardAction/CardAction.tsx +4 -4
- package/src/damier/Damier.tsx +16 -7
- package/src/damier/damier.css +11 -4
- package/src/dataTable/DataTable.tsx +542 -685
- package/src/dataTable/DataTable.view.tsx +24 -48
- package/src/dataTable/dataTable.css +159 -134
- package/src/dataTable/dataTable.interface.ts +19 -25
- package/src/dataTable/dataTableActions/DataTableAction.tsx +25 -14
- package/src/dataTable/dataTableColumn/DataTableColumn.tsx +14 -64
- package/src/dataTable/dataTableLoading/DataTableLoading.tsx +15 -4
- package/src/dataTable/dataTableRow/DataTableRow.tsx +180 -64
- package/src/expands/Expands.tsx +18 -2
- package/src/expands/expand/expand.tsx +128 -5
- package/src/expands/expands.css +8 -3
- package/src/form/Form.tsx +294 -99
- package/src/form/Form.view.tsx +9 -1
- package/src/form/form.css +64 -1
- package/src/form/form.enum.ts +2 -1
- package/src/form/form.interface.tsx +31 -3
- package/src/form/input/Input.tsx +7 -61
- package/src/form/input/input.css +1 -19
- package/src/form/inputNumber/InputNumber.tsx +11 -6
- package/src/form/inputPrice/InputPrice.tsx +4 -2
- package/src/geocoding/GeocodingService.ts +89 -0
- package/src/geocoding/geocoding.interface.ts +25 -0
- package/src/grid/Grid.tsx +34 -21
- package/src/grid/col/Col.tsx +24 -15
- package/src/grid/col/col.css +3 -0
- package/src/grid/col/colStyled/ColStyled.tsx +59 -41
- package/src/grid/container/container.css +2 -2
- package/src/grid/grid.interface.tsx +25 -5
- package/src/grid/row/Row.tsx +49 -12
- package/src/grid/row/row.css +11 -3
- package/src/header/Header.tsx +4 -2
- package/src/header/header.css +17 -0
- package/src/header/header.interface.tsx +2 -1
- package/src/icon/Icon.tsx +2 -6
- package/src/icon/icon.interface.tsx +0 -1
- package/src/index.css +58 -12
- package/src/index.ts +12 -2
- package/src/menus/Menus.tsx +1 -0
- package/src/menus/menu/Menu.tsx +2 -1
- package/src/select/Select.tsx +5 -3
- package/src/sidebar/Sidebar.tsx +107 -5
- package/src/sidebar/sidebar.css +18 -1
- package/src/tabs/Tabs.tsx +1 -20
- package/src/tabs/tabs.css +9 -3
- package/src/toaster/Toaster.tsx +7 -2
- package/src/toaster/Toaster.view.tsx +1 -0
- package/src/toaster/store/useToasterStore.tsx +2 -0
- package/src/toaster/toaster.css +7 -2
- package/dist/_core/type/generiqueType.d.ts +0 -4
- package/dist/dataTable/dataTableColumn/dataTableColumnFilter/DataTableColumnFilter.d.ts +0 -10
- package/dist/dataTable/dataTableColumn/dataTableColumnFilter/dataTableColumnFilterDate/DataTableColumnFilterDate.d.ts +0 -9
- package/dist/dataTable/dataTableColumn/dataTableColumnFilter/dataTableColumnFilterDateRange/dataTableColumnFilterDateRange.d.ts +0 -9
- package/dist/dataTable/dataTableColumn/dataTableColumnFilter/dataTableColumnFilterEnum/DataTableColumnFilterEnum.d.ts +0 -13
- package/dist/dataTable/dataTableColumn/dataTableColumnFilter/dataTableColumnFilterString/DataTableColumnFilterString.d.ts +0 -9
- package/dist/dataTable/dataTableColumn/dataTableColumnSort/DataTableColumnSort.d.ts +0 -11
- package/dist/dataTable/dataTableFilters/DataTableFilters.d.ts +0 -7
- package/dist/dataTable/dataTableRow/dataTableRowOverlay/DataTableRowOverlay.d.ts +0 -12
- package/dist/dataTable/dataTableRow/dataTableRowValue/DataTableRowValue.d.ts +0 -11
- package/package/package.json +0 -56
- package/package/src/sidebar/Sidebar.tsx +0 -208
- package/package/src/sidebar/sidebar.css +0 -104
- package/src/_core/type/generiqueType.tsx +0 -4
- package/src/dataTable/dataTableColumn/dataTableColumnFilter/DataTableColumnFilter.tsx +0 -36
- package/src/dataTable/dataTableColumn/dataTableColumnFilter/dataTableColumnFilter.css +0 -0
- package/src/dataTable/dataTableColumn/dataTableColumnFilter/dataTableColumnFilterDate/DataTableColumnFilterDate.tsx +0 -39
- package/src/dataTable/dataTableColumn/dataTableColumnFilter/dataTableColumnFilterDateRange/dataTableColumnFilterDateRange.tsx +0 -80
- package/src/dataTable/dataTableColumn/dataTableColumnFilter/dataTableColumnFilterEnum/DataTableColumnFilterEnum.tsx +0 -89
- package/src/dataTable/dataTableColumn/dataTableColumnFilter/dataTableColumnFilterString/DataTableColumnFilterString.tsx +0 -37
- package/src/dataTable/dataTableColumn/dataTableColumnSort/DataTableColumnSort.tsx +0 -42
- package/src/dataTable/dataTableFilters/DataTableFilters.tsx +0 -91
- package/src/dataTable/dataTableFilters/dataTableFilters.css +0 -16
- package/src/dataTable/dataTableRow/dataTableRowOverlay/DataTableRowOverlay.tsx +0 -80
- package/src/dataTable/dataTableRow/dataTableRowValue/DataTableRowValue.tsx +0 -123
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import { TypeInputEnum } from "./form.enum";
|
|
2
|
+
import { GridBreakpoints } from "../grid/grid.interface";
|
|
3
|
+
import {
|
|
4
|
+
GeocodedAddress,
|
|
5
|
+
GeocodingProvider,
|
|
6
|
+
} from "../geocoding/geocoding.interface";
|
|
7
|
+
|
|
8
|
+
export type FormMode = "read" | "edit";
|
|
2
9
|
|
|
3
10
|
export type FormValuesInterface<
|
|
4
11
|
T extends { [key: string]: string | number | boolean | Date | string[] }
|
|
@@ -10,8 +17,14 @@ export interface FormCondition {
|
|
|
10
17
|
regex: string;
|
|
11
18
|
}
|
|
12
19
|
|
|
20
|
+
export interface FormSection {
|
|
21
|
+
title?: React.ReactNode;
|
|
22
|
+
fields: FormInterface[];
|
|
23
|
+
className?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
13
26
|
export interface FormInterface {
|
|
14
|
-
value: string | number | boolean | Date;
|
|
27
|
+
value: string | number | boolean | Date | string[];
|
|
15
28
|
id?: string;
|
|
16
29
|
label?: string;
|
|
17
30
|
focus?: boolean;
|
|
@@ -27,13 +40,28 @@ export interface FormInterface {
|
|
|
27
40
|
searchable?: boolean;
|
|
28
41
|
options?: { value: string; label: string }[]; // <--- pour les <option>
|
|
29
42
|
className?: string;
|
|
43
|
+
col?: GridBreakpoints;
|
|
44
|
+
addressSearch?: {
|
|
45
|
+
minimumQueryLength?: number;
|
|
46
|
+
debounceTime?: number;
|
|
47
|
+
limit?: number;
|
|
48
|
+
provider?: GeocodingProvider;
|
|
49
|
+
manualEntryLabel?: string;
|
|
50
|
+
mapAddress?: (
|
|
51
|
+
address: GeocodedAddress,
|
|
52
|
+
) => Record<string, string | number | boolean | Date | string[]>;
|
|
53
|
+
onSelect?: (address: GeocodedAddress) => void;
|
|
54
|
+
};
|
|
55
|
+
formatReadValue?: (
|
|
56
|
+
value: string | number | boolean | Date | string[]
|
|
57
|
+
) => React.ReactNode;
|
|
30
58
|
autoComplete?: string;
|
|
31
59
|
step?: string;
|
|
32
60
|
onChange?: (
|
|
33
61
|
value: string | number | boolean | Date | string[],
|
|
34
62
|
name?: string
|
|
35
63
|
) => void;
|
|
36
|
-
onRequired?: (value: string | number | boolean | Date) => boolean;
|
|
37
|
-
onDisabled?: (value: string | number | boolean | Date) => boolean;
|
|
64
|
+
onRequired?: (value: string | number | boolean | Date | string[]) => boolean;
|
|
65
|
+
onDisabled?: (value: string | number | boolean | Date | string[]) => boolean;
|
|
38
66
|
onInvalid?: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
|
39
67
|
}
|
package/src/form/input/Input.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { forwardRef,
|
|
1
|
+
import { forwardRef, useState } from "react";
|
|
2
2
|
import { Icon } from "../../icon/Icon";
|
|
3
3
|
import { TypeInputEnum } from "../form.enum";
|
|
4
4
|
import "./input.css";
|
|
@@ -15,15 +15,12 @@ export interface InputProps {
|
|
|
15
15
|
disabled?: boolean;
|
|
16
16
|
autoFocus?: boolean;
|
|
17
17
|
required?: boolean;
|
|
18
|
-
disabledDebounce?: boolean;
|
|
19
18
|
minLength?: number;
|
|
20
19
|
maxLength?: number;
|
|
21
|
-
width?: string;
|
|
22
20
|
autoComplete?: string;
|
|
23
21
|
step?: string;
|
|
24
22
|
tabIndex?: number;
|
|
25
23
|
checked?: boolean;
|
|
26
|
-
showClearButton?: boolean;
|
|
27
24
|
handleKeyDown?: (e: React.KeyboardEvent<HTMLInputElement>) => void;
|
|
28
25
|
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
|
29
26
|
onBlur?: (e: React.FocusEvent<HTMLInputElement>) => void;
|
|
@@ -37,13 +34,11 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
|
|
|
37
34
|
name,
|
|
38
35
|
type = TypeInputEnum.TEXT,
|
|
39
36
|
value,
|
|
40
|
-
width,
|
|
41
37
|
onInvalid,
|
|
42
38
|
placeholder,
|
|
43
39
|
className,
|
|
44
40
|
inputMode,
|
|
45
41
|
minLength,
|
|
46
|
-
disabledDebounce = false,
|
|
47
42
|
maxLength,
|
|
48
43
|
autoFocus = false,
|
|
49
44
|
disabled = false,
|
|
@@ -52,61 +47,18 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
|
|
|
52
47
|
tabIndex = 0,
|
|
53
48
|
step,
|
|
54
49
|
checked,
|
|
55
|
-
showClearButton = false,
|
|
56
50
|
onChange,
|
|
57
51
|
onBlur,
|
|
58
52
|
onClick,
|
|
59
53
|
handleKeyDown,
|
|
60
54
|
},
|
|
61
|
-
ref
|
|
55
|
+
ref
|
|
62
56
|
) => {
|
|
63
57
|
// Si le type est password, on utilise l'input de type password
|
|
64
58
|
const [showPassword, setShowPassword] = useState(false);
|
|
65
|
-
const [localValue, setLocalValue] = useState(value ?? "");
|
|
66
|
-
const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
67
|
-
|
|
68
|
-
const triggerOnChange = (nextValue: string) => {
|
|
69
|
-
if (!onChange) return;
|
|
70
|
-
|
|
71
|
-
const syntheticEvent = {
|
|
72
|
-
target: { value: nextValue, name, id },
|
|
73
|
-
currentTarget: { value: nextValue, name, id },
|
|
74
|
-
} as unknown as React.ChangeEvent<HTMLInputElement>;
|
|
75
|
-
|
|
76
|
-
onChange(syntheticEvent);
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
80
|
-
const value = e.target.value; // 👈 capturer avant le setTimeout
|
|
81
|
-
setLocalValue(value);
|
|
82
|
-
if (!onChange) return;
|
|
83
|
-
if (disabledDebounce) {
|
|
84
|
-
onChange(e);
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
if (debounceRef.current) clearTimeout(debounceRef.current);
|
|
88
|
-
debounceRef.current = setTimeout(() => {
|
|
89
|
-
onChange({ ...e, target: { ...e.target, value } }); // 👈 passer la valeur capturée
|
|
90
|
-
}, 300);
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
const handleClear = (e: React.MouseEvent<HTMLDivElement>) => {
|
|
94
|
-
e.stopPropagation();
|
|
95
|
-
if (debounceRef.current) clearTimeout(debounceRef.current);
|
|
96
|
-
setLocalValue("");
|
|
97
|
-
triggerOnChange("");
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
const hasValue = String(localValue ?? "").length > 0;
|
|
101
|
-
const hasClearButtonSlot = showClearButton && type === TypeInputEnum.TEXT;
|
|
102
|
-
const canShowClearButton =
|
|
103
|
-
showClearButton && hasValue && !disabled && type === TypeInputEnum.TEXT;
|
|
104
59
|
|
|
105
60
|
return (
|
|
106
|
-
<div
|
|
107
|
-
className={`input-container ${`input-container-${type}`} `}
|
|
108
|
-
style={{ width }}
|
|
109
|
-
>
|
|
61
|
+
<div className={`input-container ${`input-container-${type}`} `}>
|
|
110
62
|
<input name={`hide-${name}`} type="text" style={{ display: "none" }} />
|
|
111
63
|
|
|
112
64
|
<input
|
|
@@ -116,7 +68,7 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
|
|
|
116
68
|
inputMode={inputMode ?? (type === "number" ? "numeric" : "text")}
|
|
117
69
|
type={showPassword ? TypeInputEnum.TEXT : type}
|
|
118
70
|
spellCheck="false"
|
|
119
|
-
value={
|
|
71
|
+
value={value}
|
|
120
72
|
placeholder={placeholder}
|
|
121
73
|
disabled={disabled}
|
|
122
74
|
required={required}
|
|
@@ -126,7 +78,7 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
|
|
|
126
78
|
autoFocus={autoFocus}
|
|
127
79
|
minLength={minLength}
|
|
128
80
|
maxLength={maxLength}
|
|
129
|
-
onChange={
|
|
81
|
+
onChange={onChange}
|
|
130
82
|
checked={checked}
|
|
131
83
|
onKeyDown={handleKeyDown}
|
|
132
84
|
onBlur={onBlur}
|
|
@@ -134,16 +86,10 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
|
|
|
134
86
|
e.stopPropagation();
|
|
135
87
|
onClick && onClick(e);
|
|
136
88
|
}}
|
|
137
|
-
className={`input-field ${
|
|
89
|
+
className={`input-field ${className ?? ""}`}
|
|
138
90
|
onInvalid={onInvalid}
|
|
139
91
|
/>
|
|
140
92
|
|
|
141
|
-
{canShowClearButton && (
|
|
142
|
-
<div onClick={handleClear} className="input-clear-icon" role="button">
|
|
143
|
-
<Icon classIcon="bi-x-circle" />
|
|
144
|
-
</div>
|
|
145
|
-
)}
|
|
146
|
-
|
|
147
93
|
{type === TypeInputEnum.PASSWORD && (
|
|
148
94
|
<div
|
|
149
95
|
onClick={() => setShowPassword(!showPassword)}
|
|
@@ -154,7 +100,7 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
|
|
|
154
100
|
)}
|
|
155
101
|
</div>
|
|
156
102
|
);
|
|
157
|
-
}
|
|
103
|
+
}
|
|
158
104
|
);
|
|
159
105
|
|
|
160
106
|
Input.displayName = "Input";
|
package/src/form/input/input.css
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
width: 10px;
|
|
10
10
|
|
|
11
11
|
input {
|
|
12
|
-
width:
|
|
12
|
+
width: 20px;
|
|
13
13
|
min-width: 10px;
|
|
14
14
|
}
|
|
15
15
|
}
|
|
@@ -23,26 +23,8 @@
|
|
|
23
23
|
align-items: center;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
.input-clear-icon {
|
|
27
|
-
position: absolute;
|
|
28
|
-
right: 8px;
|
|
29
|
-
width: 24px;
|
|
30
|
-
height: 100%;
|
|
31
|
-
top: 0;
|
|
32
|
-
display: flex;
|
|
33
|
-
align-items: center;
|
|
34
|
-
justify-content: center;
|
|
35
|
-
cursor: pointer;
|
|
36
|
-
z-index: 2;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
.with-clear-button {
|
|
40
|
-
padding-right: 40px;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
26
|
input {
|
|
44
27
|
width: 100%;
|
|
45
28
|
font-size: 0.8rem;
|
|
46
|
-
box-sizing: border-box;
|
|
47
29
|
}
|
|
48
30
|
}
|
|
@@ -8,8 +8,10 @@ export type InputNumberProps = {
|
|
|
8
8
|
value?: string; // ⚠️ string pour gérer la saisie manuelle
|
|
9
9
|
placeholder?: string;
|
|
10
10
|
className?: string;
|
|
11
|
+
pattern?: string; // regex pour validation (ex: "^-?\\d*[.,]?\\d*$" pour nombre décimal)
|
|
11
12
|
disabled?: boolean;
|
|
12
13
|
required?: boolean;
|
|
14
|
+
inputMode?: "numeric" | "decimal";
|
|
13
15
|
autoFocus?: boolean;
|
|
14
16
|
maxLength?: number;
|
|
15
17
|
autoComplete?: string;
|
|
@@ -17,7 +19,7 @@ export type InputNumberProps = {
|
|
|
17
19
|
onKeyDown?: (e: React.KeyboardEvent<HTMLInputElement>) => void;
|
|
18
20
|
onChange?: (
|
|
19
21
|
e: React.ChangeEvent<HTMLInputElement>,
|
|
20
|
-
valueFormat?: string
|
|
22
|
+
valueFormat?: string,
|
|
21
23
|
) => void;
|
|
22
24
|
onBlur?: (e: React.FocusEvent<HTMLInputElement>) => void;
|
|
23
25
|
onClick?: (e: React.MouseEvent<HTMLInputElement>) => void;
|
|
@@ -38,13 +40,15 @@ export const InputNumber = forwardRef<HTMLInputElement, InputNumberProps>(
|
|
|
38
40
|
maxLength = 10,
|
|
39
41
|
autoComplete = "off",
|
|
40
42
|
tabIndex = 0,
|
|
43
|
+
inputMode = "numeric",
|
|
44
|
+
pattern,
|
|
41
45
|
onChange,
|
|
42
46
|
onBlur,
|
|
43
47
|
onClick,
|
|
44
48
|
onKeyDown,
|
|
45
49
|
onFocus,
|
|
46
50
|
},
|
|
47
|
-
ref
|
|
51
|
+
ref,
|
|
48
52
|
) => {
|
|
49
53
|
const handleChange = useCallback(
|
|
50
54
|
(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
@@ -62,7 +66,7 @@ export const InputNumber = forwardRef<HTMLInputElement, InputNumberProps>(
|
|
|
62
66
|
|
|
63
67
|
onChange?.(e, newValue);
|
|
64
68
|
},
|
|
65
|
-
[onChange, maxLength]
|
|
69
|
+
[onChange, maxLength],
|
|
66
70
|
);
|
|
67
71
|
|
|
68
72
|
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
@@ -72,7 +76,7 @@ export const InputNumber = forwardRef<HTMLInputElement, InputNumberProps>(
|
|
|
72
76
|
// ✅ Autoriser chiffres, contrôle, ., , et -
|
|
73
77
|
const allowed =
|
|
74
78
|
/[0-9]|Backspace|Delete|ArrowLeft|ArrowRight|Tab|Enter|Home|End/.test(
|
|
75
|
-
e.key
|
|
79
|
+
e.key,
|
|
76
80
|
) ||
|
|
77
81
|
e.key === "." ||
|
|
78
82
|
e.key === "," ||
|
|
@@ -105,8 +109,9 @@ export const InputNumber = forwardRef<HTMLInputElement, InputNumberProps>(
|
|
|
105
109
|
required={required}
|
|
106
110
|
autoFocus={autoFocus}
|
|
107
111
|
autoComplete={autoComplete}
|
|
112
|
+
pattern={pattern}
|
|
108
113
|
tabIndex={tabIndex}
|
|
109
|
-
inputMode=
|
|
114
|
+
inputMode={inputMode} // mobile keyboard adaptatif
|
|
110
115
|
spellCheck="false"
|
|
111
116
|
className={`input-field ${className ?? ""}`}
|
|
112
117
|
onChange={handleChange}
|
|
@@ -121,7 +126,7 @@ export const InputNumber = forwardRef<HTMLInputElement, InputNumberProps>(
|
|
|
121
126
|
/>
|
|
122
127
|
</div>
|
|
123
128
|
);
|
|
124
|
-
}
|
|
129
|
+
},
|
|
125
130
|
);
|
|
126
131
|
|
|
127
132
|
InputNumber.displayName = "InputNumber";
|
|
@@ -18,7 +18,7 @@ export const InputPrice = forwardRef<HTMLInputElement, InputPriceProps>(
|
|
|
18
18
|
onChange,
|
|
19
19
|
...props
|
|
20
20
|
},
|
|
21
|
-
ref
|
|
21
|
+
ref,
|
|
22
22
|
) => {
|
|
23
23
|
const handleBlur = useCallback(() => {
|
|
24
24
|
if (onChange) {
|
|
@@ -31,10 +31,12 @@ export const InputPrice = forwardRef<HTMLInputElement, InputPriceProps>(
|
|
|
31
31
|
<InputNumber
|
|
32
32
|
{...props}
|
|
33
33
|
ref={ref}
|
|
34
|
+
pattern="[0-9]*[.,]?[0-9]*"
|
|
35
|
+
inputMode="decimal" // mobile keyboard adaptatif
|
|
34
36
|
value={value ?? ""}
|
|
35
37
|
onChange={(e) => onChange?.(e.target.value)}
|
|
36
38
|
onBlur={handleBlur} // Force le formatage à 2 décimales en sortie
|
|
37
39
|
/>
|
|
38
40
|
);
|
|
39
|
-
}
|
|
41
|
+
},
|
|
40
42
|
);
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import {
|
|
2
|
+
GeocodedAddress,
|
|
3
|
+
GeocodingProvider,
|
|
4
|
+
GeocodingSearchOptions,
|
|
5
|
+
} from "./geocoding.interface";
|
|
6
|
+
|
|
7
|
+
const DEFAULT_GEOCODING_URL = "https://data.geopf.fr/geocodage/search";
|
|
8
|
+
|
|
9
|
+
interface GeocodingFeature {
|
|
10
|
+
geometry?: {
|
|
11
|
+
coordinates?: [number, number];
|
|
12
|
+
};
|
|
13
|
+
properties?: {
|
|
14
|
+
id?: string;
|
|
15
|
+
label?: string;
|
|
16
|
+
name?: string;
|
|
17
|
+
postcode?: string;
|
|
18
|
+
city?: string;
|
|
19
|
+
citycode?: string;
|
|
20
|
+
score?: number;
|
|
21
|
+
type?: string;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface GeocodingResponse {
|
|
26
|
+
features?: GeocodingFeature[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface GeocodingServiceOptions {
|
|
30
|
+
baseUrl?: string;
|
|
31
|
+
fetcher?: typeof fetch;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export class GeocodingService implements GeocodingProvider {
|
|
35
|
+
private readonly baseUrl: string;
|
|
36
|
+
private readonly fetcher: typeof fetch;
|
|
37
|
+
|
|
38
|
+
constructor(options: GeocodingServiceOptions = {}) {
|
|
39
|
+
this.baseUrl = options.baseUrl ?? DEFAULT_GEOCODING_URL;
|
|
40
|
+
this.fetcher = options.fetcher ?? globalThis.fetch.bind(globalThis);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async search(
|
|
44
|
+
query: string,
|
|
45
|
+
options: GeocodingSearchOptions = {},
|
|
46
|
+
): Promise<GeocodedAddress[]> {
|
|
47
|
+
const normalizedQuery = query.trim();
|
|
48
|
+
if (!normalizedQuery) return [];
|
|
49
|
+
|
|
50
|
+
const params = new URLSearchParams({
|
|
51
|
+
q: normalizedQuery,
|
|
52
|
+
limit: String(options.limit ?? 5),
|
|
53
|
+
autocomplete: "1",
|
|
54
|
+
index: "address",
|
|
55
|
+
});
|
|
56
|
+
const response = await this.fetcher(`${this.baseUrl}?${params}`, {
|
|
57
|
+
signal: options.signal,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
if (!response.ok) {
|
|
61
|
+
throw new Error(`Geocoding request failed (${response.status})`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const data = (await response.json()) as GeocodingResponse;
|
|
65
|
+
return (data.features ?? []).flatMap((feature) => {
|
|
66
|
+
const properties = feature.properties;
|
|
67
|
+
const coordinates = feature.geometry?.coordinates;
|
|
68
|
+
if (!properties?.label || !coordinates) return [];
|
|
69
|
+
|
|
70
|
+
return [
|
|
71
|
+
{
|
|
72
|
+
id: properties.id ?? properties.label,
|
|
73
|
+
label: properties.label,
|
|
74
|
+
addressLine: properties.name ?? properties.label,
|
|
75
|
+
postalCode: properties.postcode ?? "",
|
|
76
|
+
city: properties.city ?? "",
|
|
77
|
+
cityCode: properties.citycode,
|
|
78
|
+
countryCode: "FR",
|
|
79
|
+
longitude: coordinates[0],
|
|
80
|
+
latitude: coordinates[1],
|
|
81
|
+
score: properties.score,
|
|
82
|
+
type: properties.type,
|
|
83
|
+
},
|
|
84
|
+
];
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export const geocodingService = new GeocodingService();
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface GeocodedAddress {
|
|
2
|
+
id: string;
|
|
3
|
+
label: string;
|
|
4
|
+
addressLine: string;
|
|
5
|
+
postalCode: string;
|
|
6
|
+
city: string;
|
|
7
|
+
cityCode?: string;
|
|
8
|
+
countryCode: string;
|
|
9
|
+
longitude: number;
|
|
10
|
+
latitude: number;
|
|
11
|
+
score?: number;
|
|
12
|
+
type?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface GeocodingSearchOptions {
|
|
16
|
+
limit?: number;
|
|
17
|
+
signal?: AbortSignal;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface GeocodingProvider {
|
|
21
|
+
search(
|
|
22
|
+
query: string,
|
|
23
|
+
options?: GeocodingSearchOptions,
|
|
24
|
+
): Promise<GeocodedAddress[]>;
|
|
25
|
+
}
|
package/src/grid/Grid.tsx
CHANGED
|
@@ -1,21 +1,34 @@
|
|
|
1
|
-
import { Col } from "./col/Col";
|
|
2
|
-
import { Container } from "./container/Container";
|
|
3
|
-
import { GridInterface } from "./grid.interface";
|
|
4
|
-
import { Row } from "./row/Row";
|
|
5
|
-
type GridProps = {
|
|
6
|
-
items: GridInterface[];
|
|
7
|
-
className?: string;
|
|
8
|
-
scroll?: boolean;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
1
|
+
import { Col } from "./col/Col";
|
|
2
|
+
import { Container } from "./container/Container";
|
|
3
|
+
import { GridInterface, GridResponsive } from "./grid.interface";
|
|
4
|
+
import { GridGap, Row } from "./row/Row";
|
|
5
|
+
type GridProps = {
|
|
6
|
+
items: GridInterface[];
|
|
7
|
+
className?: string;
|
|
8
|
+
scroll?: boolean;
|
|
9
|
+
gap?: GridGap;
|
|
10
|
+
responsive?: GridResponsive;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const Grid = ({
|
|
14
|
+
items,
|
|
15
|
+
className,
|
|
16
|
+
scroll,
|
|
17
|
+
gap,
|
|
18
|
+
responsive,
|
|
19
|
+
}: GridProps) => {
|
|
20
|
+
return (
|
|
21
|
+
<Container>
|
|
22
|
+
<Row
|
|
23
|
+
scroll={scroll}
|
|
24
|
+
className={className}
|
|
25
|
+
gap={gap}
|
|
26
|
+
responsive={responsive}
|
|
27
|
+
>
|
|
28
|
+
{items.map((item, id) => (
|
|
29
|
+
<Col key={id} {...item} />
|
|
30
|
+
))}
|
|
31
|
+
</Row>
|
|
32
|
+
</Container>
|
|
33
|
+
);
|
|
34
|
+
};
|
package/src/grid/col/Col.tsx
CHANGED
|
@@ -1,15 +1,24 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
children: React.ReactNode;
|
|
8
|
-
};
|
|
9
|
-
export const Col: React.FC<ColProps> = ({ xs, sm, md, lg, children }) => {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
1
|
+
import { useContext } from "react";
|
|
2
|
+
import { ColStyled } from "./colStyled/ColStyled";
|
|
3
|
+
import { GridBreakpoints } from "../grid.interface";
|
|
4
|
+
import { RowResponsiveContext } from "../row/Row";
|
|
5
|
+
import "./col.css"
|
|
6
|
+
type ColProps = GridBreakpoints & {
|
|
7
|
+
children: React.ReactNode;
|
|
8
|
+
};
|
|
9
|
+
export const Col: React.FC<ColProps> = ({ xs, sm, md, lg, xl, children }) => {
|
|
10
|
+
const responsive = useContext(RowResponsiveContext);
|
|
11
|
+
return (
|
|
12
|
+
<ColStyled
|
|
13
|
+
className="col"
|
|
14
|
+
$responsive={responsive}
|
|
15
|
+
$xs={xs}
|
|
16
|
+
$sm={sm}
|
|
17
|
+
$md={md}
|
|
18
|
+
$lg={lg}
|
|
19
|
+
$xl={xl}
|
|
20
|
+
>
|
|
21
|
+
{children}
|
|
22
|
+
</ColStyled>
|
|
23
|
+
);
|
|
24
|
+
};
|
|
@@ -1,41 +1,59 @@
|
|
|
1
|
-
import styled, { css } from "styled-components";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
$
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
$
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
1
|
+
import styled, { css } from "styled-components";
|
|
2
|
+
import { GridColumnSpan, GridResponsive } from "../../grid.interface";
|
|
3
|
+
|
|
4
|
+
// Largeur = part de la ligne moins la part des gaps qui revient à la colonne.
|
|
5
|
+
// Pour une ligne dont les spans font 12, la somme des largeurs + les gaps = 100%.
|
|
6
|
+
const getColWidth = (size: number) =>
|
|
7
|
+
`calc(${(size / 12) * 100}% - var(--grid-gutter, 0px) * ${1 - size / 12})`;
|
|
8
|
+
|
|
9
|
+
// "auto" : base 0 et grow 1, les colonnes "auto" d'une ligne se partagent
|
|
10
|
+
// la place restante (gaps déduits) à parts égales.
|
|
11
|
+
const colSize = (size: GridColumnSpan = 12) =>
|
|
12
|
+
size === "auto"
|
|
13
|
+
? `flex: 1 1 0; max-width: 100%;`
|
|
14
|
+
: `flex: 0 0 ${getColWidth(size)}; max-width: ${getColWidth(size)};`;
|
|
15
|
+
|
|
16
|
+
// Règle de breakpoint selon la largeur de référence choisie sur la Row
|
|
17
|
+
const breakpoint = (responsive: GridResponsive, minWidth: number) => {
|
|
18
|
+
switch (responsive) {
|
|
19
|
+
case "viewport":
|
|
20
|
+
return `@media (min-width: ${minWidth}px)`;
|
|
21
|
+
case "row":
|
|
22
|
+
return `@container kelt-grid (min-width: ${minWidth}px)`;
|
|
23
|
+
default:
|
|
24
|
+
return `@container app-container (min-width: ${minWidth}px)`;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
type ColStyledProps = {
|
|
29
|
+
$responsive: GridResponsive;
|
|
30
|
+
$xs?: GridColumnSpan;
|
|
31
|
+
$sm?: GridColumnSpan;
|
|
32
|
+
$md?: GridColumnSpan;
|
|
33
|
+
$lg?: GridColumnSpan;
|
|
34
|
+
$xl?: GridColumnSpan;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const colAt = (
|
|
38
|
+
responsive: GridResponsive,
|
|
39
|
+
minWidth: number,
|
|
40
|
+
size?: GridColumnSpan
|
|
41
|
+
) =>
|
|
42
|
+
size &&
|
|
43
|
+
css`
|
|
44
|
+
${breakpoint(responsive, minWidth)} {
|
|
45
|
+
${colSize(size)}
|
|
46
|
+
}
|
|
47
|
+
`;
|
|
48
|
+
|
|
49
|
+
export const ColStyled = styled.div<ColStyledProps>`
|
|
50
|
+
box-sizing: border-box;
|
|
51
|
+
min-width: 0;
|
|
52
|
+
${({ $xs }) => colSize($xs)}
|
|
53
|
+
margin-bottom: 0.5rem;
|
|
54
|
+
margin-top: 0.5rem;
|
|
55
|
+
${({ $responsive, $sm }) => colAt($responsive, 576, $sm)}
|
|
56
|
+
${({ $responsive, $md }) => colAt($responsive, 768, $md)}
|
|
57
|
+
${({ $responsive, $lg }) => colAt($responsive, 992, $lg)}
|
|
58
|
+
${({ $responsive, $xl }) => colAt($responsive, 1200, $xl)}
|
|
59
|
+
`;
|