@colisweb/rescript-toolkit 5.15.5 → 5.16.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": "5.15.5",
3
+ "version": "5.16.0",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "clean": "rescript clean",
@@ -35,6 +35,11 @@
35
35
  "@greenlabs/ppx-spice": "0.2.1",
36
36
  "@headlessui/react": "1.7.18",
37
37
  "@headlessui/tailwindcss": "0.2.0",
38
+ "@radix-ui/react-dropdown-menu": "^2.1.1",
39
+ "@radix-ui/react-popover": "^1.1.1",
40
+ "@radix-ui/react-scroll-area": "^1.1.0",
41
+ "@radix-ui/react-toast": "^1.2.1",
42
+ "@radix-ui/react-tooltip": "1.1.2",
38
43
  "@reach/auto-id": "0.18.0",
39
44
  "@rescript/react": "0.12.0",
40
45
  "autoprefixer": "10.4.17",
@@ -128,19 +128,20 @@ module Make = (StateLenses: Config) => {
128
128
  switch type_ {
129
129
  | "password" =>
130
130
  <Toolkit__Ui_Tooltip
131
- tooltipClassName={"min-w-[200px] text-center"}
132
- label={showPassword
131
+ contentClassName={"min-w-[200px] text-center"}
132
+ tooltipContent={showPassword
133
133
  ? <ReactIntl.FormattedMessage defaultMessage="Masquer le mot de passe" />
134
- : <ReactIntl.FormattedMessage defaultMessage="Afficher le mot de passe" />}>
135
- <button
134
+ : <ReactIntl.FormattedMessage defaultMessage="Afficher le mot de passe" />}
135
+ triggerContent={<button
136
136
  type_="button"
137
137
  className="p-1 bg-neutral-300 rounded-r border border-gray-300 text-neutral-800"
138
138
  onClick={_ => setShowPassword(v => !v)}>
139
139
  {showPassword
140
140
  ? <ReactIcons.AiFillEyeInvisible size={26} />
141
141
  : <ReactIcons.AiFillEye size={26} />}
142
- </button>
143
- </Toolkit__Ui_Tooltip>
142
+ </button>}
143
+ />
144
+
144
145
  | _ => React.null
145
146
  }
146
147
  })}
@@ -163,7 +163,7 @@ let useClipboard = (~onCopyNotificationMessage: option<string>=?, value: string)
163
163
  let (hasCopied, setHasCopied) = React.useState(() => false)
