@colisweb/rescript-toolkit 4.10.11 → 4.11.1

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/locale/fr.json CHANGED
@@ -84,6 +84,11 @@
84
84
  "defaultMessage": "Valeur non valide.",
85
85
  "message": "Valeur non valide."
86
86
  },
87
+ {
88
+ "id": "_7494b947",
89
+ "defaultMessage": "Valider",
90
+ "message": "Valider"
91
+ },
87
92
  {
88
93
  "id": "_7790a47d",
89
94
  "defaultMessage": "<lat></lat>, <lng></lng>",
@@ -164,6 +169,11 @@
164
169
  "defaultMessage": "Doit etre un entier positif",
165
170
  "message": "Doit etre un entier positif"
166
171
  },
172
+ {
173
+ "id": "_d12c3b0d",
174
+ "defaultMessage": "Annuler",
175
+ "message": "Annuler"
176
+ },
167
177
  {
168
178
  "id": "_d2c9771a",
169
179
  "defaultMessage": "Mauvais format (req: {exemple})",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@colisweb/rescript-toolkit",
3
- "version": "4.10.11",
3
+ "version": "4.11.1",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "clean": "rescript clean",
@@ -13,7 +13,8 @@ module String = {
13
13
  }
14
14
 
15
15
  /**
16
- * normalize nfd -> replace by re remove split by commponents the accent letters and deletes the accent component only, leaving the un-accented letter
16
+ *Normalize NFD*: replace by re remove split by commponents the accent letters
17
+ and deletes the accent component only, leaving the un-accented letter
17
18
  */
18
19
  let normalizeForSearch = str => {
19
20
  str
@@ -30,6 +30,7 @@ module DropdownList = Toolkit__Ui_DropdownList
30
30
  module Layout = Toolkit__Ui_Layout
31
31
  module SpinnerFullScreen = Toolkit__Ui_SpinnerFullScreen
32
32
  module MultiSelect = Toolkit__Ui_MultiSelect
33
+ module MultiSelectWithValidation = Toolkit__Ui_MultiSelectWithValidation
33
34
  module Notice = Toolkit__Ui_Notice
34
35
  module NativeDatePicker = Toolkit__Ui_NativeDatePicker
35
36
  module ErrorBoundary = Toolkit__Ui_ErrorBoundary
@@ -5,6 +5,16 @@ type position = [
5
5
  | #left
6
6
  ]
7
7
 
8
+ type dropdownContext = {hide: unit => unit}
9
+
10
+ let dropdownContext = React.createContext({
11
+ hide: () => (),
12
+ })
13
+
14
+ module Provider = {
15
+ let make = React.Context.provider(dropdownContext)
16
+ }
17
+
8
18
  @react.component
9
19
  let make = (
10
20
  ~label: React.element,
@@ -97,7 +107,7 @@ let make = (
97
107
  dropdownClassName,
98
108
  ])}
99
109
  style={adjustmentStyle->Option.getWithDefault(ReactDOM.Style.make())}>
100
- children
110
+ <Provider value={{hide: hide}}> children </Provider>
101
111
  </div>
102
112
  : React.null}
103
113
  </Toolkit__Ui_Button>
@@ -5,6 +5,10 @@ type position = [
5
5
  | #left
6
6
  ]
7
7
 
8
+ type dropdownContext = {hide: unit => unit}
9
+
10
+ let dropdownContext: React.Context.t<dropdownContext>
11
+
8
12
  @react.component
