@colisweb/rescript-toolkit 4.10.10 → 4.11.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.
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.10",
3
+ "version": "4.11.0",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "clean": "rescript clean",
@@ -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,
@@ -123,7 +123,7 @@ let make = (
123
123
  let {label, value} = item
124
124
 
125
125
  <div
126
- key={`multiselectoption-${label}-${value}`}
126
+ key={`multiselectoption-${label}-${value}-${i->Int.toString}`}
127
127
  className={cx([
128
128
  "group flex flex-row items-center gap-2 pt-3 text-left relative",
129
129
  i > 0 ? "mt-3" : "",
@@ -0,0 +1,195 @@
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
+ @react.component
39
+ let make = (
40
+ ~options: options,
41
+ ~placeholder: React.element,
42
+ ~buttonClassName="",
43
+ ~dropdownClassName="",
44
+ ~itemClassName="",
45
+ ~searchPlaceholder: option<string>=?,
46
+ ~allowFilter=true,
47
+ ~defaultValue: array<item>=[],
48
+ ~onValidate: array<item> => unit,
49
+ ~disabled: option<bool>=?,
50
+ ~onCancel: option<unit => unit>=?,
51
+ ) => {
52
+ let (selectedOptions, setSelectedOptions) = React.useState(() => defaultValue)
53
+ let previousDefaultValue = Toolkit__Hooks.usePrevious(defaultValue)
54
+ let (search, setSearch) = React.useState(() => "")
55
+ let allowFilter = options->Array.length > 5 && allowFilter
56
+
57
+ React.useEffect2(() => {
58
+ let prev =
59
+ previousDefaultValue
60
+ ->Option.getWithDefault([])
61
+ ->Array.map(({value}) => value)
62
+ ->Js.Array2.joinWith("")
63
+ let new = defaultValue->Array.map(({value}) => value)->Js.Array2.joinWith("")
64
+
65
+ if prev != new {
66
+ setSelectedOptions(_ => defaultValue)
67
+ }
68
+
69
+ None
70
+ }, (previousDefaultValue, defaultValue))
71
+
72
+ let filterOptionsBySearch = (~options, ~search) => {
73
+ options->Array.keep(({label}) =>
74
+ // normalize nfd -> replace by re remove split by commponents the accent letters and deletes the accent component only, leaving the un-accented letter
75
+ search == "" ||
76
+ label
77
+ ->Js.String2.toLowerCase
78
+ ->Js.String2.normalizeByForm("NFD")
79
+ ->Js.String2.replaceByRe(%re("/[\u0300-\u036f]/g"), "")
80
+ ->Js.String2.includes(search)
81
+ )
82
+ }
83
+
84
+ <Toolkit__Ui_Dropdown
85
+ ?disabled
86
+ buttonClassName
87
+ onClose={_ => setSelectedOptions(_ => defaultValue)}
88
+ dropdownClassName
89
+ position=#bottom
90
+ label={switch selectedOptions {
91
+ | [] =>
92
+ <p className="flex flex-row gap-2 w-full items-center relative">
93
+ <span className="ml-1"> {placeholder} </span>
94
+ <span className="absolute inset-y-0 right-0 flex items-center">
95
+ <ReactIcons.FaAngleDown />
96
+ </span>
97
+ </p>
98
+ | options =>
99
+ <div
100
+ className="table table-fixed w-full"
101
+ title={options->Array.map(({label}) => label)->Js.Array2.joinWith(", ")}>
102
+ <span className="table-cell truncate text-left">
103
+ {options->Array.map(({label}) => label)->Js.Array2.joinWith(", ")->React.string}
104
+ </span>
105
+ </div>
106
+ }}>
107
+ <div className="py-2 pl-2 pr-1 max-h-[300px] overflow-y-scroll">
108
+ {allowFilter
109
+ ? <div className="mb-3">
110
+ <Toolkit__Ui_TextInput
111
+ id="search"
112
+ autoFocus={true}
113
+ placeholder=?{searchPlaceholder}
114
+ allowWhiteSpace={true}
115
+ onKeyDown={event => {
116
+ if event->ReactEvent.Keyboard.key === "Enter" && search !== "" {
117
+ let selectedOptions = filterOptionsBySearch(~options, ~search)
118
+ setSelectedOptions(_ => {
119
+ selectedOptions
120
+ })
121
+ }
122
+ }}
123
+ value={search}
124
+ onChange={event => {
125
+ let target = event->ReactEvent.Form.currentTarget
126
+ // normalize nfd -> replace by re remove split by commponents the accent letters and deletes the accent component only, leaving the un-accented letter
127
+ setSearch(_ =>
128
+ target["value"]
129
+ ->Js.String2.toLowerCase
130
+ ->Js.String2.normalizeByForm("NFD")
131
+ ->Js.String2.replaceByRe(%re("/[\u0300-\u036f]/g"), "")
132
+ )
133
+ }}
134
+ />
135
+ </div>
136
+ : React.null}
137
+ {options
138
+ ->Array.keep(({label}) =>
139
+ // normalize nfd -> replace by re remove split by commponents the accent letters and deletes the accent component only, leaving the un-accented letter
140
+ search == "" ||
141
+ label
142
+ ->Js.String2.toLowerCase
143
+ ->Js.String2.normalizeByForm("NFD")
144
+ ->Js.String2.replaceByRe(%re("/[\u0300-\u036f]/g"), "")
145
+ ->Js.String2.includes(search)
146
+ )
147
+ ->Array.mapWithIndex((i, item) => {
148
+ let {label, value} = item
149
+
150
+ <div
151
+ key={`multiselectoption-${label}-${value}-${i->Int.toString}`}
152
+ className={cx([
153
+ "group flex flex-row items-center gap-2 pt-3 text-left relative",
154
+ i > 0 ? "mt-3" : "",
155
+ itemClassName,
156
+ ])}>
157
+ <Toolkit__Ui_Checkbox
158
+ value
159
+ className="w-full flex-shrink-0 relative"
160
+ checked={selectedOptions->Array.some(item =>
161
+ item.label == label && item.value == value
162
+ )}
163
+ onChange={(checked, _value) => {
164
+ if checked {
165
+ setSelectedOptions(selectedOptions => {
166
+ let value = selectedOptions->Array.concat([item])
167
+
168
+ value
169
+ })
170
+ } else {
171
+ setSelectedOptions(selectedOptions => {
172
+ let value =
173
+ selectedOptions->Array.keep(
174
+ selectedItem => selectedItem.value != value && selectedItem.label != label,
175
+ )
176
+
177
+ value
178
+ })
179
+ }
180
+ }}>
181
+ {item.itemLabel->Option.getWithDefault(label->React.string)}
182
+ </Toolkit__Ui_Checkbox>
183
+ </div>
184
+ })
185
+ ->React.array}
186
+ </div>
187
+ <Footer
188
+ onCancel={() => {
189
+ setSelectedOptions(_ => defaultValue)
190
+ onCancel->Option.forEach(fn => fn())
191
+ }}
192
+ onValidateClick={() => onValidate(selectedOptions)}
193
+ />
194
+ </Toolkit__Ui_Dropdown>
195
+ }
@@ -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