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.
Files changed (127) hide show
  1. package/README.md +26 -0
  2. package/dist/addressSearch/AddressSearch.d.ts +20 -0
  3. package/dist/addressSearch/AddressSearch.view.d.ts +1 -0
  4. package/dist/badge/Badge.d.ts +1 -3
  5. package/dist/button/Button.d.ts +1 -1
  6. package/dist/button/button.interface.d.ts +2 -1
  7. package/dist/card/Card.d.ts +4 -1
  8. package/dist/dataTable/DataTable.d.ts +14 -17
  9. package/dist/dataTable/dataTable.interface.d.ts +19 -24
  10. package/dist/dataTable/dataTableActions/DataTableAction.d.ts +5 -3
  11. package/dist/dataTable/dataTableColumn/DataTableColumn.d.ts +7 -13
  12. package/dist/dataTable/dataTableLoading/DataTableLoading.d.ts +3 -1
  13. package/dist/dataTable/dataTableRow/DataTableRow.d.ts +6 -5
  14. package/dist/expands/Expands.d.ts +1 -0
  15. package/dist/expands/expand/expand.d.ts +3 -2
  16. package/dist/form/Form.d.ts +10 -2
  17. package/dist/form/form.enum.d.ts +3 -2
  18. package/dist/form/form.interface.d.ts +22 -3
  19. package/dist/form/input/Input.d.ts +0 -3
  20. package/dist/form/inputNumber/InputNumber.d.ts +2 -0
  21. package/dist/geocoding/GeocodingService.d.ts +13 -0
  22. package/dist/geocoding/geocoding.interface.d.ts +20 -0
  23. package/dist/grid/Grid.d.ts +5 -2
  24. package/dist/grid/col/Col.d.ts +3 -6
  25. package/dist/grid/col/colStyled/ColStyled.d.ts +8 -5
  26. package/dist/grid/grid.interface.d.ts +20 -5
  27. package/dist/grid/row/Row.d.ts +7 -0
  28. package/dist/header/Header.d.ts +1 -1
  29. package/dist/header/header.interface.d.ts +2 -1
  30. package/dist/icon/Icon.d.ts +1 -1
  31. package/dist/icon/icon.interface.d.ts +0 -1
  32. package/dist/index.d.ts +5 -1
  33. package/dist/index.js +3724 -3628
  34. package/dist/menus/menu/Menu.d.ts +1 -0
  35. package/dist/select/Select.d.ts +2 -1
  36. package/dist/style.css +1 -1
  37. package/dist/tabs/Tabs.d.ts +1 -2
  38. package/dist/toaster/store/useToasterStore.d.ts +1 -0
  39. package/index.html +1 -2
  40. package/package.json +1 -1
  41. package/src/App.menu.tsx +8 -0
  42. package/src/addressSearch/AddressSearch.tsx +246 -0
  43. package/src/addressSearch/AddressSearch.view.tsx +52 -0
  44. package/src/addressSearch/addressSearch.css +133 -0
  45. package/src/badge/Badge.tsx +1 -9
  46. package/src/badge/badge.css +0 -4
  47. package/src/button/Button.tsx +3 -1
  48. package/src/button/button.css +2 -2
  49. package/src/button/button.interface.tsx +2 -1
  50. package/src/card/Card.tsx +37 -11
  51. package/src/card/card.css +7 -28
  52. package/src/card/cardAction/CardAction.tsx +4 -4
  53. package/src/damier/Damier.tsx +16 -7
  54. package/src/damier/damier.css +11 -4
  55. package/src/dataTable/DataTable.tsx +542 -685
  56. package/src/dataTable/DataTable.view.tsx +24 -48
  57. package/src/dataTable/dataTable.css +159 -134
  58. package/src/dataTable/dataTable.interface.ts +19 -25
  59. package/src/dataTable/dataTableActions/DataTableAction.tsx +25 -14
  60. package/src/dataTable/dataTableColumn/DataTableColumn.tsx +14 -64
  61. package/src/dataTable/dataTableLoading/DataTableLoading.tsx +15 -4
  62. package/src/dataTable/dataTableRow/DataTableRow.tsx +180 -64
  63. package/src/expands/Expands.tsx +18 -2
  64. package/src/expands/expand/expand.tsx +128 -5
  65. package/src/expands/expands.css +8 -3
  66. package/src/form/Form.tsx +294 -99
  67. package/src/form/Form.view.tsx +9 -1
  68. package/src/form/form.css +64 -1
  69. package/src/form/form.enum.ts +2 -1
  70. package/src/form/form.interface.tsx +31 -3
  71. package/src/form/input/Input.tsx +7 -61
  72. package/src/form/input/input.css +1 -19
  73. package/src/form/inputNumber/InputNumber.tsx +11 -6
  74. package/src/form/inputPrice/InputPrice.tsx +4 -2
  75. package/src/geocoding/GeocodingService.ts +89 -0
  76. package/src/geocoding/geocoding.interface.ts +25 -0
  77. package/src/grid/Grid.tsx +34 -21
  78. package/src/grid/col/Col.tsx +24 -15
  79. package/src/grid/col/col.css +3 -0
  80. package/src/grid/col/colStyled/ColStyled.tsx +59 -41
  81. package/src/grid/container/container.css +2 -2
  82. package/src/grid/grid.interface.tsx +25 -5
  83. package/src/grid/row/Row.tsx +49 -12
  84. package/src/grid/row/row.css +11 -3
  85. package/src/header/Header.tsx +4 -2
  86. package/src/header/header.css +17 -0
  87. package/src/header/header.interface.tsx +2 -1
  88. package/src/icon/Icon.tsx +2 -6
  89. package/src/icon/icon.interface.tsx +0 -1
  90. package/src/index.css +58 -12
  91. package/src/index.ts +12 -2
  92. package/src/menus/Menus.tsx +1 -0
  93. package/src/menus/menu/Menu.tsx +2 -1
  94. package/src/select/Select.tsx +5 -3
  95. package/src/sidebar/Sidebar.tsx +107 -5
  96. package/src/sidebar/sidebar.css +18 -1
  97. package/src/tabs/Tabs.tsx +1 -20
  98. package/src/tabs/tabs.css +9 -3
  99. package/src/toaster/Toaster.tsx +7 -2
  100. package/src/toaster/Toaster.view.tsx +1 -0
  101. package/src/toaster/store/useToasterStore.tsx +2 -0
  102. package/src/toaster/toaster.css +7 -2
  103. package/dist/_core/type/generiqueType.d.ts +0 -4
  104. package/dist/dataTable/dataTableColumn/dataTableColumnFilter/DataTableColumnFilter.d.ts +0 -10
  105. package/dist/dataTable/dataTableColumn/dataTableColumnFilter/dataTableColumnFilterDate/DataTableColumnFilterDate.d.ts +0 -9
  106. package/dist/dataTable/dataTableColumn/dataTableColumnFilter/dataTableColumnFilterDateRange/dataTableColumnFilterDateRange.d.ts +0 -9
  107. package/dist/dataTable/dataTableColumn/dataTableColumnFilter/dataTableColumnFilterEnum/DataTableColumnFilterEnum.d.ts +0 -13
  108. package/dist/dataTable/dataTableColumn/dataTableColumnFilter/dataTableColumnFilterString/DataTableColumnFilterString.d.ts +0 -9
  109. package/dist/dataTable/dataTableColumn/dataTableColumnSort/DataTableColumnSort.d.ts +0 -11
  110. package/dist/dataTable/dataTableFilters/DataTableFilters.d.ts +0 -7
  111. package/dist/dataTable/dataTableRow/dataTableRowOverlay/DataTableRowOverlay.d.ts +0 -12
  112. package/dist/dataTable/dataTableRow/dataTableRowValue/DataTableRowValue.d.ts +0 -11
  113. package/package/package.json +0 -56
  114. package/package/src/sidebar/Sidebar.tsx +0 -208
  115. package/package/src/sidebar/sidebar.css +0 -104
  116. package/src/_core/type/generiqueType.tsx +0 -4
  117. package/src/dataTable/dataTableColumn/dataTableColumnFilter/DataTableColumnFilter.tsx +0 -36
  118. package/src/dataTable/dataTableColumn/dataTableColumnFilter/dataTableColumnFilter.css +0 -0
  119. package/src/dataTable/dataTableColumn/dataTableColumnFilter/dataTableColumnFilterDate/DataTableColumnFilterDate.tsx +0 -39
  120. package/src/dataTable/dataTableColumn/dataTableColumnFilter/dataTableColumnFilterDateRange/dataTableColumnFilterDateRange.tsx +0 -80
  121. package/src/dataTable/dataTableColumn/dataTableColumnFilter/dataTableColumnFilterEnum/DataTableColumnFilterEnum.tsx +0 -89
  122. package/src/dataTable/dataTableColumn/dataTableColumnFilter/dataTableColumnFilterString/DataTableColumnFilterString.tsx +0 -37
  123. package/src/dataTable/dataTableColumn/dataTableColumnSort/DataTableColumnSort.tsx +0 -42
  124. package/src/dataTable/dataTableFilters/DataTableFilters.tsx +0 -91
  125. package/src/dataTable/dataTableFilters/dataTableFilters.css +0 -16
  126. package/src/dataTable/dataTableRow/dataTableRowOverlay/DataTableRowOverlay.tsx +0 -80
  127. 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
  }
