@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 +10 -0
- package/package.json +1 -1
- package/src/ui/Toolkit__Ui.res +1 -0
- package/src/ui/Toolkit__Ui_Dropdown.res +11 -1
- package/src/ui/Toolkit__Ui_Dropdown.resi +4 -0
- package/src/ui/Toolkit__Ui_MultiSelect.res +1 -1
- package/src/ui/Toolkit__Ui_MultiSelectWithValidation.res +195 -0
- package/src/ui/Toolkit__Ui_MultiSelectWithValidation.resi +22 -0
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
package/src/ui/Toolkit__Ui.res
CHANGED
|
@@ -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>
|
|
@@ -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
|