@swan-io/shared-business 2.7.37 → 3.0.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.
@@ -0,0 +1,620 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Option } from "@swan-io/boxed";
3
+ import { BottomPanel } from "@swan-io/lake/src/components/BottomPanel";
4
+ import { Box } from "@swan-io/lake/src/components/Box";
5
+ import { Fill } from "@swan-io/lake/src/components/Fill";
6
+ import { Icon } from "@swan-io/lake/src/components/Icon";
7
+ import { LakeButton } from "@swan-io/lake/src/components/LakeButton";
8
+ import { LakeLabel } from "@swan-io/lake/src/components/LakeLabel";
9
+ import { LakeSelect } from "@swan-io/lake/src/components/LakeSelect";
10
+ import { LakeText } from "@swan-io/lake/src/components/LakeText";
11
+ import { LakeTextInput } from "@swan-io/lake/src/components/LakeTextInput";
12
+ import { Popover } from "@swan-io/lake/src/components/Popover";
13
+ import { Pressable } from "@swan-io/lake/src/components/Pressable";
14
+ import { Separator } from "@swan-io/lake/src/components/Separator";
15
+ import { Space } from "@swan-io/lake/src/components/Space";
16
+ import { colors, spacings } from "@swan-io/lake/src/constants/design";
17
+ import { useDisclosure } from "@swan-io/lake/src/hooks/useDisclosure";
18
+ import { useFirstMountState } from "@swan-io/lake/src/hooks/useFirstMountState";
19
+ import { useResponsive } from "@swan-io/lake/src/hooks/useResponsive";
20
+ import { noop } from "@swan-io/lake/src/utils/function";
21
+ import { isNotNullish, isNotNullishOrEmpty, isNullishOrEmpty, } from "@swan-io/lake/src/utils/nullish";
22
+ import { getRifmProps } from "@swan-io/lake/src/utils/rifm";
23
+ import dayjs from "dayjs";
24
+ import { useCallback, useEffect, useId, useMemo, useRef, useState } from "react";
25
+ import { StyleSheet, View } from "react-native";
26
+ import { useForm } from "react-ux-form";
27
+ import { Rifm } from "rifm";
28
+ import { P, match } from "ts-pattern";
29
+ import { t } from "../utils/i18n";
30
+ import { LakeModal } from "./LakeModal";
31
+ const styles = StyleSheet.create({
32
+ label: {
33
+ flex: 1,
34
+ },
35
+ arrowContainer: {
36
+ height: 40, // input height
37
+ },
38
+ popover: {
39
+ padding: spacings[12],
40
+ },
41
+ popoverDesktop: {
42
+ padding: spacings[24],
43
+ },
44
+ rangeCalendarSide: {
45
+ flex: 1,
46
+ },
47
+ button: {
48
+ flex: 1,
49
+ },
50
+ monthSelect: {
51
+ width: 130,
52
+ },
53
+ yearSelect: {
54
+ width: 100,
55
+ },
56
+ weekRow: {
57
+ paddingVertical: spacings[4],
58
+ },
59
+ dayName: {
60
+ flex: 1,
61
+ height: 32,
62
+ alignItems: "center",
63
+ justifyContent: "center",
64
+ },
65
+ dayContainer: {
66
+ flex: 1,
67
+ alignItems: "center",
68
+ },
69
+ dayRangeIndicator: {
70
+ position: "absolute",
71
+ top: 0,
72
+ right: 0,
73
+ bottom: 0,
74
+ left: 0,
75
+ backgroundColor: colors.current[100],
76
+ },
77
+ dayStartRangeIndicator: {
78
+ left: "50%",
79
+ },
80
+ dayEndRangeIndicator: {
81
+ right: "50%",
82
+ },
83
+ dayNumber: {
84
+ width: 32,
85
+ height: 32,
86
+ alignItems: "center",
87
+ justifyContent: "center",
88
+ borderRadius: 16,
89
+ },
90
+ dayNumberFocused: {},
91
+ dayNumberHover: {
92
+ backgroundColor: colors.current[100],
93
+ },
94
+ dayNumberPressed: {},
95
+ dayNumberSelected: {
96
+ backgroundColor: colors.current[500],
97
+ },
98
+ todayIndicator: {
99
+ position: "absolute",
100
+ left: 0,
101
+ right: 0,
102
+ bottom: 0,
103
+ width: 4,
104
+ height: 4,
105
+ marginHorizontal: "auto",
106
+ borderRadius: 2,
107
+ backgroundColor: colors.current[500],
108
+ },
109
+ });
110
+ const MODALE_MOBILE_THRESHOLD = 600;
111
+ const DATE_PICKER_MOBILE_THRESHOLD = 400;
112
+ const DATE_RANGE_PICKER_THRESHOLD = 800;
113
+ const NB_DAYS_IN_WEEK = 7;
114
+ const weekDayIndex = {
115
+ sunday: 0,
116
+ monday: 1,
117
+ tuesday: 2,
118
+ wednesday: 3,
119
+ thursday: 4,
120
+ friday: 5,
121
+ saturday: 6,
122
+ };
123
+ const rifmDateProps = getRifmProps({
124
+ accept: "numeric",
125
+ charMap: { 2: "/", 4: "/" },
126
+ maxLength: 8,
127
+ });
128
+ const parseDate = (value, format) => {
129
+ const date = dayjs.utc(value, format);
130
+ return date.isValid()
131
+ ? Option.Some({ day: date.date(), month: date.month(), year: date.year() })
132
+ : Option.None();
133
+ };
134
+ const parseRange = (value, format) => {
135
+ return {
136
+ start: parseDate(value.start, format),
137
+ end: parseDate(value.end, format),
138
+ };
139
+ };
140
+ const stringifyDate = (value, format) => {
141
+ const date = dayjs.utc().year(value.year).month(value.month).date(value.day);
142
+ return date.format(format);
143
+ };
144
+ export const validateDateRangeOrder = (value, format) => {
145
+ const range = parseRange(value, format);
146
+ if (range.start.isNone() || range.end.isNone()) {
147
+ return true;
148
+ }
149
+ if (isDateAfter(range.start.value, range.end.value)) {
150
+ return false;
151
+ }
152
+ return true;
153
+ };
154
+ const range = (start, end) => {
155
+ const result = [];
156
+ for (let i = start; i <= end; i++) {
157
+ result.push(i);
158
+ }
159
+ return result;
160
+ };
161
+ const groupEvery = (input, groupSize) => {
162
+ const result = [];
163
+ const nbGroups = Math.ceil(input.length / groupSize);
164
+ for (let i = 0; i < nbGroups; i++) {
165
+ result.push(input.slice(i * groupSize, (i + 1) * groupSize));
166
+ }
167
+ return result;
168
+ };
169
+ const padEnd = (input, length, value) => {
170
+ const itemsToAppend = new Array(length - input.length).fill(value);
171
+ return [...input, ...itemsToAppend];
172
+ };
173
+ export const isTodayOrFutureDate = (date) => {
174
+ const yesterday = new Date();
175
+ yesterday.setDate(yesterday.getDate() - 1);
176
+ const yesterdayDate = {
177
+ day: yesterday.getDate(),
178
+ month: yesterday.getMonth(),
179
+ year: yesterday.getFullYear(),
180
+ };
181
+ return isDateAfter(date, yesterdayDate);
182
+ };
183
+ export const isDateInRange = (minDate, maxDate) => (date) => {
184
+ const min = {
185
+ day: minDate.getDate(),
186
+ month: minDate.getMonth(),
187
+ year: minDate.getFullYear(),
188
+ };
189
+ const max = {
190
+ day: maxDate.getDate(),
191
+ month: maxDate.getMonth(),
192
+ year: maxDate.getFullYear(),
193
+ };
194
+ return isDateAfter(date, min) && isDateBefore(date, max);
195
+ };
196
+ const isDateToday = (date) => {
197
+ const today = new Date();
198
+ return (date.day === today.getDate() &&
199
+ date.month === today.getMonth() &&
200
+ date.year === today.getFullYear());
201
+ };
202
+ const getMonthDates = (month, year) => {
203
+ const aggregate = (acc, date) => {
204
+ const dateDay = date.getDate();
205
+ const dateMonth = date.getMonth();
206
+ const dateYear = date.getFullYear();
207
+ if (date.getMonth() !== month) {
208
+ return acc;
209
+ }
210
+ return aggregate([...acc, { day: dateDay, month: dateMonth, year: dateYear }], new Date(year, month, dateDay + 1));
211
+ };
212
+ return aggregate([], new Date(year, month, 1));
213
+ };
214
+ const getMonthWeeks = (month, year, firstWeekDay) => {
215
+ const firstWeekDayIndex = weekDayIndex[firstWeekDay];
216
+ const monthFirstWeekDay = new Date(year, month, 1).getDay();
217
+ const monthDates = getMonthDates(month, year).map(date => Option.Some(date));
218
+ const nbDaysToPrepend = monthFirstWeekDay >= firstWeekDayIndex
219
+ ? monthFirstWeekDay - firstWeekDayIndex
220
+ : NB_DAYS_IN_WEEK - firstWeekDayIndex + monthFirstWeekDay;
221
+ for (let i = 0; i < nbDaysToPrepend; i++) {
222
+ monthDates.unshift(Option.None());
223
+ }
224
+ const weeks = groupEvery(monthDates, NB_DAYS_IN_WEEK);
225
+ const lastWeek = weeks[weeks.length - 1];
226
+ if (!lastWeek) {
227
+ return weeks;
228
+ }
229
+ weeks[weeks.length - 1] = padEnd(lastWeek, NB_DAYS_IN_WEEK, Option.None());
230
+ return weeks;
231
+ };
232
+ const getWeekDayNames = (dayNames, firstWeekDay = "sunday") => {
233
+ const firstWeekDayIndex = weekDayIndex[firstWeekDay];
234
+ const firstWeekDayNames = dayNames.slice(firstWeekDayIndex);
235
+ const lastWeekDayNames = dayNames.slice(0, firstWeekDayIndex);
236
+ // @ts-expect-error
237
+ return [...firstWeekDayNames, ...lastWeekDayNames];
238
+ };
239
+ const isDateEquals = (date1, date2) => {
240
+ return date1.day === date2.day && date1.month === date2.month && date1.year === date2.year;
241
+ };
242
+ const isDateBefore = (date1, date2) => {
243
+ return (date1.year < date2.year ||
244
+ (date1.year === date2.year && date1.month < date2.month) ||
245
+ (date1.year === date2.year && date1.month === date2.month && date1.day < date2.day));
246
+ };
247
+ const isDateAfter = (date1, date2) => {
248
+ return (date1.year > date2.year ||
249
+ (date1.year === date2.year && date1.month > date2.month) ||
250
+ (date1.year === date2.year && date1.month === date2.month && date1.day > date2.day));
251
+ };
252
+ const isDateRange = (value) => {
253
+ return match(value)
254
+ .with({ start: P._, end: P._ }, () => true)
255
+ .otherwise(() => false);
256
+ };
257
+ const isSelectedDate = (date, value) => {
258
+ return match(value)
259
+ .with(Option.pattern.Some(P.select()), value => isDateEquals(value, date))
260
+ .with(Option.pattern.None, () => false)
261
+ .with(P.when(isDateRange), ({ start, end }) => {
262
+ // if range is invalid, we don't display any selection
263
+ if (start.isSome() && end.isSome() && isDateAfter(start.value, end.value)) {
264
+ return false;
265
+ }
266
+ return (start.match({
267
+ Some: start => isDateEquals(start, date),
268
+ None: () => false,
269
+ }) ||
270
+ end.match({
271
+ Some: end => isDateEquals(end, date),
272
+ None: () => false,
273
+ }));
274
+ })
275
+ .exhaustive();
276
+ };
277
+ const getRangeIndicatorType = (date, value) => {
278
+ if (!isDateRange(value)) {
279
+ return "none";
280
+ }
281
+ const { start, end } = value;
282
+ if (start.isNone() || end.isNone()) {
283
+ return "none";
284
+ }
285
+ const startDate = start.value;
286
+ const endDate = end.value;
287
+ // no interval indicator if range is invalid
288
+ if (isDateAfter(startDate, endDate)) {
289
+ return "none";
290
+ }
291
+ if (isDateEquals(startDate, endDate)) {
292
+ return "none";
293
+ }
294
+ if (isDateEquals(date, startDate)) {
295
+ return "start";
296
+ }
297
+ if (isDateEquals(date, endDate)) {
298
+ return "end";
299
+ }
300
+ if (isDateAfter(date, startDate) && isDateBefore(date, endDate)) {
301
+ return "between";
302
+ }
303
+ return "none";
304
+ };
305
+ const computeDateDistanceInDays = (date1, date2) => {
306
+ const date1Date = new Date(date1.year, date1.month, date1.day);
307
+ const date2Date = new Date(date2.year, date2.month, date2.day);
308
+ const diffInMs = Math.abs(date2Date.getTime() - date1Date.getTime());
309
+ return Math.round(diffInMs / (1000 * 3600 * 24));
310
+ };
311
+ const getNewDateRange = (currentRange, selectedDate) => {
312
+ const { start, end } = currentRange;
313
+ // Handle initial selection
314
+ if (start.isNone()) {
315
+ return { start: Option.Some(selectedDate), end: Option.None() };
316
+ }
317
+ if (end.isNone()) {
318
+ if (isDateAfter(selectedDate, start.value)) {
319
+ return { start, end: Option.Some(selectedDate) };
320
+ }
321
+ return { start: Option.Some(selectedDate), end: currentRange.start };
322
+ }
323
+ // Handle selection outside of the current range
324
+ if (isDateBefore(selectedDate, start.value)) {
325
+ return { start: Option.Some(selectedDate), end: currentRange.end };
326
+ }
327
+ if (isDateAfter(selectedDate, end.value)) {
328
+ return { start: currentRange.start, end: Option.Some(selectedDate) };
329
+ }
330
+ // We change the closest date to the new date
331
+ const startDistance = computeDateDistanceInDays(start.value, selectedDate);
332
+ const endDistance = computeDateDistanceInDays(end.value, selectedDate);
333
+ if (startDistance < endDistance) {
334
+ return { start: Option.Some(selectedDate), end: currentRange.end };
335
+ }
336
+ return { start: currentRange.start, end: Option.Some(selectedDate) };
337
+ };
338
+ const getTodayYearMonth = () => ({
339
+ month: new Date().getMonth(),
340
+ year: new Date().getFullYear(),
341
+ });
342
+ const getYearMonth = (value, format) => {
343
+ if (isNullishOrEmpty(value)) {
344
+ return Option.None();
345
+ }
346
+ return parseDate(value, format);
347
+ };
348
+ const isYearMonthBefore = (date1, date2) => {
349
+ return date1.year < date2.year || (date1.year === date2.year && date1.month < date2.month);
350
+ };
351
+ const isYearMonthEquals = (date1, date2) => {
352
+ return date1.year === date2.year && date1.month === date2.month;
353
+ };
354
+ const minYearMonth = (date1, date2) => {
355
+ return isYearMonthBefore(date1, date2) ? date1 : date2;
356
+ };
357
+ const maxYearMonth = (date1, date2) => {
358
+ return isYearMonthBefore(date1, date2) ? date2 : date1;
359
+ };
360
+ const incrementYearMonth = ({ month, year }) => {
361
+ if (month === 11) {
362
+ return { month: 0, year: year + 1 };
363
+ }
364
+ return { month: month + 1, year };
365
+ };
366
+ const decrementYearMonth = ({ month, year }) => {
367
+ if (month === 0) {
368
+ return { month: 11, year: year - 1 };
369
+ }
370
+ return { month: month - 1, year };
371
+ };
372
+ const YearMonthSelect = ({ monthNames, value, arrowsPosition = "right", hideArrows, minValue, maxValue, onChange, }) => {
373
+ const monthItems = useMemo(() => monthNames.map((name, index) => ({ name, value: index })), [monthNames]);
374
+ const yearItems = useMemo(() => range(value.year - 5, value.year + 5).map(year => ({
375
+ name: year.toString(),
376
+ value: year,
377
+ })), [value.year]);
378
+ const selectMonth = (month) => {
379
+ onChange({ year: value.year, month });
380
+ };
381
+ const selectYear = (year) => {
382
+ onChange({ year, month: value.month });
383
+ };
384
+ const setPreviousMonth = () => {
385
+ onChange(decrementYearMonth(value));
386
+ };
387
+ const setNextMonth = () => {
388
+ onChange(incrementYearMonth(value));
389
+ };
390
+ const isPreviousDisabled = !minValue
391
+ ? false
392
+ : value.year <= minValue.year && value.month <= minValue.month;
393
+ const isNextDisabled = !maxValue
394
+ ? false
395
+ : value.year >= maxValue.year && value.month >= maxValue.month;
396
+ return (_jsxs(Box, { direction: "row", alignItems: "center", children: [arrowsPosition === "around" && hideArrows !== true && (_jsxs(_Fragment, { children: [_jsx(LakeButton, { size: "small", mode: "tertiary", icon: "arrow-left-filled", disabled: isPreviousDisabled, onPress: setPreviousMonth, ariaLabel: t("datePicker.month.previous") }), _jsx(Fill, { minWidth: 12 })] })), _jsx(LakeSelect, { items: monthItems, value: value.month, onValueChange: selectMonth, mode: "borderless", size: "small", hideErrors: true, style: styles.monthSelect }), _jsx(LakeSelect, { items: yearItems, value: value.year, onValueChange: selectYear, mode: "borderless", size: "small", hideErrors: true, style: styles.yearSelect }), hideArrows !== true && (_jsxs(_Fragment, { children: [_jsx(Fill, { minWidth: 12 }), arrowsPosition === "right" && (_jsxs(_Fragment, { children: [_jsx(LakeButton, { size: "small", mode: "tertiary", icon: "arrow-left-filled", disabled: isPreviousDisabled, onPress: setPreviousMonth, ariaLabel: t("datePicker.month.previous") }), _jsx(Space, { width: 12 })] })), _jsx(LakeButton, { size: "small", mode: "tertiary", icon: "arrow-right-filled", disabled: isNextDisabled, onPress: setNextMonth, ariaLabel: t("datePicker.month.next") })] }))] }));
397
+ };
398
+ const MonthCalendar = ({ month, year, value, firstWeekDay, weekDayNames, isSelectable, onChange, }) => {
399
+ const dayNames = useMemo(() => getWeekDayNames(weekDayNames, firstWeekDay), [weekDayNames, firstWeekDay]);
400
+ const weeks = useMemo(() => getMonthWeeks(month, year, firstWeekDay), [month, year, firstWeekDay]);
401
+ return (_jsxs(View, { children: [_jsx(Box, { direction: "row", alignItems: "center", style: styles.weekRow, children: dayNames.map(dayName => (_jsx(View, { style: styles.dayName, children: _jsx(LakeText, { variant: "medium", color: colors.gray[600], children: dayName.substring(0, 1) }) }, dayName))) }), weeks.map((week, weekIndex) => (_jsx(Box, { direction: "row", alignItems: "center", style: styles.weekRow, children: week.map((date, dateIndex) => {
402
+ const isDisabled = date.match({
403
+ Some: date => isNotNullish(isSelectable) && !isSelectable(date),
404
+ None: () => true,
405
+ });
406
+ const isSelected = date.match({
407
+ Some: date => isSelectedDate(date, value),
408
+ None: () => false,
409
+ });
410
+ const isToday = date.match({
411
+ Some: date => isDateToday(date),
412
+ None: () => false,
413
+ });
414
+ const rangeIndicator = date.match({
415
+ Some: date => getRangeIndicatorType(date, value),
416
+ None: () => "none",
417
+ });
418
+ return (_jsxs(View, { style: styles.dayContainer, children: [rangeIndicator !== "none" && (_jsx(View, { style: [
419
+ styles.dayRangeIndicator,
420
+ rangeIndicator === "start" && styles.dayStartRangeIndicator,
421
+ rangeIndicator === "end" && styles.dayEndRangeIndicator,
422
+ ] })), _jsxs(Pressable, { disabled: isDisabled, onPress: () => date.match({ Some: onChange, None: noop }), style: ({ focused, hovered, pressed }) => [
423
+ styles.dayNumber,
424
+ focused && styles.dayNumberFocused,
425
+ hovered && styles.dayNumberHover,
426
+ pressed && styles.dayNumberPressed,
427
+ isSelected && styles.dayNumberSelected,
428
+ ], children: [_jsx(LakeText, { variant: "smallRegular", color: isSelected
429
+ ? colors.current.contrast
430
+ : isDisabled
431
+ ? colors.gray[300]
432
+ : isToday
433
+ ? colors.current[500]
434
+ : colors.gray[900], children: date.match({ Some: ({ day }) => day.toString(), None: () => " " }) }), isToday && _jsx(View, { style: styles.todayIndicator })] })] }, dateIndex));
435
+ }) }, weekIndex)))] }));
436
+ };
437
+ const DatePickerPopoverContent = ({ value, format, firstWeekDay, monthNames, weekDayNames, desktop, isSelectable, onChange, }) => {
438
+ const [monthYear, setMonthYear] = useState(() => getYearMonth(value, format).getWithDefault(getTodayYearMonth()));
439
+ // Automatically change displayed year and month when user change the value with text input
440
+ useEffect(() => {
441
+ const yearMonth = getYearMonth(value, format);
442
+ if (yearMonth.isSome()) {
443
+ setMonthYear(yearMonth.value);
444
+ }
445
+ }, [value, format]);
446
+ const handleChange = useCallback((date) => {
447
+ const formatted = stringifyDate(date, format);
448
+ onChange(formatted);
449
+ }, [format, onChange]);
450
+ return (_jsxs(_Fragment, { children: [_jsx(YearMonthSelect, { monthNames: monthNames, value: monthYear, hideArrows: !desktop, onChange: setMonthYear }), _jsx(Space, { height: 24 }), _jsx(MonthCalendar, { month: monthYear.month, year: monthYear.year, value: isNotNullishOrEmpty(value) ? parseDate(value, format) : Option.None(), firstWeekDay: firstWeekDay, weekDayNames: weekDayNames, isSelectable: isSelectable, onChange: handleChange })] }));
451
+ };
452
+ export const DatePicker = ({ label, value, error, format, firstWeekDay, monthNames, weekDayNames, isSelectable, onChange, }) => {
453
+ const { desktop } = useResponsive(DATE_PICKER_MOBILE_THRESHOLD);
454
+ const ref = useRef(null);
455
+ const [isOpened, { open, close }] = useDisclosure(false);
456
+ const popoverId = useId();
457
+ return (_jsxs(_Fragment, { children: [_jsx(Box, { direction: "row", alignItems: "end", children: _jsx(LakeLabel, { label: label, style: styles.label, actions: _jsx(LakeButton, { mode: "secondary", icon: "calendar-ltr-regular", size: "small", onPress: open, ariaLabel: t("common.open") }), render: id => (_jsx(Rifm, { value: value ?? "", onChange: onChange, ...rifmDateProps, children: ({ value, onChange }) => (_jsx(LakeTextInput, { ref: ref, id: id, placeholder: format, value: value, error: error, onChange: onChange, ariaExpanded: isOpened })) })) }) }), _jsx(Popover, { id: popoverId, role: "dialog", onDismiss: close, referenceRef: ref, visible: isOpened, children: _jsx(View, { style: desktop ? styles.popoverDesktop : styles.popover, children: _jsx(DatePickerPopoverContent, { value: value, format: format, firstWeekDay: firstWeekDay, monthNames: monthNames, weekDayNames: weekDayNames, desktop: desktop, isSelectable: isSelectable, onChange: onChange }) }) })] }));
458
+ };
459
+ export const DatePickerModal = ({ value, format, firstWeekDay, monthNames, weekDayNames, isSelectable, onChange, visible, label, cancelLabel, confirmLabel, validate, onDissmiss, }) => {
460
+ const { desktop } = useResponsive(DATE_PICKER_MOBILE_THRESHOLD);
461
+ const { Field, submitForm, setFieldValue, resetField } = useForm({
462
+ date: {
463
+ initialValue: value ?? "",
464
+ validate,
465
+ },
466
+ });
467
+ const handleCancel = () => {
468
+ setFieldValue("date", value ?? "");
469
+ onDissmiss();
470
+ };
471
+ const handleConfirm = () => {
472
+ submitForm(({ date }) => {
473
+ if (isNotNullishOrEmpty(date)) {
474
+ onChange(date);
475
+ }
476
+ onDissmiss();
477
+ });
478
+ };
479
+ useEffect(() => {
480
+ if (!visible) {
481
+ resetField("date");
482
+ }
483
+ }, [visible, resetField]);
484
+ return (_jsxs(DateModal, { visible: visible, maxWidth: 500, onPressClose: handleCancel, children: [_jsx(Field, { name: "date", children: ({ ref, value, error, onBlur, onChange }) => (_jsxs(_Fragment, { children: [_jsx(LakeLabel, { label: label, render: id => (_jsx(Rifm, { value: value, onChange: onChange, ...rifmDateProps, children: ({ value, onChange }) => (_jsx(LakeTextInput, { ref: ref, id: id, placeholder: format, value: value, error: error, onBlur: onBlur, onChange: onChange })) })) }), _jsx(DatePickerPopoverContent, { value: value, format: format, firstWeekDay: firstWeekDay, monthNames: monthNames, weekDayNames: weekDayNames, desktop: desktop, isSelectable: isSelectable, onChange: onChange })] })) }), _jsx(Space, { height: 24 }), _jsxs(Box, { direction: "row", alignItems: "center", children: [_jsx(LakeButton, { mode: "secondary", size: "small", onPress: handleCancel, style: styles.button, children: cancelLabel }), _jsx(Space, { width: 24 }), _jsx(LakeButton, { color: "current", size: "small", onPress: handleConfirm, style: styles.button, children: confirmLabel })] })] }));
485
+ };
486
+ const DateModal = ({ children, visible, maxWidth, withCloseButton, onPressClose, }) => {
487
+ const { desktop } = useResponsive(MODALE_MOBILE_THRESHOLD);
488
+ if (desktop) {
489
+ return (_jsx(LakeModal, { visible: visible, maxWidth: maxWidth, onPressClose: withCloseButton === true ? onPressClose : undefined, children: children }));
490
+ }
491
+ return (_jsx(BottomPanel, { visible: visible, onPressClose: onPressClose, children: _jsx(View, { style: styles.popover, children: children }) }));
492
+ };
493
+ const DateRangePickerModalContent = ({ value, format, firstWeekDay, monthNames, weekDayNames, desktop, displayTwoCalendar, isSelectable, onChange, }) => {
494
+ const isFirstMount = useFirstMountState();
495
+ const [periods, setPeriods] = useState(() => {
496
+ const startYearMonth = getYearMonth(value.start, format).getWithDefault(getTodayYearMonth());
497
+ const endYearMonth = getYearMonth(value.end, format).getWithDefault(incrementYearMonth(startYearMonth));
498
+ return {
499
+ start: startYearMonth,
500
+ end: isYearMonthEquals(startYearMonth, endYearMonth)
501
+ ? incrementYearMonth(startYearMonth)
502
+ : endYearMonth,
503
+ };
504
+ });
505
+ // Automatically change displayed year and month when start date changes
506
+ useEffect(() => {
507
+ if (isFirstMount) {
508
+ return;
509
+ }
510
+ const startYearMonth = getYearMonth(value.start, format);
511
+ if (startYearMonth.isSome()) {
512
+ setPeriods(periods => {
513
+ const isStartAndEndSameMonth = isYearMonthEquals(startYearMonth.value, periods.end);
514
+ if (isStartAndEndSameMonth) {
515
+ return {
516
+ start: decrementYearMonth(periods.end),
517
+ end: periods.end,
518
+ };
519
+ }
520
+ // change end period if it becomes before start period
521
+ const endPeriod = maxYearMonth(periods.end, incrementYearMonth(startYearMonth.value));
522
+ return {
523
+ start: startYearMonth.value,
524
+ end: endPeriod,
525
+ };
526
+ });
527
+ }
528
+ }, [isFirstMount, value.start, format]);
529
+ // Automatically change displayed year and month when end date changes
530
+ useEffect(() => {
531
+ if (isFirstMount) {
532
+ return;
533
+ }
534
+ const endYearMonth = getYearMonth(value.end, format);
535
+ if (endYearMonth.isSome()) {
536
+ setPeriods(periods => {
537
+ const isStartAndEndSameMonth = isYearMonthEquals(periods.start, endYearMonth.value);
538
+ if (isStartAndEndSameMonth) {
539
+ return {
540
+ start: periods.start,
541
+ end: incrementYearMonth(periods.start),
542
+ };
543
+ }
544
+ // change start period if it becomes after end period
545
+ const startPeriod = minYearMonth(periods.start, decrementYearMonth(endYearMonth.value));
546
+ return {
547
+ start: startPeriod,
548
+ end: endYearMonth.value,
549
+ };
550
+ });
551
+ }
552
+ }, [isFirstMount, value.end, format]);
553
+ const setStartPeriod = useCallback((yearMonth) => {
554
+ setPeriods(periods => ({
555
+ start: yearMonth,
556
+ end: maxYearMonth(periods.end, incrementYearMonth(yearMonth)),
557
+ }));
558
+ }, []);
559
+ const setEndPeriod = useCallback((yearMonth) => {
560
+ setPeriods(periods => ({
561
+ start: minYearMonth(periods.start, decrementYearMonth(yearMonth)),
562
+ end: yearMonth,
563
+ }));
564
+ }, []);
565
+ const dateRange = useMemo(() => parseRange(value, format), [value, format]);
566
+ const handleSelectDate = (date) => {
567
+ const newRange = getNewDateRange(dateRange, date);
568
+ const newValue = {
569
+ start: newRange.start.match({
570
+ Some: date => stringifyDate(date, format),
571
+ None: () => value.start,
572
+ }),
573
+ end: newRange.end.match({
574
+ Some: date => stringifyDate(date, format),
575
+ None: () => value.end,
576
+ }),
577
+ };
578
+ onChange(newValue);
579
+ };
580
+ if (!displayTwoCalendar) {
581
+ return (_jsxs(_Fragment, { children: [_jsx(YearMonthSelect, { monthNames: monthNames, value: periods.start, hideArrows: !desktop, onChange: setStartPeriod }), _jsx(Space, { height: 24 }), _jsx(MonthCalendar, { month: periods.start.month, year: periods.start.year, value: dateRange, firstWeekDay: firstWeekDay, weekDayNames: weekDayNames, isSelectable: isSelectable, onChange: handleSelectDate })] }));
582
+ }
583
+ return (_jsx(View, { children: _jsxs(Box, { direction: "row", alignItems: "start", children: [_jsxs(View, { style: styles.rangeCalendarSide, children: [_jsx(YearMonthSelect, { monthNames: monthNames, value: periods.start, maxValue: decrementYearMonth(periods.end), arrowsPosition: "around", onChange: setStartPeriod }), _jsx(Space, { height: 24 }), _jsx(MonthCalendar, { month: periods.start.month, year: periods.start.year, value: dateRange, firstWeekDay: firstWeekDay, weekDayNames: weekDayNames, isSelectable: isSelectable, onChange: handleSelectDate })] }), _jsx(Separator, { space: 24, horizontal: true }), _jsxs(View, { style: styles.rangeCalendarSide, children: [_jsx(YearMonthSelect, { monthNames: monthNames, value: periods.end, minValue: incrementYearMonth(periods.start), arrowsPosition: "around", onChange: setEndPeriod }), _jsx(Space, { height: 24 }), _jsx(MonthCalendar, { month: periods.end.month, year: periods.end.year, value: dateRange, firstWeekDay: firstWeekDay, weekDayNames: weekDayNames, isSelectable: isSelectable, onChange: handleSelectDate })] })] }) }));
584
+ };
585
+ export const DateRangePicker = ({ value, error, format, startLabel, endLabel, firstWeekDay, monthNames, weekDayNames, isSelectable, onChange, }) => {
586
+ const { desktop } = useResponsive(DATE_PICKER_MOBILE_THRESHOLD);
587
+ const { desktop: displayTwoCalendar } = useResponsive(DATE_RANGE_PICKER_THRESHOLD);
588
+ const ref = useRef(null);
589
+ const [isOpened, { open, close }] = useDisclosure(false);
590
+ const handleStartChange = useCallback((start) => {
591
+ onChange({ start, end: value.end });
592
+ }, [value, onChange]);
593
+ const handleEndChange = useCallback((end) => {
594
+ onChange({ start: value.start, end });
595
+ }, [value, onChange]);
596
+ return (_jsxs(View, { children: [_jsxs(Box, { direction: "row", alignItems: "end", children: [_jsx(LakeLabel, { label: startLabel, style: styles.label, render: id => (_jsx(Rifm, { value: value.start, onChange: handleStartChange, ...rifmDateProps, children: ({ value, onChange }) => (_jsx(LakeTextInput, { ref: ref, id: id, placeholder: format, value: value, onChange: onChange, error: error, hideErrors: true, ariaExpanded: isOpened })) })) }), _jsx(Space, { width: 12 }), _jsx(Box, { style: styles.arrowContainer, justifyContent: "center", children: _jsx(Icon, { name: "arrow-right-filled", size: 20 }) }), _jsx(Space, { width: 12 }), _jsx(LakeLabel, { label: endLabel, style: styles.label, render: id => (_jsx(Rifm, { value: value.end, onChange: handleEndChange, ...rifmDateProps, children: ({ value, onChange }) => (_jsx(LakeTextInput, { id: id, placeholder: format, value: value, onChange: onChange, error: error, hideErrors: true, ariaExpanded: isOpened })) })) }), _jsx(Space, { width: 12 }), _jsx(LakeButton, { mode: "secondary", icon: "calendar-ltr-regular", size: "small", onPress: open, ariaLabel: t("common.open") })] }), _jsx(Space, { height: 4 }), _jsx(LakeText, { variant: "smallRegular", color: colors.negative[500], children: error ?? " " }), _jsx(DateModal, { visible: isOpened, maxWidth: 900, withCloseButton: true, onPressClose: close, children: _jsx(DateRangePickerModalContent, { value: value, format: format, firstWeekDay: firstWeekDay, monthNames: monthNames, weekDayNames: weekDayNames, desktop: desktop, displayTwoCalendar: displayTwoCalendar, isSelectable: isSelectable, onChange: onChange }) })] }));
597
+ };
598
+ export const DateRangePickerModal = ({ value, error, format, firstWeekDay, monthNames, weekDayNames, isSelectable, onChange, visible, startLabel, endLabel, cancelLabel, confirmLabel, onDissmiss, }) => {
599
+ const { desktop } = useResponsive(MODALE_MOBILE_THRESHOLD);
600
+ const { desktop: displayTwoCalendar } = useResponsive(DATE_RANGE_PICKER_THRESHOLD);
601
+ const [localeValue, setLocaleValue] = useState(value);
602
+ useEffect(() => {
603
+ setLocaleValue(value);
604
+ }, [value]);
605
+ const handleStartChange = (start) => {
606
+ setLocaleValue({ start, end: localeValue.end });
607
+ };
608
+ const handleEndChange = (end) => {
609
+ setLocaleValue({ start: localeValue.start, end });
610
+ };
611
+ const handleCancel = () => {
612
+ setLocaleValue(value);
613
+ onDissmiss();
614
+ };
615
+ const handleConfirm = () => {
616
+ onChange(localeValue);
617
+ onDissmiss();
618
+ };
619
+ return (_jsxs(DateModal, { visible: visible, maxWidth: 900, onPressClose: handleCancel, children: [_jsxs(View, { children: [_jsxs(Box, { direction: "row", alignItems: "end", children: [_jsx(LakeLabel, { label: startLabel, style: styles.label, render: id => (_jsx(Rifm, { value: localeValue.start, onChange: handleStartChange, ...rifmDateProps, children: ({ value, onChange }) => (_jsx(LakeTextInput, { id: id, placeholder: format, value: value, onChange: onChange, error: error, hideErrors: true })) })) }), _jsx(Space, { width: 12 }), _jsx(Box, { style: styles.arrowContainer, justifyContent: "center", children: _jsx(Icon, { name: "arrow-right-filled", size: 20 }) }), _jsx(Space, { width: 12 }), _jsx(LakeLabel, { label: endLabel, style: styles.label, render: id => (_jsx(Rifm, { value: localeValue.end, onChange: handleEndChange, ...rifmDateProps, children: ({ value, onChange }) => (_jsx(LakeTextInput, { id: id, placeholder: format, value: value, onChange: onChange, error: error, hideErrors: true })) })) })] }), _jsx(Space, { height: 4 }), _jsx(LakeText, { variant: "smallRegular", color: colors.negative[500], children: error ?? " " })] }), _jsx(DateRangePickerModalContent, { value: localeValue, format: format, firstWeekDay: firstWeekDay, monthNames: monthNames, weekDayNames: weekDayNames, desktop: desktop, displayTwoCalendar: displayTwoCalendar, isSelectable: isSelectable, onChange: setLocaleValue }), _jsx(Space, { height: 24 }), _jsxs(Box, { direction: "row", alignItems: "center", children: [_jsx(LakeButton, { mode: "secondary", size: "small", onPress: handleCancel, style: styles.button, children: cancelLabel }), _jsx(Space, { width: 24 }), _jsx(LakeButton, { color: "current", size: "small", onPress: handleConfirm, style: styles.button, children: confirmLabel })] })] }));
620
+ };
@@ -0,0 +1,10 @@
1
+ type Props = {
2
+ variant?: "none" | "pending" | "verified" | "refused";
3
+ name: string;
4
+ url?: string;
5
+ onRemove?: () => void;
6
+ title?: string;
7
+ description?: string;
8
+ };
9
+ export declare const FileTile: ({ variant, name, url, onRemove, title, description }: Props) => import("react/jsx-runtime").JSX.Element;
10
+ export {};
@@ -0,0 +1,38 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box } from "@swan-io/lake/src/components/Box";
3
+ import { LakeAlert } from "@swan-io/lake/src/components/LakeAlert";
4
+ import { LakeButton } from "@swan-io/lake/src/components/LakeButton";
5
+ import { LakeText } from "@swan-io/lake/src/components/LakeText";
6
+ import { Space } from "@swan-io/lake/src/components/Space";
7
+ import { Tag } from "@swan-io/lake/src/components/Tag";
8
+ import { commonStyles } from "@swan-io/lake/src/constants/commonStyles";
9
+ import { backgroundColor, colors, shadows, spacings } from "@swan-io/lake/src/constants/design";
10
+ import { getIconNameFromFilename } from "@swan-io/lake/src/utils/file";
11
+ import { isNotNullish, isNotNullishOrEmpty } from "@swan-io/lake/src/utils/nullish";
12
+ import { StyleSheet } from "react-native";
13
+ import { match } from "ts-pattern";
14
+ import { t } from "../utils/i18n";
15
+ const styles = StyleSheet.create({
16
+ base: {
17
+ backgroundColor: backgroundColor.accented,
18
+ borderRadius: 8,
19
+ boxShadow: shadows.tile,
20
+ overflow: "hidden",
21
+ },
22
+ content: {
23
+ height: 56,
24
+ paddingLeft: spacings[20],
25
+ paddingRight: spacings[8],
26
+ },
27
+ });
28
+ export const FileTile = ({ variant = "none", name, url, onRemove, title, description }) => (_jsxs(Box, { style: styles.base, children: [_jsxs(Box, { alignItems: "center", direction: "row", style: styles.content, children: [_jsx(Tag, { icon: getIconNameFromFilename(name), iconSize: 20, color: match(variant)
29
+ .with("none", "pending", () => "shakespear")
30
+ .with("verified", () => "positive")
31
+ .with("refused", () => "negative")
32
+ .exhaustive() }), _jsx(Space, { width: 16 }), _jsx(LakeText, { numberOfLines: 1, color: colors.gray[700], style: commonStyles.fill, children: name }), _jsx(Space, { width: 12 }), isNotNullishOrEmpty(url) && (_jsx(LakeButton, { mode: "tertiary", size: "small", icon: "open-filled", onPress: () => {
33
+ window.open(url, "_blank");
34
+ }, ariaLabel: t("common.open") })), isNotNullish(onRemove) && (_jsx(LakeButton, { mode: "tertiary", size: "small", icon: "delete-regular", color: "negative", onPress: onRemove, ariaLabel: t("common.remove") }))] }), variant !== "none" && (_jsx(LakeAlert, { anchored: true, title: title, variant: match(variant)
35
+ .with("pending", () => "info")
36
+ .with("verified", () => "success")
37
+ .with("refused", () => "error")
38
+ .exhaustive(), children: description }))] }));