allaw-ui 4.2.8 → 4.3.0

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.
@@ -8,6 +8,7 @@ export interface PhoneInputProps {
8
8
  onChange?: (value: string) => void;
9
9
  onError?: (msg: string) => void;
10
10
  disableAutofill?: boolean;
11
+ onBlur?: () => void;
11
12
  }
12
13
  declare const PhoneInput: React.FC<PhoneInputProps>;
13
14
  export default PhoneInput;
@@ -12,22 +12,40 @@ var __assign = (this && this.__assign) || function () {
12
12
  import React, { useState, useRef, useEffect } from "react";
13
13
  import styles from "./PhoneInput.module.css";
14
14
  var formatPhone = function (value) {
15
- // Garde uniquement les chiffres, espace tous les 2 chiffres
15
+ // Garde uniquement les chiffres, espace tous les 2 chiffres (début)
16
16
  var digits = value.replace(/\D/g, "");
17
17
  return digits.replace(/(\d{2})(?=\d)/g, "$1 ").trim();
18
18
  };
19
+ var formatPhoneEnd = function (value) {
20
+ // Espace tous les 2 chiffres à partir de la fin, gère aussi les cas impairs
21
+ var digits = value.replace(/\D/g, "");
22
+ if (digits.length <= 2)
23
+ return digits;
24
+ var res = "";
25
+ var i = digits.length;
26
+ while (i > 2) {
27
+ res = " " + digits.slice(i - 2, i) + res;
28
+ i -= 2;
29
+ }
30
+ res = digits.slice(0, i) + res;
31
+ return res.trim();
32
+ };
19
33
  var PhoneInput = function (_a) {
20
- var _b = _a.defaultValue, defaultValue = _b === void 0 ? "" : _b, placeholder = _a.placeholder, isRequired = _a.isRequired, disabled = _a.disabled, validationPattern = _a.validationPattern, onChange = _a.onChange, onError = _a.onError, _c = _a.disableAutofill, disableAutofill = _c === void 0 ? true : _c;
34
+ var _b = _a.defaultValue, defaultValue = _b === void 0 ? "" : _b, placeholder = _a.placeholder, isRequired = _a.isRequired, disabled = _a.disabled, validationPattern = _a.validationPattern, onChange = _a.onChange, onError = _a.onError, _c = _a.disableAutofill, disableAutofill = _c === void 0 ? true : _c, onBlur = _a.onBlur;
21
35
  var _d = useState(formatPhone(defaultValue)), value = _d[0], setValue = _d[1];
22
36
  var _e = useState(""), error = _e[0], setError = _e[1];
37
+ var _f = useState(false), isTouched = _f[0], setIsTouched = _f[1];
38
+ var _g = useState(false), isValid = _g[0], setIsValid = _g[1];
23
39
  var inputRef = useRef(null);
24
40
  useEffect(function () {
25
41
  setValue(formatPhone(defaultValue));
26
42
  }, [defaultValue]);
27
43
  var validate = function (val) {
44
+ var valid = true;
28
45
  if (isRequired && !val.replace(/\s/g, "")) {
29
46
  setError("Ce champ est requis");
30
47
  onError === null || onError === void 0 ? void 0 : onError("Ce champ est requis");
48
+ setIsValid(false);
31
49
  return false;
32
50
  }
33
51
  if (validationPattern) {
@@ -37,12 +55,21 @@ var PhoneInput = function (_a) {
37
55
  if (!regex.test(val.replace(/\s/g, ""))) {
38
56
  setError("Veuillez vérifier votre numéro de téléphone");
39
57
  onError === null || onError === void 0 ? void 0 : onError("Veuillez vérifier votre numéro de téléphone");
40
- return false;
58
+ setIsValid(false);
59
+ valid = false;
41
60
  }
61
+ else {
62
+ setIsValid(true);
63
+ }
64
+ }
65
+ else {
66
+ setIsValid(true);
42
67
  }
43
- setError("");
44
- onError === null || onError === void 0 ? void 0 : onError("");
45
- return true;
68
+ if (valid) {
69
+ setError("");
70
+ onError === null || onError === void 0 ? void 0 : onError("");
71
+ }
72
+ return valid;
46
73
  };
47
74
  var handleChange = function (e) {
48
75
  var raw = e.target.value;
@@ -53,15 +80,29 @@ var PhoneInput = function (_a) {
53
80
  validate(formatted);
54
81
  };
55
82
  var handleBlur = function () {
56
- validate(value);
83
+ setIsTouched(true);
84
+ var raw = value.replace(/\s/g, "");
85
+ // Supprime le 0 initial si présent
86
+ if (raw.startsWith("0")) {
87
+ raw = raw.slice(1);
88
+ setValue(formatPhoneEnd(raw));
89
+ onChange === null || onChange === void 0 ? void 0 : onChange(raw);
90
+ }
91
+ else {
92
+ setValue(formatPhoneEnd(raw));
93
+ }
94
+ validate(raw);
95
+ if (typeof onBlur === "function")
96
+ onBlur();
57
97
  };
58
98
  return (React.createElement("div", { className: styles.wrapper },
59
- React.createElement("input", __assign({ ref: inputRef, className: styles.input, type: "tel", inputMode: "numeric", pattern: "[0-9 ]*", placeholder: placeholder, value: value, onChange: handleChange, onBlur: handleBlur, "aria-required": isRequired, "aria-invalid": !!error, disabled: disabled }, (disableAutofill && {
60
- autoComplete: "off",
61
- "data-form-type": "other",
62
- "data-lpignore": "true",
63
- "data-1p-ignore": true,
64
- }))),
65
- error && React.createElement("div", { className: styles.error }, error)));
99
+ React.createElement("div", { className: styles.inputContainer },
100
+ React.createElement("input", __assign({ ref: inputRef, className: styles.input + (error && isTouched ? " " + styles.errorBorder : ""), type: "tel", inputMode: "numeric", pattern: "[0-9 ]*", placeholder: placeholder, value: value, onChange: handleChange, onBlur: handleBlur, "aria-required": isRequired, "aria-invalid": !!error, disabled: disabled }, (disableAutofill && {
101
+ autoComplete: "off",
102
+ "data-form-type": "other",
103
+ "data-lpignore": "true",
104
+ "data-1p-ignore": true,
105
+ }))),
106
+ isValid && isTouched && !error && (React.createElement("i", { className: styles.validIcon + " allaw-icon-check-circle" })))));
66
107
  };
67
108
  export default PhoneInput;
@@ -3,6 +3,12 @@
3
3
  flex-direction: column;
4
4
  width: 100%;
5
5
  }
