@colisweb/rescript-toolkit 4.9.3 → 4.10.1

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": "4.9.3",
3
+ "version": "4.10.1",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "clean": "rescript clean",
@@ -36,3 +36,4 @@ module ErrorBoundary = Toolkit__Ui_ErrorBoundary
36
36
  module WeekDateFilter = Toolkit__Ui_WeekDateFilter
37
37
  module Coordinates = Toolkit__Ui_Coordinates
38
38
  module Autocomplete = Toolkit__Ui_Autocomplete
39
+ module WeekdayNavigation = Toolkit__Ui_WeekdayNavigation
@@ -0,0 +1,191 @@
1
+ open ReactIntl
2
+
3
+ type rec state = {
4
+ period: period,
5
+ selectedDay: Js.Date.t,
6
+ }
7
+ and period = {
8
+ start: Js.Date.t,
9
+ end_: Js.Date.t,
10
+ }
11
+
12
+ type action =
13
+ | PreviousPeriod
14
+ | NextPeriod
15
+ | UpdateSelectedDay(Js.Date.t)
16
+
17
+ let updateQueryParams = (state: state) => {
18
+ RescriptReactRouter.replace(
19
+ Qs.stringifyWithParams(
20
+ {
21
+ "start": Some(state.period.start),
22
+ "selectedDay": state.selectedDay,
23
+ }->Obj.magic,
24
+ Qs.makeParams(
25
+ ~addQueryPrefix=true,
26
+ ~serializeDate=d => d->DateFns.formatWithPattern("yyyy-MM-dd"),
27
+ (),
28
+ ),
29
+ ),
30
+ )
31
+ }
32
+
33
+ /*
34
+ * The key is a formatted date
35
+ * ex:
36
+ * {
37
+ * "2023-03-03": 2
38
+ * }
39
+ */
40
+ type dayCounter = Js.Dict.t<int>
41
+
42
+ @react.component
43
+ let make = (
44
+ ~startOfWeek: option<Js.Date.t>=?,
45
+ ~selectedDay: option<Js.Date.t>=?,
46
+ ~className="",
47
+ ~counters: option<dayCounter>=?,
48
+ ~periodLength=7,
49
+ ~children,
50
+ ) => {
51
+ let {isSm} = Toolkit__Hooks.useMediaQuery()
52
+
53
+ let ({period, selectedDay}, dispatch) = ReactUpdate.useReducerWithMapState(
54
+ (state, action) =>
55
+ switch action {
56
+ | PreviousPeriod =>
57
+ UpdateWithSideEffects(
58
+ {
59
+ period: {
60
+ start: state.period.start->DateFns.subDays(periodLength)->DateFns.startOfDay,
61
+ end_: state.period.start->DateFns.subDays(1),
62
+ },
63
+ selectedDay: state.period.start->DateFns.subDays(periodLength)->DateFns.startOfDay,
64
+ },
65
+ ({state}) => {
66
+ updateQueryParams(state)
67
+
68
+ None
69
+ },
70
+ )
71
+ | NextPeriod =>
72
+ UpdateWithSideEffects(
73
+ {
74
+ period: {
75
+ start: state.period.end_->DateFns.addDays(1),
76
+ end_: state.period.end_->DateFns.addDays(periodLength),
77
+ },
78
+ selectedDay: state.period.end_->DateFns.addDays(1),
79
+ },
80
+ ({state}) => {
81
+ updateQueryParams(state)
82
+
83
+ None
84
+ },
85
+ )
86
+ | UpdateSelectedDay(date) =>
87
+ UpdateWithSideEffects(
88
+ {...state, selectedDay: date},
89
+ ({state}) => {
90
+ updateQueryParams(state)
91
+ None
92
+ },
93
+ )
94
+ },
95
+ () => {
96
+ let defaultPeriod: period = {
97
+ let today = Js.Date.make()
98
+ let timeSlotStart = isSm ? DateFns.startOfWeek(today, {weekStartsOn: 1}) : today
99
+
100
+ let timeSlotEnd = isSm
101
+ ? DateFns.endOfWeek(today, {weekStartsOn: 1})
102
+ : today->DateFns.addDays(2)
103
+ {
104
+ start: timeSlotStart,
105
+ end_: timeSlotEnd,
106
+ }
107
+ }
108
+
109
+ {
110
+ period: startOfWeek->Option.mapWithDefault(defaultPeriod, start => {
111
+ start,
112
+ end_: start->DateFns.addDays(periodLength - 1),
113
+ }),
114
+ selectedDay: selectedDay->Option.getWithDefault(
115
+ startOfWeek->Option.getWithDefault(defaultPeriod.start),
116
+ ),
117
+ }
118
+ },
119
+ )
120
+ let intl = useIntl()
121
+
122
+ <div className>
123
+ <div className="border-neutral-300 flex justify-center items-center py-3">
124
+ <Toolkit__Ui_IconButton
125
+ size=#xs
126
+ variant=#outline
127
+ color=#neutral
128
+ onClick={_ => dispatch(PreviousPeriod)}
129
+ ariaLabel="previous week"
130
+ icon={<ReactIcons.MdKeyboardArrowLeft size=30 />}
131
+ />
132
+ <div className="flex items-center mx-2">
133
+ {DateFns.eachDayOfInterval({
134
+ start: period.start,
135
+ end_: period.end_,
136
+ })
137
+ ->Array.mapWithIndex((i, day) => {
138
+ let formattedDay =
139
+ intl->Intl.formatDateWithOptions(day, dateTimeFormatOptions(~weekday=#long, ()))
140
+
141
+ let isSelected = DateFns.isSameDay(selectedDay, day)
142
+
143
+ <React.Fragment key={i->Int.toString ++ "-day"}>
144
+ {i == 0 || day->DateFns.isFirstDayOfMonth
145
+ ? <p className="text-xs text-neutral-700 uppercase">
146
+ <FormattedDate value=day month=#short />
147
+ </p>
148
+ : React.null}
149
+ <div
150
+ onClick={_ => dispatch(UpdateSelectedDay(day))}
151
+ className="flex flex-col items-stretch w-16 mx-1 font-display">
152
+ <p
153
+ className={cx([
154
+ "flex flex-col items-center justify-center uppercase text-xs w-full rounded-sm leading-tight py-1",
155
+ isSelected ? "text-white bg-primary-700" : "text-primary-700 bg-primary-50",
156
+ ])}>
157
+ <span>
158
+ {formattedDay
159
+ ->Js.String2.slice(~from=0, ~to_=4)
160
+ ->Js.String2.concat(formattedDay->Js.String2.length > 4 ? "." : "")
161
+ ->React.string}
162
+ </span>
163
+ <span className="text-sm font-semibold">
164
+ <FormattedDate value=day day=#"2-digit" />
165
+ </span>
166
+ </p>
167
+ <p
168
+ className="text-xs text-info-500 bg-info-50 text-center mt-1 font-semibold rounded-sm">
169
+ {counters
170
+ ->Option.flatMap(counters =>
171
+ counters->Js.Dict.get(day->DateFns.formatWithPattern("yyyy-MM-dd"))
172
+ )
173
+ ->Option.mapWithDefault(React.null, React.int)}
174
+ </p>
175
+ </div>
176
+ </React.Fragment>
177
+ })
178
+ ->React.array}
179
+ </div>
180
+ <Toolkit__Ui_IconButton
181
+ size=#xs
182
+ variant=#outline
183
+ color=#neutral
184
+ onClick={_ => dispatch(NextPeriod)}
185
+ ariaLabel="next week"
186
+ icon={<ReactIcons.MdKeyboardArrowRight size=30 />}
187
+ />
188
+ </div>
189
+ {children({period, selectedDay})}
190
+ </div>
191
+ }