@@ -1,4 +1,4 @@
1
- import { forwardRef, useRef, useState } from "react";
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={localValue}
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={handleChange}
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 ${hasClearButtonSlot ? "with-clear-button" : ""} ${className ?? ""}`}
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";
@@ -9,7 +9,7 @@
9
9
  width: 10px;
10
10
 
11
11
  input {
12
- width: 10px;
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="numeric" // mobile keyboard adaptatif
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
- export const Grid = ({ items, className, scroll }: GridProps) => {
12
- return (
13
- <Container>
14
- <Row scroll={scroll} className={className}>
15
- {items.map((item, id) => (
16
- <Col key={id} {...item} />
17
- ))}
18
- </Row>
19
- </Container>
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
+ };
@@ -1,15 +1,24 @@
1
- import { ColStyled } from "./colStyled/ColStyled";
2
- type ColProps = {
3
- xs?: number;
4
- sm?: number;
5
- md?: number;
6
- lg?: number;
7
- children: React.ReactNode;
8
- };
9
- export const Col: React.FC<ColProps> = ({ xs, sm, md, lg, children }) => {
10
- return (
11
- <ColStyled $xs={xs} $sm={sm} $md={md} $lg={lg}>
12
- {children}
13
- </ColStyled>
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
+ };
@@ -0,0 +1,3 @@
1
+ .col {
2
+ padding: 0;
3
+ }
@@ -1,41 +1,59 @@
1
- import styled, { css } from "styled-components";
2
- const getColWidth = (size = 12) => `${(size / 12) * 100}%`;
3
- type ColStyledProps = {
4
- $xs?: number;
5
- $sm?: number;
6
- $md?: number;
7
- $lg?: number;
8
- };
9
- export const ColStyled = styled.div<ColStyledProps>`
10
- padding: 0 15px;
11
- flex: 0 0 ${({ $xs }) => getColWidth($xs)};
12
- max-width: ${({ $xs }) => getColWidth($xs)};
13
- margin-bottom: 0.5rem;
14
- margin-top: 0.5rem;
15
- ${({ $sm }) =>
16
- $sm &&
17
- css`
18
- @container (min-width: 576px) {
19
- flex: 0 0 ${getColWidth($sm)};
20
- max-width: ${getColWidth($sm)};
21
- }
22
- `}
23
-
24
- ${({ $md }) =>
25
- $md &&
26
- css`
27
- @container (min-width: 768px) {
28
- flex: 0 0 ${getColWidth($md)};
29
- max-width: ${getColWidth($md)};
30
- }
31
- `}
32
-
33
- ${({ $lg }) =>
34
- $lg &&
35
- css`
36
- @container (min-width: 992px) {
37
- flex: 0 0 ${getColWidth($lg)};
38
- max-width: ${getColWidth($lg)};
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
+ `;
@@ -1,5 +1,5 @@
1
1
  .container {
2
2
  width: 100%;
3
3
  margin: 0 auto;
4
- padding: 0 15px;
5
- }
4
+ box-sizing: border-box;
5
+ }