9
13
  let make: (
10
14
  ~label: React.element,
@@ -0,0 +1,191 @@
1
+ open ReactIntl
2
+
3
+ type item = {
4
+ itemLabel?: React.element,
5
+ label: string,
6
+ value: string,
7
+ }
8
+
9
+ type options = array<item>
10
+
11
+ module Footer = {
12
+ @react.component
13
+ let make = (~onCancel, ~onValidateClick) => {
14
+ let dropdownContext = React.useContext(Toolkit__Ui_Dropdown.dropdownContext)
15
+
16
+ <footer className="bg-white p-1 flex flex-row justify-between">
17
+ <Toolkit__Ui_Button
18
+ type_="button"
19
+ onClick={_ => {
20
+ dropdownContext.hide()
21
+ onCancel()
22
+ }}>
23
+ <FormattedMessage defaultMessage={"Annuler"} />
24
+ </Toolkit__Ui_Button>
25
+ <Toolkit__Ui_Button
26
+ color=#primary
27
+ type_="button"
28
+ onClick={_ => {
29
+ onValidateClick()
30
+ dropdownContext.hide()
31
+ }}>
32
+ <FormattedMessage defaultMessage={"Valider"} />
33
+ </Toolkit__Ui_Button>
34
+ </footer>
35
+ }
36
+ }
37
+
38
+ module Options = {
39
+ @react.component
40
+ let make = (~options, ~deferredSearch, ~itemClassName, ~setSelectedOptions, ~selectedOptions) => {
41
+ options
42
+ ->Array.keep(({label}) =>
43
+ deferredSearch == "" ||
44
+ label->Toolkit__Primitives.String.normalizeForSearch->Js.String2.includes(deferredSearch)
45
+ )
46
+ ->Array.mapWithIndex((i, item) => {
47
+ let {label, value} = item
48
+
49
+ <div
50
+ key={`multiselectoption-${label}-${value}-${i->Int.toString}`}
51
+ className={cx([
52
+ "group flex flex-row items-center gap-2 pt-3 text-left relative",
53
+ i > 0 ? "mt-3" : "",
54
+ itemClassName,
55
+ ])}>
56
+ <Toolkit__Ui_Checkbox
57
+ value
58
+ className="w-full flex-shrink-0 relative"
59
+ checked={selectedOptions->Array.some(item => item.label == label && item.value == value)}
60
+ onChange={(checked, _value) => {
61
+ if checked {
62
+ setSelectedOptions(selectedOptions => {
63
+ let value = selectedOptions->Array.concat([item])
64
+
65
+ value
66
+ })
67
+ } else {
68
+ setSelectedOptions(selectedOptions => {
69
+ let value =
70
+ selectedOptions->Array.keep(
71
+ selectedItem => selectedItem.value != value && selectedItem.label != label,
72
+ )
73
+
74
+ value
75
+ })
76
+ }
77
+ }}>
78
+ {item.itemLabel->Option.getWithDefault(label->React.string)}
79
+ </Toolkit__Ui_Checkbox>
80
+ </div>
81
+ })
82
+ ->React.array
83
+ }
84
+ }
85
+
86
+ @react.component
87
+ let make = (
88
+ ~options: options,
89
+ ~placeholder: React.element,
90
+ ~buttonClassName="",
91
+ ~dropdownClassName="",
92
+ ~itemClassName="",
93
+ ~searchPlaceholder: option<string>=?,
94
+ ~allowFilter=true,
95
+ ~defaultValue: array<item>=[],
96
+ ~onValidate: array<item> => unit,
97
+ ~disabled: option<bool>=?,
98
+ ~onCancel: option<unit => unit>=?,
99
+ ) => {
100
+ let (selectedOptions, setSelectedOptions) = React.useState(() => defaultValue)
101
+ let previousDefaultValue = Toolkit__Hooks.usePrevious(defaultValue)
102
+ let (search, setSearch) = React.useState(() => "")
103
+ let deferredSearch = React.useDeferredValue(search)
104
+ let allowFilter = options->Array.length > 5 && allowFilter
105
+
106
+ React.useEffect2(() => {
107
+ let prev =
108
+ previousDefaultValue
109
+ ->Option.getWithDefault([])
110
+ ->Array.map(({value}) => value)
111
+ ->Js.Array2.joinWith("")
112
+ let new = defaultValue->Array.map(({value}) => value)->Js.Array2.joinWith("")
113
+
114
+ if prev != new {
115
+ setSelectedOptions(_ => defaultValue)
116
+ }
117
+
118
+ None
119
+ }, (previousDefaultValue, defaultValue))
120
+
121
+ let filterOptionsBySearch = (~options, ~search) => {
122
+ options->Array.keep(({label}) =>
123
+ // normalize nfd -> replace by re remove split by commponents the accent letters and deletes the accent component only, leaving the un-accented letter
124
+ search == "" ||
125
+ label
126
+ ->Js.String2.toLowerCase
127
+ ->Js.String2.normalizeByForm("NFD")
128
+ ->Js.String2.replaceByRe(%re("/[\u0300-\u036f]/g"), "")
129
+ ->Js.String2.includes(search)
130
+ )
131
+ }
132
+
133
+ <Toolkit__Ui_Dropdown
134
+ ?disabled
135
+ buttonClassName
136
+ onClose={_ => setSelectedOptions(_ => defaultValue)}
137
+ dropdownClassName
138
+ position=#bottom
139
+ label={switch selectedOptions {
140
+ | [] =>
141
+ <p className="flex flex-row gap-2 w-full items-center relative">
142
+ <span className="ml-1"> {placeholder} </span>
143
+ <span className="absolute inset-y-0 right-0 flex items-center">
144
+ <ReactIcons.FaAngleDown />
145
+ </span>
146
+ </p>
147
+ | options =>
148
+ <div
149
+ className="table table-fixed w-full"
150
+ title={options->Array.map(({label}) => label)->Js.Array2.joinWith(", ")}>
151
+ <span className="table-cell truncate text-left">
152
+ {options->Array.map(({label}) => label)->Js.Array2.joinWith(", ")->React.string}
153
+ </span>
154
+ </div>
155
+ }}>
156
+ <div className="py-2 pl-2 pr-1 max-h-[300px] overflow-y-scroll">
157
+ {allowFilter
158
+ ? <div className="mb-3">
159
+ <Toolkit__Ui_TextInput
160
+ id="search"
161
+ autoFocus={true}
162
+ placeholder=?{searchPlaceholder}
163
+ allowWhiteSpace={true}
164
+ onKeyDown={event => {
165
+ if event->ReactEvent.Keyboard.key === "Enter" && search !== "" {
166
+ let selectedOptions = filterOptionsBySearch(~options, ~search)
167
+ setSelectedOptions(_ => {
168
+ selectedOptions
169
+ })
170
+ }
171
+ }}
172
+ value={search}
173
+ onChange={event => {
174
+ let target = event->ReactEvent.Form.currentTarget
175
+
176
+ setSearch(_ => target["value"]->Toolkit__Primitives.String.normalizeForSearch)
177
+ }}
178
+ />
179
+ </div>
180
+ : React.null}
181
+ <Options deferredSearch options setSelectedOptions selectedOptions itemClassName />
182
+ </div>
183
+ <Footer
184
+ onCancel={() => {
185
+ setSelectedOptions(_ => defaultValue)
186
+ onCancel->Option.forEach(fn => fn())
187
+ }}
188
+ onValidateClick={() => onValidate(selectedOptions)}
189
+ />
190
+ </Toolkit__Ui_Dropdown>
191
+ }
@@ -0,0 +1,22 @@
1
+ type item = {
2
+ itemLabel?: React.element,
3
+ label: string,
4
+ value: string,
5
+ }
6
+
7
+ type options = array<item>
8
+
9
+ @react.component
10
+ let make: (
11
+ ~options: array<item>,
12
+ ~placeholder: React.element,
13
+ ~buttonClassName: string=?,
14
+ ~dropdownClassName: string=?,
15
+ ~itemClassName: string=?,
16
+ ~searchPlaceholder: string=?,
17
+ ~allowFilter: bool=?,
18
+ ~defaultValue: array<item>=?,
19
+ ~onValidate: array<item> => unit,
20
+ ~disabled: bool=?,
21
+ ~onCancel: unit => unit=?,
22
+ ) => React.element