@jobber/components-native 0.34.0 → 0.35.1-JOB-70282.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/dist/src/AtlantisContext/AtlantisContext.js +1 -0
- package/dist/src/InputDate/InputDate.js +68 -0
- package/dist/src/InputDate/index.js +1 -0
- package/dist/src/InputDate/messages.js +8 -0
- package/dist/src/InputTime/InputTime.js +83 -0
- package/dist/src/InputTime/InputTime.style.js +7 -0
- package/dist/src/InputTime/index.js +2 -0
- package/dist/src/InputTime/messages.js +8 -0
- package/dist/src/InputTime/utils/index.js +16 -0
- package/dist/src/hooks/useFormattedDate/index.js +1 -0
- package/dist/src/hooks/useFormattedDate/useFormattedDate.js +18 -0
- package/dist/src/index.js +2 -0
- package/dist/src/utils/format/date.js +46 -0
- package/dist/src/utils/format/index.js +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/src/AtlantisContext/AtlantisContext.d.ts +2 -1
- package/dist/types/src/InputDate/InputDate.d.ts +74 -0
- package/dist/types/src/InputDate/index.d.ts +1 -0
- package/dist/types/src/InputDate/messages.d.ts +7 -0
- package/dist/types/src/InputTime/InputTime.d.ts +61 -0
- package/dist/types/src/InputTime/InputTime.style.d.ts +6 -0
- package/dist/types/src/InputTime/index.d.ts +2 -0
- package/dist/types/src/InputTime/messages.d.ts +7 -0
- package/dist/types/src/InputTime/utils/index.d.ts +11 -0
- package/dist/types/src/hooks/useFormattedDate/index.d.ts +2 -0
- package/dist/types/src/hooks/useFormattedDate/useFormattedDate.d.ts +3 -0
- package/dist/types/src/index.d.ts +2 -0
- package/dist/types/src/utils/format/date.d.ts +13 -0
- package/dist/types/src/utils/format/index.d.ts +1 -0
- package/package.json +7 -3
- package/src/AtlantisContext/AtlantisContext.tsx +3 -1
- package/src/InputDate/InputDate.test.tsx +232 -0
- package/src/InputDate/InputDate.tsx +222 -0
- package/src/InputDate/index.ts +1 -0
- package/src/InputDate/messages.ts +9 -0
- package/src/InputTime/InputTime.style.ts +8 -0
- package/src/InputTime/InputTime.test.tsx +323 -0
- package/src/InputTime/InputTime.tsx +221 -0
- package/src/InputTime/index.tsx +2 -0
- package/src/InputTime/messages.ts +9 -0
- package/src/InputTime/utils/index.ts +26 -0
- package/src/InputTime/utils/utils.test.ts +47 -0
- package/src/hooks/useFormattedDate/index.ts +2 -0
- package/src/hooks/useFormattedDate/useFormattedDate.test.tsx +33 -0
- package/src/hooks/useFormattedDate/useFormattedDate.ts +32 -0
- package/src/index.ts +2 -0
- package/src/utils/format/date.test.ts +72 -0
- package/src/utils/format/date.ts +69 -0
- package/src/utils/format/index.ts +5 -0
|
@@ -3,6 +3,7 @@ import { createContext, useContext } from "react";
|
|
|
3
3
|
import RNLocalize from "react-native-localize";
|
|
4
4
|
import { DEFAULT_CURRENCY_SYMBOL } from "../InputCurrency/constants";
|
|
5
5
|
export const defaultValues = {
|
|
6
|
+
dateFormat: "%b %d, %Y",
|
|
6
7
|
// The system time is "p"
|
|
7
8
|
timeFormat: "p",
|
|
8
9
|
timeZone: RNLocalize.getTimeZone(),
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import React, { useMemo, useState } from "react";
|
|
2
|
+
import DateTimePicker from "react-native-modal-datetime-picker";
|
|
3
|
+
import { Platform } from "react-native";
|
|
4
|
+
import { useIntl } from "react-intl";
|
|
5
|
+
import { messages } from "./messages";
|
|
6
|
+
import { useFormattedDate } from "../hooks/useFormattedDate";
|
|
7
|
+
import { FormField } from "../FormField";
|
|
8
|
+
import { InputPressable } from "../InputPressable";
|
|
9
|
+
function formatInvalidState(error, invalid) {
|
|
10
|
+
if (invalid)
|
|
11
|
+
return invalid;
|
|
12
|
+
if (error && error.message) {
|
|
13
|
+
return error.message;
|
|
14
|
+
}
|
|
15
|
+
return Boolean(error);
|
|
16
|
+
}
|
|
17
|
+
const display = Platform.OS === "ios" ? "inline" : "default";
|
|
18
|
+
/**
|
|
19
|
+
* Allow users to select a date using the device date picker.
|
|
20
|
+
*/
|
|
21
|
+
export function InputDate(props) {
|
|
22
|
+
if (props.name) {
|
|
23
|
+
return (React.createElement(FormField, { name: props.name, defaultValue: props.defaultValue, validations: props.validations }, ({ value, onChange, onBlur }, error) => (React.createElement(InternalInputDate, Object.assign({}, props, { value: value, onChange: (newValue) => {
|
|
24
|
+
var _a;
|
|
25
|
+
onChange(newValue);
|
|
26
|
+
onBlur();
|
|
27
|
+
(_a = props.onChange) === null || _a === void 0 ? void 0 : _a.call(props, newValue);
|
|
28
|
+
}, invalid: formatInvalidState(error, props.invalid) })))));
|
|
29
|
+
}
|
|
30
|
+
return React.createElement(InternalInputDate, Object.assign({}, props));
|
|
31
|
+
}
|
|
32
|
+
function InternalInputDate({ clearable = "always", disabled, emptyValueLabel, invalid, maxDate, minDate, placeholder, value, name, onChange, accessibilityLabel, accessibilityHint, }) {
|
|
33
|
+
const [showPicker, setShowPicker] = useState(false);
|
|
34
|
+
const formatDate = useFormattedDate();
|
|
35
|
+
const { formatMessage } = useIntl();
|
|
36
|
+
const date = useMemo(() => {
|
|
37
|
+
if (typeof value === "string")
|
|
38
|
+
return new Date(value);
|
|
39
|
+
return value;
|
|
40
|
+
}, [value]);
|
|
41
|
+
const formattedDate = useMemo(() => { var _a; return (_a = (date && formatDate(date, "accountFormat"))) !== null && _a !== void 0 ? _a : emptyValueLabel; }, [date, emptyValueLabel, formatDate]);
|
|
42
|
+
const canClearDate = formattedDate === emptyValueLabel ? "never" : clearable;
|
|
43
|
+
const placeholderLabel = placeholder !== null && placeholder !== void 0 ? placeholder : formatMessage(messages.datePlaceholder);
|
|
44
|
+
return (React.createElement(React.Fragment, null,
|
|
45
|
+
React.createElement(InputPressable, { clearable: canClearDate, disabled: disabled, invalid: invalid, placeholder: placeholderLabel, prefix: { icon: "calendar" }, value: formattedDate, onClear: handleClear, onPress: showDatePicker, accessibilityLabel: accessibilityLabel, accessibilityHint: accessibilityHint }),
|
|
46
|
+
React.createElement(DateTimePicker, { testID: "inputDate-datePicker", date: date || undefined, display: display, isVisible: showPicker, maximumDate: maxDate, minimumDate: minDate, mode: "date", onCancel: handleCancel, onConfirm: handleConfirm })));
|
|
47
|
+
function showDatePicker() {
|
|
48
|
+
setShowPicker(true);
|
|
49
|
+
}
|
|
50
|
+
function handleConfirm(newVal) {
|
|
51
|
+
setShowPicker(false);
|
|
52
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(newVal);
|
|
53
|
+
}
|
|
54
|
+
function handleCancel() {
|
|
55
|
+
setShowPicker(false);
|
|
56
|
+
// Ensure a change happens so we trigger the validation of one exists
|
|
57
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(date);
|
|
58
|
+
}
|
|
59
|
+
function handleClear() {
|
|
60
|
+
// Returns null only for Form controlled scenarios due to a limitation of react-hook-form that doesn't allow passing undefined to form values.
|
|
61
|
+
if (name) {
|
|
62
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(null);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(undefined);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { InputDate } from "./InputDate";
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import React, { useMemo, useState } from "react";
|
|
2
|
+
import DateTimePicker from "react-native-modal-datetime-picker";
|
|
3
|
+
import { View } from "react-native";
|
|
4
|
+
import { useIntl } from "react-intl";
|
|
5
|
+
import { utcToZonedTime } from "date-fns-tz";
|
|
6
|
+
import { format as formatTime } from "date-fns";
|
|
7
|
+
import { styles } from "./InputTime.style";
|
|
8
|
+
import { messages } from "./messages";
|
|
9
|
+
import { getTimeZoneOffsetInMinutes, roundUpToNearestMinutes } from "./utils";
|
|
10
|
+
import { useAtlantisContext } from "../AtlantisContext";
|
|
11
|
+
import { InputPressable } from "../InputPressable";
|
|
12
|
+
import { FormField } from "../FormField";
|
|
13
|
+
const LOCALE_24_HOURS = "en_GB";
|
|
14
|
+
const LOCALE_12_HOURS = "en_US";
|
|
15
|
+
function formatInvalidState(error, invalid) {
|
|
16
|
+
if (invalid)
|
|
17
|
+
return invalid;
|
|
18
|
+
if (error && error.message) {
|
|
19
|
+
return error.message;
|
|
20
|
+
}
|
|
21
|
+
return Boolean(error);
|
|
22
|
+
}
|
|
23
|
+
export function InputTime(props) {
|
|
24
|
+
if (props.name) {
|
|
25
|
+
return (React.createElement(FormField, { name: props.name, validations: props.validations }, (field, error) => (React.createElement(InternalInputTime, Object.assign({}, props, { value: field.value, onChange: (newValue) => {
|
|
26
|
+
var _a;
|
|
27
|
+
field.onChange(newValue);
|
|
28
|
+
field.onBlur();
|
|
29
|
+
(_a = props.onChange) === null || _a === void 0 ? void 0 : _a.call(props, newValue);
|
|
30
|
+
}, invalid: formatInvalidState(error, props.invalid) })))));
|
|
31
|
+
}
|
|
32
|
+
return React.createElement(InternalInputTime, Object.assign({}, props));
|
|
33
|
+
}
|
|
34
|
+
function InternalInputTime({ clearable = "always", disabled, emptyValueLabel, invalid, placeholder, value, name, type = "scheduling", onChange, showIcon = true, }) {
|
|
35
|
+
const [showPicker, setShowPicker] = useState(false);
|
|
36
|
+
const { formatMessage } = useIntl();
|
|
37
|
+
const { timeZone, timeFormat } = useAtlantisContext();
|
|
38
|
+
const is24Hour = timeFormat === "HH:mm";
|
|
39
|
+
const dateTime = useMemo(() => (typeof value === "string" ? new Date(value) : value), [value]);
|
|
40
|
+
const formattedTime = useMemo(() => {
|
|
41
|
+
if (dateTime) {
|
|
42
|
+
const zonedTime = utcToZonedTime(dateTime, timeZone);
|
|
43
|
+
return formatTime(zonedTime, timeFormat);
|
|
44
|
+
}
|
|
45
|
+
return emptyValueLabel;
|
|
46
|
+
}, [dateTime, emptyValueLabel, timeZone, timeFormat]);
|
|
47
|
+
const canClearTime = formattedTime === emptyValueLabel ? "never" : clearable;
|
|
48
|
+
return (React.createElement(View, { style: styles.container },
|
|
49
|
+
React.createElement(InputPressable, { clearable: canClearTime, disabled: disabled, invalid: invalid, placeholder: placeholder !== null && placeholder !== void 0 ? placeholder : formatMessage(messages.time), prefix: showIcon ? { icon: "timer" } : undefined, value: formattedTime, onClear: handleClear, onPress: showDatePicker }),
|
|
50
|
+
React.createElement(DateTimePicker, { testID: "inputTime-Picker", minuteInterval: getMinuteInterval(type), date: getInitialPickerDate(dateTime), timeZoneOffsetInMinutes: getTimeZoneOffsetInMinutes(timeZone, dateTime), isVisible: showPicker, mode: "time", onCancel: handleCancel, onConfirm: handleConfirm, is24Hour: is24Hour, locale: is24Hour ? LOCALE_24_HOURS : LOCALE_12_HOURS })));
|
|
51
|
+
function showDatePicker() {
|
|
52
|
+
setShowPicker(true);
|
|
53
|
+
}
|
|
54
|
+
function handleConfirm(newValue) {
|
|
55
|
+
setShowPicker(false);
|
|
56
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(newValue);
|
|
57
|
+
}
|
|
58
|
+
function handleCancel() {
|
|
59
|
+
setShowPicker(false);
|
|
60
|
+
// Call onChange with the current value to trigger form's validation.
|
|
61
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(dateTime);
|
|
62
|
+
}
|
|
63
|
+
function handleClear() {
|
|
64
|
+
// Returns null only for Form controlled scenarios due to a limitation of
|
|
65
|
+
// react-hook-form that doesn't allow passing undefined to form values.
|
|
66
|
+
if (name) {
|
|
67
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(null);
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(undefined);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function getInitialPickerDate(date) {
|
|
75
|
+
if (date)
|
|
76
|
+
return date;
|
|
77
|
+
return roundUpToNearestMinutes(new Date(), 30);
|
|
78
|
+
}
|
|
79
|
+
function getMinuteInterval(type) {
|
|
80
|
+
if (type === "granular")
|
|
81
|
+
return 1;
|
|
82
|
+
return 5;
|
|
83
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { getTimezoneOffset } from "date-fns-tz";
|
|
2
|
+
/**
|
|
3
|
+
* Rounds up the time by increment.
|
|
4
|
+
* - 15 mins - rounds to the next quarter time of `00:15`, `00:30`, `00:45`,
|
|
5
|
+
* and `01:00`
|
|
6
|
+
* - 30 mins - rounds to the next half hour be it `00:30` or `01:00`
|
|
7
|
+
* - 60 mins - rounds to the next hour. I.e., `02:01` gets rounded up
|
|
8
|
+
* to `03:00`.
|
|
9
|
+
*/
|
|
10
|
+
export function roundUpToNearestMinutes(date, minutes) {
|
|
11
|
+
const ms = 1000 * 60 * minutes;
|
|
12
|
+
return new Date(Math.ceil(date.getTime() / ms) * ms);
|
|
13
|
+
}
|
|
14
|
+
export function getTimeZoneOffsetInMinutes(timeZone, date) {
|
|
15
|
+
return getTimezoneOffset(timeZone, date) / 1000 / 60;
|
|
16
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { useFormattedDate } from "./useFormattedDate";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { formatInTimeZone } from "date-fns-tz";
|
|
2
|
+
import { useAtlantisContext } from "../../AtlantisContext";
|
|
3
|
+
import { accountFormattedDate, shortFormattedDate, } from "../../utils/format/date";
|
|
4
|
+
function formatDate(date, format, accountTimeZoneName, accountDateFormat) {
|
|
5
|
+
if (format === "shorthand") {
|
|
6
|
+
return shortFormattedDate(date, accountTimeZoneName);
|
|
7
|
+
}
|
|
8
|
+
else if (format === "ISO8601") {
|
|
9
|
+
return formatInTimeZone(date, accountTimeZoneName, "yyyy-MM-dd");
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
return accountFormattedDate(date, accountDateFormat, accountTimeZoneName);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export function useFormattedDate() {
|
|
16
|
+
const { timeZone, dateFormat } = useAtlantisContext();
|
|
17
|
+
return (date, format) => formatDate(date, format, timeZone, dateFormat);
|
|
18
|
+
}
|
package/dist/src/index.js
CHANGED
|
@@ -19,10 +19,12 @@ export * from "./Icon";
|
|
|
19
19
|
export * from "./IconButton";
|
|
20
20
|
export * from "./InputFieldWrapper";
|
|
21
21
|
export * from "./InputCurrency";
|
|
22
|
+
export * from "./InputDate";
|
|
22
23
|
export * from "./InputNumber";
|
|
23
24
|
export * from "./InputPassword";
|
|
24
25
|
export * from "./InputPressable";
|
|
25
26
|
export * from "./InputSearch";
|
|
27
|
+
export * from "./InputTime";
|
|
26
28
|
export * from "./InputText";
|
|
27
29
|
export * from "./TextList";
|
|
28
30
|
export * from "./ProgressBar";
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { isValid } from "date-fns";
|
|
2
|
+
import { enUS } from "date-fns/locale";
|
|
3
|
+
import { format } from "date-fns-tz";
|
|
4
|
+
export function accountFormattedDate(date, accountFormat, timeZone) {
|
|
5
|
+
if (!isValid(date)) {
|
|
6
|
+
return "";
|
|
7
|
+
}
|
|
8
|
+
// must take into account the timezone before obtaining
|
|
9
|
+
// any of the day/month/year values
|
|
10
|
+
const formattedDate = format(date, "dd|MM|MMM|y", {
|
|
11
|
+
locale: enUS,
|
|
12
|
+
timeZone: timeZone,
|
|
13
|
+
});
|
|
14
|
+
const [dd, MM, MMM, y] = formattedDate.split("|");
|
|
15
|
+
if (accountFormat === "%m/%d/%Y") {
|
|
16
|
+
return `${MM}/${dd}/${y}`; // 09/30/1992
|
|
17
|
+
}
|
|
18
|
+
else if (accountFormat === "%d/%m/%Y") {
|
|
19
|
+
return `${dd}/${MM}/${y}`; // 30/09/1992
|
|
20
|
+
}
|
|
21
|
+
else if (accountFormat === "%Y-%m-%d") {
|
|
22
|
+
return `${y}-${MM}-${dd}`; // 1992-09-30
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
return `${MMM} ${dd}, ${y}`; // Sep 30, 1992
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/***
|
|
29
|
+
* Returns a date in "MMM dd" format (e.g. July 01), using the provided timezone
|
|
30
|
+
*/
|
|
31
|
+
export function shortFormattedDate(date, timeZone) {
|
|
32
|
+
if (!isValid(date)) {
|
|
33
|
+
return "";
|
|
34
|
+
}
|
|
35
|
+
return format(date, "MMM dd", { locale: enUS, timeZone: timeZone });
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
*
|
|
39
|
+
* @param date
|
|
40
|
+
* Returns a date in true utc time with the timezone offset
|
|
41
|
+
* @returns Date
|
|
42
|
+
*/
|
|
43
|
+
export function convertDateToUTC(date) {
|
|
44
|
+
return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds(), date.getUTCMilliseconds()) +
|
|
45
|
+
date.getTimezoneOffset() * 60 * 1000);
|
|
46
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { accountFormattedDate, shortFormattedDate, convertDateToUTC, } from "./date";
|