@colisweb/rescript-toolkit 2.24.0 → 2.26.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@colisweb/rescript-toolkit",
3
- "version": "2.24.0",
3
+ "version": "2.26.0",
4
4
  "scripts": {
5
5
  "clean": "rescript clean",
6
6
  "build": "rescript build",
@@ -0,0 +1,43 @@
1
+ let localStorageKey = "@colisweb/mock"
2
+
3
+ @react.component
4
+ let make = (~worker: Msw.worker, ~children) => {
5
+ let (mockEnabled, setMock) = React.useState(() =>
6
+ Browser.LocalStorage.getItem(localStorageKey)
7
+ ->Js.Nullable.toOption
8
+ ->Option.mapWithDefault(false, bool_of_string)
9
+ )
10
+
11
+ React.useEffect1(() => {
12
+ if mockEnabled {
13
+ worker->Msw.start
14
+ Browser.LocalStorage.setItem(localStorageKey, Js.Nullable.return("true"))
15
+ } else {
16
+ worker->Msw.stop
17
+ Browser.LocalStorage.removeItem(localStorageKey)
18
+ }
19
+
20
+ None
21
+ }, [mockEnabled])
22
+
23
+ <React.Suspense fallback={<Toolkit__Ui_SpinnerFullScreen />}>
24
+ <label
25
+ className={`fixed top-2 left-1/2 flex flex-row gap-2 items-center justify-center ${mockEnabled
26
+ ? "bg-success-100 border-success-400 text-success-700"
27
+ : "bg-neutral-100"} p-1 border rounded-lg z-50 cursor-pointer`}>
28
+ <span> {"Mock"->React.string} </span>
29
+ <input
30
+ type_={"checkbox"}
31
+ checked={mockEnabled}
32
+ onChange={event => {
33
+ let checked = (event->ReactEvent.Form.target)["checked"]
34
+
35
+ setMock(_ => checked)
36
+ }}
37
+ />
38
+ </label>
39
+ {children}
40
+ </React.Suspense>
41
+ }
42
+
43
+ let defaul = make
@@ -0,0 +1,2 @@
1
+ @react.component
2
+ let make: (~worker: Msw.worker, ~children: React.element) => React.element
@@ -0,0 +1,65 @@
1
+ # Mock
2
+
3
+ We use [msw](https://mswjs.io) for mocking API content.
4
+
5
+ ## Usage
6
+
7
+ Install the package
8
+
9
+ ```
10
+ yarn add msw --dev
11
+ ```
12
+
13
+ Define the mock
14
+
15
+ ```rescript
16
+ /* Mock_Page_X.res */
17
+
18
+ open Identifiers
19
+
20
+ let mocks = [
21
+ Msw.Rest.get(
22
+ "https://api.url",
23
+ (req, res, ctx) => {
24
+ open ColiswebApi.V5.Transporter.GetContactsWithParameters.Config
25
+ let _emptyResponse = []
26
+ let defaultResponse = [
27
+ {
28
+ id: ContactIdString.make("1"),
29
+ firstName: "Thomas",
30
+ lastName: "Deconinck",
31
+ email: "dck@colisweb.com",
32
+ primaryPhone: "0123456789",
33
+ parameters: {
34
+ id: ParameterId.make("1"),
35
+ email: true,
36
+ sms: true,
37
+ call: true,
38
+ },
39
+ carrier: None,
40
+ }
41
+ ]
42
+
43
+ let transporterId = req.params["transporterId"]
44
+
45
+ switch transporterId {
46
+ | "9" => res(Msw.Ctx.json(ctx, defaultResponse))
47
+ | _ => ()
48
+ }
49
+ },
50
+ ),
51
+ Msw.Rest.put(
52
+ "https://api.url",
53
+ (_req, res, ctx) => {
54
+ res(Msw.Ctx.status(ctx, 200))
55
+ },
56
+ ),
57
+ ]
58
+ ```
59
+
60
+ Define the worker with the mock at the top of your app.
61
+
62
+ ```rescript
63
+ /* Mock.res */
64
+ let worker = Msw.setupWorker([]->Array.concat(Mock_Page_X.mocks))
65
+ ```
@@ -25,6 +25,7 @@ module Reference = Toolkit__Ui_Reference
25
25
  module RichText = Toolkit__Ui_RichText
26
26
  module ProgressBar = Toolkit__Ui_ProgressBar
27
27
  module Dropdown = Toolkit__Ui_Dropdown
28
+ module DropdownList = Toolkit__Ui_DropdownList
28
29
  module Layout = Toolkit__Ui_Layout
29
30
  module SpinnerFullScreen = Toolkit__Ui_SpinnerFullScreen
30
31
  module MultiSelect = Toolkit__Ui_MultiSelect
@@ -0,0 +1,166 @@
1
+ type position = [
2
+ | #bottom
3
+ | #top
4
+ ]
5
+
6
+ type item = {
7
+ icon: React.element,
8
+ label: React.element,
9
+ onClick: unit => unit,
10
+ className: string,
11
+ }
12
+
13
+ @react.component
14
+ let make = (
15
+ ~label: React.element,
16
+ ~dropdownClassName="",
17
+ ~buttonClassName="",
18
+ ~containerClassName="",
19
+ ~defaultIsOpen=false,
20
+ ~buttonColor: Toolkit__Ui_Button.color=#white,
21
+ ~buttonSize: Toolkit__Ui_Button.size=#md,
22
+ ~buttonVariant: Toolkit__Ui_Button.variant=#default,
23
+ ~position=#bottom,
24
+ ~items: array<item>,
25
+ ) => {
26
+ let dropdownRef = React.useRef(Js.Nullable.null)
27
+ let buttonRef = React.useRef(Js.Nullable.null)
28
+ let (position, _setPosition) = React.useState(() => position)
29
+ let {isOpen, hide, toggle} = Toolkit__Hooks.useDisclosure(~defaultIsOpen, ())
30
+ let {isXs} = Toolkit__Hooks.useMediaQuery()
31
+
32
+ Toolkit__Hooks.useOnClickOutside(dropdownRef, _ => {
33
+ hide()
34
+ })
35
+ let (adjustmentStyle, setAdjustmentStyle) = React.useState(() => None)
36
+
37
+ React.useEffect1(() => {
38
+ if isOpen && !isXs {
39
+ Js.Global.setTimeout(() => {
40
+ buttonRef.current
41
+ ->Js.Nullable.toOption
42
+ ->Option.forEach(dom => {
43
+ let buttonRect = dom->Browser.DomElement.getBoundingClientRect
44
+ let dropdownRect =
45
+ dropdownRef.current
46
+ ->Js.Nullable.toOption
47
+ ->Option.map(Browser.DomElement.getBoundingClientRect)
48
+
49
+ let calculatedTop = switch position {
50
+ | #bottom => buttonRect.top +. buttonRect.height +. 10.
51
+ | #top => {
52
+ let base = buttonRect.top -. 10.
53
+ switch dropdownRect {
54
+ | None => base
55
+ | Some({height}) => {
56
+ let newBase = base -. height /. 2.
57
+ newBase < 0. ? buttonRect.top +. buttonRect.height +. 10. : newBase
58
+ }
59
+ }
60
+ }
61
+ }
62
+
63
+ let calculatedLeft = {
64
+ let baseContainerCenter = buttonRect.left +. buttonRect.width /. 2.
65
+
66
+ switch dropdownRect {
67
+ | None => baseContainerCenter
68
+ | Some({width}) => {
69
+ let baseCalculated = baseContainerCenter -. width /. 2.
70
+ let hasOverflowLeft = baseCalculated < 0.
71
+ let hasOverflowRight =
72
+ baseContainerCenter +. width /. 2. > Browser.innerWidth->Int.toFloat
73
+
74
+ if hasOverflowLeft {
75
+ 0.
76
+ } else if hasOverflowRight {
77
+ let tmp = baseContainerCenter +. width /. 2.
78
+
79
+ baseCalculated -. (tmp -. Browser.innerWidth->Int.toFloat)
80
+ } else {
81
+ baseCalculated
82
+ }
83
+ }
84
+ }
85
+ }
86
+
87
+ setAdjustmentStyle(_ => Some(
88
+ ReactDOM.Style.make(
89
+ ~top=`${calculatedTop->Js.Float.toString}px`,
90
+ ~left=`${calculatedLeft->Js.Float.toString}px`,
91
+ ~opacity="1",
92
+ (),
93
+ ),
94
+ ))
95
+ })
96
+ }, 16)->ignore
97
+ } else {
98
+ setAdjustmentStyle(_ => None)
99
+ }
100
+ None
101
+ }, [isOpen, isXs])
102
+
103
+ <div className={cx(["relative", containerClassName])}>
104
+ <Toolkit__Ui_Button
105
+ variant=buttonVariant
106
+ buttonRef={ReactDOM.Ref.domRef(buttonRef)}
107
+ size=buttonSize
108
+ type_="button"
109
+ color=buttonColor
110
+ onClick={event => {
111
+ switch dropdownRef.current->Js.Nullable.toOption {
112
+ | None => toggle()
113
+ | Some(domRef) => {
114
+ let isInDropdown = ReactEvent.Mouse.currentTarget(event)["contains"](. domRef)
115
+ if !isInDropdown {
116
+ toggle()
117
+ }
118
+ }
119
+ }
120
+ }}
121
+ className={cx([buttonClassName, "dropdown-button"])}>
122
+ label
123
+ {isOpen
124
+ ? <ReachUi.Portal>
125
+ <div
126
+ ref={ReactDOM.Ref.domRef(dropdownRef)}
127
+ className={cx([
128
+ "dropdown",
129
+ "top-0 left-0 p-2 transform transition-opacity duration-150 ease-in-out shadow rounded text-base font-normal text-neutral-700",
130
+ isXs ? "fixed !w-full !h-full z-40" : "absolute z-20 bg-white w-60 opacity-0",
131
+ dropdownClassName,
132
+ ])}
133
+ style={adjustmentStyle->Option.getWithDefault(ReactDOM.Style.make())}>
134
+ {isXs
135
+ ? <div
136
+ className="bg-neutral-500 absolute left-0 top-0 w-full h-full opacity-50 z-30"
137
+ />
138
+ : React.null}
139
+ <div className={cx([isXs ? "z-40 absolute left-0 bottom-0 w-full" : ""])}>
140
+ <div
141
+ className={cx([
142
+ "flex flex-col",
143
+ isXs ? "bg-white p-2 mx-2 shadow rounded-t-lg" : "",
144
+ ])}>
145
+ {items
146
+ ->Array.mapWithIndex((i, {icon, label, onClick, className}) => {
147
+ <div
148
+ onClick={_ => onClick()}
149
+ className={cx([
150
+ "flex flex-row gap-4 items-center py-2 hover:bg-neutral-100 focus:bg-neutral-100 cursor-pointer",
151
+ i == 0 ? "" : "border-t",
152
+ className,
153
+ ])}
154
+ key={i->Int.toString}>
155
+ {icon} {label}
156
+ </div>
157
+ })
158
+ ->React.array}
159
+ </div>
160
+ </div>
161
+ </div>
162
+ </ReachUi.Portal>
163
+ : React.null}
164
+ </Toolkit__Ui_Button>
165
+ </div>
166
+ }
@@ -0,0 +1,25 @@
1
+ type position = [
2
+ | #bottom
3
+ | #top
4
+ ]
5
+
6
+ type item = {
7
+ icon: React.element,
8
+ label: React.element,
9
+ onClick: unit => unit,
10
+ className: string,
11
+ }
12
+
13
+ @react.component
14
+ let make: (
15
+ ~label: React.element,
16
+ ~dropdownClassName: string=?,
17
+ ~buttonClassName: string=?,
18
+ ~containerClassName: string=?,
19
+ ~defaultIsOpen: bool=?,
20
+ ~buttonColor: Toolkit__Ui_Button.color=?,
21
+ ~buttonSize: Toolkit__Ui_Button.size=?,
22
+ ~buttonVariant: Toolkit__Ui_Button.variant=?,
23
+ ~position: position=?,
24
+ ~items: array<item>,
25
+ ) => React.element
@@ -3,6 +3,8 @@ module LocalStorage = {
3
3
  external getItem: string => Js.Nullable.t<string> = "localStorage.getItem"
4
4
  @val
5
5
  external setItem: (string, Js.Nullable.t<string>) => unit = "localStorage.setItem"
6
+ @val
7
+ external removeItem: string => unit = "localStorage.removeItem"
6
8
  }
7
9
 
8
10
  module Location = {
@@ -1,6 +1,7 @@
1
1
  module LocalStorage: {
2
2
  let getItem: string => Js.Nullable.t<string>
3
3
  let setItem: (string, Js.Nullable.t<string>) => unit
4
+ let removeItem: string => unit
4
5
  }
5
6
 
6
7
  module Location: {
@@ -0,0 +1,32 @@
1
+ type mock
2
+ type worker
3
+
4
+ @module("msw") @variadic
5
+ external setupWorker: array<mock> => worker = "setupWorker"
6
+
7
+ @send
8
+ external start: worker => unit = "start"
9
+ @send
10
+ external stop: worker => unit = "stop"
11
+
12
+ module Ctx = {
13
+ type t
14
+
15
+ @send
16
+ external json: (t, 'a) => 'b = "json"
17
+ @send
18
+ external status: (t, 'a) => 'b = "status"
19
+ }
20
+
21
+ module Rest = {
22
+ type req<'params> = {params: 'params}
23
+
24
+ @module("msw") @scope("rest")
25
+ external get: (string, (req<'params>, @uncurry ('z => 't), Ctx.t) => 'a) => mock = "get"
26
+ @module("msw") @scope("rest")
27
+ external put: (string, (req<'params>, @uncurry ('z => 't), Ctx.t) => 'a) => mock = "put"
28
+ @module("msw") @scope("rest")
29
+ external post: (string, (req<'params>, @uncurry ('z => 't), Ctx.t) => 'a) => mock = "post"
30
+ @module("msw") @scope("rest")
31
+ external delete: (string, (req<'params>, @uncurry ('z => 't), Ctx.t) => 'a) => mock = "delete"
32
+ }