164
164
  let onCopy = React.useCallback(() => {
165
165
  onCopyNotificationMessage->Option.forEach(message =>
166
- Toolkit__Ui_Snackbar.show(~title=message, ~variant=#success, ())->ignore
166
+ Toolkit__Ui_Snackbar.show(~title=message, ~variant=Success, ())->ignore
167
167
  )
168
168
 
169
169
  let didCopy = CopyToClipboard.copy(value)
@@ -40,3 +40,6 @@ module WeekDateFilter = Toolkit__Ui_WeekDateFilter
40
40
  module Coordinates = Toolkit__Ui_Coordinates
41
41
  module Autocomplete = Toolkit__Ui_Autocomplete
42
42
  module WeekdayNavigation = Toolkit__Ui_WeekdayNavigation
43
+ module Popover = Toolkit__Ui_Popover
44
+ module DropdownMenu = Toolkit__Ui_DropdownMenu
45
+ module ScrollArea = Toolkit__Ui_ScrollArea
@@ -0,0 +1,93 @@
1
+ open Radix
2
+ type item = {
3
+ label: React.element,
4
+ rightSlot?: React.element,
5
+ }
6
+
7
+ type itemContent = {
8
+ ...DropdownMenu.Item.options,
9
+ ...item,
10
+ }
11
+ type subTrigger = {...DropdownMenu.SubTrigger.options, ...item}
12
+
13
+ type content =
14
+ | Item(itemContent)
15
+ | Separator
16
+ | Label(React.element)
17
+ | SubItems(subTrigger, array<itemContent>)
18
+
19
+ %%private(let renderRightSlot = rightSlot => <div className="RightSlot"> rightSlot </div>)
20
+
21
+ @react.component
22
+ let make = (
23
+ ~trigger,
24
+ ~triggerOptions: option<DropdownMenu.Trigger.options>=?,
25
+ ~rootOptions: option<DropdownMenu.Root.options>=?,
26
+ ~contentOptions: option<DropdownMenu.Content.options>=?,
27
+ ~content: array<content>,
28
+ ) => {
29
+ <DropdownMenu.Root {...(rootOptions->Option.getWithDefault({})->Obj.magic)}>
30
+ <DropdownMenu.Trigger {...(triggerOptions->Option.getWithDefault({})->Obj.magic)}>
31
+ trigger
32
+ </DropdownMenu.Trigger>
33
+ <DropdownMenu.Portal>
34
+ <DropdownMenu.Content
35
+ {...(contentOptions->Option.getWithDefault({})->Obj.magic)}
36
+ className="DropdownMenuContent"
37
+ sideOffset={contentOptions
38
+ ->Option.flatMap(v => v.sideOffset)
39
+ ->Option.getWithDefault(5)}>
40
+ {content
41
+ ->Array.mapWithIndex((i, content) => {
42
+ switch content {
43
+ | Label(element) =>
44
+ <DropdownMenu.Label
45
+ key={i->Int.toString ++ "dropdown-menu-item"} className="DropdownMenuLabel">
46
+ element
47
+ </DropdownMenu.Label>
48
+ | Separator =>
49
+ <DropdownMenu.Separator
50
+ key={i->Int.toString ++ "dropdown-menu-item"} className="DropdownMenuSeparator"
51
+ />
52
+ | Item(item) =>
53
+ <DropdownMenu.Item
54
+ key={i->Int.toString ++ "dropdown-menu-item"}
55
+ className="DropdownMenuItem"
56
+ disabled=?{item.disabled}
57
+ onSelect=?{item.onSelect}>
58
+ {item.label}
59
+ {item.rightSlot->Option.mapWithDefault(React.null, renderRightSlot)}
60
+ </DropdownMenu.Item>
61
+
62
+ | SubItems(item, subContent) =>
63
+ <DropdownMenu.Sub key={i->Int.toString ++ "dropdown-menu-item"}>
64
+ <DropdownMenu.SubTrigger className="DropdownMenuSubTrigger" disabled=?{item.disabled}>
65
+ {item.label}
66
+ {item.rightSlot->Option.mapWithDefault(React.null, renderRightSlot)}
67
+ </DropdownMenu.SubTrigger>
68
+ <DropdownMenu.Portal>
69
+ <DropdownMenu.SubContent
70
+ className="DropdownMenuSubContent" sideOffset={2} alignOffset={-5}>
71
+ {subContent
72
+ ->Array.mapWithIndex((j, item) => {
73
+ <DropdownMenu.Item
74
+ key={i->Int.toString ++ j->Int.toString ++ "dropdown-menu-item"}
75
+ disabled=?{item.disabled}
76
+ onSelect=?{item.onSelect}
77
+ className="DropdownMenuItem">
78
+ {item.label}
79
+ {item.rightSlot->Option.mapWithDefault(React.null, renderRightSlot)}
80
+ </DropdownMenu.Item>
81
+ })
82
+ ->React.array}
83
+ </DropdownMenu.SubContent>
84
+ </DropdownMenu.Portal>
85
+ </DropdownMenu.Sub>
86
+ }
87
+ })
88
+ ->React.array}
89
+ <DropdownMenu.Arrow className="DropdownMenuArrow" />
90
+ </DropdownMenu.Content>
91
+ </DropdownMenu.Portal>
92
+ </DropdownMenu.Root>
93
+ }
@@ -0,0 +1,57 @@
1
+ open Radix
2
+
3
+ type color =
4
+ | White
5
+ | Black
6
+ | Neutral
7
+ | Primary
8
+
9
+ @react.component
10
+ let make = (
11
+ ~trigger,
12
+ ~triggerAsChild=false,
13
+ ~content,
14
+ ~color=White,
15
+ ~rootOptions: option<Popover.Root.options>=?,
16
+ ~contentOptions: option<Popover.Content.options>=?,
17
+ ) => {
18
+ let defaultContent: Popover.Content.props = {
19
+ children: <>
20
+ content
21
+ <Popover.Close
22
+ className="absolute w-6 h-6 right-2 top-2 inline-flex items-center justify-center rounded-full hover:bg-primary-500/25">
23
+ <ReactIcons.MdClose />
24
+ </Popover.Close>
25
+ <Popover.Arrow
26
+ className={switch color {
27
+ | White => "fill-white"
28
+ | Neutral => "fill-neutral-800"
29
+ | Black => "fill-neutral-900"
30
+ | Primary => "fill-primary-700"
31
+ }}
32
+ />
33
+ </>,
34
+ }
35
+ <Popover.Root {...(rootOptions->Option.getWithDefault({})->Obj.magic)}>
36
+ <Popover.Trigger asChild=triggerAsChild> trigger </Popover.Trigger>
37
+ <Popover.Portal>
38
+ <Popover.Content
39
+ {...contentOptions->Option.mapWithDefault(defaultContent, options => {
40
+ ...options->Obj.magic,
41
+ children: defaultContent.children,
42
+ })}
43
+ className={cx([
44
+ "rounded-lg w-72 p-4 shadow-lg z-50 cw-PopoverContent",
45
+ switch color {
46
+ | White => "bg-white text-neutral-800"
47
+ | Neutral => "bg-neutral-800 text-white"
48
+ | Black => "bg-neutral-900 text-white"
49
+ | Primary => "bg-primary-700 text-white"
50
+ },
51
+ contentOptions->Option.flatMap(v => v.className)->Option.getWithDefault(""),
52
+ ])}
53
+ sideOffset={contentOptions->Option.flatMap(v => v.sideOffset)->Option.getWithDefault(5)}
54
+ />
55
+ </Popover.Portal>
56
+ </Popover.Root>
57
+ }
@@ -0,0 +1,14 @@
1
+ open Radix
2
+ @react.component
3
+ let make = (~children, ~className="") => {
4
+ <ScrollArea.Root className={cx(["ScrollAreaRoot", className])}>
5
+ <ScrollArea.Viewport className="ScrollAreaViewport"> children </ScrollArea.Viewport>
6
+ <ScrollArea.Scrollbar className="ScrollAreaScrollbar" orientation=Vertical>
7
+ <ScrollArea.Thumb className="ScrollAreaThumb" />
8
+ </ScrollArea.Scrollbar>
9
+ <ScrollArea.Scrollbar className="ScrollAreaScrollbar" orientation=Horizontal>
10
+ <ScrollArea.Thumb className="ScrollAreaThumb" />
11
+ </ScrollArea.Scrollbar>
12
+ <ScrollArea.Corner className="ScrollAreaCorner" />
13
+ </ScrollArea.Root>
14
+ }
@@ -1,3 +1,5 @@
1
+ open Radix
2
+
1
3
  type rec state = {list: array<options>}
2
4
  and options = {
3
5
  id: id,
@@ -9,7 +11,7 @@ and options = {
9
11
  timeout: int,
10
12
  }
11
13
  and id
12
- and variant = [#success | #warning | #danger]
14
+ and variant = Success | Warning | Danger
13
15
 
14
16
  type action =
15
17
  | Show(options)
@@ -60,41 +62,37 @@ module Item = {
60
62
  let make = (~options) => {
61
63
  let {id, isVisible, variant, title, content, closable, timeout} = options
62
64
 
63
- let (mounted, setMounted) = React.useState(() => false)
64
-
65
65
  React.useEffect(() => {
66
- setMounted(_ => true)
67
-
68
66
  let timeoutId = Js.Global.setTimeout(() => {
69
67
  hide(id)
70
68
  }, timeout)
71
69
  Some(() => Js.Global.clearTimeout(timeoutId))
72
70
  }, [])
73
71
 
74
- <div
75
- style={ReactDOM.Style.make(~pointerEvents="all", ())}
72
+ <Toast.Root
73
+ open_={isVisible}
74
+ onOpenChange={v => v ? () : hide(id)}
76
75
  className={cx([
77
- "rounded shadow transition duration-500 ease-in-out mt-3 p-3 pl-3 pr-10 transform cw-snackbar",
78
- mounted ? "-translate-y-14" : "!opacity-0",
79
- isVisible ? "opacity-100" : "opacity-0 translate-x-[200px]",
76
+ "transition duration-500 ease-in-out mt-3 p-3 pl-3 pr-10 transform cw-snackbar ToastRoot",
80
77
  {
81
78
  switch variant {
82
- | #success => "bg-success-100 cw-snackbar--success"
83
- | #warning => "bg-warning-100 cw-snackbar--warning"
84
- | #danger => "bg-danger-100 cw-snackbar--danger"
79
+ | Success => "bg-success-100 cw-snackbar--success"
80
+ | Warning => "bg-warning-100 cw-snackbar--warning"
81
+ | Danger => "bg-danger-100 cw-snackbar--danger"
85
82
  }
86
83
  },
87
84
  ])}>
88
85
  {closable
89
- ? <button className="absolute right-1" onClick={_ => hide(id)}>
86
+ ? <button className="absolute top-2 right-1 " onClick={_ => hide(id)}>
90
87
  <ReactIcons.MdClose
91
88
  size={24}
92
89
  className={cx([
90
+ "rounded-full",
93
91
  {
94
92
  switch variant {
95
- | #success => "text-neutral-700"
96
- | #warning => "text-warning-700"
97
- | #danger => "text-danger-700"
93
+ | Success => "text-neutral-700 hover:bg-neutral-300"
94
+ | Warning => "text-warning-700 hover:bg-warning-300"
95
+ | Danger => "text-danger-700 hover:bg-danger-300"
98
96
  }
99
97
  },
100
98
  ])}
@@ -104,27 +102,29 @@ module Item = {
104
102
  <div className="flex flex-row gap-3 relative">
105
103
  <div>
106
104
  {switch variant {
107
- | #success => <ReactIcons.MdCheckCircle size=28 className="text-success-600" />
108
- | #warning => <ReactIcons.MdWarning size=28 className="text-warning-600" />
109
- | #danger => <ReactIcons.MdError size=28 className="text-danger-600" />
105
+ | Success => <ReactIcons.MdCheckCircle size=28 className="text-success-600" />
106
+ | Warning => <ReactIcons.MdWarning size=28 className="text-warning-600" />
107
+ | Danger => <ReactIcons.MdError size=28 className="text-danger-600" />
110
108
  }}
111
109
  </div>
112
- <div>
110
+ <Toast.Title className={"ToastTitle"}>
113
111
  <p
114
112
  className={cx([
115
- "text-lg cw-snackbar-title",
113
+ "text-lg cw-snackbar-title mb-1",
116
114
  switch variant {
117
- | #success => "text-neutral-700"
118
- | #warning => "text-warning-700"
119
- | #danger => "text-danger-700"
115
+ | Success => "text-neutral-700"
116
+ | Warning => "text-warning-700"
117
+ | Danger => "text-danger-700"
120
118
  },
121
119
  ])}>
122
120
  {title->React.string}
123
121
  </p>
124
- </div>
122
+ </Toast.Title>
125
123
  </div>
126
- {content->Option.getWithDefault(React.null)}
127
- </div>
124
+ {content->Option.mapWithDefault(React.null, content => {
125
+ <Toast.Description className="ToastDescription"> content </Toast.Description>
126
+ })}
127
+ </Toast.Root>
128
128
  }
129
129
  }
130
130
 
@@ -133,15 +133,13 @@ module Provider = {
133
133
  let make = () => {
134
134
  let {list} = store.useStore()
135
135
 
136
- <Toolkit__Ui_Portal>
137
- <div
138
- className="z-[2147483647] pointer-events-none fixed right-0 top-0 transform translate-y-14 h-full p-4 flex flex-col items-end justify-end">
139
- {list
140
- ->Array.map(e => {
141
- <Item key={e.id->Obj.magic} options=e />
142
- })
143
- ->React.array}
144
- </div>
145
- </Toolkit__Ui_Portal>
136
+ <Radix.Toast.Provider swipeDirection={Right}>
137
+ {list
138
+ ->Array.map(e => {
139
+ <Item key={e.id->Obj.magic} options=e />
140
+ })
141
+ ->React.array}
142
+ <Toast.Viewport className="ToastViewport" />
143
+ </Radix.Toast.Provider>
146
144
  }
147
145
  }
@@ -1,4 +1,4 @@
1
- type variant = [#success | #warning | #danger]
1
+ type variant = Success | Warning | Danger
2
2
  type id
3
3
 
4
4
  let show: (
@@ -23,6 +23,7 @@ let make = (
23
23
  ~tooltipClassName=?,
24
24
  ~children,
25
25
  ) => {
26
+ let disclosure = Toolkit__Hooks.useDisclosure()
26
27
  let sizeStyle = x =>
27
28
  switch x {
28
29
  | #xs => "text-xs leading-4 px-1"
@@ -59,11 +60,12 @@ let make = (
59
60
  }
60
61
  }
61
62
 
62
- <Toolkit__Ui_Tooltip
63
- canBeShowed={tooltipLabel->Option.isSome}
64
- label={tooltipLabel->Option.getWithDefault(React.null)}
65
- ?tooltipClassName>
66
- <div
63
+ <Toolkit__Ui_Tooltip.Controlled
64
+ open_={disclosure.isOpen}
65
+ onOpenChange={_ => tooltipLabel->Option.isSome ? disclosure.toggle() : ()}
66
+ tooltipContent={tooltipLabel->Option.getWithDefault(React.null)}
67
+ contentClassName=?tooltipClassName
68
+ triggerContent={<div
67
69
  className={cx([
68
70
  className,
69
71
  "inline-flex self-center rounded font-semibold max-w-xs",
@@ -71,6 +73,6 @@ let make = (
71
73
  sizeStyle(size),
72
74
  ])}>
73
75
  <span className="truncate"> children </span>
74
- </div>
75
- </Toolkit__Ui_Tooltip>
76
+ </div>}
77
+ />
76
78
  }
@@ -1,90 +1,69 @@
1
- type position = [
2
- | #bottom
3
- | #right
4
- | #top
5
- | #left
6
- ]
1
+ open Radix
2
+
3
+ type color =
4
+ | White
5
+ | Black
7
6
 
8
7
  @react.component
9
8
  let make = (
10
- ~label: React.element,
11
- ~children,
12
- ~tooltipClassName="",
13
- ~containerClassName="",
14
- ~triangleClassName="",
15
- ~position=#bottom,
16
- ~canBeShowed=true,
9
+ ~triggerContent,
10
+ ~tooltipContent,
11
+ ~side: Tooltip.side=Top,
12
+ ~sideOffset=5,
13
+ ~contentClassName="",
14
+ ~color=White,
15
+ ~defaultOpen=?,
17
16
  ) => {
18
- let ref = React.useRef(Js.Nullable.null)
19
- let (position, setPosition) = React.useState(() => position)
20
- let (children, isHover) = ReactUse.useHover(children)
21
- let (adjustmentStyle, setAdjustmentStyle) = React.useState(() => None)
22
-
23
- React.useEffect(() => {
24
- if isHover {
25
- ref.current
26
- ->Js.Nullable.toOption
27
- ->Option.forEach(dom => {
28
- let {left, width, right, top} = dom->Browser.DomElement.getBoundingClientRect
29
-
30
- let overflowFromRight = left +. width > Browser.innerWidth->Float.fromInt
31
- let overflowFromLeft = left <= 0.
32
-
33
- let overflowCorrection = {
34
- if overflowFromRight {
35
- Some(-.(width -. (Browser.innerWidth->Int.toFloat -. left)))
36
- } else if overflowFromLeft {
37
- Some(-.left)
38
- } else {
39
- None
40
- }
41
- }
42
-
43
- switch position {
44
- | #left if left < 0. => setPosition(_ => #right)
45
- | #right if right < 0. => setPosition(_ => #left)
46
- | #top if top < 0. => setPosition(_ => #bottom)
47
- | _ => ()
48
- }
49
-
50
- setAdjustmentStyle(
51
- _ => Some(
52
- ReactDOM.Style.make(
53
- ~marginLeft=overflowCorrection->Option.mapWithDefault(
54
- "",
55
- correction => `${correction->Js.Float.toString}px`,
56
- ),
57
- ~opacity="1",
58
- (),
59
- ),
60
- ),
61
- )
62
- })
63
- } else {
64
- setAdjustmentStyle(_ => None)
65
- }
66
- None
67
- }, (isHover, ref))
68
-
69
- <div className={cx(["relative", containerClassName])}>
70
- children
71
- {isHover && canBeShowed
72
- ? <div
73
- ref={ReactDOM.Ref.domRef(ref)}
17
+ <Tooltip.Provider>
18
+ <Tooltip.Root ?defaultOpen delayDuration={0}>
19
+ <Tooltip.Trigger> {triggerContent} </Tooltip.Trigger>
20
+ <Tooltip.Portal>
21
+ <Tooltip.Content
22
+ side
23
+ sideOffset
74
24
  className={cx([
75
- "dropdown",
76
- "absolute z-20 text-sm p-2 transform transition duration-150 ease-in-out shadow rounded font-normal bg-gray-800 text-white opacity-0",
77
- switch position {
78
- | #bottom => "left-1/2 top-full -translate-x-1/2 mt-2"
79
- | #top => "left-1/2 bottom-full -translate-x-1/2 mb-2"
80
- | #left => "right-full top-1/2 -translate-y-1/2 ml-2"
81
- | #right => "left-full top-1/2 -translate-y-1/2 ml-2"
82
- },
83
- tooltipClassName,
84
- ])}
85
- style={adjustmentStyle->Option.getWithDefault(ReactDOM.Style.make())}>
86
- label
87
- </div>
88
- : React.null}
89
- </div>
25
+ "TooltipContent",
26
+ `TooltipContent--${(color :> string)}`,
27
+ contentClassName,
28
+ ])}>
29
+ {tooltipContent}
30
+ <Tooltip.Arrow className="TooltipArrow" />
31
+ </Tooltip.Content>
32
+ </Tooltip.Portal>
33
+ </Tooltip.Root>
34
+ </Tooltip.Provider>
35
+ }
36
+
37
+ module Controlled = {
38
+ @react.component
39
+ let make = (
40
+ ~triggerContent,
41
+ ~tooltipContent,
42
+ ~side: Tooltip.side=Top,
43
+ ~sideOffset=5,
44
+ ~contentClassName="",
45
+ ~color=White,
46
+ ~open_,
47
+ ~onOpenChange,
48
+ ~defaultOpen=?,
49
+ ) => {
50
+ <Tooltip.Provider>
51
+ <Tooltip.Root ?defaultOpen delayDuration={0} open_ onOpenChange>
52
+ <Tooltip.Trigger> {triggerContent} </Tooltip.Trigger>
53
+ <Tooltip.Portal>
54
+ <Tooltip.Content
55
+ side
56
+ sideOffset
57
+ className={cx([
58
+ "TooltipContent",
59
+ `TooltipContent--${(color :> string)}`,
60
+ contentClassName,
61
+ ])}>
62
+ {tooltipContent}
63
+ <Tooltip.Arrow className="TooltipArrow" />
64
+ </Tooltip.Content>
65
+ </Tooltip.Portal>
66
+ </Tooltip.Root>
67
+ </Tooltip.Provider>
68
+ }
90
69
  }