6
+ .inputContainer {
7
+ position: relative;
8
+ width: 100%;
9
+ display: flex;
10
+ align-items: center;
11
+ }
6
12
  .input {
7
13
  width: 100%;
8
14
  height: 44px;
@@ -37,6 +43,19 @@
37
43
  line-height: normal;
38
44
  opacity: 0.8;
39
45
  }
46
+ .errorBorder {
47
+ border-color: var(--actions-error, #e15151) !important;
48
+ }
49
+ .validIcon {
50
+ position: absolute;
51
+ right: 14px;
52
+ top: 50%;
53
+ transform: translateY(-50%);
54
+ color: #29a36a;
55
+ font-size: 22px;
56
+ pointer-events: none;
57
+ z-index: 2;
58
+ }
40
59
  .error {
41
60
  color: var(--actions-error, #e15151);
42
61
  font-size: 13px;
@@ -1,7 +1,14 @@
1
1
  import React from "react";
2
- import { CountrySelectProps } from "./CountrySelect";
2
+ export interface PhoneNumberFieldCountryItem {
3
+ value: string;
4
+ label: string;
5
+ flagEmoji?: string;
6
+ flagUrl?: string;
7
+ code: string;
8
+ regex: string;
9
+ }
3
10
  export interface PhoneNumberFieldProps {
4
- countryItems: CountrySelectProps["items"];
11
+ countryItems: PhoneNumberFieldCountryItem[];
5
12
  defaultCountry?: string;
6
13
  defaultNumber?: string;
7
14
  numberPlaceholder?: string;
@@ -12,6 +19,7 @@ export interface PhoneNumberFieldProps {
12
19
  selectListWidth?: string | number;
13
20
  onChange?: (country: string, number: string) => void;
14
21
  onError?: (msg: string) => void;
22
+ title?: string;
15
23
  }
16
24
  declare const PhoneNumberField: React.FC<PhoneNumberFieldProps>;
17
25
  export default PhoneNumberField;
@@ -2,11 +2,16 @@ import React, { useState } from "react";
2
2
  import styles from "./PhoneNumberField.module.css";
3
3
  import CountrySelect from "./CountrySelect";
4
4
  import PhoneInput from "./PhoneInput";
5
+ import Paragraph from "../../atoms/typography/Paragraph";
6
+ import TinyInfo from "../../atoms/typography/TinyInfo";
5
7
  var PhoneNumberField = function (_a) {
6
- var countryItems = _a.countryItems, defaultCountry = _a.defaultCountry, defaultNumber = _a.defaultNumber, numberPlaceholder = _a.numberPlaceholder, validationPattern = _a.validationPattern, isRequired = _a.isRequired, disabled = _a.disabled, selectButtonWidth = _a.selectButtonWidth, selectListWidth = _a.selectListWidth, onChange = _a.onChange, onError = _a.onError;
8
+ var countryItems = _a.countryItems, defaultCountry = _a.defaultCountry, defaultNumber = _a.defaultNumber, numberPlaceholder = _a.numberPlaceholder, validationPattern = _a.validationPattern, isRequired = _a.isRequired, disabled = _a.disabled, selectButtonWidth = _a.selectButtonWidth, selectListWidth = _a.selectListWidth, onChange = _a.onChange, onError = _a.onError, title = _a.title;
7
9
  var _b = useState(defaultCountry || ""), country = _b[0], setCountry = _b[1];
8
10
  var _c = useState(defaultNumber || ""), number = _c[0], setNumber = _c[1];
9
11
  var _d = useState(""), error = _d[0], setError = _d[1];
12
+ var _e = useState(false), isTouched = _e[0], setIsTouched = _e[1];
13
+ var selectedCountryObj = countryItems.find(function (c) { return c.value === country; });
14
+ var effectivePattern = (selectedCountryObj === null || selectedCountryObj === void 0 ? void 0 : selectedCountryObj.regex) || validationPattern;
10
15
  var handleCountry = function (c) {
11
16
  setCountry(c);
12
17
  if (onChange)
@@ -23,11 +28,20 @@ var PhoneNumberField = function (_a) {
23
28
  setError(msg);
24
29
  onError === null || onError === void 0 ? void 0 : onError(msg);
25
30
  };
31
+ var handleInputBlur = function () {
32
+ setIsTouched(true);
33
+ };
26
34
  return (React.createElement("div", { className: styles.container },
27
- React.createElement("div", { className: styles.selectWrapper },
28
- React.createElement(CountrySelect, { items: countryItems, defaultCountry: defaultCountry, buttonWidth: selectButtonWidth, listWidth: selectListWidth, isRequired: isRequired, disabled: disabled, onSelect: handleCountry, onError: setErrorMsg })),
29
- React.createElement("div", { className: styles.inputWrapper },
30
- React.createElement(PhoneInput, { defaultValue: defaultNumber, placeholder: numberPlaceholder, validationPattern: validationPattern, isRequired: isRequired, disabled: disabled, onChange: handleNumber, onError: setErrorMsg }),
31
- error && React.createElement("div", { className: styles.errorMessage }, error))));
35
+ title && (React.createElement(Paragraph, { variant: "medium", color: "noir", text: React.createElement(React.Fragment, null,
36
+ title,
37
+ " ",
38
+ isRequired && React.createElement("span", { className: styles.required }, "*")) })),
39
+ React.createElement("div", { className: styles.phoneNumberField },
40
+ React.createElement("div", { className: styles.selectWrapper },
41
+ React.createElement(CountrySelect, { items: countryItems, defaultCountry: defaultCountry, buttonWidth: selectButtonWidth, listWidth: selectListWidth, isRequired: isRequired, disabled: disabled, onSelect: handleCountry, onError: setErrorMsg })),
42
+ React.createElement("div", { className: styles.inputWrapper },
43
+ React.createElement(PhoneInput, { defaultValue: defaultNumber, placeholder: numberPlaceholder, validationPattern: effectivePattern ? new RegExp(effectivePattern) : undefined, isRequired: isRequired, disabled: disabled, onChange: handleNumber, onError: setErrorMsg, onBlur: handleInputBlur }),
44
+ error && isTouched && (React.createElement("div", { className: styles.errorMessage },
45
+ React.createElement(TinyInfo, { variant: "medium12", color: "actions-error", text: error })))))));
32
46
  };
33
47
  export default PhoneNumberField;
@@ -1,16 +1,27 @@
1
1
  .container {
2
2
  display: flex;
3
- align-items: center;
4
- gap: 8px;
3
+ flex-direction: column;
4
+ align-items: flex-start;
5
+ gap: 11px;
5
6
  width: 100%;
6
7
  }
7
8
  .selectWrapper {
8
9
  flex: 0 0 auto;
10
+ min-height: 64px;
9
11
  }
12
+
13
+ .phoneNumberField {
14
+ display: flex;
15
+ flex-direction: row;
16
+ align-items: center;
17
+ gap: 8px;
18
+ }
19
+
10
20
  .inputWrapper {
11
21
  flex: 1 1 auto;
12
22
  display: flex;
13
23
  flex-direction: column;
24
+ min-height: 64px;
14
25
  }
15
26
  .errorMessage {
16
27
  color: var(--actions-error, #e15151);
@@ -18,3 +29,7 @@
18
29
  margin-top: 4px;
19
30
  margin-left: 2px;
20
31
  }
32
+ .required {
33
+ color: #e15151;
34
+ margin-left: 2px;
35
+ }
package/dist/index.d.ts CHANGED
@@ -31,6 +31,8 @@ export type { ComboBoxProps, ComboBoxRef, } from "./components/atoms/selects/Com
31
31
  export { default as Select } from "./components/atoms/selects/Select";
32
32
  export type { SelectItem, SelectProps, SelectRef, } from "./components/atoms/selects/Select";
33
33
  export { default as SelectableListItem } from "./components/atoms/selects/SelectableListItem";
34
+ export { default as PhoneNumberField } from "./components/molecules/phoneNumberField/PhoneNumberField";
35
+ export type { PhoneNumberFieldProps } from "./components/molecules/phoneNumberField/PhoneNumberField";
34
36
  export { default as Datepicker } from "./components/atoms/datepickers/Datepicker";
35
37
  export type { DatepickerProps } from "./components/atoms/datepickers/Datepicker";
36
38
  export { default as DatepickerForm } from "./components/molecules/datepickerForm/DatepickerForm";
package/dist/index.js CHANGED
@@ -31,6 +31,8 @@ export { default as SelectCard } from "./components/atoms/radios/SelectCard";
31
31
  export { default as ComboBox } from "./components/atoms/selects/ComboBox";
32
32
  export { default as Select } from "./components/atoms/selects/Select";
33
33
  export { default as SelectableListItem } from "./components/atoms/selects/SelectableListItem";
34
+ // Phone Number Field
35
+ export { default as PhoneNumberField } from "./components/molecules/phoneNumberField/PhoneNumberField";
34
36
  // Datepickers
35
37
  export { default as Datepicker } from "./components/atoms/datepickers/Datepicker";
36
38
  // DatepickerForm
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "allaw-ui",
3
- "version": "4.2.8",
3
+ "version": "4.3.0",
4
4
  "description": "Composants UI pour l'application Allaw",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.esm.js",