@colisweb/rescript-toolkit 1.27.0 → 1.28.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": "1.27.0",
3
+ "version": "1.28.0",
4
4
  "scripts": {
5
5
  "clean": "rescript clean",
6
6
  "build": "rescript build",
@@ -132,7 +132,7 @@ let useClipboard = (~onCopyNotificationMessage: option<string>=?, value: string)
132
132
  let (hasCopied, setHasCopied) = React.useState(() => false)
133
133
  let onCopy = React.useCallback1(() => {
134
134
  onCopyNotificationMessage->Option.forEach(message =>
135
- Toolkit__Ui_Snackbar.show(~title=message, ~variant=#success, ())
135
+ Toolkit__Ui_Snackbar.show(~title=message, ~variant=#success, ())->ignore
136
136
  )
137
137
 
138
138
  let didCopy = BsCopyToClipboard.copy(value)
@@ -1,30 +1,43 @@
1
- type rec state = {
2
- isVisible: bool,
3
- options: option<options>,
4
- }
1
+ type rec state = {list: array<options>}
5
2
  and options = {
3
+ id: id,
4
+ isVisible: bool,
6
5
  title: string,
7
6
  description: option<React.element>,
8
7
  closable: bool,
9
8
  variant: variant,
10
9
  timeout: int,
11
10
  }
11
+ and id
12
12
  and variant = [#success | #warning | #danger]
13
13
 
14
14
  type action =
15
15
  | Show(options)
16
- | Hide
16
+ | Hide(id)
17
+ | Remove(id)
17
18
 
18
- let store = Restorative.createStore({isVisible: false, options: None}, (state, action) =>
19
+ let store = Restorative.createStore({list: []}, (state, action) =>
19
20
  switch action {
20
- | Show(options) => {isVisible: true, options: Some(options)}
21
- | Hide => {...state, isVisible: false}
21
+ | Show(options) => {list: state.list->Array.concat([options])}
22
+ | Hide(id) => {
23
+ list: state.list->Array.map(e =>
24
+ if e.id === id {
25
+ {...e, isVisible: false}
26
+ } else {
27
+ e
28
+ }
29
+ ),
30
+ }
31
+ | Remove(id) => {list: state.list->Array.keep(e => e.id !== id)}
22
32
  }
23
33
  )
24
34
 
25
- let show = (~title, ~description=?, ~closable=true, ~variant, ~timeout=5000, ()) =>
35
+ let show = (~title, ~description=?, ~closable=true, ~variant, ~timeout=5000, ()) => {
36
+ let id: id = Js.Math.random()->Js.Float.toString->Obj.magic
26
37
  store.dispatch(
27
38
  Show({
39
+ id: id,
40
+ isVisible: true,
28
41
  title: title,
29
42
  description: description,
30
43
  closable: closable,
@@ -32,76 +45,95 @@ let show = (~title, ~description=?, ~closable=true, ~variant, ~timeout=5000, ())
32
45
  timeout: timeout,
33
46
  }),
34
47
  )
35
- let hide = () => store.dispatch(Hide)
48
+ id
49
+ }
36
50
 
37
- module Provider = {
51
+ let remove = id => store.dispatch(Remove(id))
52
+ let hide = id => {
53
+ store.dispatch(Hide(id))
54
+ Js.Global.setTimeout(() => {
55
+ remove(id)
56
+ }, 160)->ignore
57
+ }
58
+
59
+ module Item = {
38
60
  @react.component
39
- let make = () => {
40
- let {isVisible, options} = store.useStore()
41
- let timeoutRef = React.useRef(None)
61
+ let make = (~options) => {
62
+ let {id, isVisible, variant, title, description, closable, timeout} = options
42
63
 
43
- React.useEffect1(() => {
44
- if isVisible {
45
- timeoutRef.current->Option.map(timeoutRef => timeoutRef->Js.Global.clearTimeout)->ignore
64
+ let (mounted, setMounted) = React.useState(() => false)
46
65
 
47
- let id = Js.Global.setTimeout(
48
- () => hide(),
49
- options->Option.mapWithDefault(5000, option => option.timeout),
50
- )
66
+ React.useEffect0(() => {
67
+ setMounted(_ => true)
68
+ let timeoutId = Js.Global.setTimeout(() => {
69
+ hide(id)
70
+ }, timeout)
71
+ Some(() => Js.Global.clearTimeout(timeoutId))
72
+ })
51
73
 
52
- timeoutRef.current = Some(id)
53
- }
54
- None
55
- }, [isVisible])
74
+ <div
75
+ className={cx([
76
+ "bg-white p-3 rounded shadow transition duration-500 ease-in-out mt-3 transform",
77
+ {
78
+ open Css
79
+ style(list{bottom(40->px)})
80
+ },
81
+ mounted ? "-translate-y-14 opacity-1" : "opacity-0",
82
+ isVisible ? "opacity-100 z-50" : "opacity-0 translate-x-[200px]",
83
+ {
84
+ switch variant {
85
+ | #success => "bg-success-500 text-white"
86
+ | #warning => "bg-warning-500 text-white"
87
+ | #danger => "bg-danger-500 text-white"
88
+ }
89
+ },
90
+ ])}>
91
+ <div className="flex flex-gap-3">
92
+ <div>
93
+ {switch variant {
94
+ | #success => <BsReactIcons.MdCheckCircle size=28 />
95
+ | #warning => <BsReactIcons.MdWarning size=28 />
96
+ | #danger => <BsReactIcons.MdError size=28 />
97
+ }}
98
+ </div>
99
+ <div>
100
+ <strong> {title->React.string} </strong>
101
+ {description->Option.mapWithDefault(React.null, description =>
102
+ <p
103
+ className={cx([
104
+ "text-sm",
105
+ {
106
+ open Css
107
+ style(list{maxWidth(250->px)})
108
+ },
109
+ ])}>
110
+ description
111
+ </p>
112
+ )}
113
+ </div>
114
+ {closable
115
+ ? <button className="self-start" onClick={_ => hide(id)}>
116
+ <BsReactIcons.MdClose />
117
+ </button>
118
+ : React.null}
119
+ </div>
120
+ </div>
121
+ }
122
+ }
123
+
124
+ module Provider = {
125
+ @react.component
126
+ let make = () => {
127
+ let {list} = store.useStore()
56
128
 
57
129
  <ReachUi.Portal>
58
130
  <div
59
- className={cx([
60
- "absolute bg-white p-3 rounded shadow transition duration-500 ease-in-out right-0 mr-6 transform",
61
- {
62
- open Css
63
- style(list{bottom(40->px)})
64
- },
65
- isVisible ? "opacity-100 translate-y-0 z-50" : "opacity-0 translate-y-10",
66
- options->Option.mapWithDefault("", options =>
67
- switch options.variant {
68
- | #success => "bg-success-500 text-white"
69
- | #warning => "bg-warning-500 text-white"
70
- | #danger => "bg-danger-500 text-white"
71
- }
72
- ),
73
- ])}>
74
- {options->Option.mapWithDefault(React.null, options =>
75
- <div className="flex flex-gap-3">
76
- <div>
77
- {switch options.variant {
78
- | #success => <BsReactIcons.MdCheckCircle size=28 />
79
- | #warning => <BsReactIcons.MdWarning size=28 />
80
- | #danger => <BsReactIcons.MdError size=28 />
81
- }}
82
- </div>
83
- <div>
84
- <strong> {options.title->React.string} </strong>
85
- {options.description->Option.mapWithDefault(React.null, description =>
86
- <p
87
- className={cx([
88
- "text-sm",
89
- {
90
- open Css
91
- style(list{maxWidth(250->px)})
92
- },
93
- ])}>
94
- description
95
- </p>
96
- )}
97
- </div>
98
- {options.closable
99
- ? <button className="self-start" onClick={_ => hide()}>
100
- <BsReactIcons.MdClose />
101
- </button>
102
- : React.null}
103
- </div>
104
- )}
131
+ className="fixed right-0 top-0 transform translate-y-14 h-full p-4 flex flex-col items-end justify-end">
132
+ {list
133
+ ->Array.map(e => {
134
+ <Item key={e.id->Obj.magic} options=e />
135
+ })
136
+ ->React.array}
105
137
  </div>
106
138
  </ReachUi.Portal>
107
139
  }
@@ -1,4 +1,5 @@
1
1
  type variant = [#success | #warning | #danger]
2
+ type id
2
3
 
3
4
  let show: (
4
5
  ~title: string,
@@ -7,8 +8,8 @@ let show: (
7
8
  ~variant: variant,
8
9
  ~timeout: int=?, // default: 5000
9
10
  unit,
10
- ) => unit
11
- let hide: unit => unit
11
+ ) => id
12
+ let hide: id => unit
12
13
 
13
14
  module Provider: {
14
15
  @react.component